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;
}

有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜菜的小彭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值