轮转数组(时间复杂度不同的三种思路)

(来源: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)。

到此,本文结束^_^

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>