有10人圍成圈數數,每次數到3的人退出其他人繼續,問最後剩下的一個人原來的位置是多少

這個問題是一個典型的約瑟夫環問題,對於這類題目我做了一些總結,下面我就分別用:數組、ArrayList、LinkedList、以及通項公式去解決。
首先用數組:把人都按順序放到數組中,每次叫到三的人改變其的值爲-1,最後剩下的那個就是編號。

public class Test01 {
    public static void main(String[] args) {
        int n = 10;//參與遊戲的人數
        //將數字放入數組
        int [] persons=new int[n];
        for(int i=1;i<persons.length;i++){
            persons[i-1]=i;

        }
        int saveCount=0;//目前數組中還存在的人數
        int index=0;//我們要進行報的數
        while(true){
            //移除人出局

            saveCount=0;//記錄目前存活的人數每次循環都被初始化
            for(int i= 0;i<persons.length;i++){
                if(persons[i]!=-1){

                    index++;
                    if(index==3){
                        index=0;
            //當計數器等於3的時候改變計數器的值並且把當前人的值改爲-1
                        persons[i]=-1;
                    }
                }
                //如果數組中的值不爲-1存活人數+1
                if(persons[i]!=-1){
                    saveCount++;
                }
            }
            //如果存活的人只剩下一個,退出循環
            if(saveCount==1){
                break;
            }
        }
        System.out.println(Arrays.toString(persons));
    }
}

用數組的方式進行求解是我們思考最少的,其原理就是,我們把初始的編號放到數組中,每次找到要移出的人的時候把這個值改變爲-1,直至數組中只有一個不是-1,結束循環。但是這種方法每次循環都要循環整個數組,那我們可不可以每次循環後移出出局的人呢?答案是肯定的,java爲我們提供了很多容器,容器都帶有這種功能,下面我們就分別用ArrayList於LinkedList來解決。
ArrayList:

public class Test02 {

    public static void main(String[] args) {
        //這裏我用了字母初始化數組,爲了更明顯的表示
        List<String> list = new ArrayList<String>();
        for(char i = 'A'; i <= 'A' + 9; i++){
            list.add(Character.toString(i));
        }

        int n = 1;//計數器,因爲每次都要移除一個人,所以n = 1
        while(list.size() > 1){
            for(int i = 0; i < list.size(); i++){
                if(n == 3){
                    list.set(i, "");//找到叫到三的人把它的值變爲空
                    n = 1;
                }else{
                    n++;
                }   
            }
            for(int i = list.size() - 1; i >= 0; i--){
            //每次循環完成之後,統一移除元素,不然會報錯
                if(list.get(i).length() == 0){
                    list.remove(i);
                }
            }
        }
        System.out.println(list.toString());
    }
}

上面這個ArrayList有一點小瑕疵,因爲我們不能每次找到人後就移除此人,而是要找到後先改變它的值,讓它爲空,然後再每次循環完成後統一移除,那有的人就該問了:那與用數組有什麼差別,都是改變元素的值,只不過數組進行判斷,ArrayList進行移除。
事實的確如此,雖然用ArrayList每次循環後都移除了一些元素,下次循環的時候不用循環10次,但還不是我們想要的。所以我們在對這個代碼進行改進,如下:

public class Test03 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for(char i = 'A'; i < 'A' + 10; i++){
            list.add(Character.toString(i));
        }
        /*
         * 思路:
         *  1、得到每一次隊列中,最後一個人應該報數的值:int n = list.size % 3
         *  2、每次找到元素移除後,把計數器的值+1,等於跳過一個人,因爲找到人是叫到3的人退出,所以剛移除過的人旁邊的人肯定不會移除
         */
        int a = 0;
        System.out.println(list.toString());
        while(list.size() > 1){
            for(int i = 0; i < list.size(); i++){
                a++;//2
                if(a == 3){
                    if(i == list.size() - 1){
                        a = 0;
                    }else{
                        a = 1;
                    }
                    list.remove(i);
                }
            }
            System.out.println(list.toString());
        }
    }
}

這種方法可能剛開始沒有那麼好理解,但是在第二個方法的基礎下應該很快能夠理解。
下面就是用LinkedList來解決:

public class Monkey {

    public static void main(String[] args) {
        int number = 0;//計數器
        int count = 10;//玩家的總數

        LinkedList<Integer> monkeys = new LinkedList<>();
        for(int i = 1; i <= count; i++){
            monkeys.add(i);
        }

        //這裏用了迭代器,每次取出數組中的下個元素
        Iterator<Integer> it = monkeys.iterator();
        while(count > 1){
        //每次進行迭代,如果有下個元素,計數器+1
            if(it.hasNext()){
                it.next();
                ++number;
            }else{
            //如果沒有下個元素,迭代器從新賦值,即從頭開始
                it = monkeys.iterator();
            }
            //如果找到元素,把計數器歸零,移除元素,總數-1
            if(number == 3){
                number = 0;
                it.remove();
                count--;
            }
        }
        System.out.println("編號爲:" + monkeys.element());
    }
}

LinkedList與ArrayList方法類似,都是找到元素並且移除,都是計數器進行計數每次記錄循環結果,當做下次循環開始的計數。

最後介紹一種比較難理解的方法,一種通項公式,因爲這是約瑟夫環問題,如果我門改變人數,多往下寫,就會找到規律,用高中學到的求解通項公式的方法,可以找到這道題的通項公式 : f(n) = (f(n-1) + k) % n f(1) = 0;
n代表人數,k代表叫到的數字(本題中:n = 10, k = 3);
實現代碼如下:

public class Test04 {

    public static void main(String[] args){

        Scanner sc = new Scanner(System.in);
        System.out.println("請輸入參與遊戲的人數:");
        int number = sc.nextInt();
        System.out.println("請輸入數到幾的人退出遊戲");
        int k = sc.nextInt();
        //通項公式   f(n) = (f(n-1) + k) % n
        int last = 0; // f(1) = 0
        for(int i = k-1; i <= number; ++i){
            last = (last + k) % i;
        }
        System.out.println(last + 1);
    }
}

使用通項公式很簡潔的就解決了此類問題,看起來也簡單,但是這一種方法卻是最難理解的,因爲我們需要自己去找通項公式。當然如果你的腦容量夠大,你也可以記下來。

四種方法各有優劣,選擇你最能理解的去使用,嘗試找到別的方法。
計算機中的問題不可能只有一種解決方式。

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