2024.3.22
- 题目来源
- 我的题解
- 方法一 传统的深度优先遍历 超时
- 方法二 优先队列
题目来源
力扣每日一题;题序:2617
我的题解
方法一 传统的深度优先遍历 超时
直接从(0,0)开始深度优先遍历,直到遍历到(m-1,n-1)截止。
优化成记忆化搜索仍然无法通过全部测试用例。
时间复杂度:O(mn(m+n))
空间复杂度:O(n)
int res=Integer.MAX_VALUE;
public int minimumVisitedCells(int[][] grid) {dfs(grid,0,0,1);return res==Integer.MAX_VALUE?-1:res;
}
public void dfs(int[][] grid,int row,int col,int count){int m=grid.length;int n=grid[0].length;if(row==m-1&&col==n-1){res=Math.min(res,count);return;}int canC=grid[row][col]+col<n?grid[row][col]+col:n-1;for(int c=col+1;c<=canC;c++){dfs(grid,row,c,count+1);}int canR=grid[row][col]+row<m?grid[row][col]+row:m-1;for(int r=row+1;r<=canR;r++){dfs(grid,r,col,count+1);}
}
方法二 优先队列
官方题解。
只能向下或者向右走,因此可以直接用二重循环来计算到达每一个位置的最少步数:当遍历到位置 (i,j) 时,所有其左侧和上方位置的最少步数都已经计算完成。
当在位置 (i,j) 时,如何计算到达该位置的最少步数呢?我们可以考虑上一步是向下还是向右走的。如果是「向下走」的,那么上一个位置应该是 (i′,j),其中 i′<i。除此之外,i′ 还需要满足下面两个要求:
- (i′,j)要能走到 (i,j);
- 到达 (i′,j) 的步数要最少。
对于第二个要求,我们可以想到使用优先队列(小根堆)来维护所有的 i′,堆顶对应着步数最少的位置。同时对于第一个要求,可以在获取堆顶的 i opt ′ i_\textit{opt}' iopt′时进行判断,如果 ( i opt ′ i_\textit{opt}' iopt′,j)不满足一步到达 (i,j)的要求,就可以将它从优先队列中直接移除,因为之后遍历到的同一列的位置,i 的值只会更大,也就更不可能一步走到。如果优先队列中的所有元素均被移除,说明无法走到 (i,j),否则就可以得到最少的步数,并将 i 放入优先队列。
这样一来,需要对每一列都维护一个优先队列。第 j 个优先队列存储的是所有位于第 j列的位置,其中的元素是一个二元组,第一个值是到达 (i′,j)的最少步数,作为比较的关键字;第二个值是 i′,用来判断是否可以一步到达。
同理,对于每一行也维护一个优先队列,这样就可以处理「向右走」的情况了。
时间复杂度:O(mn(logm+logn))
空间复杂度:O(mn)
public int minimumVisitedCells(int[][] grid) {int m = grid.length, n = grid[0].length;//存储到(i,j)的最小步数int[][] dist = new int[m][n];for (int i = 0; i < m; ++i) {Arrays.fill(dist[i], -1);}dist[0][0] = 1;//维护行的优先队列 ()PriorityQueue<int[]>[] row = new PriorityQueue[m];//维护列的优先队列PriorityQueue<int[]>[] col = new PriorityQueue[n];for (int i = 0; i < m; ++i) {row[i] = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]);}for (int i = 0; i < n; ++i) {col[i] = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]);}for (int i = 0; i < m; ++i) {for (int j = 0; j < n; ++j) {//行还未遍历完,并且满足 i < k <= grid[i][j] + i 的格子 (k, j) (向下移动)。while (!row[i].isEmpty() && row[i].peek()[1] + grid[i][row[i].peek()[1]] < j) {row[i].poll();}if (!row[i].isEmpty()) {dist[i][j] = update(dist[i][j], dist[i][row[i].peek()[1]] + 1);}//列还未遍历完,并且满足 j < k <= grid[i][j] + j 的格子 (i, k) (向右移动)。while (!col[j].isEmpty() && col[j].peek()[1] + grid[col[j].peek()[1]][j] < i) {col[j].poll();}if (!col[j].isEmpty()) {dist[i][j] = update(dist[i][j], dist[col[j].peek()[1]][j] + 1);}//检查当前单元格是否已经被访问过if (dist[i][j] != -1) {row[i].offer(new int[]{dist[i][j], j});col[j].offer(new int[]{dist[i][j], i});}}}return dist[m - 1][n - 1];
}public int update(int x, int y) {return x == -1 || y < x ? y : x;
}
有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~