给你一个下标从 0 开始的 m x n
整数矩阵 grid
。你一开始的位置在 左上角 格子 (0, 0)
。
当你在格子 (i, j)
的时候,你可以移动到以下格子之一:
- 满足
j < k <= grid[i][j] + j
的格子(i, k)
(向右移动),或者 - 满足
i < k <= grid[i][j] + i
的格子(k, j)
(向下移动)。
请你返回到达 右下角 格子 (m - 1, n - 1)
需要经过的最少移动格子数,如果无法到达右下角格子,请你返回 -1
。
示例 1:
输入:grid = [[3,4,2,1],[4,2,3,1],[2,1,0,0],[2,4,0,0]] 输出:4 解释:上图展示了到达右下角格子经过的 4 个格子。
示例 2:
输入:grid = [[3,4,2,1],[4,2,1,1],[2,1,1,0],[3,4,1,0]] 输出:3 解释:上图展示了到达右下角格子经过的 3 个格子。
示例 3:
输入:grid = [[2,1,0],[1,0,0]] 输出:-1 解释:无法到达右下角格子。
题解:这道题递归很好写,直接枚举就行。
class Solution {
public static int minimumVisitedCells(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int tmp = process(grid, 0, 0, m, n);
return tmp==0||tmp>=m+n?-1:tmp;
}
public static int process(int[][] grid, int i, int j, int m, int n) {
//System.out.println(i+","+j);
if (i >= m || j >= n) {
return m+n;
}
if (i == m - 1 && j == n - 1) {
return 1;
}
// 分为两种情况,向右走
int current = grid[i][j];
int min1 = m+n;
for (int jx = j + 1; jx <= current + j && jx < n; jx++) {
min1 = Math.min(process(grid, i, jx, m, n) + 1, min1);
}
//System.out.println("min1:"+min1);
// 分为两种情况,向下走
int min2 = m+n;
for (int ix = i + 1; ix <= current + i && ix < m; ix++) {
min2 = Math.min(process(grid, ix, j, m, n) + 1, min2);
}
//System.out.println("min2:"+min2);
return Math.min(min1, min2);
}
}
然后用记忆化搜索,就是用一个数组将不必要的递归不需要进行。
class Solution {
public static int minimumVisitedCells(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m+1][n+1];
int tmp = process(grid, 0, 0, m, n,dp);
return tmp == 0 || tmp >= m + n ? -1 : tmp;
}
public static int process(int[][] grid, int i, int j, int m, int n,int[][] dp) {
if(dp[i][j]!=0)
return dp[i][j];
if (i >= m || j >= n) {
dp[i][j] = m+n;
return dp[i][j];
}
if (i == m - 1 && j == n - 1) {
return 1;
}
// 分为两种情况,向右走
int current = grid[i][j];
int min1 = m + n;
for (int jx = j+1; jx <= current + j && jx < n; jx++) {
min1 = Math.min(process(grid, i, jx, m, n,dp) + 1, min1);
}
// 分为两种情况,向下走
int min2 = m + n;
for (int ix = i + 1; ix <= current + i && ix < m; ix++) {
min2 = Math.min(process(grid, ix, j, m, n,dp) + 1, min2);
}
dp[i][j] = Math.min(min1, min2);
return dp[i][j];
}
}
结果还是超时,然后将记忆化搜索改为动态规划。
class Solution {
public static int minimumVisitedCells(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m + 1][n + 1];
for(int i=0;i<m;i++) {
for(int j = 0;j<n;j++) {
dp[i][j] = m+n;
}
}
dp[m - 1][n - 1] = 1;
for (int i = m-1; i >= 0; i--) {
for (int j = n-1; j >= 0; j--) {
// 分为两种情况,向右走
int current = grid[i][j];
int min1 = m + n;
for (int jx = j + 1; jx <= current + j && jx < n; jx++) {
min1 = Math.min(dp[i][jx] + 1, min1);
}
// 分为两种情况,向下走
int min2 = m + n;
for (int ix = i + 1; ix <= current + i && ix < m; ix++) {
min2 = Math.min(dp[ix][j] + 1, min2);
}
dp[i][j] = Math.min(Math.min(min1, min2),dp[i][j]);
}
}
return dp[0][0] == 0 || dp[0][0] >= m + n ? -1 : dp[0][0];
}
}
结果还是超时,在这里,我们发现每一次枚举最小值是不必要的,要是在求出dp的同时能存储在某个区间的值,时间复杂度是O(1),就可以解决问题了。可以利用线段树来实现。