leetcode[189]:轮转数组

32 阅读4分钟

分享两种比较容易理解的朴素解法。

1、题目内容

直接截图,不多描述。

添加图片注释,不超过 140 字(可选)

2、思路梳理

就拿实例 1,k = 3 的情形来分析。我们作出移动的示意图, 第一条链路的移动就是 1--->4--->7,7--->3 虽然也可以连起来,但它是把右边的数字移到左边,跟我们主链路上左边数字移到右边不同,需要单独考虑,图中用虚线表示。

k = 3 的移动情形

1--->4--->7 这条链路,实现的过程是 暂存 7,执行 4--->7,再执行 1--->4;

2---> 5 这条链路,暂存 5,再执行 2---> 5;

3---> 6 这条链路,暂存 6,再执行 3---> 6。

有第四条链路吗?没了,我们发现执行了上面三条链路后,除了最后 3 个数字,其他数字都已经移动到最后一个位置了。而且,这三条链路互相间无覆盖,既然无覆盖,先后顺序就没有了关系,我们大可以把这三条链路合并到一起执行,先把最后 3 个数字暂存起来,然后执行 4--->7, 3--->6, 2--->5, 1--->4,最后借助暂存的数字,执行 7--->3, 6--->2, 5--->1。

我们推出一个比较通用的过程:

  1. 暂存最后 k 个数字;
  2. 从最后一个数字往前,依次把 第 i-k 位置上的数字移到位置 i 上,当 i-k < 0 时,则退出;
  3. 将暂存的最后 k 个数字移动到前面的对应位置。
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int size = nums.size();
        k = k % size;
        // 暂存最后 k 个数字
        vector<int> tmp;
        for (int i = size - 1; i > size - k - 1; --i) {
            tmp.emplace_back(nums[i]);
        }
        // 从最后一个数字开始,依次用前面 k 个位置上的数字替换
        for (int i = size - 1; i - k > -1; --i) {
            nums[i] = nums[i-k];
        }
        // 将暂存的最后 k 数字移动到最前面
        for (int i = 0; i < k; ++i) {
            nums[i] = tmp[k-1-i];
        }
    }
};

执行结果

3、进一步思考

上面的代码里,要将最后 k 个数字都存储起来,需要用到另一个数组,有没有办法只暂存一个值呢?

回忆一下,我们上面的思路里其实是将 k 条链路合并起来了一起执行,所以有 k 个值需要暂存,那如果我们每次只执行一条链路,则存一个值就够了。

另外,我们刚刚的主链路只考虑了从左往右移动的情形,从右往左的情况是单独处理的。现在我们则不区分,链路一直往下走,直到回到开始的位置。

从 1 开始的循环链路

1--->4--->7--->3--->6--->2--->5--->1,现在推的时候是正向推的,从 1 开始,但实际写代码时,还是从最后一个(7)开始,这样比较好处理。

上面的例子一条链路就把所有数字循环完了,让我们换一个场景,8 个数字,向右移动 2。第一轮移动如下

从 1 开始,移动一轮

1--->3--->5--->7--->1,这一轮并不能移动完全部的数字,需要进行下一轮,从 2 开始。

从 2 开始,移动第二轮

经过两轮,所有数字都被移动了一遍。有理由假设,不同的数组长度和不同的移动个数 k,需要移动的轮数不一样。但是,只要所以数字都移动了一遍,我们就能肯定可以退出了,不管移动了几轮。

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int size = nums.size();
        k = k % size;
        int count = 0; // 移动过的数字个数,每移动一个就 +1,当移动完 size 个数字后就退出
        // round 表示循环的轮数,第一次从最后一个数字开始,第二次从倒数第二个开始
        for (int round = 0; count != size; ++round) {
            // 当前轮次,开始的数字
            int start = size - 1 - round;
            // 暂存开始的数字,因为它最先被替换
            int tmp = nums[start];
            int cur = start;
            // 将要移动到 cur 上的数字的下标
            int next = (cur - k + size)%size;
            // 当下一个数字的下标是开始时的下标时退出
            while (next != start) {
                nums[cur] = nums[next];
                cur = next;
                next = (cur - k + size)%size;
                ++count;
            }
            // 读取暂存的数字
            nums[cur] = tmp;
            ++count;
        }
    }
};

添加图片注释,不超过 140 字(可选)

两种解法的时间复杂度都是 O(n),第一种的空间复杂度为 O(n),第二种的为 O(1)。第二种解法和官方给出的环装替换思路基本一样,但推理过程不同,少了数学公式,容易理解一些。

上面两种解法可能不算优秀,但我认为还是比较好理解的,思路简单,以后再碰到基本能想起来或者推出来。