数据结构与算法再探(六)动态规划
目录
动态规划 (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++中,动态规划的实现通常涉及状态定义、状态转移方程的推导以及最终解的计算。通过具体的算法问题(如背包问题、最长公共子序列、爬楼梯问题等)来理解和应用动态规划,可以帮助解决复杂的优化问题。
相关文章:
数据结构与算法再探(六)动态规划
目录 动态规划 (Dynamic Programming, DP) 动态规划的基本思想 动态规划的核心概念 动态规划的实现步骤 动态规划实例 1、爬楼梯 c 递归(超时)需要使用记忆化递归 循环 2、打家劫舍 3、最小路径和 4、完全平方数 5、最长公共子序列 6、0-1背…...
使用PC版本剪映制作照片MV
目录 制作MV模板时长调整拖动边缘缩短法分割删除法变速法整体调整法 制作MV 导入音乐 导入歌词 点击歌词 和片头可以修改字体: 还可以给字幕添加动画效果: 导入照片,自动创建照片轨: 修改片头字幕:增加两条字幕轨&…...
Python爬虫获取custom-1688自定义API操作接口
一、引言 在电子商务领域,1688作为国内领先的B2B平台,提供了丰富的API接口,允许开发者获取商品信息、店铺信息等。其中,custom接口允许开发者进行自定义操作,获取特定的数据。本文将详细介绍如何使用Python调用1688的…...
Autogen_core: Reflection
目录 代码代码逻辑解释:数据类定义:CoderAgent 类:ReviewerAgent 类:主程序: 完成的功能: 代码 from dataclasses import dataclassdataclass class CodeWritingTask:task: strdataclass class CodeWritin…...
GitHub 仓库的 Archived 功能详解:中英双语
GitHub 仓库的 Archived 功能详解 一、什么是 GitHub 仓库的 “Archived” 功能? 在 GitHub 上,“Archived” 是一个专门用于标记仓库状态的功能。当仓库被归档后,它变为只读模式,所有的功能如提交代码、创建 issue 和 pull req…...
.NET Core缓存
目录 缓存的概念 客户端响应缓存 cache-control 服务器端响应缓存 内存缓存(In-memory cache) 用法 GetOrCreateAsync 缓存过期时间策略 缓存的过期时间 解决方法: 两种过期时间策略: 绝对过期时间 滑动过期时间 两…...
Ubuntu 20.04安装Protocol Buffers 2.5.0
个人博客地址:Ubuntu 20.04安装Protocol Buffers 2.5.0 | 一张假钞的真实世界 安装过程 Protocol Buffers 2.5.0源码下载:https://github.com/protocolbuffers/protobuf/tree/v2.5.0。下载并解压。 将autogen.sh文件中以下内容: curl htt…...
【贪心算法】洛谷P1090 合并果子 / [USACO06NOV] Fence Repair G
2025 - 01 - 21 - 第 45 篇 【洛谷】贪心算法题单 -【 贪心算法】 - 【学习笔记】 作者(Author): 郑龙浩 / 仟濹(CSND账号名) 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G 【贪心算法】 文章目录 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06…...
14.模型,纹理,着色器
模型、纹理和着色器是计算机图形学中的三个核心概念,用通俗易懂的方式来解释: 1. 模型:3D物体的骨架 通俗解释: 模型就像3D物体的骨架,定义了物体的形状和结构。 比如,一个房子的模型包括墙、屋顶、窗户等…...
【微服务与分布式实践】探索 Dubbo
核心组件 服务注册与发现原理 服务提供者启动时,会将其服务信息(如服务名、版本、所在节点的网络地址等)注册到注册中心。服务消费者则可以从注册中心发现可用的服务提供者列表,并与之通信。注册中心会存储服务的信息,…...
Scale AI 创始人兼 CEO采访
Scale AI 创始人兼 CEO 亚历山大王(Alexander Wang)首次亮相节目接受采访。他的公司专注于为人工智能工具提供准确标注的数据。早在 2022 年,王成为世界上最年轻的白手起家亿万富翁。 美国在全球人工智能竞赛中的地位,以及它与中…...
Java 大视界 -- Java 大数据在生物信息学中的应用与挑战(67)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
NeuIPS 2024 | CoT推理的新突破:推理边界框架(RBF)
近年来,大型语言模型(LLMs)在推理任务上的能力不断提升,尤其是 思维链(Chain-of-Thought, CoT) 技术,使得模型可以逐步推演逻辑,提高预测准确率。然而,当前的CoT推理仍然…...
【C】memory 详解
<memory.h> 是一个 C 标准库头文件,提供了一组内存管理函数,用于分配、释放和操作动态内存。这些函数主要操作的是未初始化的内存块,是早期 C 编程中常用的内存操作工具。 尽管在现代 C 编程中更推荐使用<cstring>或<memory&…...
linux——进程树的概念和示例
一些程序进程运行后,会调用其他进程,这样就组成了一个进程树。 比如,在Windows XP的“运行”对话框中输入“cmd”启动命令行控制台,然后在命令行中输入“notepad”启动记事本,那么命令行控制台进程“cmd.exe”和记事本进程“note…...
分布式系统相关面试题收集
目录 什么是分布式系统,以及它有哪些主要特性? 分布式系统中如何保证数据的一致性? 解释一下CAP理论,并说明在分布式系统中如何权衡CAP三者? 什么是分布式事务,以及它的实现方式有哪些? 什么是…...
CSAPP学习:前言
前言 本书简称CS:APP。 背景知识 一些基础的C语言知识 如何阅读 Do-做系统 在真正的系统上解决具体的问题,或是编写和运行程序。 章节 2025-1-27 个人认为如下章节将会对学习408中的操作系统与计算机组成原理提供帮助,于是先凭借记忆将其简单…...
kaggle比赛入门 - House Prices - Advanced Regression Techniques(第三部分)
本文承接上一篇。 1. 数据预处理流水线(pipelines) from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OneHotEnc…...
Linux 命令之技巧(Tips for Linux Commands)
Linux 命令之技巧 简介 Linux 是一种免费使用和自由传播的类Unix操作系统,其内核由林纳斯本纳第克特托瓦兹(Linus Benedict Torvalds)于1991年10月5日首次发布。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户…...
从 GShard 到 DeepSeek-V3:回顾 MoE 大模型负载均衡策略演进
作者:小天狼星不来客 原文:https://zhuanlan.zhihu.com/p/19117825360 故事要从 GShard 说起——当时,人们意识到拥有数十亿甚至数万亿参数的模型可以通过某种形式的“稀疏化(sparsified)”来在保持高精度的同时加速训…...
【番外篇】鸿蒙扫雷天纪:运混沌灵智勘破雷劫天局
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。 这一节课我们不学习新的知识,我们来做一个扫雷小游戏 目录 扫雷小游戏概述一、扫雷游戏分析…...
【反悔堆】力扣1642. 可以到达的最远建筑
给你一个整数数组 heights ,表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。 你从建筑物 0 开始旅程,不断向后面的建筑物移动,期间可能会用到砖块或梯子。 当从建筑物 i 移动到建筑物 i1(下标 从 0 开始 )…...
字符串算法笔记
字符串笔记 说到字符串,首先我们要注意的就是字符串的输入以及输出,因为字符串的输入格式以及要求也分为很多种,我们就来说几个比较常见的格式 g e t s gets gets 我们先来说这个函数的含义...
AWTK 骨骼动画控件用法
创建骨骼动画控件 atlas 指定纹理图集文件,skeleton 指定骨骼动画数据文件。可以是相对路径或绝对路径。atlas 中引用的图片文件需要和 skeleton 文件在同一目录下。 scale_x 和 scale_y 指定缩放比例,根据实际情况调整。 scale_time 指定播放速度&am…...
解决Oracle SQL语句性能问题(10.5)——常用Hint及语法(7)(其他Hint)
10.5.3. 常用hint 10.5.3.7. 其他Hint 1)cardinality:显式的指示优化器为SQL语句的某个行源指定势。该Hint具体语法如下所示。 SQL> select /*+ cardinality([@qb] [table] card ) */ ...; --注: 1)这里,第一个参数(@qb)为可选参数,指定查询语句块名;第二个参数…...
如何写美赛(MCM/ICM)论文中的Summary部分
美赛(MCM/ICM)作为一个数学建模竞赛,要求参赛者在有限的时间内解决一个复杂的实际问题,并通过数学建模、数据分析和计算机模拟等手段给出有效的解决方案。在美赛的论文中,Summary部分(通常也称为摘要)是非常关键的,它是整个论文的缩影,能让评审快速了解你解决问题的思…...
DataWhale组队学习 fun-transformer task5
1. 词向量:单词的“身份证” 首先,我们定义了四个单词的词向量,每个向量维度为3。你可以把这些词向量想象成每个单词的“身份证”。每个身份证上有3个特征,用来描述这个单词的“性格”或“特点”。 word_1 np.array([1, 0, 0])…...
【huawei】云计算的备份和容灾
目录 1 备份和容灾 2 灾备的作用? ① 备份的作用 ② 容灾的作用 3 灾备的衡量指标 ① 数据恢复时间点(RPO,Recoyery Point Objective) ② 应用恢复时间(RTO,Recoyery Time Objective) 4…...
电力晶体管(GTR)全控性器件
电力晶体管(Giant Transistor,GTR)是一种全控性器件,以下是关于它的详细介绍:(模电普通晶体管三极管进行对比学习) 基本概念 GTR是一种耐高电压、大电流的双极结型晶体管(BJT&am…...
LQ1052 Fibonacci斐波那契数列
题目描述 Fibonacci斐波那契数列也称为兔子数列,它的递推公式为:FnFn-1Fn-2,其中F1F21。 当n比较大时,Fn也非常大,现在小蓝想知道,Fn除以10007的余数是多少,请你编程告诉她。 输入 输入包含一…...
