扑克牌洗牌问题<用java写出算法:54张扑克,分成上下两等份有规律的洗牌,多少次可以返回初始值>

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

}
  • 测试结果
    这里写图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值