数据结构与算法再探(六)动态规划

目录

动态规划 (Dynamic Programming, DP)

动态规划的基本思想

动态规划的核心概念

动态规划的实现步骤

动态规划实例

1、爬楼梯

c++ 递归(超时)需要使用记忆化递归

循环

2、打家劫舍

3、最小路径和

4、完全平方数

5、最长公共子序列

6、0-1背包问题

总结


动态规划 (Dynamic Programming, DP)

释义:动态规划是一种解决复杂问题的优化方法,通过将大问题拆解成小问题,逐步解决小问题,最终得到大问题的解。它通常用于求解具有最优子结构重构子问题的优化问题。C++中的动态规划算法应用广泛,尤其是在最短路径、背包问题、最长公共子序列、矩阵链乘法等领域。

动态规划的基本思想

动态规划方法通过建立状态转移方程(recurrence relation)来表示问题的子问题之间的关系。每个子问题的解只计算一次,然后保存起来避免重复计算,从而达到减少计算量的目的。常见的动态规划问题通常可以通过两种方式实现:自顶向下的递归(带记忆化)自底向上的迭代

动态规划的核心概念

最优子结构问题的最优解可以通过子问题的最优解得到。换句话说问题的最优解包含了子问题的最优解
重叠子问题

问题可以被分解成多个子问题,且这些子问题在计算过程中会被多次求解。

状态转移方程动态规划的核心是通过状态转移方程将大问题分解为小问题,进而通过小问题的解推导出大问题的解

动态规划的实现步骤

定义状态首先定义子问题的状态。通常状态的定义取决于问题的具体性质。例如,在最短路径问题中,可以定义状态为当前节点到起点的最短路径长度
初始化状态为状态的初值赋值。通常,某些边界条件会初始化为已知值。
状态转移方程根据问题的性质,推导出状态转移方程,描述如何从当前状态推导到下一个状态
计算最优解根据状态转移方程,从最简单的子问题开始逐步计算,直到得到最终问题的解
结果回溯(如果需要):如果问题要求返回具体的解路径(例如路径、序列等),则需要在求解过程中保存路径信息,最后回溯得到结果

动态规划实例

1、爬楼梯

70. 爬楼梯 - 力扣(LeetCode)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢

状态定义:dp[i]表示爬到第 i 阶楼梯的不同方法数。

状态转移方程:dp[i] =dp[i-1]+dp[i-2],即爬到第 i 阶楼梯可以从第 i-1 阶或第 i-2 阶跳跃过来。

为什么 d[i] = d[i-1] + d[i-2] 不会重复包含 d[i-2]?
在动态规划中,d[i] 表示到达第 i 阶楼梯的总方法数。当计算 d[i] 时,d[i-1] 和 d[i-2] 分别表示到达第 i-1 阶和第 i-2 阶楼梯的方法数。这两个数并没有交叉或重复计算的部分,它们分别独立表示从不同阶梯出发的跳跃方式。更具体的解释:d[i-1] 代表的是从第 i-1 阶楼梯跳一步到达第 i 阶楼梯的方法数。d[i-2] 代表的是从第 i-2 阶楼梯跳两步到达第 i 阶楼梯的方法数。
两个值分别代表不同的跳跃方式:从 i-1 到 i 是一次跳跃,跳跃的过程中,不需要考虑 i-2。
从 i-2 到 i 是两次跳跃,这一步也不会再次涉及到 i-1。
因此,d[i-1] 和 d[i-2] 并没有重叠,它们分别计算的是不同的路径,最终的 d[i] = d[i-1] + d[i-2] 是将这两条路径相加,得到的总方法数。

c++ 递归(超时)需要使用记忆化递归

class Solution {
public:int climbStairs(int n) {if(n<=2) return n;return climbStairs(n-1)+climbStairs(n-2);}
};//记录搜素值
vector<int> res;
int dfs(int i){if(i<=1){return 1;}if(res[i]){return res[i];}return res[i]=dfs(i-1)+dfs(i-2);
}

循环

    int climbStairs(int n) {if (n <= 2)return n;vector<int> dp(n + 1, 1);for (int i = 2; i <= n; ++i) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];}//空间优化int climbStairs(int n) {if (n <= 2)return n;int pre2 = 1, pre1 = 2, cur;for (int i = 2; i < n; ++i) {cur = pre1 + pre2;pre2 = pre1;pre1 = cur;}return cur;}

2、打家劫舍

198. 打家劫舍 - 力扣(LeetCode)

一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

设 dp[i] 表示偷窃前 i 家房子时的最大金额。显然,对于每个房子,存在两个选择:偷窃第 i 家房子:如果你偷窃第 i 家房子,那么你不能偷窃第 i-1 家房子,因此最大金额是 dp[i-2] + nums[i],其中 nums[i] 表示第 i 家房子的金额。
不偷窃第 i 家房子:那么最大金额就是 dp[i-1],即偷窃前 i-1 家房子时的最大金额。因此,递推关系式为:dp[i]=max(dp[i−1],dp[i−2]+nums[i])其中,dp[0] 为偷窃第一家房子的金额,dp[1] 为偷窃前两家房子时的最大金额。

状态转移:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
初始状态:dp[0] = nums[0],dp[1] = max(nums[0], nums[1])。

  int rob(vector<int>& nums) {int n=nums.size();vector<int> dp(n,0);if(n==1){return nums[0];}dp[0]=nums[0];dp[1]=max(nums[0],nums[1]);for(int i=2;i<n;++i){dp[i]=max(dp[i-1],dp[i-2]+nums[i]);}return dp[n-1];}
//空间优化int rob(vector<int>& nums) {int n=nums.size();//vector<int> dp(n,0);if(n==1){return nums[0];}auto pre=0;auto cur=0;int res=0;for(int i=0;i<n;++i){res=max(cur,pre+nums[i]);pre=cur;cur=res;}return res;}

3、最小路径和

64. 最小路径和 - 力扣(LeetCode)

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

状态定义:用 dp[i][j] 表示到达位置 (i, j) 的最小路径和。
状态转移:从上方到达 (i, j) 的路径和为 dp[i-1][j] + grid[i][j]。从左方到达 (i, j) 的路径和为 dp[i][j-1] + grid[i][j]。因此,状态转移方程为:dp[i][j]=min(dp[i−1][j],dp[i][j−1])+grid[i][j];其中,dp[i-1][j] 和 dp[i][j-1] 分别表示到达上方和左方的最小路径和。

初始状态:dp[0][0] = grid[0][0],即起点的值。第一行和第一列的值需要单独处理,因为它们只能从一个方向到达:第一行:dp[0][j] = dp[0][j-1] + grid[0][j];第一列:dp[i][0] = dp[i-1][0] + grid[i][0]
返回结果:最终的结果为 dp[m-1][n-1],即到达右下角的最小路径和。

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

4、完全平方数

279. 完全平方数 - 力扣(LeetCode)

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量。完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

状态定义:用 dp[i] 表示和为 i 的最小完全平方数的数量。
状态转移:对于每个 i,我们可以尝试用每个小于等于 i 的完全平方数 j*j 来更新 dp[i]:dp[i]=min(dp[i],dp[i−j∗j]+1), 其中 j 是从 1 到 \sqrt{i} 的整数(位置 i 只依赖 i - j*j 的位置,如 i - 1、 i - 4、 i - 9 等等,才能满足完全平方分割的条件)。
初始状态:dp[0] = 0,表示和为 0 时不需要任何数。

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

5、最长公共子序列

1143. 最长公共子序列 - 力扣(LeetCode)

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

状态定义:用 dp[i][j] 表示 text1 的前 i 个字符与 text2 的前 j 个字符的最长公共子序列的长度。
状态转移:如果 text1[i-1] == text2[j-1],那么 dp[i][j] = dp[i-1][j-1] + 1,表示当前字符相等时,公共子序列长度加一。如果 text1[i-1] != text2[j-1],那么 dp[i][j] = \max(dp[i-1][j], dp[i][j-1]),表示当前字符不相等时,最长公共子序列的长度为去掉当前字符后的最大值。
初始状态:dp[0][j] = 0 和 dp[i][0] = 0,表示任意一个字符串为空时,公共子序列长度为 0。
返回结果:最终的结果为 dp[m][n],其中 m 和 n 分别是 text1 和 text2 的长度。

class Solution {
public:int longestCommonSubsequence(string text1, string text2) {int m = text1.length(), n = text2.length();vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));for (int i = 1; i <= m; ++i) {for (int j = 1; j <= n; ++j) {if (text1[i - 1] == text2[j - 1]) {dp[i][j] = dp[i - 1][j - 1] + 1;} else {dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);}}}return dp[m][n];}
};

6、0-1背包问题

有 n 个物品,每个物品 i 有一个重量 weights[i] 和一个价值 values[i]。背包的最大容量为 W。需要找到一个选择方案,使得所选物品的总重量不超过 W,并且总价值最大。动态规划的思路
状态定义:用 dp[i][j] 表示前 i 个物品中,能够放入容量为 j 的背包的最大价值。
状态转移:如果不选择第 i 个物品,最大价值为 dp[i-1][j]。如果选择第 i 个物品,最大价值为 values[i-1] + dp[i-1][j-weights[i-1]](前提是 j 大于等于 weights[i-1])。
因此,状态转移方程为:dp[i][j]=max(dp[i−1][j],values[i−1]+dp[i−1][j−weights[i−1]])if j≥weights[i−1]初始状态:dp[0][j] = 0,表示没有物品时,最大价值为 0。返回结果:最终的结果为 dp[n][W],即使用所有物品时,背包容量为 W 的最大价值。

int knapsack(vector<int> weights, vector<int> values, int N, int W)
{vector<vector<int>> dp(N + 1, vector<int>(W + 1, 0));for (int i = 1; i <= N; ++i){int w = weights[i - 1], v = values[i - 1];for (int j = 1; j <= W; ++j){if (j >= w){dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w] + v);}else{dp[i][j] = dp[i - 1][j];}}}return dp[N][W];
}

总结

动态规划是一种强大的方法,可以解决很多最优化问题。其核心思想是将问题拆解为子问题,通过记忆化或迭代的方式避免重复计算,从而提高效率。在C++中,动态规划的实现通常涉及状态定义、状态转移方程的推导以及最终解的计算。通过具体的算法问题(如背包问题、最长公共子序列、爬楼梯问题等)来理解和应用动态规划,可以帮助解决复杂的优化问题。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3+elementPlus之后台管理系统(从0到1)(day4-完结)

面包屑 创建一个面包屑组件 将路由导入然后格式化map对象 key-value 将当前路由的key和value获取然后存入list数组中 遍历list数据&#xff0c;渲染内容 <!--BreadcrumbCom.vue--> <template><el-breadcrumb separator">"><el-breadcrum…

非根目录部署 nextjs 项目,资源文件 请求404 的问题

最近在学习next项目编写的代码放到服务器上静态资源404 先分析问题 到服务器上查看是有资源目录的是不是项目配置有问题是不是nginx配置有问题 经过排查1和2是没有问题的目前来看只有3 检查一下nginx配置 尝试着把静态资源的配置禁用 问题解决 我的next项目用的是pm2管理…

【Leetcode刷题记录】15.三数之和

15. 三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。 请你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三…

豆包 MarsCode + 开源 = ?AI 助力开源社区新人成长

来源&#xff5c;豆包 MarsCode “开源” 这个词&#xff0c;对开发者来说&#xff0c;可能是入门时的第一步&#xff0c;也可能是追求极致技术的终点。无数优秀的开源项目不仅推动了技术的进步&#xff0c;也成为开发者学习和成长的宝藏&#xff0c;但同时也因为其规模庞大、代…

【Linux】IPC:匿名管道、命名管道、共享内存

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;Linux 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 1、管道2、进程池3、命名管道4、共享内存 1、管道 我们知道进程具有独立性&#xff0c;但是在一些场景中进程间也需要通信&#…

python生成图片和pdf,快速

1、下载安装 pip install imgkit pip install pdfkit2、wkhtmltopdf工具包&#xff0c;下载安装 下载地址&#xff1a;https://wkhtmltopdf.org/downloads.html 3、生成图片 import imgkit path_wkimg rD:\app\wkhtmltopdf\bin\wkhtmltoimage.exe # 工具路径&#xff0c;安…

location的使用规则

1、基于URL的location 负责均衡配置 后端集群中的web服务器&#xff0c;必须要有对应的目录和文件才能被访问到 http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;upstream default_pool {server 10.0.0.7:…

ComfyUI实现老照片修复——AI修复老照片(ComfyUI-ReActor / ReSwapper)解决天坑问题及加速pip下载

AI修复老照片&#xff0c;试试吧&#xff0c;不一定好~~哈哈 2023年4月曾用过ComfyUI&#xff0c;当时就感慨这个工具和虚幻的蓝图很像&#xff0c;以后肯定是专业人玩的。 2024年我写代码去了&#xff0c;AI做图没太关注&#xff0c;没想到&#xff0c;现在ComfyUI真的变成了工…

基于C++的DPU医疗领域编程初探

一、大型医院数据处理困境与 DPU 的崛起 在数字化浪潮的席卷下,医疗行业正经历着深刻变革,大型医院作为医疗服务的核心枢纽,积累了海量的数据,涵盖患者的基本信息、诊断记录、检验报告、影像资料等多个维度。这些数据不仅规模庞大,而且增长速度迅猛,传统的中央处理器(C…

C#新语法

目录 顶级语句&#xff08;C#9.0&#xff09; using 全局using指令&#xff08;C#10.0&#xff09; using资源管理问题 using声明&#xff08;C#8.0&#xff09; using声明陷阱 错误写法 正确写法 文件范围的命名空间声明&#xff08;C#10.0&#xff09; 可空引用类型…

WPF基础 | WPF 布局系统深度剖析:从 Grid 到 StackPanel

WPF基础 | WPF 布局系统深度剖析&#xff1a;从 Grid 到 StackPanel 一、前言二、Grid 布局&#xff1a;万能的布局王者2.1 Grid 布局基础&#xff1a;构建网格世界2.2 子元素定位与跨行列&#xff1a;布局的精细操控2.3 自适应布局&#xff1a;灵活应变的秘诀 三、StackPanel…

基于微信小程序的网上订餐管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

网盘资源查找工具---AI功能

01 软件介绍 这是一款融入了ai技术的网盘搜索神器&#xff0c;可以让你更快&#xff0c;更精准的找到自己需要的文件&#xff0c;不管你是找影视&#xff0c;音乐&#xff0c;还是找软件或者学习资料都可以&#xff0c;欢迎前来使用。 02 功能展示 该软件非常简洁&#xff…

JAVA:利用 Content Negotiation 实现多样式响应格式的技术指南

1、简述 Content Negotiation&#xff08;内容协商&#xff09; 是 RESTful 服务的重要特性&#xff0c;允许客户端和服务器根据请求的不同特性动态选择适合的响应格式。它是一种在 HTTP 协议中实现的机制&#xff0c;通过它&#xff0c;服务器能够根据客户端需求返回适合的内…

Hook 函数

什么是hook函数&#xff1f; 在计算机编程中&#xff0c;hook函数是指在特定的事件发生时被调用的函数&#xff0c;用于在事件发生前或后进行一些特定的操作。通常&#xff0c;hook函数作为回调函数被注册到事件处理器中&#xff0c;当事件发生时&#xff0c;事件处理器会自动…

飞牛NAS新增虚拟机功能,如果使用虚拟机网卡直通安装ikuai软路由(如何解决OVS网桥绑定失败以及打开ovs后无法访问飞牛nas等问题)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 飞牛NAS虚拟机安装爱快教程 📒🛠️ 前期准备🌐 网络要求💾 下载爱快镜像🚀 开始安装💻 开启IOMMU直通🌐 配置网络🚨 解决OVS网桥绑定失败以及打开ovs后无法访问飞牛nas等问题➕ 创建虚拟机🎯 安装ikuai💻 进…

js手撕 | 使用css画一个三角形 使用js修改元素样式 驼峰格式与“-”格式相互转化

1.使用css画一个三角形 借助 border 实现&#xff0c;在 width 和 height 都为 0 时&#xff0c;设置 border&#xff0c;便会呈现三角形。想要哪个方向的三角形&#xff0c;设置其他三边为 透明即可。同时&#xff0c;可以通过调整不同边的宽度&#xff0c;来调整三角形的高度…

IoTDB 2025 春节值班与祝福

2025 春节快乐 瑞蛇迎吉庆&#xff0c;祥光映华年&#xff0c;2025 春节已近在眼前。社区祝福 IoTDB 的所有关注者、支持者、使用者 2025 新年快乐&#xff0c;“蛇”来运转&#xff01; IoTDB 团队的春节放假时间为 2025 年 1 月 27 日至 2 月 4 日&#xff0c;1 月 25 日、26…

React和Vue有什么区别,如何选择?

React和Vue有什么区别&#xff0c;如何选择&#xff1f; React 和 Vue 是当前最受欢迎的前端框架之一&#xff0c;两者在开发者中都有极高的声誉。它们都旨在帮助开发人员构建用户界面&#xff0c;但在实现方式和适用场景上有所不同。如果你正考虑在项目中选择 React 或 Vue&a…

poi在word中打开本地文件

poi版本 5.2.0 方法1&#xff1a;使用XWPFFieldRun&#xff08;推荐&#xff09; 比如打开当前相对路径的aaaaa.docx XWPFFieldRun run paragraph.createFieldRun();CTRPr ctrPr run.getCTR().addNewRPr();CTFonts font ctrPr.addNewRFonts();// 设置字体font.setAscii(&quo…