death00 · 2019年12月23日

力扣64——最小路径和

原题

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

解法

错误的正向思路

我一开始的想法是正向思路,从起点开始,每个点都有两种后续走法——向下或者向右,当然其中需要判断是否可以向下或者向右以及到达终点就停止。我想到的优化是当走到终点后,将当前走过的路径和记录下来,找出最小值,别的路径上在走的时候,如果比当前最小和大,就没必要继续了。

来看看我的代码:

class Solution {
    private long min = Long.MAX_VALUE;

    private int[][] map;

    public int minPathSum(int[][] grid) {
        map = grid;
        dfs(0, 0, 0);
        return (int)min;
    }

    public void dfs(int x, int y, long sum) {
        sum += Long.valueOf(map[x][y]);
        // 如果已经大于等于当前的最小值,那么就没有必要继续走了
        if (sum >= min) {
            return;
        }

        // 是否到达终点
        if (x == map.length - 1 && y == map[x].length - 1) {
            if (sum < min) {
                min = sum;
            }

            return;
        }

        // 是否可以向右
        if (y < map[x].length - 1) {
            dfs(x, y + 1, sum);
        }
        // 是否可以向下
        if (x < map.length - 1) {
            dfs(x + 1, y, sum);
        }
    }
}

感觉很理想,然而现实是超时了,确实效率不高,除了第一列和第一行的点,其他点都有可能存在重复计算的可能。

逆向思路

既然正向不行,那咋们就逆向,从终点出发,以终点为起点,计算当前点到终点的最小值,最后推算出到达起点的最小值(这也是我看了别人的解法才知道的,看来自己的思路果然有问题)。这样就能保证每个点只计算一次,时间效率就是O(m * n),看起来就高效多了。

接下来看看代码:

class Solution {
    public int minPathSum(int[][] grid) {
        // 从终点开始找起,算当前节点到终点的最小值
        int right, down;
        for (int i = grid.length - 1; i >= 0; i--) {
            for (int j = grid[i].length - 1; j >= 0; j--) {
                // 如果是终点,则保持不变
                if (i == grid.length - 1 && j == grid[i].length - 1) {
                    continue;
                }

                // 如果没有右节点
                if (j == grid[i].length - 1) {
                    // 那么就设置当前节点的值加上下节点的值
                    grid[i][j] += grid[i + 1][j];
                    continue;
                }

                // 如果没有下节点
                if (i == grid.length - 1) {
                    // 那么就设置当前节点的值加上右节点的值
                    grid[i][j] += grid[i][j + 1];
                    continue;
                }

                // 如果下节点和右节点都有的话,则加上其中较小的那个
                grid[i][j] += grid[i + 1][j] < grid[i][j + 1] ? grid[i + 1][j] : grid[i][j + 1];
            }
        }

        return grid[0][0];
    }
}

OK,通过了,执行用时:3ms,内存消耗:39.5MB

核心思想就是:

从终点出发,每个点到终点的最小值 = 每个点当前的值 + Min(该点下一个点值, 该点右一个点)。

你想想,是否是如此呢?

既然知道了反向思路,我们可以优化一下我们之前的正向思路解法。

优化正向思路

之前的超时,是因为每个点可能会被计算多次,那么我们如果计算出,从起点出发,到每个节点的最小值,最终计算到终点,也应该是终点的最小值,你想想是不是这样呢?

来看看代码

class Solution {
    public int minPathSum(int[][] grid) {
        // 从起点开始找起,算当前节点到起点的最小值 

        // 总行数
        int row = grid.length;
        // 总列数
        int col = grid[0].length;
        // 左节点和上节点计算出的最小值
        int left, up;
        // 遍历并计算
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                // 起点不计算
                if (i == 0 && j == 0) {
                    continue;
                }

                // 如果没有左节点
                if (j == 0) {
                    // 就设置当前节点的值加上节点
                    grid[i][j] += grid[i - 1][j];
                    continue;
                }

                // 如果没有上节点
                if (i == 0) {
                    // 就设置当前节点的值加上左节点
                    grid[i][j] += grid[i][j - 1];
                    continue;
                }

                // 如果左节点和上节点都存在,就加上其中最小的值
                grid[i][j] += (grid[i][j - 1] < grid[i - 1][j] ? grid[i][j - 1] : grid[i - 1][j]);
            }
        }
        
        return grid[row - 1][col - 1];
    }
}

OK,也通过了,执行用时:3ms,内存消耗:42.4MB

总结

以上就是这道题目我的解答过程了,不知道大家是否理解了。算法本来就是就是在做优化,如何能比之前更好更合适,就是优化了。希望能与大家共同进步。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

公众号:健程之道

推荐阅读
关注数
0
文章数
70
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息