leecode“旋转数组”问题,“环状替换”解法的思路(小白向)
思路:
假设一数组 a[1,2,3,4,5,6,7,8,9] 移动位数k=3
从1开始,要将a[0]右移三位,移到a[3]
a[3]右移三位,移到a[6]
a[6]移到a[0]
回到了a[0],我们不妨称这叫一个环路,这个环路的长度是一个数组的长度,按下标表示为0360,以元素来表示是1471。暂且抛去其他,我们看怎样实现一个环路:
如果只简单地令a[3]=a[0],那么数组变为a[1,2,3,1,5,6],此时出现问题,a[3]原本的值被覆盖丢失了。
于是我们先交换a[3]和a[0],数组变为a[4,2,3,1,5,6],这样交换完成后,原本a[3]的值被临时存储到a[0]中,
而a[0]已经赋值给了a[3],所以值不会丢失。
接下来很简单,依次将剩下的非首元素的环路元素与a[0]交换。
具体到上面的0360,可以表述为,a[3]、a[6]依次与a[0]交换位置,整个环路就交换完成。
解决了一个环路内的元素如何旋转的问题,我们要知道这个数组到底有几个环路
用数组b[1,2,3,4,5,6,7,8]来举例
从元素1开始,很容易看出环路为147258361,
然后看元素2,但元素2已经在元素1的环路中了,所以我们知道,此数组一共三个环路
我们将环路遍历过程完整地写下来,为了更容易理解。如果右移超出了数组长度,我们将数组复制一份到右边
(但这只是为了方便理解所做,真正的数组不变)
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
1 4 7 2 5 8 3 6
一共走过了三个数组的长度
将环路中的每个元素与其后面的两个元素看为一个整体。如123是一个整体、456也是一个整体
那么
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
1 4 7 2 5 8 3 6
| | | | | | | |
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
很容易看出:
环路中元素的个数b*移动的位数k=数组的长度n*遍历走过的数组个数x 即bk=nx
k、n是已知的。我们求出一个环路中元素的个数b,就可以求出环路个数x了。
关键是x
我们看,从元素1开始,以两个元素的间隔向后遍历,最终会回到元素1.那如果再向后遍历呢?
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
1 4 7 2 5 8 3 6 1 4 7 2 5 8 3 6
再向后遍历一个循环,还是一样会回到元素1,此时x=6。以此类推,当x=3 6 9 12....时,遍历一定还会回到元素1
但是因为第一个环路已经遍历完了数组内的所有元素,以后的遍历会把已经遍历完的元素再遍历一遍,这是不需要的。
所以我们取x的最小值。
在bk=nx中
n是固定的,所以我们要取nx的最小值。
显然,nx可以被n、k整除且结果都大于0
所以,nx是n、k的公倍数,又因为nx要取最小,所以
bk=nx=nk的最小公倍数
故 b=nk的最小公倍数/k
每个环路遍历的元素是一样多的,且总和为数组长度n
所以环路数x=n/b
代码:
public static int[] moveArray(int[] list,int k){
int n=list.length;
int x=n/(myMath.minMultiple(n,k)/k);
for(int i=0;i<x;i++){
int j=i,l=i;
do{
int h=((l+k)%n);
int temp=list[j];;
list[j]=list[h];
list[h]=temp;
l=h;
}while (l!=j);
}
return list;
}
以上