问题来源于<知乎>用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;
}
}
- 测试结果