【Lintcode】396. Coins in a Line III

题目地址:

https://www.lintcode.com/problem/coins-in-a-line-iii/description

给定有 n n n个硬币排成一条线,硬币的价值为以数组 A A A表示。两个参赛者轮流从任意一边取一枚硬币,直到没有硬币为止。拿到硬币总价值更高的获胜。问先手是否有必胜策略。

参考https://blog.csdn.net/qq_46105170/article/details/104091040

如果 n n n是偶数的话,我们可以将 A [ 0 ] , A [ 2 ] , . . . A[0],A[2],... A[0],A[2],...涂成红色, A [ 1 ] , A [ 3 ] , . . . A[1],A[3],... A[1],A[3],...涂成蓝色,如果红色硬币总价值高于蓝色,那么先手可以总是取红色,从而必胜;如果蓝色硬币总价值高于红色,先手也可以必胜;如果价值一样高,那么无论先手取哪个颜色,后手可以取不同的颜色,从而导致两个人各自始终取的是相同颜色的硬币,从而后手可以保持不败,也就是先手不会有必胜策略。

如果 n n n是奇数,则需要用动态规划或者记忆化搜索来解决了。

法1:记忆化搜索。设 f [ i ] [ j ] f[i][j] f[i][j]表示如果只剩下 A [ i : j ] A[i:j] A[i:j]这么多硬币的时候,先手与后手的分差最大值是多少(也就是先手能比后手多赢多少价值),那么有两种可能,分别是先手取 A [ i ] A[i] A[i],此时后手最多能赢 f [ i + 1 ] [ j ] f[i+1][j] f[i+1][j]分,那么先手最多能赢的分差是 A [ i ] − f [ i + 1 ] [ j ] A[i]-f[i+1][j] A[i]f[i+1][j];类似的,先手取 A [ j ] A[j] A[j],分差最大值是 A [ j ] − f [ i ] [ j − 1 ] A[j]-f[i][j-1] A[j]f[i][j1]。base case是 i = j i=j i=j的时候, f [ i ] [ i ] = A [ i ] f[i][i]=A[i] f[i][i]=A[i]。最后只需要返回 f [ 0 ] [ l A − 1 ] f[0][l_A-1] f[0][lA1]是否为正即可。代码如下:

public class Solution {
    /**
     * @param values: a vector of integers
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int[] values) {
        // write your code here
        if (values.length % 2 == 0) {
            int odd = 0, even = 0;
            for (int i = 0; i < values.length; i++) {
                if (i % 2 == 0) {
                    even += values[i];
                } else {
                    odd += values[i];
                }
            }
            
            return odd != even;
        }
        
        int len = values.length;
        return dfs(0, len - 1, new int[len][len], values) > 0;
    }
    
    private int dfs(int i, int j, int[][] dp, int[] values) {
    	// 如果有记忆,则调取记忆
        if (dp[i][j] != 0) {
            return dp[i][j];
        }
        
        // 只有一个硬币,那么就返回其价值。返回之前做记忆
        if (i == j) {
            dp[i][i] = values[i];
            return values[i];
        }
        
        // 分两种情况讨论,先手拿左边硬币还是拿右边硬币
        int r1 = values[i] - dfs(i + 1, j, dp, values), r2 = values[j] - dfs(i, j - 1, dp, values);
        // 返回之前做记忆
        dp[i][j] = Math.max(r1, r2);
        return dp[i][j];
    }
}

时空复杂度 O ( n 2 ) O(n^2) O(n2)

注意,这里if (dp[i][j] != 0) { return dp[i][j]; }这句话,如果if判断成立则一定有记忆,但不代表有记忆这个判断就一定成立,因为有可能恰好dp[i][j] = 0,这就是记忆。也就是说,如果恰好算出来dp[i][j] = 0,那么这个结果是会被重复计算的。但这一点并不会有太大的影响,因为发生的概率很低。

法2:动态规划。思路和上面是一样的,只不过写成递推的形式。代码如下:

public class Solution {
    /**
     * @param values: a vector of integers
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int[] values) {
        // write your code here
        if (values.length % 2 == 0) {
            int odd = 0, even = 0;
            for (int i = 0; i < values.length; i++) {
                if (i % 2 == 0) {
                    even += values[i];
                } else {
                    odd += values[i];
                }
            }
        
            return odd != even;
        }
        
        int[][] dp = new int[values.length][values.length];
        for (int len = 1; len <= values.length; len++) {
            for (int i = 0; i + len - 1 < values.length; i++) {
                int j = i + len - 1;
                if (len == 1) {
                    dp[i][i] = values[i];
                } else {
                    dp[i][j] = Math.max(values[i] - dp[i + 1][j], values[j] - dp[i][j - 1]);
                }
            }
        }
        
        return dp[0][values.length - 1] > 0;
    }
}

时空复杂度一样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值