趣味算法:國王和100個囚犯

在這裏插入圖片描述
前幾天在網上看到了一個有趣的問題,就是 國王和100個囚犯 的問題。第一次看到這個問題時,當時也懵了,這是什麼鬼?你確定你題出的木有問題?當時就是這感覺…

但仔細思索後還是想到了解決方法,讓我們一起來看看這個有趣的問題吧。
在這裏插入圖片描述
題目如下:

國王招來100個囚犯,對他們說:你們犯的是死罪,但我給你們一次求生的機會。15分鐘以後,你們將被關進一個有100間隔離牢房的監獄裏,每人一間牢房,都與外界隔絕,什麼也聽不見看不到,連時間都沒法計算,無法獲得外界的任何信息。

這所監獄有一個院子,每天只少隨機(注意是完全隨機)打開一間牢房的門,讓一個囚犯到院子裏來放風。院子裏有一盞燈,放風的囚犯可以控制它的開關,將它打開或是關閉。除囚犯之外,其他人都不會去碰開關。這盞燈會永遠有充足的能源供應,燈泡和電路不會出故障。
  在這裏插入圖片描述

除了開關這盞燈,放風的囚犯放風時留下的任何其它痕跡都會在夜晚被清除乾淨(包括在燈上作的任何記號)。牢房是完全封閉的,院子裏的燈光在牢房裏看不到。只有放風到院子裏的人才能看到。

國王:好了現在我向你們提出一個要求,只要你們做到了,就可以全部獲得釋放:

給你們15分鐘商量你們的方案。15分鐘以後,你們將被關進我剛纔說的那個監獄,永遠無法再交流,被關若干天后,你們中間如果任何一個人能夠向我證明你們每個人都至少放風了一次,我就把你們放了,不然永遠別想再出來。

如果你們有誰現在可以告訴我這個方法,也就是能夠證明你們每人至少放風一次的方法,我就放掉你們!

其中一個囚犯想了幾分鐘,回答了這個問題,國王聽後,如自己所說的把他們全部給放了。請問那個囚犯是用什麼方法證明的?

大致的解題思路(建議思考後再看):

還趕緊用你的2.4G赫茲的4核CPU大腦思考下,該如何解決?
在這裏插入圖片描述

100個囚犯商量選出一個囚犯作爲計數員(PrisonerCounter),**普通囚犯(Prisoner)**每次出去,如果自己沒有打開過燈,並且燈是滅的,則打開燈;其它情況均不操作。計數員每次出去,如果燈是亮的就自己計數一次,並把燈關掉,其它情況什麼也不幹。一直到計數員計數到100,則全部囚犯都出去過至少打過一次燈。

再來細化化下每個角色的職責:

  • 計數員: 如果燈亮,計數一次,並關燈。如果燈滅,啥事不幹。
  • 普通囚犯:如果自己沒有開關燈,並且現在燈滅,就打開燈;如果自己以前開過燈或現在燈亮,則什麼也不做。
  • 燈:能開、能關

看到這裏,你應該有一種虎軀一震的感覺,還有這騷操作…

來看看代碼具體怎麼實現的吧

對象:阿拉丁神燈(這個燈好像不太神,只能開與關,但卻掌握着100人的生與死)

    /**
     * 阿拉丁神燈
     * @Author: danding
     * @Date: 2019/8/30
     */
    public class Light {
        //燈的狀態(true-開,false-關)
        private Boolean state;
    
        public Light() {
            this.state = false;//默認爲關
        }
    
        public Boolean getState() {
            return state;
        }
    
        public void setState(Boolean state) {
            this.state = state;
        }
    }

對象:普通囚犯

/**
 * 囚犯
 * @Author: danding
 * @Date: 2019/8/30
 */
public class Prisoner {
    //囚犯編號
    private int id;
    //是否打開過燈
    private Boolean isOpenLight = false;
    //院子裏的燈
    protected Light light;

    public Prisoner(int id) {
        this.id = id;
    }

    /**
     * 放風時要乾的事件
     * @return 普通囚犯返回值無意義
     */
    public boolean doSomeThing(){
        if(light==null){
            return false;
        }
        //如果這個囚犯放風時,打開過燈,則這次出去什麼也不做
        if(isOpenLight==true){
            System.out.println(String.format("囚犯編號:%d,大爺的,怎麼又是我,我已經開過燈了....",id));
            return false;
        }
        //如果出去發現燈的亮的,則什麼也不做
        if(light.getState()==true){
            System.out.println(String.format("囚犯編號:%d,大爺的,第一次出來,燈竟然被佔用了...",id));
            return false;
        }
        //如果這個囚犯被放風還沒有開過燈:
        if(light.getState()==false){//如果出去發現燈的滅的,他就打開燈
            System.out.println(String.format("囚犯編號:%d,我終於出來放風了,還是第一次呢,有點小雞凍(激動)呢!!!",id));
            light.setState(true);
            this.isOpenLight = true;
        }
        return false;
    }


    /**
     * 賦予或移除燈
     * @param light
     */
    public void setLight(Light light) {
        this.light = light;
    }
}

對象:囚犯計數員(比普通囚犯多一個計數功能)

/**
 * 囚犯計數人
 * @Author: danding
 * @Date: 2019/8/30
 */
public class PrisonerCounter extends Prisoner{
    //已開燈的人數(自己不計算在內)
    private int prison_out_count = 1;

    public PrisonerCounter(int id) {
        super(id);
    }

    /**
     * 囚犯計數者每次放風:
     * 如果燈亮,則計數一次,並把燈關了
     * 如果燈滅,不作任何操作
     * @return true-全部人都打開過燈一次,false-還有其它人沒有開過燈
     */
    public boolean doSomeThing(){
        if(super.light==null){
            return false;
        }
        if(super.light.getState()==false){
            return false;
        }

        this.prison_out_count++;
        System.out.println(String.format("當前第%d個囚犯打開了燈",this.prison_out_count));
        if(this.prison_out_count>=100){
            return true;
        }
        super.light.setState(false);
        return false;
    }
}

對象:上帝(主宰一切,什麼都是我說了算)

import java.time.Period;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 上帝,掌握着一切
 * @Author: danding
 * @Date: 2019/8/30
 */
public class God {

    public static void main(String[] args){
        //所有囚犯集合
        List<Prisoner> prisonerList = new ArrayList<>();
        //初始化1個計數者
        Prisoner prisoner = new PrisonerCounter(0);
        prisonerList.add(prisoner);
        //初始化99個囚犯
        for(int i=1;i<100;i++){
            prisoner = new Prisoner(i);
            prisonerList.add(prisoner);
        }

        //初始化一個院子裏的燈
        Light light = new Light();
        Random random = new Random();
        int day = 0;
        boolean isOver;
        do{
            day++;//計天數
            //每天隨機抽一個囚犯
            int index = random.nextInt(prisonerList.size());
            prisoner = prisonerList.get(index);
            //出去放風,這個時候這個囚犯擁有控制燈的權限
            prisoner.setLight(light);
            //幹約定好的事件
            isOver = prisoner.doSomeThing();
            //回老方,沒收控制燈的權限
            prisoner.setLight(null);
        }while(!isOver);
        System.out.println(String.format("經歷了%d天,所有囚犯都出去開過至少一次燈",day));
    }
}

代碼完了,讓我們一起來看看這100個囚犯大概多久能出來吧

...
...省略部分
...
囚犯編號:88,我終於出來放風了,還是第一次呢,有點小雞凍(激動)呢!!!
囚犯編號:72,大爺的,怎麼又是我,我已經開過燈了....
囚犯編號:50,大爺的,怎麼又是我,我已經開過燈了....
囚犯編號:86,大爺的,怎麼又是我,我已經開過燈了....
囚犯編號:53,大爺的,怎麼又是我,我已經開過燈了....
囚犯編號:48,大爺的,怎麼又是我,我已經開過燈了....
囚犯編號:23,大爺的,怎麼又是我,我已經開過燈了....
囚犯編號:4,大爺的,怎麼又是我,我已經開過燈了....
囚犯編號:17,大爺的,怎麼又是我,我已經開過燈了....
囚犯編號:35,大爺的,怎麼又是我,我已經開過燈了....
囚犯編號:99,大爺的,怎麼又是我,我已經開過燈了....
當前第100個囚犯打開了燈
經歷了10483天,所有囚犯都出去開過至少一次燈

在這裏插入圖片描述
什麼?這不得等上將近30年…隨間歇菜…
想來也對,因爲每天放風的囚犯都是隨機的,至於多少天能出來,完全看運氣了。
以上運行結果並沒有什麼參考意義,因爲你每次運行的結果都不一樣,並且差別應該也不小。

運氣絕對好,至少需要多少天?

作爲高逼格的蝸牛哥絕不能就達此結束了,我們得想想他們運氣絕對好,每次都能中彩票一等獎,這種情況下,他們最快多少天能出來呢?

給你5分鐘思考時間
在這裏插入圖片描述

如果運氣絕對好,那麼他們的出場順序應該是這樣的:

第01天:囚犯01號
第02天:囚犯計數員
第03天:囚犯02號
第04天:囚犯計數員
第05天:囚犯03號
第06天:囚犯計數員
......
第195天:囚犯98號
第196天:囚犯計數員
第197天:囚犯99號
第198天:囚犯計數員

也就是說他們運氣絕對好,至少需要198天就可以全部出獄了,這個結果還是讓人滿意的,也就大半年時間。

到這裏就該真的結束了,希望通過這個趣味小實驗,能讓你從中學到些什麼,哪怕有一丁點的幫助,小蝸牛在此也欣慰了。


關注公衆號:「Java 知己」,每天更新Java知識哦,期待你的到來!

  • 發送「1024」,免費領取 30 本經典編程書籍。
  • 發送「Group」,與 10 萬程序員一起進步。
  • 發送「JavaEE 實戰」,領取《JavaEE 實戰》系列視頻教程。
  • 發送「玩轉算法」,領取《玩轉算法》系列視頻教程。

如何new出這個對象?在線等,挺急的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章