问题描述:
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
进阶:
- 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
- 你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?
例子:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
算法分析与实现:
题目分析:
将数组
n
u
m
s
[
n
]
nums[n]
nums[n]中的元素向右移动 k 个位置,其意思相当于,将数组首尾相连,形成一个环,然后按顺时针移动
k
k
k个位置。
很明显,题目将一个数组分成两部分,然后交换两部分的位置;
1. 方法一:
增加一个数组
a
[
n
]
a[n]
a[n],这个数组先将后面
k
k
k个元素按序存入
a
[
n
]
a[n]
a[n]中,然后再将前面
n
−
k
n-k
n−k个元素存入
a
[
n
]
a[n]
a[n]即可。最后赋值
n
u
m
s
=
a
nums=a
nums=a。这样需要用两次
f
o
r
for
for循环,为了代码简洁,可以利用循环来替代。即
a
[
(
i
+
k
)
a[(i+k)
a[(i+k)%
n
]
=
n
u
m
s
[
i
]
n]=nums[i]
n]=nums[i],意思是将数组全部往后移
k
k
k个位置,超出最大位置,就从0号位置排列。
例如:
输入:
n
u
m
s
=
[
1
,
2
,
3
,
4
,
5
,
6
,
7
]
,
k
=
3
nums = [1,2,3,4,5,6,7], k = 3
nums=[1,2,3,4,5,6,7],k=3
第一次存入:
a
[
n
]
=
[
5
,
6
,
7
]
a[n]=[5,6,7]
a[n]=[5,6,7] //先将后面
k
k
k个元素按序存入
a
[
n
]
a[n]
a[n]中
第一次存入:
a
[
n
]
=
[
5
,
6
,
7
,
1
,
2
,
3
]
a[n]=[5,6,7,1,2,3]
a[n]=[5,6,7,1,2,3]
代码如下:
//方法一:
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
vector<int>abs(n);
k = k % n; //限制k只能在0~n-1之间,防止数组内存不足
for(int i=0; i < n; i++) {
abs[(i+k)%n] = nums[i];//排序
}
nums.assign(abs.begin(),abs.end());//换回给原数组相当于赋值语句。
}
};
//如果代码看不懂,可能是因为没怎么用过c++容器vector,以及相关函数,这个我也没系统学过,
//遇到就查询用法即可。个人觉得,函数全都是工具,工具会用就行,不会就查说明书。
复杂度分析:
时间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 为数组的长度。
空间复杂度:
O
(
n
)
O(n)
O(n)。
2. 方法二:
方法一增加了空间复杂度,需要额外数组消耗,如果原数组上变化,也是可以的,方法如下:
构造一个倒叙函数
r
e
v
e
r
c
e
(
)
reverce()
reverce(),这个函数功能是将数组倒叙排列。以
n
u
m
s
=
[
1
,
2
,
3
,
4
,
5
,
6
,
7
]
,
k
=
3
nums = [1,2,3,4,5,6,7], k = 3
nums=[1,2,3,4,5,6,7],k=3为例子:
1.第一步:将整个数组倒叙排列;得
n
u
m
s
=
[
7
,
6
,
5
,
4
,
3
,
2
,
1
]
nums = [7,6,5,4,3,2,1]
nums=[7,6,5,4,3,2,1]
2.第二步:将前面
k
k
k个数倒叙排列;得
n
u
m
s
=
[
5
,
6
,
7
,
4
,
3
,
2
,
1
]
nums = [5,6,7,4,3,2,1]
nums=[5,6,7,4,3,2,1]
3.第三步:将后面
n
−
k
n-k
n−k个数倒叙排列;得
n
u
m
s
=
[
5
,
6
,
7
,
1
,
2
,
3
,
4
]
nums = [5,6,7,1,2,3,4]
nums=[5,6,7,1,2,3,4]
代码如下:
//方法二:
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k = k % n;//限制k只能在0~n-1之间,防止数组内存不足
reverse(nums,0,n-1);//原数组倒叙排列
reverse(nums,0,k-1);//前k个数组倒叙排列
reverse(nums,k,n-1);//后n-k个数排序排列
}
void reverse(vector<int>& nums,int start,int end) {
while(start < end) {
swap(nums[start],nums[end]);
start++;
end--;
}
}
};
复杂度分析:
时间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 为数组的长度。
空间复杂度:
O
(
1
)
O(1)
O(1)。
**3. 方法三:** 这个方法有点复杂,如果想深入,可以去看这位[旋转数组](https://leetcode-cn.com/problems/rotate-array/solution/xuan-zhuan-shu-zu-yuan-di-huan-wei-xiang-xi-tu-jie/)大佬的解释,其大概思路如下:
分两种情况讨论:
1、当
k
k
k和
n
n
n无有公因数的时候,可以一次循环就可以完成交换。
其做法是将数组第0号位置与
(
j
+
k
)
(j+k)
(j+k)%
n
n
n(j=0)进行交换,因为
k
k
k和
n
n
n无有公因数,所以,进行n-1次交换后,又回到原来的位置,即
(
j
+
k
)
(j+k)
(j+k)%
n
=
0
n=0
n=0,此时,数组完成排序,例如
n
u
m
s
=
[
1
,
2
,
3
,
4
]
,
k
=
3
nums = [1,2,3,4], k = 3
nums=[1,2,3,4],k=3,其过程如下:
第一次,数组第0号位置和第
(
0
+
3
)
(0+3)
(0+3)%
4
=
3
4=3
4=3号位置交换顺序,得
n
u
m
s
=
[
4
,
2
,
3
,
1
]
nums = [4,2,3,1]
nums=[4,2,3,1];
第二次,数组第0号位置和第
(
3
+
3
)
(3+3)
(3+3)%
4
=
2
4=2
4=2号位置交换顺序,得
n
u
m
s
=
[
3
,
2
,
4
,
1
]
nums = [3,2,4,1]
nums=[3,2,4,1];
第三次,数组第0号位置和第
(
2
+
3
)
(2+3)
(2+3)%
4
=
1
4=1
4=1号位置交换顺序,得
n
u
m
s
=
[
2
,
3
,
4
,
1
]
nums = [2,3,4,1]
nums=[2,3,4,1];完成排序。
2、当
k
k
k和
n
n
n有公因数的时候,需要进行最大公因数次循环就才可以完成交换。
每一次循环做法和方法一一样。例如
n
u
m
s
=
[
1
,
2
,
3
,
4
]
,
k
=
2
nums = [1,2,3,4], k = 2
nums=[1,2,3,4],k=2,其过程如下:
第一次,数组第0号位置和第
(
0
+
2
)
(0+2)
(0+2)%
4
=
2
4=2
4=2号位置交换顺序,得
n
u
m
s
=
[
3
,
2
,
1
,
4
]
nums = [3,2,1,4]
nums=[3,2,1,4];
第二次,数组第0号位置和第
(
2
+
2
)
(2+2)
(2+2)%
4
=
0
4=0
4=0号位置交换顺序,由于陷入循环,所以应该退出这次循环,进行下一次循环,此时将1号位置作为交换点,即数组第1号位置和第
(
1
+
2
)
(1+2)
(1+2)%
4
=
3
4=3
4=3号位置交换顺序,得
n
u
m
s
=
[
3
,
4
,
1
,
2
]
nums = [3,4,1,2]
nums=[3,4,1,2];
循环退出条件是数组
i
i
i号位置是否等于
(
i
+
k
)
(i+k)
(i+k)%
n
n
n。
代码如下:
//第三种:
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k = k % n;
int count = gcd(n,k);
cout<<count;
int j;
for (int i=0; i<count; i++) {
j=i;//将数组i号位置作为交换点
do{
j=(j+k)%n;
swap(nums[i],nums[j]); //nums[i],nums[j]交换位置
}while(i!=j); //不满足退出
}
}
};
复杂度分析:
时间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 为数组的长度。
空间复杂度:
O
(
1
)
O(1)
O(1)。
总结:
做算法题的时候,应该将思路缕清----用公式推到出具体步骤,或者用流程图将每一步写清楚,同时将每一步需要条件写明。只有这样,写出来的程序才会逻辑情绪无明显bug。