题目
给定一个整数数组 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]
示例 2:输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释: 向右轮转 1 步: [99,-1,-100,3] 向右轮转 2 步: [3,99,-1,-100]
题解
方法一:额外数组
这道题的解法很简单。我们只需要将该数组中对应k位置之后的元素复制到新数组的最前位置,再将k位置前的元素复制到新数组的后续位置即可以完成题目要求。看代码:
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());
}
};
首先我们获取该数组的大小n,并且创建一个新的大小为n的数组。我们遍历原数组,将原数组下标为i的元素放置在新数组下标为(i + k)mod n的位置即可。最后调用assign函数将新数组拷贝回原数组中即可。至此,方法一结束。
方法二:数组翻转
数组翻转是一个很巧妙的解决方法。通过一个表格,我们来看看数组反转的原理。
不难看出,对于任意一个符合题意的数组,只需要进行三次翻转,即可获得所求的数组。下面来看看代码:
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);
}
};
这段代码中,我们声明了一个新的函数reverse用于处理整个数组序列的翻转。该函数有三个参数,分别为一个传入数组和需要翻转的部分的首尾序号。在这个函数中,我们使用了一个循环,依次使用swap函数交换两数的位置,向中间靠拢并执行,最终在数组的中间位置停止。
在rotate函数中,我们首先处理k这个参数。如果k过大,意味着我们需要旋转的圈数就会过多,但是旋转具有周期性,我们只需要得到一个相同的结果,并不需要实际真正把数组旋转k圈。这将提高我们的算法性能。通过对k取n的模,我们得到了旋转的最小次数:新的k。再分别调用三次刚刚写好的reverse函数,分别翻转整个数组,k前半部分数组,k后半部分数组,最终得到所求的数组。封装好一个函数能够极大提升代码的复用性,避免了我们后续的冗余和重复工作。至此,方法二也结束了。
写在最后
对于这一道题,其实还有更多的解决方法,需要我们脱离代码和程序的桎梏,发现一定的规律,去解决问题。永远没有最完美最优秀的算法,但是我们有更合适更具针对性的算法。