剑指 Offer 47. 礼物的最大价值 题目改动版本
题意: 应该是在在一个 m*n 的棋盘的每一格都有一个值,表示能量(能量值大于 0)。你可以从棋盘的左上角开始走,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的能量的价值,请计算你第一次走最多能拿到多少价值的礼物?然后把你拿过的能量的棋盘的位置上的能量值置为0,然后计算第二次走最多能拿到多少价值的礼物?
解题思路:
这个题目难点的地方在于需要对拿到最大能量的走过的路径需要进行记录处理然后更新能量矩阵,对下面的能量矩阵:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
第一次走,我们能发现能拿到最大的能量是12,路径是 1→3→5→2→1 ,然后我们需要根据路径进行路径的更新,更新后的矩阵是:
[
[0,0,1],
[1,0,1],
[4,0,0]
]
之后在求第二次能拿到的最大能量,就是5,路径是0->1->->4->0->0
考虑使用DP或使用DFS搜索+memo进行解题:
(1)使用DP: 对于求第一次能量的最大值,使用dp[i][j]
表示走到位置(i-1,j-1)
的时候能拿到的最大能量,那么转移方程就是:
dp[i][j] = Math.max( dp[i-1][j],dp[i][j-1]) + nums[i-1][j-1]
所以使用dp求能拿到的最大能量的核心代码如下:
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]) + grid[i-1][j-1];
}
}
但是我们需要找到这条最大的路径,可以利用dp状态矩阵的值,对于上面的例子,进行一次dp计算后,得到的dp状态矩阵为:
[
[1,4,5],
[2,9,10],
[6,11,12],
]
那么其实我们是可以根据dp状态矩阵找到路径的,从右下角出发,比较位置(i-1,j)
和(i,j-1)
哪一个值更大,就是我们这个节点之前走过来的位置:所以能回退找到最大的路径的位置:12->11->9->4->1
刚好就是我们的走过的最大路径,然后能够根据路径更新能量矩阵,代码如下:
int x = m;
int y = n;
grid[m-1][n-1] =0;
while(x>=1&&y>=1){
// System.out.println(x+" "+y);
if(dp[x-1][y]>dp[x][y-1]){
if(x-2>=0) grid[x-2][y-1] = 0;
x = x - 1;
}
else{
if(y-2>=0) grid[x-1][y-2] = 0;
y = y - 1;
}
}
之后在求一遍dp就能拿到第二次走能获得最大能量的值了,整体代码如下:
public int maxValue_double(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m+1][n+1];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]) + grid[i-1][j-1];
}
}
// //打印dp状态矩阵
// for(int i=1;i<=m;i++){
// for(int j=1;j<=n;j++){
// System.out.print(dp[i][j]+" ");
// }
// System.out.println();
// }
System.out.println("first " + dp[m][n]);
int x = m;
int y = n;
grid[m-1][n-1] =0;
while(x>=1&&y>=1){
// System.out.println(x+" "+y);
if(dp[x-1][y]>dp[x][y-1]){
if(x-2>=0) grid[x-2][y-1] = 0;
x = x - 1;
}
else{
if(y-2>=0) grid[x-1][y-2] = 0;
y = y - 1;
}
}
// //打印新的
// for(int i=0;i<m;i++){
// System.out.print("[");
// for(int j=0;j<n;j++){
// System.out.print(grid[i][j]);
// if(j!=n-1) System.out.print(",");
// }
// System.out.print("],");
// }
//第二次
int[][] dp1 = new int[m+1][n+1];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp1[i][j] = Math.max(dp1[i-1][j],dp1[i][j-1]) + grid[i-1][j-1];
}
}
System.out.println("\nsecond "+dp1[m][n]);
return dp[m][n];
}
(2)使用DFS+memo记录状态
在原来的只求礼物的最大价值的题目中,dfs函数返回的值是最大的礼物的价值,但是我们现在还需要记录获得能量最大的价值路径,所以把dfs的代码的返回值改成当前的能拿到的最大能量的路径就好,然后根据路径求和,然后我们选择向右走或向下走返回的路径里的和最大的哪一条进行更新就好,下面是dfs搜索的核心代码:
public List<int[]> dfs(int x,int y,int[][] grid){
if(x<0||x>=grid.length||y<0||y>=grid[0].length) return new LinkedList<int[]>();
String key = x + "*" + y;
if(memo.containsKey(key)) return memo.get(key);
List<int[]> p1 = dfs(x+1,y,grid);
List<int[]> p2 = dfs(x,y+1,grid);
int sum1 = getSum(p1,grid);
int sum2 = getSum(p2,grid);
List<int[]> ans;
if(sum1>sum2) ans = new LinkedList<int[]>(p1);
else ans = new LinkedList<int[]>(p2);
ans.add(new int[]{x,y});
memo.put(key,ans);
return ans;
}
整体代码如下:
HashMap<String,List<int[]>> memo = new HashMap<>();
public int getSum(List<int[]> L,int[][] grid){
int sum = 0;
for(int i=0;i<L.size();i++){
int[] tp = L.get(i);
sum += grid[tp[0]][tp[1]];
}
return sum;
}
public void printt(List<int[]> res){
for(int i=0;i<res.size();i++){
int[] tp = res.get(i);
System.out.print(tp[0]+" "+tp[1]+": ");
}
System.out.println("end1");
}
public List<int[]> dfs(int x,int y,int[][] grid){
if(x<0||x>=grid.length||y<0||y>=grid[0].length) return new LinkedList<int[]>();
String key = x + "*" + y;
if(memo.containsKey(key)) return memo.get(key);
List<int[]> p1 = dfs(x+1,y,grid);
List<int[]> p2 = dfs(x,y+1,grid);
int sum1 = getSum(p1,grid);
int sum2 = getSum(p2,grid);
List<int[]> ans;
if(sum1>sum2) ans = new LinkedList<int[]>(p1);
else ans = new LinkedList<int[]>(p2);
ans.add(new int[]{x,y});
memo.put(key,ans);
return ans;
}
public int maxValue(int[][] grid) {
List<int[]> res = dfs(0,0,grid);
// printt(res);
int sum1 = getSum(res,grid);
//然后更新矩阵
for(int i=0;i<res.size();i++){
int[] tp = res.get(i);
grid[tp[0]][tp[1]] = 0;
}
// for(int i=0;i<grid.length;i++){
// for(int j=0;j<grid[0].length;j++){
// System.out.print(grid[i][j]);
// }
// System.out.println();
// }
//需要先清空memo
memo = new HashMap<>();
List<int[]> res1 = dfs(0,0,grid);
int sum2 = getSum(res1,grid);
System.out.println("first:"+sum1+" \nsecond:"+sum2);
return sum1;
}
上面例子的结果图: