代码解密刘谦春晚魔术

1 前言

随着2024 年春晚的完美落幕,刘谦的魔术表演再度成为了全国人们热议的焦点。作为一名程序员我们尝试从编程的角度来揭秘刘谦的魔术,通过代码穷举所有情况验证是否剩下的两张牌都一致。

2 魔术步骤

1、准备4张扑克牌并平均撕成两份,叠放在一起,形成8个半张牌的形式:ABCD-ABCD。

2、将牌堆顶的数量为名字字数的牌移至牌堆底。这一步实际上并不改变牌的本质顺序,只是营造神秘感。

3、将前三张牌放在牌堆中间,并取出牌堆顶的牌放置在一旁。此操作确保头尾两张牌相同。

4、取出牌堆顶的若干张牌插入牌堆中间。具体取牌数依据地域差异,如南方人取1张,北方人取2张,若不确定则取3张。但这一步在魔术中并不起决定性作用。

5、男生扔掉牌堆顶1张,女生扔掉牌堆顶2张。这一步是为了调整牌的数量,以适应后续的操作。

6、执行"见证奇迹的时刻"循环。每说一个字,就取出牌堆顶的牌放置在牌堆底。这一步骤通过多次循环操作,改变了牌的顺序。

7、执行关键操作:从牌堆顶开始,每次先将牌堆顶的一张牌放在牌堆底,再扔掉牌堆顶的一张牌。重复此操作直到只剩一张牌。检查这张牌和最初放置在一旁的牌是否吻合。若吻合,则魔术成功。

需要注意的是,这个魔术的关键在于通过特定的操作步骤和牌的数量变化,最终找到和最初放置在一旁的牌相同的牌。而地域、性别等因素在魔术中并不起决定性作用,更多的是为了增加魔术的趣味性和神秘感。

3 代码实现

代码如下:

ini 复制代码
package com.mrxu.admin.test;

public class MoShuTest {

    /**
     * 计算某种情况下 最后剩余的两张牌
     * @param nameLength 名字长度
     * @param pre3ToMiddle 前三张插入的中间位置
     * @param district 根据地区获得的数据 1、2、3
     * @param districtToMiddle 地区获得的数据插入的中间位置
     * @param sex 性别变量
     */
    private static void run(int nameLength,int pre3ToMiddle,int district,int districtToMiddle,int sex) {
        char firstChar,lastChar;
        // 1 准备四张扑克牌,将它们平均撕成两份,并叠放在一起,形成八张半张牌的形式,即"ABCD-ABCD"的形式。
        char[] orgArray = {'A','B','C','D','A','B','C','D'};
        // 2 将牌堆顶的牌数量为名字字数的牌移至牌堆底。这个步骤其实并不影响牌的顺序,只是为了营造神秘感
        orgArray = moveToBottom(orgArray,nameLength);
        // 3 从牌堆顶取出三张牌,随意地插入到牌堆的中间位置。这一步是为了确保头尾两张牌是一样的。
        orgArray = moveToMiddle(orgArray,3,pre3ToMiddle);
        // 4 将牌堆顶的一张牌取出,放置在一旁,这张牌将作为后续验证的关键牌。
        firstChar = orgArray[0];
        orgArray = removeTop(orgArray,1);
        // 5 根据南北地区不同,从牌堆顶取出一定数量的牌插入到牌堆中间。这一步骤中的牌数选择可以根据实际情况进行调整,南方人取1张,北方人取2张,如果不确定可以取3张。但实际上这个步骤并不会影响魔术的成功与否。
        orgArray = moveToMiddle(orgArray,district,districtToMiddle);
        // 5 如果是男生,就扔掉牌堆顶的一张牌;如果是女生,就扔掉牌堆顶的两张牌。这一步是为了让男女生的牌堆数量有所不同,增加魔术的趣味性。
        orgArray = removeTop(orgArray,sex);
        // 6 执行"见证奇迹的时刻"循环,即每说一个字就从牌堆顶取出一张牌放置在牌堆底。这个步骤会改变牌的顺序,但不会影响头尾两张牌的一致性。
        for(int i = 0;i < "见证奇迹的时刻".length();i++) {
            orgArray = moveToBottom(orgArray,1);
        }
        // 7 最后,从牌堆顶开始,每次先将牌堆顶的一张牌放在牌堆底,再扔掉牌堆顶的一张牌。重复以上操作直到只剩一张牌。检查这张牌和之前放置在一旁的牌是否吻合。如果吻合,则魔术成功。
        while(orgArray.length != 1) {
            orgArray = moveToBottom(orgArray,1);
            orgArray = removeTop(orgArray,1);
        }
        lastChar = orgArray[0];
        System.out.println(firstChar+":"+lastChar);
    }

    /**
     * 移动前n张牌到最下面
     * @param orgArray 牌数据
     * @param count 移动数量
     * @return 移动后的牌数据
     */
    private static char[] moveToBottom(char[] orgArray,int count) {
        char[] rsArray = new char[orgArray.length];
        for(int i = 0;i < orgArray.length;i ++) {
            if(i < count) {
                rsArray[orgArray.length - count + i] = orgArray[i];
            } else {
                rsArray[i - count] = orgArray[i];
            }
        }
        return rsArray;
    }

    /**
     * 移动上面moveCount张牌到下面中间位置
     * @param orgArray 牌数据
     * @param moveCount 移动数量
     * @param middlePoint 插入中间位置
     * @return 插入后的牌数据
     */
    private static char[] moveToMiddle(char[] orgArray,int moveCount,int middlePoint) {
        char[] rsArray = new char[orgArray.length];
        for(int i = 0;i < orgArray.length;i++) {
            if(i < moveCount) {
                rsArray[middlePoint+i] = orgArray[i];
            }
            else {
                if(i < moveCount+middlePoint) {
                    rsArray[i-moveCount] = orgArray[i];
                }
                else {
                    rsArray[i] = orgArray[i];
                }
            }
        }
        return rsArray;
    }

    /**
     * 删除上面n张牌
     * @param orgArray 牌数据
     * @param count 删除数量
     * @return 删除后的牌数据
     */
    private static char[] removeTop(char[] orgArray,int count) {
        char[] rsArray = new char[orgArray.length - count];
        for(int i = count;i < orgArray.length;i++) {
            rsArray[i-count] = orgArray[i];
        }
        return rsArray;
    }

    public static void main(String[] args) {
        int allCount = 0;
        for(int nameLength = 1;nameLength <= 8;nameLength ++) {
            for(int pre3ToMiddle = 1;pre3ToMiddle <= 4;pre3ToMiddle ++) {
                for(int district = 1;district <= 3;district ++) {
                    for(int districtToMiddle = 1;districtToMiddle <= 6-district;districtToMiddle ++) {
                        for(int sex = 1;sex <= 2;sex ++) {
                            run(nameLength,pre3ToMiddle,district,districtToMiddle,sex);
                            allCount++;
                        }
                    }
                }
            }
        }
        System.out.println("总次数:"+allCount);
    }

}

3.1 run 给特定参数输出最终剩余的两张牌

3.2 moveToBottom方法,移动前n张牌到最下面

3.3 moveToMiddle方法,移动上面n张牌到下面中间位置

3.4 removeTop方法,删除上面n张牌

3.5 main方法,穷举所有情况输出结果

最终输出有768种情况,没中情况下留下的两张数字都一样。

4 总结

刘谦老师魔术本质其实就是约瑟夫环的问题。作为一名程序员应有把现实问题通过程序解决的能力,对编程感兴趣的可以关注我,我会定期分享编程技术。

相关推荐
ZHOUZAIHUI1 小时前
WSL(Ubuntu24.04) 安装PostgreSQL
开发语言·后端·scala
i02082 小时前
SpringBoot 项目配置
java·spring boot·后端
月屯2 小时前
后端go完成文档分享链接功能
开发语言·后端·golang
Franciz小测测3 小时前
Python连接RabbitMQ三大方案全解析
开发语言·后端·ruby
海梨花3 小时前
又是秒杀又是高并发,你的接口真的扛得住吗?
java·后端·jmeter
Livingbody3 小时前
win11上wsl本地安装版本ubuntu25.10
后端
用户8356290780514 小时前
如何在 C# 中自动化生成 PDF 表格
后端·c#
星释4 小时前
Rust 练习册 44:Trait 中的同名函数调用
开发语言·后端·rust
京东零售技术4 小时前
并发丢数据深度剖析:JED的锁机制与事务实战踩坑及解决方案
后端
f***68604 小时前
问题:Flask应用中的用户会话(Session)管理失效
后端·python·flask