[LintCode] Coins in a line II

有 n 个不同价值的硬币排成一条线。两个参赛者轮流从左边依次拿走 1 或 2 个硬币,直到没有硬币为止。计算两个人分别拿到的硬币总价值,价值高的人获胜。

请判定 第一个玩家 是输还是赢?

样例
给定数组 A = [1,2,2], 返回 true.
给定数组 A = [1,2,4], 返回 false.

类似题目:[Google] 拿纸牌游戏

纸牌上面有值,比如说 100 1 -1 2 200 1. 然后两个人轮流拿,直到拿完。 但是每次只能拿从左边数起的前三个,但是如果你要拿第三个,就必须前两个都拿了,你要拿第二个,就必须第一个也拿了,大家都最优策略,问最后第一个人能拿多少分。

解法1:DP,Time: O(n)

考虑先手玩家在状态dp[i],dp[i]表示在在第i的硬币的时候,这一位玩家能拿到的最高价值。
如果先手玩家取一枚硬币,那么dp[i] = values[i] + sum[i + 1] - dp[i + 1]。减去dp[i + 1]的原因是,对手玩家,每次也要想办法拿到最大的价值,所以先手玩家能拿到的价值是在对手玩家拿到最大价值的硬币之后的剩余的硬币价值。

如果先手玩家取两枚,那么dp[i] = values[i] + values[i + 1] + sum[i + 2] - dp[i + 2];

注意判断是否先手玩家赢的条件,是2倍dp[0]的值是不是大于sum[0],因为是拿到硬币价值多的玩家算赢。

动态规划4要素

  • State:
    • dp[i] 现在还剩i个硬币,现在当前取硬币的人最后最多取硬币价值
  • Function:
    • i 是所有硬币数目
    • sum[i] 是后i个硬币的总和
    • dp[i] = sum[i]-min(dp[i-1], dp[i-2])
  • Intialize:
    • dp[0] = 0
    • dp[1] = coin[n-1]
    • dp[2] = coin[n-2] + coin[n-1]
  • Answer:
    • dp[n]

可以画一个树形图来解释:

                  [5, 1, 2, 10] dp[4]
        (take 5) /             \ (take 5, 1)
                /               \
        [1, 2, 10] dp[3]         [2, 10] dp[2]
(take 1) /     \ (take 1, 2)  (take 2) / \ (take 2, 10)
        /       \                     /   \
  [2, 10] dp[2]  [10] dp[1]     [10] dp[1] [] dp[0]

也就是说,每次的剩余硬币价值最多值dp[i],是当前所有剩余i个硬币价值之和sum[i],减去下一手时对手所能拿到最多的硬币的价值,即 dp[i] = sum[i] - min(dp[i - 1], dp[i - 2])

解法2: DFS + Memorization,  复杂度O(N),O(N)

思路是一样,不过是从bottom-up的算法,用map来保存已经搜索过的路线。

Java: DP

public class Solution {
    /**
     * @param values: an array of integers
     * @return: a boolean which equals to true if the first player will win
     */
    public boolean firstWillWin(int[] values) {
        if (values == null || values.length == 0) {
            return false;
        }
        int n = values.length;
        int[] dp = new int[n + 1];
        boolean[] flag = new boolean[n + 1];

        int[] sum = new int[n + 1];
        int total = 0;
        sum[0] = 0;
        for (int i = n - 1; i >= 0; i--) {
            sum[n - i] = sum[n - i - 1] + values[i];
            total += values[i];
        }

        return search(n, n, dp, flag, values, sum) > total / 2;
    }
    public int search(int i, int n, int[] dp, boolean[] flag, int[] values, int[] sum) {
        if (flag[i] == true) {
            return dp[i];
        }
        if (i == 0) {
            dp[i] = 0;
        } else if (i == 1) {
            dp[i] = values[n - 1];
        } else  if (i == 2) {
            dp[i] = values[n - 1] + values[n - 2];
        } else {
            dp[i] = sum[i] - Math.min(search(i - 1, n, dp, flag, values, sum), search(i - 2, n, dp, flag, values, sum));
        }
        flag[i] = true;
        return dp[i];
    }
}  

Java: DP

public boolean firstWillWin(int[] values) {
    int len = values.length;
    int[] sum = new int[len];
    int[] dp = new int[len];
    for(int i = len - 1; i >= 0; i --) {
        if(i == len - 1) sum[i] = values[i];
        else sum[i] = values[i] + sum[i + 1];
    }
    for(int i = len - 1; i >= 0; i --) {
        if(i == len - 1) dp[i] = values[i];
        else if(i == len - 2) dp[i] = values[i] + values[i + 1];
        else {
            dp[i] = Math.max(values[i] + sum[i + 1] - dp[i + 1], values[i] + values[i + 2] + sum[i + 2] - dp[i + 2]);
        }
    }
    return dp[0] * 2 > sum[0];
}

Java: DFS  

Map<Integer, Integer> map = new HashMap<>();
public int helper(int left, int right, int[] values, int[] sum) {
    // Base case;
    if(left > right) return 0;
    if(left == right) return values[left];
    if(left + 1 == right) return values[left] + values[right];
    if(map.containsKey(left)) return map.get(left);
 
    int val = Math.max(values[left] + sum[left + 1] + helper(left + 1, right, values, sum), \
                       values[left] + values[left + 1] + sum[left + 2] + helper(left + 2, right, values, sum));
    map.put(left, val);
    return val;
}

  

类似题目:

[LintCode] Coins in a line

 

 

 

 

posted @ 2018-10-19 09:28  轻风舞动  阅读(391)  评论(0编辑  收藏  举报