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

news2024/11/24 0:37:50

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.关于动态规划
二.三种思路
1.思路一(深搜)
2.思路二(动态规划)
3.思路三(数论方法)
总结
三.题目(最短路径和)

一.题目(最短路径)

1.关于动态规划

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

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

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

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

输入: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. 思路一(深搜)

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

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

如图举例:

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

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数组

如图所示:

以上动规五部曲分析完毕,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 步。

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

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

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

那么答案,如图所示:

求组合的时候,要防止两个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,

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

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:努力进大厂的新青年

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366302.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java|golang】 1238. 循环码排列---格雷编码

给你两个整数 n 和 start。你的任务是返回任意 (0,1,2,…,2^n-1) 的排列 p&#xff0c;并且满足&#xff1a; p[0] start p[i] 和 p[i1] 的二进制表示形式只有一位不同 p[0] 和 p[2^n -1] 的二进制表示形式也只有一位不同 示例 1&#xff1a; 输入&#xff1a;n 2, start …

Linux基础命令-netstat显示网络状态

文章目录 netstat 命令介绍 语法格式 基本参数 显示各列内容分析 1&#xff09;netstat -a显示各列内容分析 2&#xff09;netstat -r显示各列内容分析 3&#xff09;netstat -i 显示各列内容分析 参考实例 1&#xff09;显示系统网络状态的所有连接 2&#xff09;…

Kubernetes之探针probe

deployment只保证pod的状态为running。如果pod状态是running&#xff0c;但是里面丢失了文件&#xff0c;导致用户不能访问数据&#xff0c;则deployment是不管用的&#xff0c;此时就需要probe来检测pod是否正常工作。 probe是定义在容器里的&#xff0c;可以理解为容器里加的…

python基于vue品牌首饰售卖平台

可定制框架:ssm/Springboot/vue/python/PHP/小程序/安卓均可开发目录 开发语言&#xff1a;Python python框架&#xff1a;django/flask 软件版本&#xff1a;python 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;PyCharmscode 项目介…

每日学术速递2.22

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.PriSTI: A Conditional Diffusion Framework for Spatiotemporal Imputation 标题&#xff1a;PriSTI&#xff1a;时空插补的条件扩散框架 作者&#xff1a;Mingzhe Liu, Han Huan…

mysql学习(复习)总结 - 索引机制及分类

mysql索引机制及分类1、索引简介2、索引机制2.1 红黑树(平衡二叉树)2.2 哈希2.3 平衡多路查找树&#xff08;B-Tree&#xff09;2.4 Btree2.5 不同数据结构的索引对比3、索引分类3.2 存储分类-聚簇、二级(辅助)索引3.2.1 聚簇(集)索引3.2.1.1 定义3.2.1.2 聚集索引的适用情形3.…

计算机网络(五):三次握手和四次挥手,TCP,UDP,TIME-WAIT,CLOSE-WAIT,拥塞避免,

文章目录零. TCP和UDP的区别以及TCP详解TCP是如何保证可靠性的TCP超时重传的原理TCP最大连接数限制TCP流量控制和拥塞控制流量控制拥塞控制TCP粘包问题一、三次握手和四次挥手二、为什么要进行三次握手&#xff1f;两次握手可以吗&#xff1f;三、为什么要进行四次挥手&#xf…

23.2.23 22湖北省赛 B

好久没打卡了, 随便找的个水题写 这题是简单难度的 ab1 所以可以找到固定规律, 通过手动模拟可以发现 假设两种水叫做a水和b水 先倒入a水 1:0 倒入b水 1:1 此时水杯为 倒出一半的混合物, 因为ab水互溶, 比例不变 再加入a水或者b水将容器填满 比例现在变为 3:1 混合之后再…

一文让你了解Docker的前世今生

Docker概述 Docker为什么会出现&#xff1f; 我们在开发一款产品的过程中一定是需要至少两套环境的&#xff1a;开发和生产。现在更多的公司都是在使用开发、测试、生产三套环境&#xff0c;我们对不同环境的配置和打包都是非常繁琐和复杂的&#xff0c;而且重复度很高&#…

代码随想录 || 回溯算法93 78 90

Day2493.复原IP地址力扣题目链接给定一个只包含数字的字符串&#xff0c;复原它并返回所有可能的 IP 地址格式。有效的 IP 地址 正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 . 分隔。例如&#…

高级信息系统项目管理(高项 软考)原创论文——成本管理(2)

1、如果您想了解如何高分通过高级信息系统项目管理师(高项)你可以点击链接: 高级信息系统项目管理师(高项)高分通过经验分享_高项经验 2、如果您想了解更多的高级信息系统项目管理(高项 软考)原创论文,您可以点击链接:

Apache Hadoop生态部署-zookeeper分布式安装

目录 查看服务架构图-服务分布、版本信息 一&#xff1a;安装前准备 1&#xff1a;zookeeper安装包选择--官网下载 2&#xff1a;zookeeper3.5.7安装包--百度网盘 二&#xff1a;安装与常用配置 2.1&#xff1a;下载解压zk安装包 2.2&#xff1a;配置环境变量 2.3&#x…

Firebase常用功能和官方Demo简介

一、Firebase简介Firebase刚开始是一家实时后端数据库创业公司&#xff0c;它能帮助开发者很快的写出Web端和移动端的应用。自2014年10月Google收购Firebase以来&#xff0c;用户可以在更方便地使用Firebase的同时&#xff0c;结合Google的云服务。现在的Firebase算是谷歌旗下的…

17.1 Display system tasks

系统任务的显示组分为三类&#xff1a;显示和写入任务、选通监视任务和连续监视任务。17.1.1 The display and write tasks $display和$write系统任务的语法如语法17-1所示。 display_tasks ::display_task_name [ ( list_of_arguments ) ] ; display_task_name ::$display | …

AI技术的发展,人工智能对我们的生活有那些影响?

人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 人工智能是计算机科学的一个分支&#xff0c;它企图了解智能的实质&#xff0c;并生产出一…

使用spring boot自带log

spring boot starter 版本2.7.8 这个版本自带了slf4j-api 里面包含了logback-classic logback其他包 可以直接使用slf4j 但是配置的日志输出到文件是不能正常生效的 官网说明&#xff1a; Spring Boot Reference Documentation springboot 所有内部日志都使用 common loggin…

替代启攀微8按键触控八通道触摸芯片-GTC08L

能完美替代启攀微8按键触控八通道电触摸芯片-GTC08L芯片是一款非常适用于音响上超稳定超抗干扰低功耗八通道电容式触摸IC&#xff1b;可通过触摸实现各种逻辑功能控制&#xff1b;操作简单、方便实用&#xff1b;电压范围宽&#xff0c;可在2.7V&#xff5e;5.5V&#xff08;单…

JavaWeb--Mybatis练习

Mybatis练习Mybatis练习1 配置文件实现CRUD1.1 环境准备1.2 查询所有数据1.2.1 编写接口方法1.2.2 编写SQL语句1.2.3 编写测试方法1.2.4 起别名解决上述问题1.2.5 使用resultMap解决上述问题1.2.6 小结1.3 查询详情1.3.1 编写接口方法1.3.2 编写SQL语句1.3.3 编写测试方法1.3.4…

Redis学习(10)之Redis主从集群(2)

文章目录零 主从集群上篇文章参看一 CAP理论1.1 CAP概念1.2 CAP定理1.3 BASE 理论1.4 CAP 的应用二 Raft 算法2.1 Raft算法简介2.2 角色、任期及角色转变2.3 leader 选举2.3.1 follower要参加选举2.3.2 follower被要求投票2.3.3 Candidate等待响应2.3.4 选举时机2.4 数据同步2.…

stm32f407探索者开发板(二十一)——窗口看门狗

文章目录一、窗口看门狗概述1.1 看门狗框图1.2 窗口看门狗工作过程总结1.3 超时时间1.4 为什么需要窗口看门狗1.5 其他注意事项二、常用寄存器和库函数2.1 控制寄存器WWDG_ CR2.2 配置寄存器WWDG_ CFR2.3 状态寄存器WWDG_SR三、手写窗口看门狗3.1 配置过程3.2 初始化窗口看门狗…