LintCode 395. Coins in a Line II (博弈类DP经典题!!!)

  1. Coins in a Line II

There are n coins with different value in a line. Two players take turns to take one or two coins from left side until there are no more coins left. The player who take the coins with the most value wins.

Could you please decide the first player will win or lose?

If the first player wins, return true, otherwise return false.

Example
Example 1:

Input: [1, 2, 2]
Output: true
Explanation: The first player takes 2 coins.
Example 2:

Input: [1, 2, 4]
Output: false
Explanation: Whether the first player takes 1 coin or 2, the second player will gain more value.

解法1:
思路:把两个选手当成1个人,每次都让自己面对剩下的棋子选择最优局面。
sums[i]:从i到len-1的values之和。
dp[i]: 第i轮时,该选手在该轮到比赛结束所能取得的最多coin。
关键代码:
dp[i] = max(sums[i + 1] - dp[i + 1] + values[i],
sums[i + 2] - dp[i + 2] + values[i] + values[i + 1]);
因为第i轮时,该选手可取1个或2个。若取1个,对手下一轮到比赛结束所能取得的最多coin是dp[i + 1],所以自己下一轮到比赛结束所取得的最多coin是sums[i + 1] - dp[i + 1],同时加上这一轮所取的values[i]。若取2个,情况类似。

注意: 该DP要从后往前推,最后判断sums[0] - dp[0] < dp[0]。因为dp[len - 1]和dp[len - 2]好算。

代码如下:

class Solution {
public:
    /**
     * @param values: a vector of integers
     * @return: a boolean which equals to true if the first player will win
     */
    bool firstWillWin(vector &values) {
        int len = values.size();
        if (len < 3) return true;
        vector sums(len, 0);
        sums[len - 1] = values[len - 1];
        for (int i = len - 2; i >= 0; --i) {
            sums[i] = sums[i + 1] + values[i];
        }
        
        vector dp(len, 0);
        dp[len - 1] = values[len - 1];
        dp[len - 2] = values[len - 1] + values[len - 2];
        
        for (int i = len - 3; i >= 0; --i) {
            dp[i] = max(sums[i + 1] - dp[i + 1] + values[i],
                        sums[i + 2] - dp[i + 2] + values[i] + values[i + 1]);
        }
        
        return sums[0] - dp[0] < dp[0];
    }
};

解法2:
上面的sums[]其实可以用滚动数组优化。
sum2: 当i循环结束时,从values[i]到values[len - 1]的累加和。
sum1: 当i循环结束时,从values[i + 1]到values[len - 1]的累加和。
代码如下:

class Solution {
public:
    /**
     * @param values: a vector of integers
     * @return: a boolean which equals to true if the first player will win
     */
    bool firstWillWin(vector &values) {
        int len = values.size();
        if (len < 3) return true;
        int sum1 = values[len - 1];
        int sum2 = values[len - 1] + values[len - 2];

        vector dp(len, 0);
        dp[len - 1] = values[len - 1];
        dp[len - 2] = values[len - 1] + values[len - 2];
        
        for (int i = len - 3; i >= 0; --i) {
            dp[i] = max(sum2 - dp[i + 1] + values[i],
                        sum1 - dp[i + 2] + values[i] + values[i + 1]);
            sum1 = sum2;
            sum2 += values[i];
        }
        
        return sum2 - dp[0] < dp[0];
    }
};

解法3:从解法2中可以看出dp[i]实际上也取决于dp[i+1]和dp[i+2],所以,又可以用2个变量dp1和dp2取代整个dp[]。这样空间复杂度就是O(1)了。
代码如下:

class Solution {
public:
    /**
     * @param values: a vector of integers
     * @return: a boolean which equals to true if the first player will win
     */
    bool firstWillWin(vector &values) {
        int len = values.size();
        if (len < 3) return true;
        
        int sum1 = values[len - 1];
        int sum2 = values[len - 1] + values[len - 2];

        int dp2 = sum2;
        int dp1 = sum1;
        
        for (int i = len - 3; i >= 0; --i) {
         
            int dp = max(sum2 - dp2 + values[i],
                      sum1 - dp1 + values[i] + values[i + 1]);
            dp1 = dp2;
            dp2 = dp;
            sum1 = sum2;
            sum2 += values[i];
        }
        
        return sum2 - dp2 < dp2;
    }
};

注意这里必须用一个临时变量dp,要不然dp1和dp2会有一个被覆盖。sum1和sum2不需要临时变量是因为它们没有转换方程,而dp有。

解法4:九章解法。
f[i]表示本轮选手A在面对A[i…n-1]这些coins时,所能得到的最大的与对方选手B的coins数之差。
转换方程:
f[n] = 0;
f[i] = max(values[i] - f[i + 1], values[i] + values[i + 1] - f[i + 2]);

因为f[i+1]是对方B所能取得的与A的coin的最大差,所以从A的角度看来,要加一个负号。

f[n-1]=values[n-1],
f[n-2]=values[n-1]+values[n-2];
f[n-3]=max(values[n-3]-f[n-2], values[n-3]+values[n-2]-f[n-1])
=max(values[n-3]-values[n-1]-values[n-2], values[n-3]+values[n-2]-values[n-1]);

public class Solution {
    public boolean firstWillWin(int[] A) {
        int n = A.length;
        int[] f = new int[n + 1];
        f[n] = 0;
        int i;
        for (i = n - 1; i >= 0; --i){
            f[i] = A[i] - f[i + 1];
            if (i < n - 1) {
                f[i] = Math.max(f[i], A[i] + A[i + 1] - f[i + 2]);
            }
        }
        
        return f[0] >= 0;
    }
}

解法5:九章版本之O(1)空间。参考网上代码。

public class Solution {
    public boolean firstWillWin(int[] values) {
        if(values.length == 0){
            return true;
        }
        if(values.length == 1){
            return values[0] >=0;
        }
        int last1 = values[values.length - 1];
        int last2 = values[values.length - 2] + last1;
        for(int i = values.length - 3; i >= 0; i --){
            int temp = Math.max(values[i] - last2, values[i] + values[i + 1] - last1);
            last1 = last2;
            last2 = temp;
        }
        return last2 >= 0;
    }
}

你可能感兴趣的:(LintCode 395. Coins in a Line II (博弈类DP经典题!!!))