完美洗牌问题——核心思想与代码

本文深入探讨完美洗牌问题,解释如何通过循环挤兑解决长度为2,8,26,80等特定长度数组的洗牌。关键在于识别和挤兑环状结构,首先扭转数组部分以形成合规特定长度,然后应用循环函数依次挤兑各个环。文章提供详细的代码实现,解析了整个洗牌过程,包括扭转、循环挤兑等核心步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

完美洗牌问题系列文章:

提示:很重要的思想
1.完美洗牌问题——整体交换数组的左右2部分
2.完美洗牌问题——计算位置i下次要被挤到哪个位置j?
这2个文章,都是为本文做铺垫的,循序渐进理解什么是完美洗牌问题,如何解决完美洗牌问题?


完美洗牌问题的定义

完美洗牌问题的定义:

给你一个arr,长度是偶数长度S,左边部分长度为N,右边部分长度为N
请将左边右边部分交叉组合,保证时间复杂度不会超过o(n),空间复杂度不会超过o(1)——即完美洗牌
图1


一、通过循环挤兑i到j,实现完美洗牌问题

根据文章:2.完美洗牌问题——计算位置i下次要被挤到哪个位置j?
可以知道,我们从start=1位置出发,可以不断地调用f(arr,i,S)函数【i位置应该去哪里?j,要求j,arr长度是偶数长S】,去挤兑另一个位置j,然后当j回到start位置时,全部挤兑完成,看图:
图2
经过一圈循环,将i的数,挤兑到j处,完成了完美洗牌问题


二、但是一个数组arr,它可能存在多个挤兑环

比如,当长度S=6时
图3
发现了没?S=6时,N=3,而从1挤兑到2,2挤兑到4,4很快挤兑到了1,形成了一个环,另外的那些还搞不定呢!!!

所以到这里,本题的重要记忆结论来了!!!!,直接背
记结论!
记结论!
记结论!

当长度S=3^^k - 1 时:即2,8,26,80这种特定的长度时,
他们的环起点有这些:1,3,9,……,S=3^^k - 1

而且,我们求解的时候,一定要找长度最长那个环开始挤兑,这就要求,我们需要找到<=S的3^^k - 1
比如S=14这种,显然不符合特定长
那要找到哪个3^^k - 1<=14
显然k=2时,3^^k - 1=8,它死最近的<=4的特定长度。
故我们要先扭转一部分数据到左边,凑合为左边能挤兑的8长度(S=8,N=4)【然后就能用环起点挤兑了】
剩下的再看看他们能组成的最长特定长是多少?再去挤兑合适的数组。——这就是破解完美洗牌问题的方法,下面细说


三、通过扭转、挤兑最大那个环,破解完美洗牌问题

二中的举例:
图3
(1)S=14,N=7,所以呢,咱先把那个能挤兑的整体长度8挤兑了,要求,右边有half=8/2=4这么多,要和左边的N-half那部分,相互扭转交换。
——这就是上面所说的文章1做的事情:1.完美洗牌问题——整体交换数组的左右2部分

(2)剩下的S=6长度的呢,还是不符合特定长,没法挤兑【看下图粉色部分】
一样的,我们还需要找到哪个3^^k - 1<=6
显然,k=1时,到哪个3^^k - 1 = 2<=6
2这个特定长度,先挤兑再说:也就是把L5R5放一起,
即把右边R5R6R7中,half=2/2=1这么多数,跟左边N-half这么多扭转交换,再挤兑
图4
(3)还剩4个,仍然不是特定的长度【看上面这个图:橘黄色部分】
一样的,我们还需要找到哪个3^^k - 1<=4
显然,k=1时,到哪个3^^k - 1 = 2<=4
2这个特定长度,先挤兑再说:也就是把L6R6放一起,
即把右边R6R7中,half=2/2=1这么多数,跟左边N-half这么多扭转交换,再挤兑

(4)还剩下2个,恰好满足了特定的长度,直接挤兑即可;
最终,从最长的那个环开始挤兑,直到全部环都挤兑玩,完美洗牌问题敲定!!!!
图5

我们先单独写
一个合规特定长度为S,循环挤兑各个环,达成这种特定长度S下的完美洗牌问题的代码【比如S=2,8,28,80这种】:
从arr的start环点开始,合规特定长度为S,最大合规满足3^^k - 1=S的k跟着来,k意味着有k个环需要挤兑
因为我不用0坐标,所有i=0开始要抠清楚arr的那些边界,
i控制最大到k个环,全部环都要挤兑完成
每次出发点从trigger=1出发,依次寻找这些环触发点:1,3,9,27,81……
cur=j就是位置x下一次需要去的地方
整体代码好好细品:

//给你一个起点,给你一个3^k-1的长度S,给你一个环的个数k,让你挨个挤兑到相应的位置
    public static void cycle(int[] arr, int start, int S, int k){
        //S=3^k-1,即长度为2,8,26,80的长度,一定有k个环需要挤兑
        //环有k个,每个的出发点trigger是1,3,9,27,81,,,3^k-1,共k个触发点
        for (int i = 0, trigger = 1; i < k; i++, trigger *= 3) {
            //挤兑出去之前,我是preValue
            int preValue = arr[start + trigger - 1];//-1是因为起点用1表示
            //即将去哪个位置?
            int cur = findNextIndex(trigger, S);//用S这个长,S/2==N去推下一个去的位置,先拿变量记住
            while (cur != trigger){
                int nextValue = arr[start + cur - 1];//tmp保存我
                arr[start + cur - 1] = preValue;//被挤进来
                preValue = nextValue;//刚刚cur处的值就作为我,要去挤兑别人的数值了
                cur = findNextIndex(cur, S);//nextValue去挤兑谁呢,cur开始推理
            }
            //直到最后一个元素,此时cur==trigger时,回到了原点,则最后那个nextValue放回原点
            arr[start + cur - 1] = preValue;//最后挤兑,本环结束
        }
    }

大流程: —— 完美洗牌问题的代码
就是不断寻找,满足满足3^^k - 1<=S的特定长度,然后扭转half这么多,凑成左边合规特定长度,再利用上面cycle函数循环去挤兑
代码细细品味:

public static void shuffle(int[] arr, int L, int R){
        //当洗牌长度为0就完成了所有任务
        while (R - L + 1 > 0){
            int S = R - L + 1;//区间长度
            int base = 3;//找一个3^k方够近S的值
            int k = 1;//从k==1次方开始,统计环的个数
            while (base < (S + 1) / 3){
                base *= 3;
                k++;//每次统计k个环
            }
            //此时3^k-1已经最接近S了,那我们可以先挤兑3^k-1这么长的的区间,
            //首先,需要我们拿右边的half=base-1/2这么多扭到左边,也就是把L+hanf---mid---mid+half区间调换位置
            //让左边组合层俩half,一个base-1这么长
            int half = (base - 1) >> 1;
            int mid = L + ((R - L) >> 1);//取数组中点,左边右边界,上中点
            rotate(arr, L + half, mid, mid + half);
            //然后洗牌左边的base-1长度
            cycle(arr, L, base - 1, k);//参数,L是起点,S==base-1==3^k-1这么长,k个环需要触发挤兑去

            L = L + base - 1;//切换我左边的L,已经搞定了base-1这么长了,剩下的右边部分继续分
        }
    }

测试一波:

public static void test(){
        int[] arr = {1,2,3,4,5,6,7,8};
        perfectShuffle(arr);
        for(Integer i:arr) System.out.print(i +" ");
    }

    public static void main(String[] args) {
        test();
    }

总结

提示:重要经验:

1)理解清楚完美洗牌问题的定义
2)记忆一个结论:当长度S=3^^k - 1 时:即2,8,26,80这种特定的长度时,
他们的环起点有这些:1,3,9,……,S=3^^k - 1 ,也就是说,整到一个合规特定的长度S时,要依次把这k个环全部挤兑完,就是上面说的cycle函数
3)完美洗牌问题中由于arr可能不止一个环,而且还有先搞定最大的合规特定长度S那个部分,就需要先将右边部分的half扭转交换到左边,这里就需要扭转交换函数,3次逆序,一定要掌握;
4)破解完美洗牌问题,先不断寻找,满足满足3^^k - 1<=S的特定长度,然后扭转half这么多,凑成左边合规特定长度,再利用上面cycle函数循环去挤兑
5)这个题目,笔试时只能抄写代码,面试时尽力写吧,先给面试官讲清楚思想,差不多了就行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值