当前位置:网站首页>动态规划前路径问题优化方式

动态规划前路径问题优化方式

2022-07-06 09:25:00 爱学习的小耿

动态规划前路径问题

路径问题:本章讲三角形最小路径和(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的学习,学有所成再来解此题。

原网站

版权声明
本文为[爱学习的小耿]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_42705158/article/details/124785407