1. 程式人生 > >leetcode 486. Predict the Winner (Alpha-Beta剪枝實現關鍵點小總結)

leetcode 486. Predict the Winner (Alpha-Beta剪枝實現關鍵點小總結)

題意

  • 小的博弈遊戲,兩個人輪流從一個數組的兩端取數,直到取完,最後取的和最大的人獲勝。問先手能否贏?其中如果和相同,先手勝。

思路

  • 首先如果有偶數個元素,先手必勝,這個可以參考leetcode 877題求解思路,證明連結
  • 奇數個的時候就沒有這麼好的結論了,博弈dp是很容易做的方法,這裡就不討論dp了,我們嘗試用alpha-beta剪枝的博弈樹來解決。
  • 博弈樹很簡單,其實就是個dfs,比如我要走下一步了,那從我當前步開始搜尋,其實就在構建一顆搜尋樹。設根為第0層,那麼搜尋樹裡偶數層對應的就是我要走的狀態,奇數層對應對手要走的狀態,所以偶數層的目標是從孩子裡選一個局面分數對我來說最大的,奇數層則是從孩子裡選一個局面分數對我來說最小的。
  • alpha-beta剪枝:核心思想就是,alpha是當前的下界,beta是當前的上界,設當前節點,在遍歷了一部分孩子後,他當前的分數是N,應該滿足alpha <= N <= beta
  • 詳細的解釋參見:詳細解釋
  • 這裡給一下個人覺得的實現時需要記住的關鍵點:
    • 偶數層節點,只會修改下界alpha,奇數層節點只修改上界beta
    • 判斷alpha和beta,如果不滿足條件alpha < beta,則不用繼續向下搜尋了(這個判斷是在每遞迴遍歷完一個孩子之後都要判斷的)
    • 除了上下界,當前節點算出的結果是同樣是要儲存的,最後返回的是當前節點的計算結果

實現

class Solution {
public:
    int INF = 2e8+5;
    bool PredictTheWinner(vector<int>& nums) {
        if (!(nums.size() & 1))
            return true;
        int ret = FindMax(nums, 0, nums.size() - 1, -INF, INF, 0);
        return ret >= 0;
    }
    int FindMax(vector<int
>
& nums, int st, int ed, int alpha, int beta, int pre){ if (st == ed){ return pre + nums[st]; } int ret = FindMin(nums, st + 1, ed, alpha, beta, pre + nums[st]); alpha = max(alpha, ret); if (alpha >= beta) return alpha; ret = max(ret, FindMin(nums, st, ed - 1, alpha, beta, pre + nums[ed])); return ret; } int FindMin(vector<int>& nums, int st, int ed, int alpha, int beta, int pre){ if (st == ed){ return pre - nums[st]; } int ret = FindMax(nums, st + 1, ed, alpha, beta, pre - nums[st]); beta = min(beta, ret); if (alpha >= beta) return beta; ret = min(ret, FindMax(nums, st, ed - 1, alpha, beta, pre - nums[ed])); return ret; } };