数据结构与算法之最短路路径与最短路径和&&动态规划

简介: 数据结构与算法之最短路路径与最短路径和&&动态规划

If every unfolding we experience takes us further along in life, then, we are truly experiencing what life is offering.

如果我们在人生中体验的每一次转变都让我们在生活中走得更远,那么,我们就真正的体验到了生活想让我们体验的东西。

Do not try and bend the spoon. That's impossible. Instead, only try to realize the truth. There is no spoon. Then you'll see that it is not the spoon that bends. It is only yourself.

不要试图弯曲汤匙。那是不可能的。你只能试着去理解一件事实。汤匙不存在。你会发现被弯曲的不是汤匙。那只是你自己。


一.题目(最短路径)


1.关于动态规划


动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。动态规划算法将问题的解决方案视为一系列决策的结果。


一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。


机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为Finish)。


问总共有多少条不同的路径?


540e407e131e58dc6f67344893ba60e5.png


输入:m = 3, n = 7

输出:28

示例 2:

输入:m = 2, n = 3

输出:3

解释: 从左上角开始,总共有 3 条路径可以到达右下角。

向右 -> 向右 -> 向下

向右 -> 向下 -> 向右

向下 -> 向右 -> 向右

示例 3:

输入:m = 7, n = 3

输出:28

示例 4:

输入:m = 3, n = 3

输出:6

提示:

1 <= m, n <= 100

题目数据保证答案小于等于 2 * 10^9


二.三种思路

  1. 思路一(深搜)


这道题目,刚一看最直观的想法就是用图论里的深搜,来枚举出来有多少种路径。

注意题目中说机器人每次只能向下或者向右移动一步,那么其实机器人走过的路径可以抽象为一棵二叉树,而叶子节点就是终点!

如图举例:


image.png



此时问题就可以转化为求二叉树叶子节点的个数,代码如下:

class Solution {
private:
    int dfs(int i, int j, int m, int n) {
        if (i > m || j > n) return 0; // 越界了
        if (i == m && j == n) return 1; // 找到一种方法,相当于找到了叶子节点
        return dfs(i + 1, j, m, n) + dfs(i, j + 1, m, n);
    }
public:
    int uniquePaths(int m, int n) {
        return dfs(1, 1, m, n);
    }
};


大家如果提交了代码就会发现超时了!


来分析一下时间复杂度,这个深搜的算法,其实就是要遍历整个二叉树。


这棵树的深度其实就是m+n-1(深度按从1开始计算)。


那二叉树的节点个数就是 2^(m + n - 1) - 1。可以理解深搜的算法就是遍历了整个满二叉树(其实没有遍历整个满二叉树,只是近似而已)


所以上面深搜代码的时间复杂度为O(2^(m + n - 1) - 1),可以看出,这是指数级别的时间复杂度,是非常大的。


2.思路二(动态规划)


机器人从(0 , 0) 位置出发,到(m - 1, n - 1)终点。


按照动规五部曲来分析:


1)确定dp数组(dp table)以及下标的含义


dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。


2)确定递推公式


想要求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1]。


此时在回顾一下 dp[i - 1][j] 表示啥,是从(0, 0)的位置到(i - 1, j)有几条路径,dp[i][j - 1]同理。


那么很自然,dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来。


3)dp数组的初始化


如何初始化呢,首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。


所以初始化代码为:

for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;

4)确定遍历顺序

这里要看一下递推公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了。


这样就可以保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值的。


5)举例推导dp数组


如图所示:


796d3498129fb3441f8e3a875b865c31.png


以上动规五部曲分析完毕,C++代码如下:

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m, vector<int>(n, 0));
        for (int i = 0; i < m; i++) dp[i][0] = 1;
        for (int j = 0; j < n; j++) dp[0][j] = 1;
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
};


  • 时间复杂度:O(m × n)
  • 空间复杂度:O(m × n)

3.思路三(数论方法)

在这个图中,可以看出一共m,n的话,无论怎么走,走到终点都需要 m + n - 2 步。


dc3b1d64f70e55520c7cfa69803af85e.png


在这m + n - 2 步中,一定有 m - 1 步是要向下走的,不用管什么时候向下走。

那么有几种走法呢? 可以转化为,给你m + n - 2个不同的数,随便取m - 1个数,有几种取法。

那么这就是一个组合问题了。

那么答案,如图所示:


54229554fe5ff8252725148dac21dfd0.png

求组合的时候,要防止两个int相乘溢出! 所以不能把算式的分子都算出来,分母都算出来再做除法。

例如如下代码是不行的。


class Solution {
public:
    int uniquePaths(int m, int n) {
        int numerator = 1, denominator = 1;
        int count = m - 1;
        int t = m + n - 2;
        while (count--) numerator *= (t--); // 计算分子,此时分子就会溢出
        for (int i = 1; i <= m - 1; i++) denominator *= i; // 计算分母
        return numerator / denominator;
    }
};


需要在计算分子的时候,不断除以分母,代码如下:

class Solution {
public:
    int uniquePaths(int m, int n) {
        long long numerator = 1; // 分子
        int denominator = m - 1; // 分母
        int count = m - 1;
        int t = m + n - 2;
        while (count--) {
            numerator *= (t--);
            while (denominator != 0 && numerator % denominator == 0) {
                numerator /= denominator;
                denominator--;
            }
        }
        return numerator;
    }
};


  • 时间复杂度:O(m)
  • 空间复杂度:O(1)

计算组合问题的代码还是有难度的,特别是处理溢出的情况!


总结


本文分别给出了深搜,动规,数论三种方法。

深搜当然是超时了,顺便分析了一下使用深搜的时间复杂度,就可以看出为什么超时了。然后在给出动规的方法,依然是使用动规五部曲,这次我们就要考虑如何正确的初始化了,初始化和遍历顺序其实也很重要!


三.题目(最短路径和)


给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和。


例如:当输入[[1,3,5,9],[8,1,3,4],[5,0,6,1],[8,8,4,0]]时,对应的返回值为12,


所选择的最小累加和路径如下图所示:


46149267f9f02dc88de20666ef8461b4.png


74be20cd9b45d7d65d0a955d9b1c8fef.png


1.思路


最朴素的解法莫过于枚举所有的路径,然后求和,找出其中最大值。但是像这种有状态值可以转移的问题,我们可以尝试用动态规划

具体做法:


  • step 1:我们可以构造一个与矩阵同样大小的二维辅助数组,其中]dp[i][j]表示以(i,j)位置为终点的最短路径和,则dp[0][0]=matrix[0][0]。
  • step 2:很容易知道第一行与第一列,只能分别向右或向下,没有第二种选择,因此第一行只能由其左边的累加,第一列只能由其上面的累加。
  • step 3:边缘状态构造好以后,遍历矩阵,补全矩阵中每个位置的dp数组值:如果当前的位置是(i,j),上一步要么是(i−1,j)往下,要么就是(i,j−1)往右,那么取其中较小值与当前位置的值相加就是到当前位置的最小路径和,因此状态转移公式为dp[i][j]=min(dp[i−1][j],dp[i][j−1])+matrix[i][j]。
  • step 4:最后移动到(n−1,m−1)的位置就是到右下角的最短路径和。



2.代码实现

class Solution {
public:
    int minPathSum(vector<vector<int> >& matrix) {
        // write code here
        int m=matrix.size();
        int n=matrix[0].size();
        vector<vector<int>>dp(m,vector<int>(n,0));
        dp[0][0]=matrix[0][0];
        for(int i=1;i<m;i++)
        {
            dp[i][0]=dp[i-1][0]+matrix[i][0];
        }
        for(int j=1;j<n;j++)
        {
            dp[0][j]=dp[0][j-1]+matrix[0][j];
        }
        for(int i=1;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                dp[i][j]=min(dp[i-1][j],dp[i][j-1])+matrix[i][j];
            }
        }
return dp[m-1][n-1];
    }
};


这是两道经典的dp题目,值得反反复思琢磨。

2023.02.23

From:努力进大厂的新青年

相关文章
|
1月前
|
机器学习/深度学习 算法 C++
【DFS/回溯算法】2016年蓝桥杯真题之路径之谜详解
题目要求根据城堡北墙和西墙箭靶上的箭数,推断骑士从西北角到东南角的唯一路径。每步移动时向正北和正西各射一箭,同一格不重复经过。通过DFS回溯模拟“拔箭”过程,验证路径合法性。已知箭数约束路径唯一,最终按编号输出行走顺序。
|
2月前
|
传感器 机器学习/深度学习 编解码
MATLAB|主动噪声和振动控制算法——对较大的次级路径变化具有鲁棒性
MATLAB|主动噪声和振动控制算法——对较大的次级路径变化具有鲁棒性
195 3
|
2月前
|
机器学习/深度学习 存储 算法
动态规划算法深度解析:0-1背包问题
0-1背包问题是经典的组合优化问题,目标是在给定物品重量和价值及背包容量限制下,选取物品使得总价值最大化且每个物品仅能被选一次。该问题通常采用动态规划方法解决,通过构建二维状态表dp[i][j]记录前i个物品在容量j时的最大价值,利用状态转移方程避免重复计算子问题,从而高效求解最优解。
443 1
|
3月前
|
算法 机器人 定位技术
【VRPTW】基于matlab秃鹰算法BES求解带时间窗的骑手外卖配送路径规划问题(目标函数:最优路径成本 含服务客户数量 服务时间 载量 路径长度)(Matlab代码实现)
【VRPTW】基于matlab秃鹰算法BES求解带时间窗的骑手外卖配送路径规划问题(目标函数:最优路径成本 含服务客户数量 服务时间 载量 路径长度)(Matlab代码实现)
114 0
|
2月前
|
机器学习/深度学习 传感器 算法
【无人车路径跟踪】基于神经网络的数据驱动迭代学习控制(ILC)算法,用于具有未知模型和重复任务的非线性单输入单输出(SISO)离散时间系统的无人车的路径跟踪(Matlab代码实现)
【无人车路径跟踪】基于神经网络的数据驱动迭代学习控制(ILC)算法,用于具有未知模型和重复任务的非线性单输入单输出(SISO)离散时间系统的无人车的路径跟踪(Matlab代码实现)
196 2
|
2月前
|
机器学习/深度学习 传感器 算法
基于全局路径的无人地面车辆的横向避让路径规划研究[蚂蚁算法求解](Matlab代码实现)
基于全局路径的无人地面车辆的横向避让路径规划研究[蚂蚁算法求解](Matlab代码实现)
202 8
|
2月前
|
算法 数据挖掘 区块链
基于遗传算法的多式联运车辆路径网络优优化研究(Matlab代码实现)
基于遗传算法的多式联运车辆路径网络优优化研究(Matlab代码实现)
110 2
|
2月前
|
存储 算法 数据可视化
基于禁忌搜索算法的TSP问题最优路径搜索matlab仿真
本程序基于禁忌搜索算法解决旅行商问题(TSP),旨在寻找访问多个城市的最短路径。使用 MATLAB 2022A 编写,包含城市坐标生成、路径优化及结果可视化功能。通过禁忌列表、禁忌长度与藐视准则等机制,提升搜索效率与解的质量,适用于物流配送、路径规划等场景。
|
2月前
|
机器学习/深度学习 负载均衡 算法
【卡车和无人机协同配送路径优化】遗传算法求解利用一辆卡车和两架无人机配合,将小包裹递送给随机分布的客户,以使所有站点都由卡车或无人机递送一次后返回起始位置(中转站)研究(Matlab代码实现)
【卡车和无人机协同配送路径优化】遗传算法求解利用一辆卡车和两架无人机配合,将小包裹递送给随机分布的客户,以使所有站点都由卡车或无人机递送一次后返回起始位置(中转站)研究(Matlab代码实现)
242 7
|
2月前
|
机器学习/深度学习 传感器 算法
基于matlab瞬态三角哈里斯鹰算法TTHHO多无人机协同集群避障路径规划(目标函数:最低成本:路径、高度、威胁、转角)(Matlab代码实现)
基于matlab瞬态三角哈里斯鹰算法TTHHO多无人机协同集群避障路径规划(目标函数:最低成本:路径、高度、威胁、转角)(Matlab代码实现)
124 1

热门文章

最新文章

下一篇
oss云网关配置