leetcode_189 轮转数组
1. 题意
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
2. 题解
2.1 开一个数组
由于我比较笨比,所以只能想到这种办法。
当前位置为iii,那么它将移动到(i+k)%n(i+k)\%n(i+k)%n的位置上去。
class Solution {
public:void rotate(vector<int>& nums, int k) {int n = nums.size();vector<int> tmp = nums;for (int i = 0;i < n; ++i) {tmp[ (i + k) % n] = nums[i];}nums = tmp;}
};
除此之外,我还能想到的就是,既然是向右移kkk位,那么实际上相当于
把最后的kkk位的数给移动到前面来,而这也是反转法的一个前提,不过
我没有想到反转的这一做法,而是直接这样移动。因此空间复杂度仍然是O(n)O(n)O(n)。
class Solution {
public:void rotate(vector<int>& nums, int k) {int n = nums.size();k %= n;if ( k ) {for (int i = 0; i < n - k; ++i) {nums.emplace_back( nums[i]);}nums.erase(nums.begin(), nums.begin() + n - k);}}
};
要注意的一点是kkk城对nnn取余。
2.2 环状替换
这个其实是比较数学的做法,涉及到最大公约数最小公倍数。
对于0→n−10\to n-10→n−1任意一个位置来说,它每次可以跳kkk步,
它最少需要跳几次才能回到原来的位置呢?
我们假设需要跳bbb次,同时假设跳了aaa圈,那么我们容易得到
an=bkan=bk an=bk
我们知道n∣ann \mid ann∣an且k∣ank \mid ank∣an,也就是ananan同时是n,kn,kn,k的倍数,那么
必然有lcm(n,k)∣anlcm(n,k) \mid anlcm(n,k)∣an,由于要尽可能的让ananan小,
因此an=lcm(n,k)an=lcm(n,k)an=lcm(n,k)。
进而我们可以得到
b=lcm(n,k)kb=\frac{lcm(n,k)}{k} b=klcm(n,k)
根据最大公因数和最小公倍数之间的关系
nkgcd(n,k)=lcm(n,k)\frac{nk}{\gcd(n,k)}=\mathop{lcm}(n,k) gcd(n,k)nk=lcm(n,k)
代入得到
b=ngcd(n,k)b=\frac{n}{\gcd(n,k)} b=gcd(n,k)n
由于一个位置需要跳bbb次才能回到原来位置,又因为这是个环所以
跳跃的次数就是这个环的元素个数。一个环有b=ngcd(n,k)b=\frac{n}{\gcd(n,k)}b=gcd(n,k)n个
元素,因此环的个数就是nb=gcd(n,k)\frac{n}{b}=\gcd(n,k)bn=gcd(n,k)。这个环的个数也就是
代码中的countcountcount。
class Solution {
public:int gcd(int a, int b) {return b == 0 ? a : gcd( b, a % b);}void rotate(vector<int>& nums, int k) {int n = nums.size();k %= n;int cnt = gcd(n, k);for (int i = 0;i < cnt; ++i) {int st = i;for (int j = 1;j < n / cnt; ++j) {int next_pos = (st + j * k) % n;std::swap( nums[st], nums[next_pos]);}}}
};
还有一个问题,当startstartstart所在的环遍历完了之后,
为什么start+1start+1start+1就是下一个环了呢?
startstartstart所在环不会已经遍历过start+1start+1start+1这个位置了吗?
我们假设存在这样一种情况,那么必然有
start+mk≡start+1(modn)mk≡1(modn)start +mk \equiv start+1 \quad (\ \bmod\ n)\\ mk\equiv 1 \quad (\ \bmod \ n) start+mk≡start+1( mod n)mk≡1( mod n)
那么gcd(n,k)=1\gcd(n,k)=1gcd(n,k)=1,而这与start+1start+1start+1的存在性是矛盾的。
因此不会存在这种情况。
2.3 反转数组
令+++表示数组的拼接操作,
用R(Arr)R(Arr)R(Arr)表示将数组反转这一操作。
我们容易得到
R(A+B)=R(B)+R(A)R(R(A))=AR(A+B)=R(B)+R(A)\\ R(R(A)) =A R(A+B)=R(B)+R(A)R(R(A))=A
这两种性质
对于循环右移kkk位这种操作,实际上可以通过反转来实现。
我们令AAA 表示numsnumsnums中的前n−kn-kn−k个数依次序组成的数组,
BBB表示numsnumsnums中后kkk个数依次组成的数组。
实际上我们就是要通过反转将
A+B→B+AA+B \to B+AA+B→B+A。
根据上面的性质,我们可以很容易的通过三次反转得到最终的结果。
class Solution {
public:void rotate(vector<int>& nums, int k) {int n = nums.size();k %= n;reverse(nums.begin(), nums.end());reverse(nums.begin(), nums.begin() + k);reverse(nums.begin() + k, nums.end());}
};
时间复杂度O(2n)=O(n)O(2n)=O(n)O(2n)=O(n)
空间复杂度O(1)O(1)O(1)
3. 参考
0x3f
leetcode