题目:在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格,直到到达棋盘的右下角。给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?
例如,在下面的棋盘中,如果沿着加粗的数字的线路(1、12、5、7、7、16、5),那么我们能拿到最大价值为53的礼物。
1 10 3 8
12 2 9 6
5 7 4 11
3 7 16 5
思路:
1、跟上一题很相似,所以很容易想到动态规划来求解。我们注意到当前最大值只跟其上一个及左邻居两个点处的最大礼物值有关,所以有:f(i,j) = max(f(i-1,j),f(i,j-1))+gift(i,j);其中gift(i,j)为当前位置的值。然后使用已知二维数组长度新建辅助二维数组用来记录当前点的最大值就可以了。
2、其次,我们还可以优化代码,使用一维数组记录最大值,具体看下方。
public int getMaxValue1(int[][] arr) {
if (arr == null || arr.length <= 0) return 0;
int rows = arr.length;
int cols = arr[0].length;
int[][] maxValue = new int[rows][cols]; // 使用二维数组记录当前礼物最大值
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
int up = 0, left = 0;
if (i > 0) {
up = maxValue[i - 1][j];
}
if (j > 0) {
left = maxValue[i][j - 1];
}
maxValue[i][j] = Math.max(up, left) + arr[i][j];
}
}
return maxValue[rows - 1][cols - 1];
}
上述代码很容易理解,空间结构上我们可以在做一些优化。根据上述我们不难发现二维数组记录了很多我们用不到的最大值(因为我们每次都只是用到了上一个和左边的两个最大礼物值),如下图:
- 时间复杂度:O(mn)。
- 空间复杂度:O(mn)。
当我们求a处最大值时,其实1就可以被替换掉了,因为根据规则下一步只能往下或者往右走,所以以后不会再用到它,再如求到b处最大值时11就可以被替换掉了。换个说法就是b处最大值求出来以后就可以直接覆盖掉14这个位置。从走势不难发现,数据走向是一直向右的,直到到右下角位置停止。说到这里我们其实我们脑子了大概就有了一维数组存储的模型了,就是一行的长度。类似于下边这种模式。
x x x x
x x o o
o o m x
x x x x
其中m为我们要求的最大礼物值得位置。o代表我们存储的最大值点。x代表没用到的点。如果不明白自己断点试下吧,哈哈。
public int getMaxValue2(int[][] arr) { if(arr==null||arr.length<=0)return 0; int rows = arr.length; int cols = arr[0].length; int [] maxValue = new int [cols]; for (int i=0;i<rows;i++){ for (int j =0;j<cols;j++){ int up=0,left=0; if(i>0) { up = maxValue[j]; // 该点代表所求位置的上一个点的最大值是多少 } if(j>0) { left = maxValue[j-1]; // 该点代表所求位置的左一个点的最大值是多少 } maxValue[j] = Math.max(up,left)+arr[i][j]; } } return maxValue[cols-1]; }
- 时间复杂度:O(mn)。
- 空间复杂度:O(n)。