LeetCode : 390. 消除遊戲(Elimination Game)分析和解答
390. 消除遊戲
給定一個從1 到 n 排序的整數列表。
首先,從左到右,從第一個數字開始,每隔一個數字進行刪除,直到列表的末尾。
第二步,在剩下的數字中,從右到左,從倒數第一個數字開始,每隔一個數字進行刪除,直到列表開頭。
我們不斷重複這兩步,從左到右和從右到左交替進行,直到只剩下一個數字。
返回長度為 n 的列表中,最後剩下的數字。
例項:
輸入:
n = 9,
1234567892
468
266
輸出:
6
解答
思路1:
先說一個不好的實現。首先看到這個題,很容易聯想到使用一個連結串列,然後依次刪一個跳一個,正序倒序來回執行,直到長度為 1 時返回。這樣確實也可以實現,但是耗時太長了,無法滿足需求。
思路2:
上述思路行不通,我們只好想別的辦法了。當我們仔細觀察後,其實這裡面隱藏著很強的規律。我們先把答案貼出來,下面再詳細證明。
實現:
public int lastRemaining(int n) {
return n == 1 ? 1 : 2 * (n / 2 + 1 - lastRemaining(n / 2));
}
證明
假如輸入為 n,我們使用 f(n)
表示 從左到右(forward) 的最終結果,使用 b(n)
表示 從右到左(backward) 的最終結果。則
規律1: 當 n = 1 時,存在 f(n) = 1, b(n) = 1
。
規律2:
f(n) + b(n) = n + 1
。
我們先 假設 這條規律是成立的(因為我們會在後面證明它)。我們先來看一些例子:
當 n = 1 時,由定理 1 可知:f(n) = 1, b(n) = 1,存在 f(n) + b(n) = n + 1 = 2。 當 n = 2 時,f(n) = 2, b(n) = 1,存在 f(n) + b(n) = n + 1 = 3。 當 n = 3 時,f(n) = 2, b(n) = 2,存在 f(n) + b(n) = n + 1 = 4。 當 n = 4 時,f(n) = 2, b(n) = 3,存在 f(n) + b(n) = n + 1 = 5。 …
規律3: 對於 n > 2 的情況下,f(n) = 2 * b(n / 2)
。
假如現在有一個輸入為 [1,2,3,4,5,6],第一次刪除後結果為 [2,4,6],可視為 2 * [1,2,3],而我們知道下一次是 “從右到左” 的,即我們可以使用 b(3) 表示 [1,2,3] “從右到左” 的執行結果,則 f(6) = 2 * b(3)
。
同樣,如果輸入為 [1,2,3,4,5,6,7],第一次刪除後結果同上,也有 f(7) = 2 * b(3)
。
那麼,對於任意數 k,我們其實都可以按照上述方式將 f(k) 表示為:f(k) = 2 * b(k / 2)
。
證明規律2正確:
我們想要證明 f(n) + b(n) = n + 1
成立,又已知 f(n) = 2 * b(n / 2)
我們可以將他們表示為:b(n) = n + 1 - f(n) = n + 1 - 2 * b(n / 2)
和 f(n) = 2 * b(n / 2) = 2 * (n / 2 + 1 - f(n / 2))
即:f(n) + b(n) = n + 1
可以表示為:2 \* (n / 2 + 1 - f(n / 2)) + n + 1 - 2 \* b(n / 2) = n + 1
化簡可得:f(n / 2) + b(n / 2) = n / 2 + 1
所以:欲證明 f(n) + b(n) = n + 1
成立,及證明 f(n / 2) + b(n / 2) = n / 2 + 1
成立。
證明過程:
使用數學歸納法,先從前面幾個例子開始,即:
當 n = 1 時,已知 f(1) = 1, b(1) = 1,f(1) + b(1) = 2 成立。 當 n = 2 時,已知 f(2) = 2, b(2) = 1,f(2) + b(2) = 3 成立。 當 n = 3 時,已知 f(3) = 2, b(3) = 2,f(3) + b(3) = 4 成立。 … 當 n = k 時,已知 f(k / 2) + b(k / 2) = k / 2 + 1 成立,所以 f(k) + b(k) = k + 1 成立。 …
最終,對於任意值 n,我們都可以說 f(n) + b(n) = n + 1。
結論:
當 n = 1 時:
f(n) = b(n) = 1。
當 n > 1 時:
f(n) = 2 * (n / 2 + 1 - f(n / 2))
b(n) = n + 1 - 2 * b(n / 2)