高级农民
- 积分
- 3693
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2014-9-5
- 最后登录
- 1970-1-1
|
本帖最后由 mnmunknown 于 2016-7-20 05:10 编辑
试着用 MiniMax 做了一下,已 AC 代码如下:
这题按类型分的话可以划为博弈类 DP,类似的还有 Lintcode 上的 Coins in a Line 1,2,3
我们先定义 dp[j],代表着如果我们在区间 [i , j] 内进行查找,所需要的最少 cost 来保证找到结果。(当然,因为给定数字是 [1, n],这里有一个 index off by one 的问题)。不难发现对于最开始的函数输入 n ,我们的最终结果就是 dp[0][n - 1] ,也即数字区间 [1 , n] 保证得到结果所需要的最小 cost.
[i]如果以 top-down recursion 的方式分析这个问题,可以发现对于区间 [i, j] ,我们的猜测 i <= k <= j 我们可能出现以下三种结果:
1. k 就是答案,此时子问题的额外 cost = 0 ,当前位置总 cost = k + 0;
2. k 过大,此时我们的有效区间缩小为 [i , k - 1] 当前操作总 cost = k + dp[start][k - 1];
3. k 过小,此时我们的有效区间缩小为 [k + 1 , j] 当前操作总 cost = k + dp[k + 1][j];
由于我们需要 “保证得到结果”,也就是说对于指定 k 的选择,我们需要准备最坏情况 cost 是以下三种结果生成的 subproblem 中cost 最大的那个; 然而同时对于一个指定区间 [i , j] ,我们可以选择任意 i <= k <= j ,对于这个 k 的主观选择可以由我们自行决定,我们要选的是 k s.t. 其子问题的 cost + 当前操作 cost 最小的一个,至此,每次决策就构成了一次 MiniMax 的博弈。
同时因为我们有很多的 overlapping subproblems ,而且问题本身具有 optimal substructure,提高算法效率最简单直观的方式,就是用 int[][] dp 做缓存,来进行自顶向下的记忆化搜索 ( top-down memoized search).- public class Solution {
- public int getMoneyAmount(int n) {
- // dp[i][j] min cost to guarantee to win from interval [i , j]
- return getMinCost(0, n - 1, new int[n][n]);
- }
-
- private int getMinCost(int start, int end, int[][] dp){
- if(start >= end) return 0;
-
- if(dp[start][end] != 0) return dp[start][end];
-
- int minCost = Integer.MAX_VALUE;
-
- for(int i = start; i < end; i++){
- minCost = Math.min(minCost, (i + 1) + Math.max(getMinCost(start, i - 1, dp),
- getMinCost(i + 1, end, dp)));
- }
-
- dp[start][end] = minCost;
-
- return dp[start][end];
- }
- }
复制代码 为了进行这类算法的比较,也附上自己写的 Coins in a line II 的 MiniMax 代码,可以看到核心的地方就是这里:- <i> </i> int oneMax = values[n - coins] + Math.min(memoizedSearch(coins - 2, values, dp),
- memoizedSearch(coins - 3, values, dp));
- int twoMax = values[n - coins] + values[n - coins + 1]
- + Math.min(memoizedSearch(coins - 3, values, dp),
- memoizedSearch(coins - 4, values, dp));
- dp[coins] = Math.max(oneMax, twoMax);
复制代码 代表着在每一步上我有两个选择:拿一个硬币,拿两个硬币; 然而同时对手也有两个选择, 拿一个硬币,或者拿两个硬币。那么对于还有 n 个硬币剩余的情况下,我当前决策所能获得的最大收益就是 max ( 当前拿硬币的收益 + min(对手拿一个硬币留给我的最大收益,对手拿两个硬币留给我的最大收益) )
可以看到这题的 MiniMax 思路完全一致,只不过每一步的选择上变成了 [i , j] 区间内的个数,而不仅仅是 2 个,同时我们试图在最外围取 min cost ,而不是硬币问题中的 max profit.- 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) {
- // write your code here
- int n = values.length;
- if(n <= 2) return true;
- int[] dp = new int[n + 1];
- Arrays.fill(dp, -1);
- dp[0] = 0;
- dp[1] = values[n - 1];
- dp[2] = values[n - 1] + values[n - 2];
- int sum = 0;
- for(int num : values){
- sum += num;
- }
- return 2 * memoizedSearch(n, values, dp) > sum;
- }
- private int memoizedSearch(int coins, int[] values, int[] dp){
- if(coins < 0) return 0;
- if(dp[coins] != -1) return dp[coins];
- int n = values.length;
- int oneMax = values[n - coins] + Math.min(memoizedSearch(coins - 2, values, dp),
- memoizedSearch(coins - 3, values, dp));
- int twoMax = values[n - coins] + values[n - coins + 1]
- + Math.min(memoizedSearch(coins - 3, values, dp),
- memoizedSearch(coins - 4, values, dp));
- dp[coins] = Math.max(oneMax, twoMax);
- return dp[coins];
- }
- }
复制代码 |
|