問題來源於<知乎>用java寫出算法:54張撲克,分成上下兩等份有規律的洗牌,多少次可以返回初始值
代碼思路源於<知乎> @Tim Chen
- 相關代碼:OneTest.java
package pers.mine.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class OneTest {
public static void main(String[] args) {
shufflePoker(54);
}
/**
* 計算洗牌多少次可以回到初始值 奇數-偶數效果一樣 shufflePoker(53)<==>shufflePoker(54)
* 洗牌規則:後半部分依次間隔插入前半部分 123abc --->1a2b3c
* @param n 牌數
*/
public static void shufflePoker(int n){
if(n<2){return;}
int[] poker=new int[n];
//初始化poker
for(int i=0;i<n;i++){
poker[i]=i+1;
}
//洗一次 54 的話從1-27 28-54 53的話1-27 28-53 索引27
int halfIndex=(n+1)/2; //撲克後半部分起始點索引
int[] newPoker=new int[n];
for(int i=0;i<n;i++){
if(i%2==0){
newPoker[i]=poker[i/2];
}else{
newPoker[i]=poker[halfIndex+(i/2)];
}
}
//System.out.println(Arrays.toString(newPoker));
//存放每一個環
List<List<Integer>> rings=new ArrayList<List<Integer>>();
/**標記數組-->觀察環是否查找完畢 索引 0 表示第一張牌 依次到53表示最後一張
* 值爲0表示還沒有被包含到環中,已包含則值爲1
*/
int[] flagArr=new int[n];
int nextRingStartNum; //下一個ring開始數字
while((nextRingStartNum=nextRingStartNum(flagArr))!=-1){
List<Integer> one=getRing(poker, newPoker, nextRingStartNum, flagArr);
rings.add(one);
}
//存放每個ring大小
List<Integer> ringLengths=new ArrayList<Integer>();
//獲取每個ring大小然後計算最大公倍數
System.out.println("打印所有環:");
for(List<Integer> one:rings){
System.out.print("\t");
for(int x:one){
System.out.printf("%-3d - ", x);
}
System.out.print(one.get(0));
System.out.println();
ringLengths.add(one.size());
}
System.out.println("每個環的長度:"+ringLengths.toString());
//計算最大公倍數
System.out.println("最小洗牌次數:"+nlcm(ringLengths));
}
//返回下一個Ring開始的數字 -1代表沒有了
public static int nextRingStartNum(int[] flagArr){
int length=flagArr.length;
for(int i=0;i<length;i++){
if(flagArr[i]==0){
return i+1;
}
}
return -1;
}
//從指定數字開始獲取一個環 ,返回環長度 並標記走過的 牌號
public static List<Integer> getRing(int[] oldArr,int[] newArr,int startNum,int[] flagArr){
List<Integer> ring=new ArrayList<Integer>();
//獲取開始時索引位置
int startIndex=getIndex(oldArr,startNum);
int nowNum=startNum;
int nextIndex=0;
do{
//記住走過的數字
flagArr[nowNum-1]=1;
//加入環
ring.add(nowNum);
//查詢當前牌號的下一落腳索引
nextIndex=getIndex(newArr,nowNum);
//獲取oldArr中【落腳點索引】處數字,並設置爲當前數字
nowNum=oldArr[nextIndex];
}while(startIndex!=nextIndex);
//System.out.println(ring);
return ring;
}
//遍歷獲取數組中指定數字的索引
public static int getIndex(int[] arr,int key){
int length = arr.length;
for(int i=0;i<length;i++){
if(arr[i]==key){
return i;
}
}
return -1;
}
//n個數的最大公倍數---先計算兩個的,再慢慢向後推移
public static long nlcm(List<Integer> ls){
if(ls==null||ls.size()<2){
return 0;
}
int size=ls.size();
//兩個環長度的公倍數 a*b/gcd(a,b)
long oneCM=ls.get(0);
for(int i=1;i<size;i++){
oneCM=ls.get(i)*oneCM/gcd(oneCM,ls.get(i));
}
return oneCM;
}
//兩個數的最大公約數--輾轉相除法
public static long gcd(long x, long y)
{
long temp=x%y;
while(temp!=0){
x=y;
y=temp;
temp=x%y;
}
return y;
}
}
- 測試結果