Nim 游戏 、⽯头游戏1、石头游戏2
Nim 游戲 、?頭游戲1、石頭游戲2
文章目錄
- Nim 游戲 、?頭游戲1、石頭游戲2
- **一:Nim 游戲**
- **二:?頭游戲**
- **三、石頭游戲2**
- **方法一:DP 函數**
- **方法二:DP table**
一:Nim 游戲
你和你的朋友,兩個人一起玩 Nim 游戲:桌子上有一堆石頭,每次你們輪流拿掉 】1 - 3 塊石頭。 拿掉最后一塊石頭的人就是獲勝者。你作為先手。你們是聰明人,每一步都是最優解。 編寫一個函數,來判斷你是否可以在給定石頭數量】的情況下贏得游戲。示例:
輸入: 4 輸出: false 解釋: 如果堆中有 4 塊石頭,那么你永遠不會贏得比賽;因為無論你拿走 1 塊、2 塊 還是 3 塊石頭,最后一塊石頭總是會被你的朋友拿走解題思路:
我們解決這種問題的思路?般都是反著思考:
1 . 如果我能贏, 那么最后輪到我取??的時候必須要剩下 1~3 顆??, 這樣 我才能?把拿完。
2.如何營造這樣的?個局?呢? 顯然, 如果對?拿的時候只剩 4 顆??, 那么?論他怎么拿, 總會剩下 1~3 顆??, 我就能贏。
3.如何逼迫對??對 4 顆??呢? 要想辦法, 讓我選擇的時候還有 5~7 顆??, 這樣的話我就有把握讓對?不得不?對 4 顆??。
4.如何營造 5 ~ 7 顆??的局?呢? 讓對??對 8 顆??, ?論他怎么拿, 都會給我剩下 5~7 顆, 我就能贏。
5.這樣?直循環下去, 我們發現只要踩到 4 的倍數, 就落?了圈套, 永遠逃不出 4 的倍數, ?且?定會輸。 所以這道題的解法?常簡單:
bool canWinNim(int n) { // 如果上來就踩到 4 的倍數, 那就認輸吧 // 否則, 可以把對?控制在 4 的倍數, 必勝 return n % 4 != 0; }二:?頭游戲
亞歷克斯和李用幾堆石子在做游戲。偶數堆石子排成一行,每堆都有正整數顆石子 piles[i] 。
游戲以誰手中的石子最多來決出勝負。石子的總數是奇數,所以沒有平局。
亞歷克斯和李輪流進行,亞歷克斯先開始。 每回合,玩家從行的開始或結束處取走整堆石頭。 這種情況一直持續到沒有更多的石子堆為止,此時手中石子最多的玩家獲勝。
假設亞歷克斯和李都發揮出最佳水平,當亞歷克斯贏得比賽時返回 true ,當李贏得比賽時返回 false 。
示例:
輸入:[5,3,4,5] 輸出:true 解釋: 亞歷克斯先開始,只能拿前 5 顆或后 5 顆石子 。 假設他取了前 5 顆,這一行就變成了 [3,4,5] 。 如果李拿走前 3 顆,那么剩下的是 [4,5],亞歷克斯拿走后 5 顆贏得 10 分。 如果李拿走后 5 顆,那么剩下的是 [3,4],亞歷克斯拿走后 4 顆贏得 9 分。 這表明,取前 5 顆石子對亞歷克斯來說是一個勝利的舉動,所以我們返回 true 。提示:
強調雙?都很聰明的原因, 算法也是求最優決策過程下你是否能贏。這道題?涉及到兩?的博弈, 也可以?動態規劃算法暴?試, ?較?煩。 但我們只要對規則深?思考, 就會?驚失?: 只要你?夠聰明, 你是必勝?疑的, 因為你是先?。
boolean stoneGame(int[] piles) {return true; }這是為什么呢, 因為題?有兩個條件很重要: ?是?頭總共有偶數堆, ?頭的總數是奇數。
這兩個看似增加游戲公平性的條件, 反?使該游戲成為了?個割?菜游戲。
我們以 piles=[2, 1, 9, 5] 講解, 假設這四堆?頭從左到
右的索引分別是 1, 2, 3, 4。
- 如果我們把這四堆?頭按索引的奇偶分為兩組, 即第 1、 3 堆和第 2、 4 堆,
- 那么這兩組?頭的數量?定不同, 也就是說?堆多?堆少。 因為?頭的總數是奇數, 不能被平分。
- ?作為第?個拿?頭的?, 你可以控制??拿到所有偶數堆, 或者所有的奇數堆。
- 你最開始可以選擇第 1 堆或第 4 堆。 如果你想要偶數堆, 你就拿第 4 堆, 這樣留給對?的選擇只有第 1、 3 堆, 他不管怎么拿, 第 2 堆?會暴露出來,你就可以拿。
- 同理, 如果你想拿奇數堆, 你就拿第 1 堆, 留給對?的只有第2、 4 堆, 他不管怎么拿, 第 3 堆?給你暴露出來了。
- 也就是說, 你可以在第?步就觀察好, 奇數堆的?頭總數多, 還是偶數堆的?頭總數多, 然后步步為營, 就?切盡在掌控之中了。
三、石頭游戲2
亞歷克斯和李繼續他們的石子游戲。許多堆石子 排成一行,每堆都有正整數顆石子 piles[i]。游戲以誰手中的石子最多來決出勝負。
亞歷克斯和李輪流進行,亞歷克斯先開始。最初,M = 1。
在每個玩家的回合中,該玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M。然后,令 M = max(M, X)。
游戲一直持續到所有石子都被拿走。
假設亞歷克斯和李都發揮出最佳水平,返回亞歷克斯可以得到的最大數量的石頭。
示例:
輸入:piles = [2,7,9,4,4] 輸出:10 解釋: 如果亞歷克斯在開始時拿走一堆石子,李拿走兩堆,接著亞歷克斯也拿走兩堆。在這種情況下,亞歷克斯可以拿到 2 + 4 + 4 = 10 顆石子。 如果亞歷克斯在開始時拿走兩堆石子,那么李就可以拿走剩下全部三堆石子。在這種情況下,亞歷克斯可以拿到 2 + 7 = 9 顆石子。 所以我們返回更大的 10。提示:
- 1 <= piles.length <= 100
- 1 <= piles[i] <= 10 ^ 4
解題思路:
定義:
- stones:與題目中 piles 意思保持一致;
- n:stone個數;
- score[i, j]:當前玩家從 stones[i] 開始,且 M = j 時,該玩家所能得到的最高分數;
- sum[i]:從stones[i]開始,剩下石頭分數之和(即,stones[i] + … + stones[n - 1]);
考慮:
- 如何使得當前玩家所能獲得的分數score[i, j]最高?取1個?取2個?… 取 2 * j 個?
- 當前玩家拿1個stone時,另一個玩家接下來所能獲得的最高分數為score[i + 1, max(j, 1)];
- 當前玩家拿2個stone時,另一個玩家接下來所能獲得的最高分數為score[i + 2, max(j, 2)];
- …
- 當前玩家拿2*j個stone時,另一個玩家接下來所能獲得的最高分數為score[i + 2 * j, max(j, 2 * j)];
- 因為sum[i]是一定的,只需要使另一個玩家接下來所能獲得的最高分數最小,即可保證當前玩家所得到的分數score[i, j]最大
基于上述考慮,得到遞歸方程如下:
score[i,j]=sum[i] ? min(socre[i + 1,max(j,1)],...,score[i + 2 ? j,max(j,2 ? j)])進一步考慮,遞歸終止條件:
因為所有stone的分數都為正數;當 n - i <= 2 * j 時(即,當前玩家可以直接拿下剩下的所有stone),則當前玩家所得最高分 score[i, j] 直接為剩下所有stone分數之和(即,score[i, j] = sum[i]);
完整遞歸方程如下:
回到題目
- 一場游戲,Alex所能獲得的最高分為score[0,1];
- 對應的,Lee所獲得的分數為 sum[0]?score[0,1];
- 誰獲得的分數高,誰贏;
為了可以在常數時間內計算到sum[i],我們需要提前計算好后綴和數組;
當n = 8時,示例如下:
正如前面所說,當 2 * j > n - i 時,當前玩家可以拿走剩下的所有stone,因此這里 j 的上限,我們取 n 的一半向上取整;
為了計算score[0, 1],我們需要知道score[1, 1],score[2, 2],所以計算時我們采用從下到上(從左到右或者從右到左都行)的計算順序;
方法一:DP 函數
class Solution { public:int n = 0;//表示從i開始取M個獲取的最大價值int* vec;//備忘錄int mem[105][105];//dp函數int dp(int s, int M) {//如果在備忘錄中直接在備忘錄中返回,避免重復計算if(mem[s][M] != 0) return mem[s][M];//如果s >= n代表下標已經越界,相當于piles越界if(s >= n) return 0;//如果發現 s + 2 * M >= n 說明玩家可以一次取完,直接返回if(s + 2 * M >= n){return vec[s];}int ans = 0;for(int i = 1; i <= 2 * M; i++) {//當前的最大價值為,當前剩余價值 - 下一個狀態的最大價值。ans = max(ans, vec[s] - dp(s + i, max(i, M))); }//記憶化搜索mem[s][M] = ans;return ans;}int stoneGameII(vector<int>& piles) {n = piles.size();//初始化相關操作memset(mem, 0, sizeof(mem));vec = new int[n];//從 后往前計算每個 i 位置前 的所有石頭之和int sum = 0;for(int i = n - 1; i >= 0; i--) {sum += piles[i];vec[i] = sum;}//遞歸調用dp函數求結果,0:代表玩家從piles的0下標處開始可以獲得的最大石頭數量 ;1:代表m的初始值為1return dp(0,1);} };方法二:DP table
class Solution { public:int stoneGameII(vector<int>& piles) {int len = piles.size();//用來從前往后計算每個 i 位置的所有石頭之和int sum = 0; // dp[i][j]表示當前是第i波,m = j,玩家所能獲得的最大石頭的數量;vector<vector<int>>dp(len + 1, vector<int>(len + 1, 0));//注意要反向遍歷for(int i = len - 1; i >= 0; i--){sum += piles[i];// 表示當前所剩下的所有棋子的和for(int M = 1; M <= len; M++){//代表玩家可以一次取完剩下的所有石頭,直接放到dp中,繼續循環測試if(i + 2 * M >= len){//直接把剩下的所有石頭保存在dp數組中dp[i][M] = sum;continue;}//用循環來枚舉所有的情況,取其中玩家可以獲得最大石頭數量的一個結果保存//x代表每次取的堆數//i + x <= len 是為了防止越界//x <= 2 * M 代表每次可以取的石頭的堆數范圍for(int x = 1; i + x <= len && x <= 2 * M; x++){//每次選取其中結果最大 的來保存dp[i][M] = max(dp[i][M], sum - dp[i + x][max(M, x)]);}}}return dp[0][1];} };總結
以上是生活随笔為你收集整理的Nim 游戏 、⽯头游戏1、石头游戏2的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最⻓公共⼦序列
- 下一篇: 无重叠区间及用最少的箭射爆气球