题意
- 一个
n*n
矩阵A,矩阵包含-1
, 1
, 0
,其中-1
不能通行,从矩阵左上角走到右下角,再走回左上角,第一次遇到1
收益+1
,第二次则不加,问你最大收益。
思路
- 这个题困扰我好久,网上题解包括discussion感觉都不是特别直观,现在终于完全想通了。。这里记录一下,希望能对大家有帮助。
- 这个题如果没有反复,那么显然可以用一个普通二维dp,直接求解
- 那么首先,我们想到的肯定是一个贪心的思路,先找正向一条最优的路,把路过的
1
变0
,再重新dp一遍 - 这个思路是错误的,我自己验证的方法是先假设这个贪心正确,再用反证法尝试证明,在证明过程中,你就会发现一些无法cover的情况,比如一条最大权路径收益为
6
,两条次大收益路径相互没有重叠,收益为5
,而最大路径分别和这两个次大路径重叠了2
,假如其它位置都是0
,那么贪心方法收益最大是9
,而选两个次大路径收益是10
- 那么我们就需要重新设计dp状态,把重叠这个问题考虑进去
- 首先我们知道,正反两次走,等价于分别做两次正着走。问题就变成分别走两次找最大收益,其中第一次走过的
1
会变成0
。 - 然后我们进一步,简化理解,可以是
2
个人同时正着走,且速度一样,希望两人总体的收益最大,如果它们同时走到一个格子上,那它们只能拿一次。可以简单理解一下为什么这个问题,和刚才的问题等价:设速度都是1
,则第t
个时刻,设第一个人走到(x1, y1)
,第二个人走到(x2, y2)
,那么一定有x1 + y1 = t
,x2 + y2 = t
,假如x1 != x2
,那么这一次行程中,第一个人永远不会走到(x2, y2)
,同理第二人永远不会走到(x1, y1)
。因此,拿重的问题只会在它们同时走到一个格子的时候遇到,因此我们判断他们每个时刻是否会到达同一个格子就可以去重了。 - 把这个思想转换成dp的状态,则可以表示为
dp(t, x1, x2)
,也就是第t
时刻第一个人走到(x1, t - x1)
,第二个人走到(x2, t - x2)
时两人的最大收益。 - 状态转移也非常简单:
dp(t, x1, x2) = grid(x1, t - x1) + (x1 == x2 ? 0 : grid(x2, t - x2)) + max(dp(t-1, x1, x2), dp(t - 1, x1, x2 - 1), dp(t - 1, x1 - 1, x2), dp(t - 1, x1 - 1, x2 - 1))
- 最后就是
t
这一维我们可以通过滚动数组压掉,注意这样的话需要反向遍历更新dp
实现
class Solution {
public:
int cherryPickup(vector<vector<int>>& grid) {
int n = grid.size();
vector<vector<int>> dp(n, vector<int>(n, -1));
dp[0][0] = grid[0][0];
for (int k = 1; k < (n << 1) - 1; k++){
for (int i = min(n - 1, k); i >= 0 && i >= k - n + 1; i--){
for (int j = min(n - 1, k); j >= 0 && j >= k - n + 1; j--){
int p = k - i, q = k - j;
if (grid[i][p] == -1 || grid[j][q] == -1){
dp[i][j] = -1;
continue;
}
if (p > 0 && j > 0){
dp[i][j] = max(dp[i][j], dp[i][j-1]);
}
if (i > 0){
if (j > 0)
dp[i][j] = max(dp[i][j], dp[i-1][j-1]);
if (q > 0)
dp[i][j] = max(dp[i][j], dp[i-1][j]);
}
if (dp[i][j] == -1)
continue;
if (i == j)
dp[i][j] += grid[i][p];
else
dp[i][j] += grid[i][p] + grid[j][q];
}
}
}
return max(dp[n-1][n-1], 0);
}
};