代码解密刘谦春晚魔术

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 总结

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

相关推荐
Estar.Lee10 分钟前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
新知图书1 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
盛夏绽放1 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Ares-Wang1 小时前
Asp.net Core Hosted Service(托管服务) Timer (定时任务)
后端·asp.net
Rverdoser3 小时前
RabbitMQ的基本概念和入门
开发语言·后端·ruby
Tech Synapse3 小时前
Java根据前端返回的字段名进行查询数据的方法
java·开发语言·后端
.生产的驴3 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
微信-since811924 小时前
[ruby on rails] 安装docker
后端·docker·ruby on rails
代码吐槽菌6 小时前
基于SSM的毕业论文管理系统【附源码】
java·开发语言·数据库·后端·ssm
豌豆花下猫6 小时前
Python 潮流周刊#78:async/await 是糟糕的设计(摘要)
后端·python·ai