题目: 轮转数组
描述:
给定一个整数数组 nums,将数组中的元素向右轮转 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]
leetcode链接
一开始就想到了将整体移动k次分为k轮,一轮移动一个位置,最后一个多出的元素存到第一个元素中
void rotate(vector<int>& nums, int k) {
while(k--){
int tag = nums[nums.size()-1];
for(int i = nums.size()-1;i>=1;i--){
nums[i] = nums[i-1];
}
nums[0] = tag;
}
}
但是该方法的时间复杂度为o(n²),提交不通过,那么我们如何避免此种重复的复制,只需要一次就能够把元素复制到最终的位置呢,这里就引出了一种时间复杂度为o(n)的方法。
方法一:额外数组
我们对下标为i的元素移动k个位置,那么它最后的位置应该为k+i,但是k+i可能超过了数组的界限,因此我们将(k+i)%n,其中n为数组的长度,那么就得到了元素的最终位置,我再申请一个同样长度为n的数组,将元素存储到它移动后的位置上,最后将此数组复制到原数组中。此方法的缺点是空间复杂度为o(n).
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());
}
关于vector的assign函数,assign()的主要功能是给vector重新赋值,更具体的,它会先把vector中的内容删除,然后根据用户提供的参数进行填充,assign一共有三种参数形式:
1:void assign (InputIterator first, InputIterator last);
将vector的内容替换为[first,last)地址存储的内容,复制的区间为左闭右开
2:void assign (size_type n, const value_type& val);
将vector中的内容替换为n个值为val的元素
3:void assign (initializer_list<value_type> il);
此形式将vector的内容替换为初始化列表il中的元素。
方法二:轮转数组
我们移动k次,一共有k%n个元素被移出数组外,那么我们假如翻转整个数组一次,末尾的k%n个元素就到了数组的头部,此时的整体的位置是正确的,但是顺序是逆序的,所以我们将0到k-1的元素再翻转一次,k到n-1的元素再翻转一次,就能够得到顺序正确的数组。次方法的时间复杂度为o(n),空间复杂度为o(1)
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);//第二次翻转0到k-1
reverse(nums, k, nums.size() - 1);//第三次翻转k到n-1
}
方法三:环状替换
我们方法一的唯一缺点就是需要额外的额数组,假如我们不存到额外的数组中,直接覆盖掉最终位置的元素,此时就会造成元素的丢失,那么我们应该用一个temp变量来存储交换后的元素,我们假设从第一个元素nums[0]开始,最开始temp存储的为nums[0],然后将(0+k)%n处位置的元素与temp交换,然后再交换temp和(0+k+k)%n处位置的元素,然后以此类推,最终我们一定会回到最初的元素,证明如下:
假设从位置为i的元素开始,经过n次替换后,到达的位置应该为(i+nk)%n = i,也就是元素i所在的位置,所以我们最多需要n次来达到最初的位置,此种情况也是遍历的数组n个元素,但是我们从一个元素出发开始替换,不一定会遍历到所有元素,此时我们应该从第二个元素开始继续替换,那我们什么时候停止替换呢,答案很明显就是我们替换了n个元素的时候,也就是把每个元素都放在了其最终的位置上的时候,我们停止替换,那么我们需要一个count来记录我们替换的元素的个数。
该方法的时间复杂度为o(n)
空间复杂度为o(1)
void rotate(vector<int> &nums,int k){
int temp;//temp来保存替换后的元素
int count = 0;
for(int i=0;i<nums.size();i++){
temp = nums[i];//从位置i开始替换
int j = i;
do{
j = (j+k)%nums.size();
int tag = temp;
temp = nums[j];//交换temp和nums[j]
nums[j] = tag;
count++;
}while(j!=i);//回到最开始的位置循环结束
if(count == nums.size()){//当每个元素都移动后,退出循环
break;
}
}
}