1. 程式人生 > 實用技巧 >每日一題 - 劍指 Offer 62. 圓圈中最後剩下的數字

每日一題 - 劍指 Offer 62. 圓圈中最後剩下的數字

題目資訊

  • 時間: 2019-07-06

  • 題目連結:Leetcode

  • tag: 動態規劃 迭代 約瑟夫環

  • 難易程度:中等

  • 題目描述:

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

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

示例1:

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

示例2:

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

注意

1. 1 <= n <= 10^5
2. 1 <= m <= 10^6

解題思路

本題難點

約瑟夫環

N個人圍成一圈,第一個人從1開始報數,報M的將被殺掉,下一個人接著從1開始報。如此反覆,最後剩下一個,求最後的勝利者。

具體思路

約塞夫問題就是用人來舉例的,那我們也給每個人一個編號(索引值),每個人用字母代替

下面這個例子是N=8 m=3的例子

我們定義F(n,m)表示最後剩下那個人的索引號,因此我們只關係最後剩下來這個人的索引號的變化情況即可

從8個人開始,每次殺掉一個人,去掉被殺的人,然後把殺掉那個人之後的第一個人作為開頭重新編號

  • 第一次C被殺掉,人數變成7,D作為開頭,(最終活下來的G的編號從6變成3)
  • 第二次F被殺掉,人數變成6,G作為開頭,(最終活下來的G的編號從3變成0)
  • 第三次A被殺掉,人數變成5,B作為開頭,(最終活下來的G的編號從0變成3)
  • 以此類推,當只剩一個人時,他的編號必定為0!(重點!)

現在我們知道了G的索引號的變化過程,那麼我們反推一下
N = 7N = 8 的過程

如何才能將N = 7 的排列變回到N = 8 呢?

我們先把被殺掉的C補充回來,然後右移m個人,發現溢位了,再把溢位的補充在最前面

因此我們可以推出遞推公式f(8,3)=[f(7,3)+3]%8

進行推廣泛化,即f(n,m)=[f(n−1,m)+m]%n

  • 遞推公式

提示 : 最終剩下的數字的陣列下標為0;

程式碼

class Solution {
    public int lastRemaining(int n, int m) {
        int res = 0 ;
        for(int i = 2 ; i <= n; i++){
            res = (res + m) % i;
        }
        return res;
    }
}

複雜度分析:

  • 時間複雜度 O(N) :其中 N為 迭代n次的長度。
  • 空間複雜度 O(1) : 變數 res 使用常數大小的額外空間。

其他優秀解答

解題思路

模擬連結串列。純暴力的做法,每次找到刪除的那個數字,需要 O(m) 的時間複雜度,然後刪除了 n−1 次。但實際上我們可以直接找到下一個要刪除的位置的。假設當前刪除的位置是 idx,下一個刪除的數字的位置是 idx+m 。但是,由於把當前位置的數字刪除了,後面的數字會前移一位,所以實際的下一個位置是 idx+m−1。由於數到末尾會從頭繼續數,所以最後取模一下,就是 (idx+m−1)(modn)。

程式碼

class Solution {
    public int lastRemaining(int n, int m) {
        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);
    }
}