原题
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:1
2
3
4
5
6
7
8输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
解法
错误的正向思路
我一开始的想法是正向思路,从起点开始,每个点都有两种后续走法——向下或者向右,当然其中需要判断是否可以向下或者向右以及到达终点就停止。我想到的优化是当走到终点后,将当前走过的路径和记录下来,找出最小值,别的路径上在走的时候,如果比当前最小和大,就没必要继续了。
来看看我的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37class 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),看起来就高效多了。
接下来看看代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33class 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(该点下一个点值, 该点右一个点)。
你想想,是否是如此呢?
既然知道了反向思路,我们可以优化一下我们之前的正向思路解法。
优化正向思路
之前的超时,是因为每个点可能会被计算多次,那么我们如果计算出,从起点出发,到每个节点的最小值,最终计算到终点,也应该是终点的最小值,你想想是不是这样呢?
来看看代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40class 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。
总结
以上就是这道题目我的解答过程了,不知道大家是否理解了。算法本来就是就是在做优化,如何能比之前更好更合适,就是优化了。希望能与大家共同进步。
有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。
公众号:健程之道
 
    