(来源:LeetCode)
题目
分析
其实一次轮转就是将最后一个数据放到最前面,其他数据整体向后移动一位。k为几就重复这个行为几次。
思路1
我们应该很快能想到最直接的一种思路。while(k--)……循环内完成两件事,保存最后一个数据,将其他数据整体向后移动1位,将最后一个数据放到最前面的位置。
在我们将需要平移的数据向后移动时,同样需要使用到循环。
平台已经给好了函数头,有函数名和参数。我们只需要去补完函数体。
我们可以先看看给的参数是些什么,第一个是给的数组,第二个是数组长度,第三个是要轮转的次数。
OJ平台是不需要我们写main函数和头文件的。
此时可以看到,点击运行后的用例是通过的。
但是,这个算法真的好吗?
我们点一下提交,多来几个用例看看:
说明我们的算法在数据大时,时间复杂度太大。
我们可以计算这个算法的时间复杂度。
因为numsSize、k都为变量,都可看做N,而外循环执行k次,内循环执行N次左右,所以时间复杂度为O(N²)。
可以看到N²这种复杂度, 变化很抖,也就是程序效能很差。
那么我们现在能不能有更好的算法,也就是不要两层循环的嵌套。
其实在平台的题目下面还有这样一段话:
思路2
现在我们来申请一个新的数组,大小和原数组一样大。
我们知道,其实轮转几次,也可以看做是把原来数组里的几个数据放到最后,将其他的放到前面:
对应代码:
void rotate(int* nums, int numsSize, int k)
{
int newArr[numsSize];
for (int i = 0; i < numsSize; ++i)
{
newArr[(i + k) % numsSize] = nums[i];
}
for (int i = 0; i < numsSize; ++i)
{
nums[i] = newArr[i];
}
}
newArr[(i + k) % numsSize] = nums[i];这一句代码就能实现两种操作:将开头k个数据放到最后,将剩余数据放到前面。
我们来理解一下这句代码(假设k为3,numsSize为7):
最开始i=0时,3%7,结果为3,所以新数组下标为3的地方放入了1。以此类推,1~4被放入到新数组下标为3 4 5 6的四个位置,也就是最后的四个位置。
直到i为4时,7%7的结果为0,也就是将下标为4,即5放到新数组下标为0的位置。以此类推,5 6 7就被放到了新数组的前三个位置。
最后得到的就是我们要的效果。
但是我们是要改变原数组的,所以还要把重新排好的数据再挪回去。
那么,这个算法的时间复杂度是多少呢?可以看到是2N去掉系数,最后为O(N)。
但是空间复杂度也是O(N),我们申请了新的大小为N的数组空间。也就是说,在这个算法中,我们牺牲了空间换来了时间。
思路2的算法点击提交后,这次我们就通过了。
但这个思路还不是最佳的。我们来看看更巧妙的思路3
思路3
原数组: 1 2 3 4 5 6 7
• 前n-k个数逆置:4 3 2 1 5 6 7
• 后k个数逆置: 4 3 2 1 7 6 5
• 整体逆置: 5 6 7 1 2 3 4
我们要做的一件事是逆置。什么是逆置?首先我们在将前n-k个数据进行逆置时,也就是将1234变为了4321,后面的567没有变化。这就是逆置。
我们可以把逆置封装成一个函数。
怎么实现逆置呢?
我们用到两个指针。left与right。left最开始指向第一个数据,right最开始指向最后一个数据,两个数据进行交换。然后left向右走,right向左走,走完再交换。重复这个步骤直到left和right重合时无需再交换。
有了这个思路后我们可以写出代码:
可以看到运行通过,但是当我们提交:
却执行出错。
我们可以在下方看到,当数组中只有一个-1,k=2时,我们的输出与预期输出不符。
我们需要再对k进行一些处理。
这样就保证了我们的k不会超过numsSize。
这时我们的代码就能通过提交了。
只有一个循环,交换N/2次,因为去掉系数,所以这个算法的时间复杂度为O(N);没有额外申请空间或者说只有三次函数调用开辟的函数栈帧,所以空间复杂度为O(1)。
到此,本文结束^_^