leetcode刷题(126)——1289. 下降路径最小和 II

Posted 伯努力不努力

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode刷题(126)——1289. 下降路径最小和 II相关的知识,希望对你有一定的参考价值。

给你一个 n x n 整数矩阵 arr ,请你返回 非零偏移下降路径 数字和的最小值。

非零偏移下降路径 定义为:从 arr 数组中的每一行选择一个数字,且按顺序选出来的数字中,相邻数字不在原数组的同一列。

示例 1:

输入:arr = [[1,2,3],[4,5,6],[7,8,9]]
输出:13
解释:
所有非零偏移下降路径包括:
[1,5,9], [1,5,7], [1,6,7], [1,6,8],
[2,4,8], [2,4,9], [2,6,7], [2,6,8],
[3,4,8], [3,4,9], [3,5,7], [3,5,9]
下降路径中数字和最小的是 [1,5,7] ,所以答案是 13 。

示例 2:

输入:grid = [[7]]
输出:7

提示:

n == grid.length == grid[i].length
1 <= n <= 200
-99 <= grid[i][j] <= 99

动态规划

我们可以使用动态规划来解决这个问题。

我们用 f[i][j] 表示从数组 arr 的前 i 行分别选择一个数字,并且第 i 行选择的数字为 arr[i][j] 时,可以得到的路径数字和的最小值。f[i][j] 可以从第 i - 1 行除了 f[i - 1][j] 之外的任意状态转移而来,这样我们可以写出如下的状态转移方程:

f[i][j] = min(f[i - 1][j']) + arr[i][j]    其中 j != j'
f[0][j] = arr[0][j]
class Solution 
    public int minFallingPathSum(int[][] arr) 
        int n = arr.length;
        int[][] f = new int[n][n];
        // 初始化首行的路径和
        for (int i = 0; i < n; i++) f[0][i] = arr[0][i];
        // 从第一行进行转移
        for (int i = 1; i < n; i++) 
            for (int j = 0; j < n; j++) 
                f[i][j] = Integer.MAX_VALUE;
                int val = arr[i][j];
                // 枚举上一行的每个列下标
                for (int p = 0; p < n; p++) 
                    // 只有列下标不同时,才能更新状态
                    if (j != p) 
                        f[i][j] = Math.min(f[i][j], f[i-1][p] + val);
                    
                
            
        
        // 在所有的 f[n - 1][i] 中取最小值
        int ans = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) 
            ans = Math.min(ans, f[n-1][i]);
        
        return ans;
    

这个动态规划的时间复杂度为 O(N^3):我们需要使用三重循环分别枚举 i,j 和 j0。若使用 C++ 语言实现该算法,则可以恰好在规定时间内通过所有测试数据,但对于 Python 语言则无法通过。因此我们必须对该算法进行优化。

我们注意到,状态转移方程中的 min(f[i - 1][j’]) 在大多数情况下的值都是相同的。不妨记 f[i - 1][jmin] 是第 i - 1 行所有状态中的最小值,可以发现,在状态转移方程中:

如果 j != jmin,那么 f[i][j] 一定会从 f[i - 1][jmin] 转移而来,因为此时 min(f[i - 1][j’]) 这一项可以取到最小值;

如果 j == jmin,那么 f[i][j] 不能从 f[i - 1][jmin] 转移而来,那么我们需要选择第 i - 1 行所有状态中的次小值,使得 min(f[i - 1][j’]) 这一项取到最小值。

因此我们可以修改状态转移方程:

f[i][j] = f[i - 1][jmin[i - 1]] + arr[i][j]    其中 j != jmin[i - 1]
f[i][j] = f[i - 1][jnext[i - 1]] + arr[i][j]   其中 j == jmin[i - 1]
f[0][j] = arr[0][j]

其中 jmin[i - 1] 表示第 i - 1 行所有状态中最小值所在的位置,jnext[i - 1] 表示第 i - 1 行所有状态中次小值所在的位置,最小值和次小值可以相等。在计算完第 i - 1 行的所有状态之后,我们可以在 O(N)O(N) 的时间得到 jmin[i - 1] 和 jnext[i - 1],这样在计算第 i 行的状态时,我们不需要枚举原先的 j0,时间复杂度从 O(N^2),
降低为 O(N)。因此总时间复杂度降低为 O(N^2)。

此外,我们还可以对空间复杂度进行优化。由于 f[i][j] 只会从 f[i - 1][jmin[i - 1]] 或 f[i - 1][jnext[i - 1]] 转移而来,那么我们并不用将第 i - 1 行的所有状态存储下来,而是可以浓缩成三个变量:

first_sum 表示这一行的最小值;

first_pos 表示这一行最小值对应的 jmin;

second_sum 表示这一行的次小值。

状态转移方程修改为:

f[i][j] = first_sum + arr[i][j]    其中 j != first_pos
f[i][j] = second_sum + arr[i][j]   其中 j == first_pos

通过这三个变量计算出第 i 行的所有状态之后,我们也不用将它们存储下来,同样可以浓缩成三个变量,为第 i + 1 行的动态规划提供转移基础。由于在计算第 i + 1 行的状态时,不需要第 i - 1 行的任何信息,因此第 i - 1 行浓缩成的三个变量此时可以被丢弃。这样以来,我们就将空间复杂度从 O(N^2) 降低至了 O(1)。

class Solution 
    public int minFallingPathSum(int[][] arr) 
        int n = arr.length;
        int first_sum = 0, first_pos = -1, second_sum = 0;
        for (int i = 0; i < n; ++i) 
            int fs = Integer.MAX_VALUE, fp = -1, ss = Integer.MAX_VALUE;
            for (int j = 0; j < n; ++j) 
                int cur_sum = (first_pos != j ? first_sum : second_sum) + arr[i][j];
                if (cur_sum < fs) 
                    ss = fs;
                    fs = cur_sum;
                    fp = j;
                
                else if (cur_sum < ss) 
                    ss = cur_sum;
                
            
            first_sum = fs;
            first_pos = fp;
            second_sum = ss;
        
        return first_sum;
    

以上是关于leetcode刷题(126)——1289. 下降路径最小和 II的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode刷题(126)~二叉树的层序遍历BFS

leetcode刷题(125)——931. 下降路径最小和

LeetCode刷题模版:121 - 130

LeetCode刷题模版:121 - 130

leetcode1289

leetcode1289. Minimum Falling Path Sum II