礼物的最大价值
1. 题目
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
示例 1:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
2. 分析
这道题目来看,相当于最长路径,移动的方向只能够是向下和向右单项移动,且起点和终点的确认为左上角和右下角。可以将这个情景建模为一个动态规划的问题,设置状态函数为
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为在坐标
(
i
,
j
)
(i,j)
(i,j)上的达到该点最大价值,它的取值和它上面和右面相邻的最大值有关
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
−
1
]
[
j
]
)
+
g
r
i
d
[
i
]
[
j
]
dp[i][j]=max(dp[i][j-1],dp[i-1][j]) + grid[i][j]
dp[i][j]=max(dp[i][j−1],dp[i−1][j])+grid[i][j]
因为到达该点只可能有两种情况,第一个是从上面来,第二个是从左面来,这时我们只需要选择更大的一个方向进入再加上改点本身的礼物价值,即为达到该点所获得的最大价值。
3. 代码
根据上面的转移方程可以直接写出的代码为:
class Solution {
public:
int maxValue(vector<vector<int>>& grid) {
if(grid.size()==0){
return 0;
}
if(grid[0].size()==0){
return 0;
}
int gridState[300][300];
for(int i = 0;i<300;i++){
for(int j =0;j<300;j++){
gridState[i][j] = 0;
}
}
gridState[0][0] = grid[0][0];
// init the left bound and upper bound
for(int i =1;i<grid.size();i++){
gridState[i][0] = gridState[i-1][0] + grid[i][0];
}
for(int i =1;i<grid[0].size();i++){
gridState[0][i] = gridState[0][i-1] + grid[0][i];
}
// update the inner
for(int i =1;i<grid.size();i++){
for(int j=1;j<grid[0].size();j++){
gridState[i][j] = max(gridState[i-1][j] ,gridState[i][j-1]) + grid[i][j];
}
}
// get the final
return gridState[grid.size()-1][grid[0].size()-1];
}
};
这份代码中使用了gridState这个变量来存储到达每一个点的最大值,来求解最终的值,此时的时间复杂度为O(mn),空间复杂度O(mn)。
但我们可以注意到有很多状态都是用完后不会再次访问,比如在求解 d p [ i ] [ j ] dp[i][j] dp[i][j]的时候 d p [ i − 2 ] [ j ] dp[i-2][j] dp[i−2][j]的信息就不会再次使用,那是否可以节约一些空间呢?
答案是可以的,我们注意到每次更新 d p [ i ] [ j ] dp[i][j] dp[i][j]其实只是用到了该位置的上邻居和左邻居,我们可以考虑只用两个列表来存储这些邻居,写出的代码有。
class Solution {
public:
int maxValue(vector<vector<int>>& grid) {
if(grid.size()==0){
return 0;
}
if(grid[0].size()==0){
return 0;
}
int height = grid.size();
int width = grid[0].size();
int* front = new int[width];
int* left = new int[height];
left[0] = grid[0][0];
for(int i=1;i<height;i++){
left[i] = left[i-1] + grid[i][0];
}
front[0] = grid[0][0];
for(int i=1;i<width;i++){
front[i] = front[i-1] + grid[0][i];
}
for(int i = 1;i<height;i++){
for(int j=1;j<width;j++){
int newLeft = max(left[i], front[j]) + grid[i][j];
left[i] = newLeft;
front[j] = newLeft;
}
}
return front[width-1];
}
};
新的代码我们用front和left两个序列来保存邻居,此时的时间复杂度仍然为O(mn),但是空间复杂度下降为O(m+n)。
更进一步,如果我们允许在原来的grid数组上进行修改的话,可以使用使得空间复杂度下降为 O ( 1 ) O(1) O(1)。