• 力扣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/

    公众号:健程之道

  • 相关阅读:
    Java实时读取日志文件
    Trie树的应用:查询IP地址的ISP
    vue.extend,mixins和vue.component的区别
    前端性能优化10个方面
    vue组件和插件是实现
    vue指令用法
    promise retry实现
    linux管道与重定向
    linux文件颜色与类型
    linux文件权限说明
  • 原文地址:https://www.cnblogs.com/death00/p/12082195.html
Copyright © 2020-2023  润新知