代码解密刘谦春晚魔术

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

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

相关推荐
2401_8955213414 分钟前
SpringBoot Maven快速上手
spring boot·后端·maven
disgare29 分钟前
关于 spring 工程中添加 traceID 实践
java·后端·spring
ictI CABL34 分钟前
Spring Boot与MyBatis
spring boot·后端·mybatis
小江的记录本2 小时前
【Linux】《Linux常用命令汇总表》
linux·运维·服务器·前端·windows·后端·macos
yhole5 小时前
springboot三层架构详细讲解
spring boot·后端·架构
香香甜甜的辣椒炒肉6 小时前
Spring(1)基本概念+开发的基本步骤
java·后端·spring
白毛大侠6 小时前
Go Goroutine 与用户态是进程级
开发语言·后端·golang
ForteScarlet7 小时前
从 Kotlin 编译器 API 的变化开始: 2.3.20
android·开发语言·后端·ios·开源·kotlin
大阿明7 小时前
SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现
java·spring boot·后端
Binary-Jeff7 小时前
Spring 创建 Bean 的关键流程
java·开发语言·前端·spring boot·后端·spring·学习方法