动态规划一:博弈类

170 阅读5分钟

dfs + memo模版

private boolean canIWin(int n) {
    boolean[] memo = new boolean[n + 1];    // 设置memo
    return dfs(n, memo);                    // call dsf
}

private boolean dfs(int n, boolean[] memo) {
    if (n < 0) {             // index边界结束
        return false;
    }
    // 如果已经有结果了直接返回(不这样大概率会超时)
    if (memo[n] == true) {
        return true;
    }
    boolean res = false;    // 初始化 res
    for (int i = 1; i < 4; i++) {   // 核心递推公式
        if (n >= i) {
            res = res | !dfs(n - i, memo);  // 让对手结果最小化(我们的结果就是对手的结果取反)
        }
    }
    return memo[n] = res;   // 返回结果并保存在memo中
}

1. Nim 游戏

image.png
class Solution {
    public boolean canWinNim(int n) {
        // return n%4!=0;

        // 动态规划会超时
        boolean memo[] = new boolean[Math.max(n + 1, 4)];
        memo[1] = true;
        memo[2] = true;
        memo[3] = true;
        for (int i = 4; i <= n; i++) {
            memo[i] = !memo[i - 1] || !memo[i - 2] || !memo[i - 3];
        }
        return memo[n];
    }
}

2. 预测赢家

image.png
class Solution {
    // 核心思想就是计算差值
    // 1. memo+dfs:模版方式
    public boolean PredictTheWinner(int[] nums) {
        int[][] memo = new int[nums.length][nums.length];
        return dfs(nums, 0, nums.length - 1, memo) >= 0;
    }

    private int dfs(int[] nums, int i, int j, int[][] memo) {
        if (i > j) {
            return 0;   // index边界结束
        }
        // 虽然没有不会超时,但是加上判断速度更快
        if(memo[i][j]!=0){
            return memo[i][j];
        }
        memo[i][j] = Math.max(nums[i] - dfs(nums, i + 1, j, memo), nums[j] - dfs(nums, i, j - 1, memo));
        return memo[i][j];
    }

    // 2: 非dfs方式
    public boolean PredictTheWinner(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[n][n];
        for (int i = 0; i < n; i++) {
            dp[i][i] = nums[i];
        }
        for (int len = 1; len < n; len++) {
            for (int i = 0; i < n - len; i++) {
                int j = i + len;
                dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);
            }
        }
        return dp[0][n - 1] >= 0;
    }
}

3. 除数博弈

image.png
class Solution {
    // dfs+memo模版
    public boolean divisorGame(int n) {
        boolean[] memo = new boolean[n + 1];
        return dfs(n, memo);
    }

    private boolean dfs(int n, boolean[] memo) {
        if (n < 2) {
            return false;
        }
        // 不这样会超时
        if (memo[n] == true) {
            return true;
        }
        boolean res = false;
        for (int i = 1; i <= n / 2; i++) {
            if (n % i == 0 && !dfs(n - i, memo)) {
                res = true;
                break;
            }
        }
        return memo[n] = res;
    }

    // 2. 最先到偶数的赢
    public boolean divisorGame(int n) {
        return n%2==0;
    }
}

4. 石子游戏

image.png
class Solution {
    // 1. dfs+memo模版
    public boolean stoneGame(int[] piles) {
        int n = piles.length;
        int[][] memo = new int[n][n];
        return dfs(piles, 0, n - 1, memo) > 0;
    }

    private int dfs(int[] piles, int i, int j, int[][] memo) {
        if (i > j) {
            return 0;
        }
        if (memo[i][j] != 0) {
            return memo[i][j];
        }
        memo[i][j] = Math.max(piles[i] - dfs(piles, i + 1, j, memo), piles[j] - dfs(piles, i, j - 1, memo));
        return memo[i][j];
    }

    // 2. 非dfs方式
    public boolean stoneGame(int[] piles) {
        int n = piles.length;
        int[][] dp = new int[n][n];
        for (int i = 1; i < n; i++) {
            dp[i][i] = piles[i];
        }
        for (int len = 1; len < n; len++) {
            for (int i = 0; i < n - len; i++) {
                int j = i + len;
                dp[i][j] = Math.max(piles[i]-dp[i+1][j] , piles[j]-dp[i][j-1]);
            }
        }
        return dp[0][n - 1] > 0;
    }
}

5. 石子游戏 II

image.png
class Solution {
    // 这里增加了一个限制条件,每次取的数量都可以是前一次的一半
    // 所以当次取值最优策略是限制对手下一次取的值要最小,那自己这次取得值就是最大了
    // 当剩下的石头数量小于等于2M时,证明剩下的全部被对手拿走了,那就不用计算了
    public int stoneGameII(int[] piles) {
        int n = piles.length;
        int[] sum = new int[n]; // 后缀和
        for (int i = n - 1; i >= 0; i--) {
            if (i == n - 1) {
                sum[i] = piles[n - 1];
            } else {
                sum[i] = sum[i + 1] + piles[i];
            }
        }
        int[][] memo = new int[n][n * 2];
        return dfs(piles, 0, 1, memo, sum);
    }

    private int dfs(int[] piles, int index, int M, int[][] memo, int[] sum) {
        if (index == piles.length) { //边界限制
            return 0;
        }
        if (piles.length - index <= 2 * M) {    // 剩下的石头小于等于2M,全部被对手拿走
            return sum[index];
        }
        // 防止超时
        if(memo[index][M] != 0){
            return memo[index][M];
        }
        // 我们拿1-2M,应该拿几个,怎么拿能让对手最小我们就怎么拿
        int min = Integer.MAX_VALUE;
        for (int i = 1; i <= 2 * M; i++) {
            // 我们怎么拿值能让对手拿的最小:选择限制对手拿到最小的值
            min = Math.min(min, dfs(piles, index + i, Math.max(M, i), memo, sum));
        }
        return memo[index][M] = sum[index] - min;
    }
}

6. 石子游戏 III

image.png
class Solution {
    // dfs+memo模版
    public String stoneGameIII(int[] stoneValue) {
            int n = stoneValue.length;
            int[] memo = new int[n + 1];
            int res = dfs(stoneValue, 0, memo);
            if (res > 0) {
                return "Alice";
            } else if (res == 0) {
                return "Tie";
            } else {
                return "Bob";
            }
        }

        private int dfs(int[] stoneValue, int index, int[] memo) {
            if (index == stoneValue.length) {     // 边界限制
                return 0;
            }
            if (memo[index] != 0) {
                return memo[index];
            }
            int res = Integer.MIN_VALUE;
            int sum = 0;
            // 求差值最大值
            for (int i = index; i < index + 3 && i<stoneValue.length; i++) {
                sum += stoneValue[i];
                res = Math.max(res, sum - dfs(stoneValue, i + 1, memo));
            }
            return memo[index] = res;
        }
    }

7. 石子游戏 IV

image.png
class Solution {
    // dfs+memo模版
    public boolean winnerSquareGame(int n) {
        boolean[] memo = new boolean[n + 1];
        return dfs(n, memo);
    }

    boolean dfs(int n, boolean[] memo) {
        if (n == 0) {
            return false;       // 设置边界
        }
        // 防止超时
        if (memo[n] == true) {
            return true;
        }
        // 只要对手输就说明我赢
        boolean res = false;
        for (int i = 1; i *i<=n; i++) {
            res |= !dfs(n - i * i, memo);
        }
        return memo[n] = res;
    }
}

8. 石子游戏 V

image.png
class Solution {
    // 1. dfs+memo模版
    public int stoneGameV(int[] stoneValue) {
        int n = stoneValue.length;
        int[][] memo = new int[n][n];
        int[] sum = new int[n + 1];     // 后缀和
        for (int i = n - 1; i >= 0; i--) {
            if (i == n - 1) {
                sum[i] = stoneValue[i];
            } else {
                sum[i] = sum[i + 1] + stoneValue[i];
            }
        }
        return dfs(memo, 0, n - 1, sum);
    }

    private int dfs(int[][] memo, int i, int j, int[] sum) {
        if (i >= j) {       // 边界限制
            return 0;
        }
        // 防止超时
        if (memo[i][j] != 0) {
            return memo[i][j];
        }
        int max = Integer.MIN_VALUE;
        for (int k = i; k < j; k++) {
            if (sum[i] - sum[k + 1] > sum[k + 1] - sum[j + 1]) {
                max = Math.max(max, sum[k + 1] - sum[j + 1] + dfs(memo, k + 1, j, sum));
            } else if (sum[i] - sum[k + 1] < sum[k + 1] - sum[j + 1]) {
                max = Math.max(max, sum[i] - sum[k + 1] + dfs(memo, i, k, sum));
            } else {
                max = Math.max(max, Math.max(sum[k + 1] - sum[j + 1] + dfs(memo, k + 1, j, sum), sum[i] - sum[k + 1] + dfs(memo, i, k, sum)));
            }
        }
        return memo[i][j] = max;
    }
}

9. 石子游戏 VI

image.png
class Solution {
    public int stoneGameVI(int[] aliceValues, int[] bobValues) {
        // 得分最大化: num[i] = aliceValues[i]+bobValues[i]
        int n = aliceValues.length;
        // 必须是Integer,否则无法倒序排序,不能是基本数据类型
        Integer[] nums = new Integer[n];
        // map的目的是记录下标,将来对nums排序后下标乱就无法从aliceValues和bobValues中获取值
        List<Map.Entry<Integer, Integer>> list = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            nums[i] = aliceValues[i] + bobValues[i];
            list.add(new HashMap.SimpleEntry<Integer, Integer>(i, nums[i]));
        }
        Collections.sort(list, (a, b) -> b.getValue() - a.getValue());
        int aRes = 0;
        int bRes = 0;
        for (int i = 0; i < n; i++) {
            if (i % 2 == 0) {
                aRes += aliceValues[list.get(i).getKey()];
            } else {
                bRes += bobValues[list.get(i).getKey()];
            }
        }
        if (aRes > bRes) {
            return 1;
        } else if (aRes < bRes) {
            return -1;
        } else {
            return 0;
        }
    }
}

10. 石子游戏 VII

image.png
class Solution {
    // 1. dfs+memo模版
    public int stoneGameVII(int[] stones) {
        int n = stones.length;
        int[][] memo = new int[n][n];
        int[] sum = new int[n + 1];    // 后缀和
        for (int i = n - 1; i >= 0; i--) {
            if (i == n - 1) {
                sum[i] = stones[i];
            } else {
                sum[i] = stones[i] + sum[i + 1];
            }
        }
        return dfs(sum, 0, n - 1, memo);
    }

    private int dfs(int[] sum, int i, int j, int[][] memo) {
        if (i >= j) {
            return 0;   // 边界限制
        }
        // 防止超时
        if (memo[i][j] != 0) {
            return memo[i][j];
        }
        int res = Integer.MIN_VALUE;
        res = Math.max(sum[i+1]-sum[j+1] - dfs(sum , i+1,j,memo) , sum[i]-sum[j] - dfs(sum , i,j-1,memo));
        return memo[i][j]=res;
        
    }
}

11. 如果相邻两个颜色均相同则删除当前颜色

image.png
class Solution {
    // 只需统计双方可删除位置的数量,即双方最多可操作回合数,操作回合少的一方会先陷入无法继续操作的局面,即输掉,
    // 若可操作回合数相同,由于 Alice 先手,所以 Alice 会输掉
    public boolean winnerOfGame(String cs) {
        int alice = 0;
        int bob = 0;
        int n = cs.length();
        for (int i = 1; i < n - 1; i++) {
            if (cs.charAt(i) == 'A' && cs.charAt(i - 1) == 'A' && cs.charAt(i + 1) == 'A') {
                alice++;
            }
            if (cs.charAt(i) == 'B' && cs.charAt(i - 1) == 'B' && cs.charAt(i + 1) == 'B') {
                bob++;
            }
        }
        if (alice <= bob) {
            return false;
        }
        return true;
    }
}

12. 你可以获得的最大硬币数目

image.png
class Solution {
    public int maxCoins(int[] piles) {
        // 贪心策略:每次从piles中选择最大的两个数和最小的一个数
        Arrays.sort(piles);
        int n = piles.length;
        int res = 0;
        int idx = n - 2;
        for (int i = 0; i < n / 3; i++) {
            // 累加第二大的数n/3次,就是我们可以拿到的数
            res += piles[idx];
            idx -= 2;
        }
        return res;
    }
}

13. 求和游戏

image.png
class Solution {
    public static boolean sumGame(String num) {
        int n = num.length();
        int alice = 0, aliceNum = 0;
        int bob = 0, bobNum = 0;
        for (int i = 0; i < n / 2; i++) {
            if (num.charAt(i) == '?') {
                // 前半部分问号数
                aliceNum++;
            } else {
                // 前半部分数字和
                alice += num.charAt(i) - '0';
            }
            if (num.charAt(i+n/2) == '?') {
                // 后半部分问号数
                bobNum++;
            } else {
                // 后半部分数字和
                bob += num.charAt(i+n/2) - '0';
            }
        }
        // 这一步理解起来很困难: 等左右两边的?互相抵消之后,剩余的?就都在一侧
        // Alice只有两种策略(走极端):
        // 1、增大,Alice回合置换问号为9(Bob阻止,Bob回合置换问号为0); 2、减小,Alice回合置换问号为0(Bob阻止,Bob回合置换问号为9)
        // 所以,每个周期剩余问号一侧都会增加9,如果最终两侧相等,则Bob赢,否则Alice赢。
        // alice - bob = (bobNum - aliceNum) / 2 * 9
        if ((alice - bob) * 2 == 9 * (bobNum - aliceNum)) {
            return false;
        }
        return true;
    }
}