前两天做了一道轮转数组的 LeetCode,题目虽然简单,但解法挺多的,感觉有点意思,记录并巩固下~

# 1. 题目

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]

示例 2:

** 输入:**nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]

# 2. 思路

# (1). 创建新数组

最简单的办法可以直接创建一个新数组来存储旋转后的数据,最后将新数组数据赋值回目标数组。

p
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> newArr(n);
        for (int i = 0; i < n; ++i) {
            newArr[(i + k) % n] = nums[i];
        }
        nums.assign(newArr.begin(), newArr.end());
    }
};

这种做法,时间复杂度是 O (n),空间复杂度是 O (n)。因为至少需要遍历一遍,所以时间复杂度 O (n) 已经没有多少提升空间了,但是理论上只是进行交换的话不需要 O (n) 的额外空间,空间复杂度上是可以优化的。

# (2). 环形交换

原地交换的话,就不需要额外的空间了,只需要将被交换的数据存入一个临时变量,然后再替换他的下一个目标位置就可以,这样额外只需要一个临时变量的空间,空间复杂度就变为了 O (1)。但是这种方法的有一个关键点就是如何遍历的问题,因为单从一点出发回到原点,此时可能是有未遍历到的元素的。

官方给了一种数学推导方式去确定如何遍历:

由于每次循环最终回到了起点,故该过程恰好走了整数数量的圈,不妨设为 a 圈;再设该过程总共遍历了 b 个元素。因此,我们有 an=bk,即 an 一定为 n,k 的公倍数。又因为我们在第一次回到起点时就结束,因此 a 要尽可能小,故 an 就是 n,k 的最小公倍数 lcm (n,k),因此 b 就为 lcm (n,k)/k。

总结来说,其实一次循环后,遍历到的元素每个是相隔 lcm (n,k) 的,所以只需要分别以数组前 lcm (n,k) 个元素为起点进行一个上述的循环,就可以遍历完所有元素。可以看下图例子:

traversal

知道这个规律后使用一个变量对遍历过的元素计数就行了,从头开始挨个进行循环是不会遍历到重复元素的,所以最终总的遍历过的数目与数组长度相同就可以了。

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        k = k % n;
        int count = gcd(k, n);
        for (int start = 0; start < count; ++start) {
            int current = start;
            int prev = nums[start];
            do {
                int next = (current + k) % n;
                swap(nums[next], prev);
                current = next;
            } while (start != current);
        }
    }
};

这种方法每个元素仅会遍历一遍,额外空间只需要一些固定的临时变量,所以时间复杂度是 O (n),空间复杂度是 O (1)。

# (3). 数组翻转

这种方法比较有意思,是通过翻转的方式实现的。

该方法通过整体翻转后再局部翻转的方式实现数组旋转,引用一条评论可以很清楚的看懂思路:

nums = "----->-->"; k =3
result = "-->----->";

reverse "----->-->" we can get "<--<-----"
reverse "<--" we can get "--><-----"
reverse "<-----" we can get "-->----->"

知道思路后代码就很容易出来了:

class Solution {
public:
    void reverse(vector<int>& nums, int start, int end) {
        while (start < end) {
            swap(nums[start], nums[end]);
            start += 1;
            end -= 1;
        }
    }
    void rotate(vector<int>& nums, int k) {
        k %= nums.size();
        reverse(nums, 0, nums.size() - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.size() - 1);
    }
};

# 3. 总结

很多题目虽然挺简单,但是能看到很多有意思的思路和方法,感觉之前评论区看少了,今后可以多看看~

# 参考资料

  • 轮转数组 - 力扣
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Nirvana 支付宝

支付宝