LeetCode:1140. Stone Game II
这一题意义重大,是我一直忽视的动态规划中:从上至下+记忆化的方法。也就是“带备忘的自顶向下法”
这一题我是开始准备按照以往思路想自底向上来做的,也就是将问题按照规模由小到大排序。但是发现不好做。相反,按照自然的递归形式写程序是最好的。这时候就应该想到带备忘的自顶向下法
class Solution {
public:
int stoneGameII(vector<int>& piles) {
int sz = piles.size();
if (sz == 0)
return 0;
vector<vector<int>> dp(sz, vector<int>(32, 0));//大小的选取
vector<int> sums(sz, 0);
sums[sz-1] = piles[sz-1];
for (int i = sz-2; i >= 0; --i)
sums[i] = piles[i] + sums[i+1];
return helper(piles, 0, 1, dp, sums);
}
private:
int helper(vector<int>& piles, int i, int M, vector<vector<int>>& dp, vector<int>& sums) {//需要注意,这个函数返回的就是dp值
if (i >= piles.size())
return 0;
if (2*M >= piles.size()-i)//这里也不需要记下来了,因为要用的时候直接这里返回了,不需要到后面了
return sums[i];
if (dp[i][M] != 0)
return dp[i][M];
int minVal = INT_MAX;
for (int x = 1; x <= 2 * M; ++x)
minVal = min(minVal, helper(piles, i+x, max(M, x), dp, sums));//这里直接把函数返回值当作dp值来用
dp[i][M] = sums[i] - minVal;//记忆化方法
return dp[i][M];
}
};
//dp[i][m]:从i位置开始,M为m,第一个拿的人所能拿到的最多的石头的数目。
//dp[i][m] = sums[i] - min(dp[i+x][max(m, x)]), x:1->2*m
//具体解释为:需要找到一个x的大小,使对手在i+x开始,M为max(m, x)这个情况下拿到的最少(对手也是相同的策略来拿的)。使用总共的减去对手拿的,就是我拿的。
//题目一个重要前提就是:两个人都是按照“同样的”最优策略来拿的。
关于dp大小的选取:
第二个大小的含义使M的大小,选取32的含义是因为最大为32,因为加倍M最快的方式是1+2+4+8+16+32+64,而且我们用不到64(helper函数中第二个判断直接返回了),所以32就ok了。(当然也可以设置为sz)
这是很重要的一题,之后如果还碰到这种自顶向下的记忆化方法的题目再添加到这儿。
另外,更方便的,使用map的用法:
class Solution {
public:
int stoneGameII(vector<int>& piles) {
map<pair<int, int>, int> m;
int sz = piles.size();
vector<int> sums(sz, 0);
sums[sz-1] = piles[sz-1];
for (int i = sz-2; i >= 0; --i)
sums[i] = piles[i] + sums[i+1];
return helper(m, piles, 1, 0, sums);
}
private:
int helper(map<pair<int, int>, int>& m, vector<int>& piles, int M, int i, vector<int>& sums) {
if (piles.size() - i <= 2 * M)
return sums[i];
auto p = make_pair(M, i);
if (m.find(p) != m.end())
return m[p];
int val = 0;
for (int x = 1; x <= 2*M; ++x) {
val = max(val, sums[i] - helper(m, piles, max(M, x), i+x, sums));
}
m[p] = val;
return val;
}
};