IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 动态规划前路径问题优化方式 -> 正文阅读

[数据结构与算法]动态规划前路径问题优化方式

动态规划前路径问题

路径问题:本章讲三角形最小路径和(Leetcode 120中等)、下降路径最小和(Leecode 931 中等)、下降路径最小和 II(Leedcode 1289 困难)



前言

路径问题作为经典动态规划问题背包问题的前传,用于理解dp动态规划的思想模型。本文着重讲解当问题复杂之后,如何去找转移方程、优化空间、时间复杂度


提示:以下是本篇文章正文内容,下面案例可供参考

一、三角形最小路径和(Leetcode 120中等)

1.问题描述

给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。

2.问题分析

此问题由之前的计算路径的矩形转换成了三角形,那么可以通过遍历时只遍历上三角或者下三角。

2.1 定义数据结构

问题是有多少不同的路径,此时应当使用dp[m-1][n-1]来表达最终的结果,这个和前文没啥区别

2.2 结构含义的解读

本问题中dp[i][j]就可以解读为:i代表地图的行,j代表列

2.3 状态转移方程的建立

本问题中:
如下图,地图为一个三角形且规定了相邻的结点,因此我们只能往下或者往右下角移动,因此我们按照「当前可选方向」进行分析:
当前位置只能 「往下」 移动,即有 dp[i][j] = dp[i-1][j]+grid[i][j]
当前位置只能 「往右下」 移动,即有 dp[i][j] = dp[i-1][j-1]+grid[i][j]
当前位置即能 「往下」 也能 「往右下」 移动,即有dp[i][j] =min(dp[i-1][j]+triangle[i][j],dp[i-1][j-1]+triangle[i][j])
推导过程:
dp[i][j]代表的是到达地图grid[i][j]点到达路径的经过的路径和,那么往上递推一步:如何到达grid[i][j]点,只有两种方法:由前面的点往下或者往右下进入到这个点,因此到达grid[i][j]的和就只有到达dp[i][j-1]的和+ triangle[i][j]、dp[i-1][j-1]+triangle[i][j]两种可能,并取其中更小的,由此可推导出dp[i][j]的状态转移方程:dp[i][j] =min(dp[i-1][j]+triangle[i][j],dp[i-1][j-1]+triangle[i][j])
在这里插入图片描述

3.代码展示:

代码如下(示例):

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        
        int m=triangle.size();
        int n=triangle[m-1].size();
        vector<vector<int>> dp(m, vector<int>(n));
        dp[0][0]=triangle[0][0];
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<=i;j++)
            {
                if(i-1>j||i-1==j)
                {
                    if(i>0&&j>0)
                    {
                        dp[i][j]=min(dp[i-1][j]+triangle[i][j],dp[i-1][j-1]+triangle[i][j]);
                    }
                    else if(i>0)
                    {
                        dp[i][j]=dp[i-1][j]+triangle[i][j];
                    }
                }
                else
                {
                    if(i>0&&j>0)
                    {
                        dp[i][j]=dp[i-1][j-1]+triangle[i][j];
                    }
                }
            }
        }
        int minSum=dp[m-1][0];
        for(int i=1;i<n;i++)
        {
            if(dp[m-1][i]<minSum)
            {
                minSum=dp[m-1][i];
            }
        }
        return minSum;
    }
};

4.升级:优化算法

我们在递推的过程中发现,其实第i+1行的值只会依赖于第i行的结果,因此在空间上可以只申请2*n。如下图所示:比如第一行的计算出来的结果放在二维数组的第一行,第二行根据第一行的结果放在第二行,当计算第三行结果的时候,此时第一行的结果已经无效了,因此可以使用第三行的结果覆盖掉第一行的数据。
在这里插入图片描述

5.优化算法的代码展示

代码如下(示例):

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        
        int m=triangle.size();
        int n=triangle[m-1].size();
        vector<vector<int>> dp(2, vector<int>(n));
        dp[0][0]=triangle[0][0];
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<=i;j++)
            {
                if(i-1>j||i-1==j)
                {
                    if(i>0&&j>0)
                    {
                        dp[i&1][j]=min(dp[(i-1)&1][j]+triangle[i][j],dp[(i-1)&1][j-1]+triangle[i][j]);
                    }
                    else if(i>0)
                    {
                        dp[i&1][j]=dp[(i-1)&1][j]+triangle[i][j];
                    }
                }
                else
                {
                    if(i>0&&j>0)
                    {
                        dp[i&1][j]=dp[(i-1)&1][j-1]+triangle[i][j];
                    }
                }
            }
        }
        int minSum=dp[(m-1)&1][0];
        for(int i=1;i<n;i++)
        {
            if(dp[(m-1)&1][i]<minSum)
            {
                minSum=dp[(m-1)&1][i];
            }
        }
        return minSum;
    }
};

二、下降路径最小和(Leecode 931 中等)

1.问题描述

给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径 的 最小和 。
下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)、(row + 1, col) 或者 (row + 1, col + 1) 。

2.问题分析

此问题由上个问题的计算路径的三角形转换成了矩形,这次直接使用升级后的算法分析

2.1 定义数据结构

问题是有多少不同的路径,此时应当使用dp[m-1][n-1]来表达最终的结果,这个和前文没啥区别

2.2 结构含义的解读

本问题中dp[i][j]就可以解读为:i代表地图的行,j代表列

2.3 状态转移方程的建立

如下图,地图规定了相邻的结点,因此我们只能往下或者往右下角或者左下角移动,因此我们按照「当前可选方向」进行分析:
当前位置只能 「往下」 移动,即有 dp[i][j] = dp[i-1][j]+matrix[i][j]
当前位置只能 「往左下」 移动,即有 dp[i][j] = dp[i-1][j+1]+matrix[i][j]
当前位置只能 「往右下」 移动,即有 dp[i][j] = dp[i-1][j-1]+matrix[i][j]
当前位置即能 「往下」 也能 「往右下」 移动,即有 dp[i&1][j]=min(dp[(i-1)&1][j-1],min(dp[(i-1)&1][j],dp[(i-1)&1][j+1]))+matrix[i][j];

2.4.代码展示:

代码如下(示例):

class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {
        int n=matrix.size();
        vector<vector<int>>dp(2,vector<int>(n,0));
        dp[0][0]=matrix[0][0];
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(i==0)
                {
                    dp[i][j]=matrix[i][j];
                }
                else
                {
                    if(j!=0&&j!=n-1)
                    {
                        dp[i&1][j]=min(dp[(i-1)&1][j-1],min(dp[(i-1)&1][j],dp[(i-1)&1][j+1]))+matrix[i][j];
                    }
                    if(j==0)
                    {
                        dp[i&1][j]=min(dp[(i-1)&1][j],dp[(i-1)&1][j+1])+matrix[i][j];
                    }
                    if(j==n-1)
                    {
                        dp[i&1][j]=min(dp[(i-1)&1][j-1],dp[(i-1)&1][j])+matrix[i][j];
                    }
                }
            }
        }
        int res = dp[(n-1)&1][0];
        for (int i = 0; i < n; i++) res = min(res, dp[(n - 1)&1][i]);
        return res;
    }
};

二、下降路径最小和 II(Leecode 1289 困难)

1.问题描述

给你一个 n x n 整数矩阵 arr ,请你返回 非零偏移下降路径 数字和的最小值。
非零偏移下降路径 定义为:从 arr 数组中的每一行选择一个数字,且按顺序选出来的数字中,相邻数字不在原数组的同一列。

2.问题分析

此问题和之前不同的是,第i+1行可以从第i行的任一位置过来,除了同一列的。首先按照原有思路求解,随后再说如何优化:

2.1 定义数据结构

问题是所有路径和,此时应当使用dp[m-1][n-1]来表达最终的结果,这个和前文没啥区别

2.2 结构含义的解读

本问题中dp[i][j]就可以解读为:i代表地图的行,j代表列

2.3 状态转移方程的建立

此处已经没有固定的方向了,因此需要通过遍历上一行除了同一列的所有值:dp[i][j]=min(dp[i-1][0],dp[i-1][1]…dp[i-1][n])+arr[i][j];

3.代码展示:

代码如下(示例):

class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& grid) {
        int n=grid.size();
        vector<vector<int>>dp(2,vector<int>(n,0));
        dp[0][0]=grid[0][0];
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(i==0)
                {
                    dp[i][j]=grid[i][j];
                }
                else
                {
                   
                        int br=INT_MAX;
                        for(int m=0;m<n;m++)
                        {
                            if(m!=j){
                                br=min(br,dp[(i-1)&1][m]);
                            }
                        }
                        dp[i&1][j]=br+grid[i][j];
                    
                    
                }
            }
        }
        int res = dp[(n-1)&1][0];
        for (int i = 0; i < n; i++) res = min(res, dp[(n - 1)&1][i]);
        return res;
        
    }
};

4.升级:优化算法

我们在递推的过程中发现,其实第i+1行的值只会依赖于第i行的最小值和次最小值,当最小值所在的列和当前列在同一列,只能选择次最小值,否则选择最小值。因此在计算过程中,只需要定义两个临时变量保存上一行的最小值和次最小值即可。第一行的这两个值单独处理。

5.代码展示:

代码如下(示例):

class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& grid) {
        int n=grid.size();
        int dp[n][n];
        dp[0][0]=grid[0][0];
        int _min=-1;
        int _Maxmin=-1;
         for (int i = 0; i < n; i++) {
            int val = grid[0][i];
            dp[0][i] = val;
            if (val < (_min == -1 ? INT_MAX : dp[0][_min])) {
                _Maxmin = _min;
                _min = i;
            } else if (val < (_Maxmin == -1 ? INT_MAX : dp[0][_Maxmin])) {
                _Maxmin = i;
            }
        }
        for(int i=1;i<n;i++)
        { 
            
            int min=-1;
            int Maxmin=-1;
            for(int j=0;j<n;j++)
            {
                dp[i][j] = INT_MAX;
                if(j!=_min)
                {
                     dp[i][j]=dp[i-1][_min]+grid[i][j];
                }
                else{
                    dp[i][j]=dp[i-1][_Maxmin]+grid[i][j];
                } 
                if (dp[i][j] < (min == -1 ? INT_MAX : dp[i][min])) {
                    Maxmin = min;
                    min = j;
                } else if (dp[i][j] < (Maxmin == -1 ? INT_MAX : dp[i][Maxmin])) {
                    Maxmin = j;
                }
            }
            _min=min;
            _Maxmin=Maxmin;
        }
        int res = dp[(n-1)][0];
        for (int k = 0; k < n; k++) res = min(res, dp[(n - 1)][k]);
        return res;
    }
};

总结

本文主要着重讲述了在状态转移方程方面的优化方式,主要是分析是否第i+1行和前面的所有行都有关系还是只和上一行有关。leecode 931我们可以分析出当前行只和上一行存在联系,因此可以优化空间复杂度nxn–>2*n,leecode 1289我们可以发现当前行只和上一行的最小值和此最小值有关,因此时间复杂度由n的三次方优化为n的平方。下文的统计所有可行路径(困难)【记忆化搜索】,由于很难直接从动态规划求解,因此先去学习DFS,在从dfs转换成动态规划。下一章开始DFS的学习,学有所成再来解此题。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-18 17:54:13  更:2022-05-18 17:56:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 1:30:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码