数组循环移位中的学问

  始终相信能听明白和能说明白是两种不同的境界,如果对于一件问题,你说你明白了,但是你却不能说明白,那在我眼里就会有这样的一个事实,那就是:你并没有真正地明白。当然,也存在这种情况,那就是说是说明白了,但是未必对,这正是我担心的情况,所以还请阅读过我所有文章的朋友们指出我文章中的错误。谢谢你们……

1.问题定义

  设计一个算法,把一个含有n个元素的数组A循环右移k位,要求时间复杂度是O(n)的。举个例子说明一下算法要达到的效果:数组中包含的元素为123456,现在要循环右移2位,则变成了561234 。

2.解决方案

2.1 朴素的思路

  往往是这样,朴素的思路虽不能满足题目的要求,但是确可能是优化的依据。对于这个问题朴素的解法是,每次完成一次右移,并且完成一次右移的时间复杂度是O(n),这样的操作要进行k次,所以时间复杂度是O(n*k)。但是现在想一想,既然已经知道要右移的位数k,那么要是还一步一步的移是不是显得有点笨了呢。为什么不一步到位呢?

2.2 空间换时间

  对于算法上的优化,以空间换时间是一种比较常规的办法了。可以借助一个辅助数组T,先把数组A的n-k+1到n位数组中的元素存储到T中,然后再把数组A中的1到n-k位数组元素存储到辅助数组T中,然后将数组T中的元素复制回数组A,这样就完成了数组的循环右移,时间复杂度为O(n),但是呢,却增加了O(n)的空间复杂度。如果要求最多只能允许使用两个辅助空间呢?这可就要想一想能不能进一步优化了。

2.3 利用“翻转”完成优化

  考虑一下数组A中元素123456循环右移2位到底是怎么个情况!!!可不可以这样实现呢?将数组A分成两个部分:A[0~n-k-1] 和 A[n-k~n-1] ,将这两个部分分别翻转,然后放在一起在翻转(逆序)。具体是这样的:

(1)翻转1234:123456 ---> 432156

(2)翻转56:     432156 ---> 432165

(3)翻转432165:432165 ---> 561234

看看吧,确实完成了翻转的操作。现在来看一下代码(灰常简单):

 1 //逆序
 2 void Reverse(int A[],int b,int e)
 3 {
 4     for(;b < e;b++,e--)
 5     {
 6         int temp = A[b];
 7         A[b] = A[e];
 8         A[e] = temp;
 9     }
10 }
11 //循环右移
12 void RightShift(int A[],int,int n,int k)
13 {
14     Reverse(A,0,n-k-1);
15     Reverse(A,n-k,n-1);
16     Reverse(A,0,n-1);
17 }

现在可以分析一下时空复杂度了,时间复杂度显然是O(n),主要是完成翻转(逆序)操作,并且只用了一个辅助空间。显然这是一个不错算法哦。。。

注:

细心的读者可能就发现,这里的右移并没有说小于n呀,那么一个k>n那就会出现问题,经过简单的分析,我们发现如果A[]={1,2,3,4,5,6},数组长度是6,那么循环右移7位和循环右移1位是一样的,所以只需要把函数RightShift()中的k取值为k=k%n。如此一来问题就解决了。

学习中的一点总结,欢迎拍砖哦^^

原文地址:https://www.cnblogs.com/BeyondAnyTime/p/2585789.html