前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【动态规划/路径问题】本系列的首道 Hard ,使用有限变量来代替遍历查找 ...

【动态规划/路径问题】本系列的首道 Hard ,使用有限变量来代替遍历查找 ...

作者头像
宫水三叶的刷题日记
发布2021-03-23 16:11:19
7420
发布2021-03-23 16:11:19
举报
文章被收录于专栏:宫水三叶的刷题日记

前言

今天是我们讲解「动态规划专题」中的 路径问题 的第六天

我在文章结尾处列举了我所整理的关于 路径问题 的相关题目。

路径问题 我会按照编排好的顺序进行讲解(一天一道)。

你也先可以尝试做做,也欢迎你向我留言补充,你觉得与路径相关的 DP 类型题目 ~

题目描述

这是 LeetCode 上的「1289. 下降路径最小和 II」,难度为 Hard

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

请你返回非零偏移下降路径数字和的最小值。

示例 1:

代码语言:javascript
复制
输入: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 。

提示:

  • 1 <= arr.length == arr[i].length <= 200
  • -99 <= arr[i][j] <= 99

动态规划

我们已经做过好几道「路径问题」了。

凭借我们的经验,一个直观的做法是定义 为到达位置 的最小路径和。

那么答案必然是所有的 中的最小值,i 的取值范围为 [0, n)。

代表最优路径的最后一个数可能取自最后一行的任意下标。

由于题目要求每一行的取数,不能与上一行的取数列下标相同。

也就是规定了我们为每行进行取数时不能取「正上方」的值。

因此我们在进行状态转移的时候,需要枚举上一行的所有列下标。

这样的做法复杂度是 ,题目范围为 ,因此计算量为 ,可以过。

代码:

代码语言:javascript
复制
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;
    }
}
  • 时间复杂度:
  • 空间复杂度:

动态规划(进阶)

那么是否有比 更好的做法呢?

要知道我们上述的解法,当数据范围出到 就会超时了。

我们来分析一下上述解法有哪些可优化的点:

1. DP 状态转移部分,共有 个状态需要转移

2. 每次转移的时候,枚举上一行的所有列

我们要确保所有的方案都枚举到,得到的才是全局最优解。

因此 DP 部分,我们是无法优化的。

那就只剩下「枚举上一行的所有列」这个部分可以优化了。

其实细想就可以发现,当我们在计算某行的状态值的时候,只会用到「上一行」的两个值:「最小值」「次小值」

举个?,当我们已经处理完第 行的状态值。

假设第 行状态中的最小值对应的列下标是 ,次小值对应的列下标是 。

那么当我们处理第 行时,显然有:

  • 处理第 行中列下标为 的状态值时,由于不能选择「正上方」的数字,用到的是次小值。转移方程为:
  • 处理第 行其他列下标的状态值时,这时候用到的是最小值。转移方程为:

因此我们可以使用 i1 保存上一行的最小值对应的列下标,用 i2 保存次小值对应的列下标。

而无需每次转移都枚举上一行的所有列。

代码:

代码语言:javascript
复制
class Solution {
    int MAX = Integer.MAX_VALUE;
    public int minFallingPathSum(int[][] arr) {
        int n = arr.length;
        int[][] f = new int[n][n];
        
        // i1 代表最小值列下标,i2 代表次小值列下标
        int i1 = -1, i2 = -1;
        
        // 先转移第一行
        for (int i = 0; i < n; i++) {
        
            // 更新动规值
            int val = arr[0][i];
            f[0][i] = val;
            
            // 更新 i1 和 i2
            if (val < (i1 == -1 ? MAX : f[0][i1])) {
                i2 = i1;
                i1 = i;
            } else if (val < (i2 == -1 ? MAX : f[0][i2])) {
                i2 = i;
            }
        }
        
        // 再转移剩余行
        for (int i = 1; i < n; i++) {
        
            // 当前转移第 i 行,使用临时变量保存转移过程中的「最小值列下标」&「次小值列下标」
            int ti1 = -1, ti2 = -1;
            
            for (int j = 0; j < n; j++) {
                f[i][j] = MAX;
                int val = arr[i][j];
                
                // 更新动规值
                // 可以选择「最小值」的列选择「最小值」
                if (j != i1) {
                    f[i][j] = f[i - 1][i1] + val;
                    
                // 不能选择「最小值」的列选择「次小值」
                } else {
                    f[i][j] = f[i - 1][i2] + val;
                }
                
                // 更新 ti1 和 ti2
                if (f[i][j] < (ti1 == -1 ? MAX : f[i][ti1])) {
                    ti2 = ti1;
                    ti1 = j;
                } else if (f[i][j] < (ti2 == -1 ? MAX : f[i][ti2])) {
                    ti2 = j;
                }
            }
            
            // 使用临时变量更新 i1 和 i2
            i1 = ti1; i2 = ti2;
        }
        int ans = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            ans = Math.min(ans, f[n-1][i]);
        }
        return ans;
    }
}
  • 时间复杂度:
  • 空间复杂度:

路径问题(目录)

62.不同路径(中等):路径问题第一讲

63.不同路径 II(中等):路径问题第二讲

64.最小路径和(中等):路径问题第三讲

120.三角形最小路径和(中等):路径问题第四讲

931.下降路径最小和(中等):路径问题第五讲

1289.下降路径最小和 II(困难):本篇

1575.统计所有可行路径(困难)

576.出界的路径数(中等)

1301.最大得分的路径数目(困难)

欢迎补充 ~

最后

这是我们「刷穿 LeetCode」系列文章的第 No.1289 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先将所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 宫水三叶的刷题日记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 题目描述
  • 动态规划
  • 动态规划(进阶)
  • 路径问题(目录)
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档