【博弈论系列算法】

1 先手必胜问题

1.1 区间博弈

1.2 取数字博弈

     问题描述


1 先手必胜问题

       1.1 区间博弈

        给定一个数组区间,两位玩家只能从数组区间的开始位置或者末尾位置开始取数,最后比谁取得的数字和最大。

      参考题目链接leetcode在线题目

       思路答案

 public static boolean predictTheWinner(int[] nums) {
        return predict(nums, 0, nums.length, 0, 0, true);
    }

    private static boolean predict(int[] nums, int s, int e, int sum1, int sum2, boolean first) {
        if (e == s + 1) {
            if (first)
                return sum1 + nums[s] >= sum2;
            else return sum2 + nums[s] <= sum1;
        }
        if (first)   // 先手方的两种情况,能赢一种就可以
            return predict(nums, s + 1, e, sum1 + nums[s], sum2, false)
                    || predict(nums, s, e - 1, sum1 + nums[e - 1], sum2, false);
        else       // 后手方选择的两种情况,必须保证先手方都能赢
            return predict(nums, s + 1, e, sum1, sum2 + nums[s], true)
                    && predict(nums, s, e - 1, sum1, sum2 + nums[e - 1], true);
    }

    这是一个递归的思路,如果我们对时间复杂度要求更高的话,比如同样的题目leetcode486stone-game题,这个解法会超时,这时就需要进行记忆化动态规划。

dp[i][j] means the biggest number of stones you can get more than opponent picking piles in piles[i] ~ piles[j].
You can first pick piles[i] or piles[j].

  1. If you pick piles[i], your result will be piles[i] - dp[i + 1][j]
  2. If you pick piles[j], your result will be piles[j] - dp[i][j - 1]

So we get:
dp[i][j] = max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1])
We start from smaller subarray and then we use that to calculate bigger subarray.

     follow this thought, you can implement the code in java below:

 public  boolean stoneGame(int[] piles) {
      int[][] dp = new int[piles.length][piles.length];
        for (int i = 0; i < piles.length; i++) {
            dp[i][i] = piles[i];
            if (i < piles.length - 1)
                dp[i][i + 1] = Math.max(piles[i], piles[i + 1]) - Math.min(piles[i], piles[i + 1]);
        }
        for (int distance = 1; distance < piles.length; distance++) {
            for (int j = 0; j + distance < piles.length; j++) {
                dp[j][j + distance] = Math.max(piles[j] -  dp[j+1][j+distance], piles[j +distance] - dp[j][j+distance -1]);
            }
        }
        return dp[0][piles.length -1] > 0;
    }

1.2 取数字博弈

     问题描述

leetcode 464在 "100 game" 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到 100 的玩家,即为胜者。

如果我们将游戏规则改为 "玩家不能重复使用整数" 呢?

例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。

给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?

你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。

思路:

必胜和必败的两个特判
然后用dp记录每个特定状态(已经选了哪些)的胜负
再来一个options以供选择

然后dfs
如果currentState已经出现在记忆化dp直接返回,need表示还需要多少
否则,开始遍历每一个商品看看是否取过
如果取过了就不考虑
如果没有取过就nextState取,然后判断如果options[i] >= need或者dfs(nextState, need - options[i]) == False
都可以证明当前state是获胜的,所以记录dp并返回
如果整个for都没有取胜,就是lose了,记录dp并返回
最后调用dfs(0, desire)

class Solution {
    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
      if (desiredTotal <= 0) return true;
        if (maxChoosableInteger * (maxChoosableInteger + 1) / 2 < desiredTotal) return false;
        char[] used = new char[maxChoosableInteger];
        Arrays.fill(used, '0');
        Map map = new HashMap<>();

        return canIWin(desiredTotal, used, map);
    }

    private static boolean canIWin(int state,char[] used, Map hashMap) {
        if (state <= 0) return false;
        String status = new String(used);
        if (hashMap.containsKey(status)) return hashMap.get(status);
        for (int i = 1; i <= used.length; i++) {
            if(used[i-1] == '1') {
                continue;
            }
            used[i-1] = '1';
            if ( !canIWin(state - i, used, hashMap)) {
                used[i-1] = '0';
                hashMap.put(status, true);
                return true;
            }
            used[i-1] = '0';
        }
        hashMap.put(status, false);
        return false;
    }
}

在leetcode时间限制下会超时,更快速的

class Solution {
  int mem[1<<20] = {};
    
public:
    bool canIWin(int M, int T) 
  {
    int sum = M*(M+1)/2; // sum of entire choosable pool

    // I just pick 1 to win
    if (T < 2) return true;
    
    // Total is too large, nobody can win
    else if (sum < T) return false;
    
    // Total happens to match sum, whoever picks at odd times wins
    else if (sum == T) return M%2;
    
    // Non-trivial case: do DFS
    // Initial total: T
    // Initial game state: k = 0 (all numbers are not picked)
    else return dfs(M, T, 0);
  }

  // DFS to check if I can win
  // k: current game state
  // T: remaining total to reach
  bool dfs(int M, int T, int k) 
  {
    // memorized
    if (mem[k] != 0) return mem[k] > 0;
    
    // total is already reached by opponent, so I lose
    if (T <= 0) return false;

    // try all currently available numbers
    for (int i = 0; i < M; ++i)
      // if (i+1) is available to pick and my opponent can't win after I picked, I win!
      if (!(k&(1<

你可能感兴趣的:(算法,IT,算法)