劍指offer45:約瑟夫環

0,1,,n-1這n個數字排成一個圓圈,從數字0開始,每次從這個圓圈裏刪除第m個數字。求出這個圓圈裏剩下的最後一個數字。

例如,012345個數字組成一個圓圈,從數字0開始每次刪除第3個數字,則刪除的前4個數字依次是2041,因此最後剩下的數字是3。


示例 1:

輸入: n = 5, m = 3
輸出: 3

示例 2:

輸入: n = 10, m = 17
輸出: 2





方法一:數學 + 遞歸

思路

題目中的要求可以表述爲:給定一個長度爲 n 的序列,每次向後數 m 個元素並刪除,那麼最終留下的是第幾個元素?

這個問題很難快速給出答案。但是同時也要看到,這個問題似乎有拆分爲較小子問題的潛質:如果我們知道對於一個長度 n - 1 的序列,留下的是第幾個元素,那麼我們就可以由此計算出長度爲 n 的序列的答案。

算法

我們將上述問題建模爲函數 f(n, m),該函數的返回值爲最終留下的元素的序號。

首先,長度爲 n 的序列會先刪除第 m % n 個元素,然後剩下一個長度爲 n - 1 的序列。那麼,我們可以遞歸地求解 f(n - 1, m),就可以知道對於剩下的 n - 1 個元素,最終會留下第幾個元素,我們設答案爲 x = f(n - 1, m)。

由於我們刪除了第 m % n 個元素,將序列的長度變爲 n - 1。當我們知道了 f(n - 1, m) 對應的答案 x 之後,我們也就可以知道,長度爲 n 的序列最後一個刪除的元素,應當是從 m % n 開始數的第 x 個元素。因此有 f(n, m) = (m % n + x) % n = (m + x) % n。

官方解答就扔了個公式,還寫錯了(劃掉,官解沒錯了,因爲。。它改了),然後就得結論了。我們現在來捋一捋這公式到底咋推出來的。

我們有n個數,下標從0到n-1,然後從index=0開始數,每次數m個數,最後看能剩下誰。我們假設能剩下的數的**下標**爲y,則我們把這件事表示爲

f(n,m) = y

這個y到底表示了啥呢?注意,y是下標,所以就意味着你從index=0開始數,數y+1個數,然後就停,停誰身上誰就是結果。

行了,我們假設f(n-1,m)=x,然後來找一找f(n,m)f(n-1,m)到底啥關係。

f(n-1,m)=x意味着啥呢?意味着有n-1個數的時候從index=0開始數,數x+1個數你就找到這結果了。那我不從index=0開始數呢?比如我從index=i開始數?那很簡單,你把上面的答案也往後挪i下,就得到答案了。當然了,你要是挪到末尾了你就取個餘,從頭接着挪。

於是我們來思考f(n,m)時考慮以下兩件事:

    有n個數的時候,要劃掉一個數,然後就剩n-1個數了唄,那劃掉的這個數,**下標**是多少?
    劃完了這個數,往後數,數x+1個數,停在誰身上誰就是我們的答案。當然了,數的過程中你得取餘

**問題一**:有n個數的時候,劃掉了誰?**下標**是多少?

因爲要從0數m個數,那最後肯定落到了下標爲m-1的數身上了,但這個下標可能超過我們有的最大下標(n-1)了。所以攢滿n個就歸零接着數,逢n歸零,所以要模n。

所以有n個數的時候,我們劃掉了下標爲(m-1)%n的數字。

**問題二**:我們劃完了這個數,往後數x+1下,能落到誰身上呢,它的下標是幾?

你往後數x+1,它下標肯定變成了(m-1)%n +x+1,和第一步的想法一樣,你肯定還是得取模,所以答案爲[(m-1)%n+x+1]%n,則

f(n,m)=[(m-1)%n+x+1]%n

其中x=f(n-1,m)

我們化簡它!

定理一:兩個正整數a,b的和,模另外一個數c,就等於它倆分別模c,模完之後加起來再模。

(a+b)%c=((a%c)+(b%c))%c

定理二:一個正整數a,模c,模一遍和模兩遍是一樣的。

a%c=(a%c)%c

你稍微一琢磨就覺得,嗯,說得對。

所以

f(n,m)=[(m-1)%n+x+1]%n
      =[(m-1)%n%n+(x+1)%n]%n
      =[(m-1)%n+(x+1)%n]%n
      =(m-1+x+1)%n
      =(m+x)%n

剩下的故事你們就都知道了。





package com.atguigu.linkedlist;

import java.util.ArrayList;

public class Josepfu {
    public static void main(String[] args) {
        Josepfu m1=new Josepfu();

        System.out.println(m1.LastRemaining(3,2));


    }


    public int LastRemaining(int n,int m) {
        /**
         * 方法一:
         *
         */
        ArrayList<Integer> kids=new ArrayList<>();
        for (int i = 0; i < n; i++) {
            kids.add(i);
        }

        int index=-1;
        while(kids.size()>1){
            int count=0;
            while(count<m){
                count++;
                index++;
                if(index==kids.size()){
                    index=0;
                }
            }
            kids.remove(index);
            index--;
        }

return kids.get(0);



        //方法二

//        ArrayList<Integer> list = new ArrayList<>(n);
//        for (int i = 0; i < n; i++) {
//            list.add(i);
//        }
//        int idx = 0;
//        while (n > 1) {
//            idx = (idx + m - 1) % n;
//            list.remove(idx);
//            n--;
//        }
//        return list.get(0);
//    }


        //方法3


//        int f = 0;
//        for (int i = 2; i != n + 1; ++i)
//            f = (m + f) % i;
//        return f;


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