一个时间复杂度为O(n),空间复杂度为O(n)的算法:java
n=4时,i<=n,位置变化(原位置->目的位置)面试
1->2, 2->4, 3->6, 4->8算法
i>n,位置变化(原位置->目的位置)编程
5->1, 6->3, 7->5, 8->7数组
任意第i个元素最终位置都为(2i)%(2n+1)。spa
public void LocationReplace( int a[], int n){ int n2=2*n; int b[] = new int[n2+1]; for( int i=1; i<=n2; i++) b[(2*i)%(n2+1)] = a[i]; for( int i=1; i<=n2; i++) a[i]=b[i]; }
完美洗牌算法:code
由上面的算法可知,n=4时,1->2->4->8->7->5->1, 3->6->3 两个圈。io
i->2i mod(2n+1),[1=<i<=2n]class
void CycleLeader(int *a, int from, int mod){//mod is 2n+1 int t; for( int i=from*2%mod; i!=from; i=i*2%mod){ t=a[i]; a[i]=a[from]; a[from]=t; } }
结论:若2n=3^k-1,则可肯定圈的个数及各自头部的起始位置 ,且刚好有k个圈,每一个圈头部位置为1,3,9,...,3^(k-1)。读书笔记
分治,先找到2m=3^k-1<2n,其中m为小于n中最大的知足该式子的。
这样,1,...,m 与 n+1,...,n+m知足上面的结论,可是现实的状况是:
1,...,m,m+1,...,n,n+1,...,n+m,n+m+1,...,2n
因此这两个颜色的数应该先换位子,这样前2m个数就刚好有k个圈。
void reverse(int *a, int from, int to){ int t; for( ; from<to; ++from, --to){ t=a[from]; a[from]=a[to]; a[to]=t; } } void rotate(int *a,int m, int n){ reverse( a, 1, n-m); reverse( a, n-m+1, n); reverse( a, 1, n); }
完美洗牌算法步骤:
输入:a[1,2,...,2n]
第1步:找到2m=3^k-1,使得3^k=<2n<3^(k+1)
第2步:把数组中的a[m+1, ...,n+m]部分循环右移m位(换位子)
第3步:前面2m长度的数组,k-1圈,每圈都执行CycleLeader方法,且mod=2*m+1
第4步:对后面的a[2m+1,...,2n]
void perfectShuffle( int *a, int n){ int n2, m, i=0, k,t; for( ; n>1;){ //first step n2=2*n; for( k=0, m=1; n2/m>=3; ++k, m*=3) ; m/=2; //second step rotate( a+m, m, n); //third step for( i=0,t=1; i<k; ++i, t*=3) CycleLeader(a, t, m*2+1); //fourth step a+=2*m; n -=m; } //n==1 t=a[1]; a[1]=a[2]; a[2]=t; }
本文为《编程之法 面试和算法心得》读书笔记。