当前位置: 首页 > news >正文

二刷 动态规划

什么是动态规划 Dynamic Programming DP

如果某一问题有很多重叠子问题,使用动态规划时最有效的

动态规划中每一个状态是由上一个状态推导出来的。

动规五部曲

1.确定dp数组以及下标的含义

2.确定递归公式

3.dp数组如何初始化

4.确定遍历顺序

5.举例推导dp数组


509.斐波那契数

1.确定dp数组和下标的含义

dp[i]:第i个数的斐波那契数值是dp[i]

2.确定递推公式

dp[i] = dp[i-1]+dp-i-2

3.dp数组如何初始化

dp[0] = 0;

dp[1]=  1

4.确定遍历顺序

dp[i]依赖dp[i-1],dp[i-2],因此是从前向后

5.举例推导

0 1 1 2 3 5 8 13

class Solution {
public:int fib(int n) {//如果是0,1,直接返回if(n <= 1) return n;//创建dp数组vector<int> dp(n+1);//初始化dp[0] = 0;dp[1] = 1;for (int i = 2; i <= n; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];}
};

70.爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

  • 输入: 2
  • 输出: 2
  • 解释: 有两种方法可以爬到楼顶。
    • 1 阶 + 1 阶
    • 2 阶

1.确定dp数组及下标含义

dp[i]:爬上i楼有dp[i]种方法

2.确定递推公式

第一层有一种方法

第二层有两种方法

第一层跨两步到第三层;第二层跨一步到第三层;

所以第三层楼梯的状态可以由第二层楼梯和到第一层楼梯的状态推导出来

dp[i-1],上i-1层楼梯,有dp[i-1]种方法,再一步跳一个台阶到dp[i]

dp[i-2],上i-2层楼梯,有dp[i-2]种方法,再一步跳两个台阶就是dp[i]。

dp = dp[i-1]+dp[i-2]

3.dp数组初始化

dp[1] = 1

dp[2] = 2

4.确定遍历顺序

从前向后

5.模拟

class Solution {
public:int climbStairs(int n) {if ( n <= 1) return n;vector<int> dp(n+1);dp[1] = 1;dp[2] = 2;for (int i = 3; i <= n; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];}
};

746. 使用最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

1.确定dp数组和下标含义

dp[i]:到达i楼层的最低花费是dp[i]

2.确定递推公式

dp[i] = dp[i-1] + cost[i -1]

dp[i] = dp[i-2] + cost[i -2]

dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);

3.初始化

到达第0和第1台阶不需要花费体力

dp[0] = 0;

dp[1] = 0;

4.遍历顺序

从前向后

5.模拟

此处的n是cost.size()

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {vector<int> dp(cost.size() + 1);dp[0] = 0;dp[1] = 0;for (int i = 2; i <= cost.size(); i++) {dp[i] = min (dp[i-1] + cost[i - 1], dp[i-2] + cost[i - 2]);}return dp[cost.size()];}
};

62.不同路径

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

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

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

机器人从00出发到m-1,n-1

1.确定dp数组和下标含义

dp[i][j]:从00出发到ij有dp[i][j]条不同的路径

2.确定递推公式

只有两个方向能来

dp[i][j] = dp[i-1][j] + dp[i][j-1];

3.初始化

for  dp[i][0] = 1

for  dp[0][j] = 1

4.遍历顺序

从左到右一层一层遍历

注意最后一个点是m-1,n-1

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][j - 1] + dp[i - 1][j];}}return dp[m - 1][n - 1];}
};

63. 不同路径 II

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

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

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

思路:

1.确定dp数组和下标含义

dp[i][j]:从00到m-1,n-1有dp[i-1][j-1]条路径

2.确定递推公式

没有障碍情况下

if(obstacleGrid[i][j] == 0)

dp[i][j] = dp[i-1][j] + dp[i][j-1]

3.确定初始化

没有障碍才能为1

4.左上到右下

class Solution {
public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m = obstacleGrid.size();int n = obstacleGrid[0].size();//起点或终点有障碍物,直接返回0;if (obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1) return 0;vector<vector<int>> dp(m, vector<int>(n, 0));// 初始化,没有障碍物才能为1,遇到障碍物就为0for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1; //遍历,遇到障碍物就跳过for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {if (obstacleGrid[i][j] == 1) continue;dp[i][j] = dp[i-1][j] + dp[i][j-1];}}return dp[m-1][n-1];}
};

343. 整数拆分

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例 1:

  • 输入: 2
  • 输出: 1
  • 解释: 2 = 1 + 1, 1 × 1 = 1。

1.确定dp数组和下标的含义

dp[i]:正整数i划分后得到的最大乘积

2.确定递推公式

从1遍历j,

将i划分为两个数 :dp = j*(i-j)

继续划分:j*dp[i-j]

dp[i] = max({dp[i], j*(i-j),j*dp[i-j]})

3.初始化

dp[2] = 1

4.递归顺序

从前向后

class Solution {
public:int integerBreak(int n) {vector<int> dp(n + 1);dp[2] = 1;for (int i = 3; i <= n; i++) {//i/2:一般乘积最大 是中间的数for (int j = 1; j <= i/2; j++) {//j * (i-j)表示分成两个数字;j*dp[i - j]表示分成两个以上数字dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));}}return dp[n];}
};

96.不同的二叉搜索树

给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?

1.确定dp[i]数组及下标的含义

dp[i]:以1-i为节点组成的二叉搜索树由dp[i]种

2.确定递推公式

n=3时

元素1为头节点搜索树的数量 = 右子树有2个元素的搜索树数量*左子树有0个元素的搜索树数量

元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量*左子树有1个元素的搜索树数量

元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量*左子树有2个元素的搜索树数量

有两个元素的搜索树数量是dp[2]

有1个元素的搜索树数量是dp[1]

有0个元素的搜索树数量是dp[0]

dp[3] = dp[2]*dp[0] + dp[1]*dp[1]+dp[0]*dp[2]

dp[i] += dp[以j为头结点左子树节点数量]*dp[以j为头结点右子树节点数量]

j相当于头节点的元素,从1遍历到i为止

递推公式:dp[i] += dp[j - 1]*dp[i-j]

3.初始化

dp[0] = 1;

4.遍历顺序

i是依靠i之前的节点数的状态

for(int i = 1; i<=n; i++) {

        for (int j = 1;j <= i;j++) {

                dp[i] += dp[j - 1]* dp[i-j] }

}

5.模拟

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

198.打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

  • 示例 1:
  • 输入:[1,2,3,1]
  • 输出:4

1.确定dp[i]数组及下标的含义

dp[i]:考虑下标i以内的房屋,偷窃的最高金额

2.确定递推公式

如果偷第i间房,找出i-2以内的房屋最多可偷的金额+这间房的金额:dp[i] = dp[i - 2] + nums[i]

如果不偷第i间房,则可以考虑第i-1间房,但不一定就要偷i-1:dp[i] = dp[i-1]

dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);

3.初始化

dp[0] = nums[0];

dp[1] = max(nums[0], nums[0]);

4.遍历顺序

从前往后

  

注意要当nums只有一个时,直接返回这个即可。

class Solution {
public:int rob(vector<int>& nums) {if (nums.size() == 1) return nums[0];vector<int> dp(nums.size());dp[0] = nums[0];dp[1] = max(nums[0], nums[1]);for (int i = 2; i < nums.size(); i++) {dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);}return dp[nums.size() - 1];}
};

213.打家劫舍II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。

示例 1:

  • 输入:nums = [2,3,2]

  • 输出:3

1.确定dp数组和下标的含义

dp[i]:i房屋以内的房屋最大的偷窃金额是dp[i]

2.确定递推公式

考虑两种情况:只包含头和只包含尾分别考虑,取最大值

class Solution {
public:int rob(vector<int>& nums) {if (nums.size() == 1) return nums[0];int result1 = robRange(nums, 0, nums.size() - 2);int result2 = robRange(nums, 1, nums.size() - 1);return max (result1, result2);}int robRange(vector<int>& nums, int start, int end) {vector<int> dp(nums.size());dp[start] = nums[start];dp[start + 1] = max(nums[start], nums[start + 1]);for (int i = start + 2; i <= end; i++) {dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);}return dp[end];
}
};

121. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

  • 示例 1:

  • 输入:[7,1,5,3,6,4]

  • 输出:5

1.确定dp[i]及下标的含义

dp[i][0]:第i天不持有股票

dp[i][1]:第i天持有股票

2.递推公式

dp[i][0]:第i天不持有股票有两种情况

第i-1天就不持有:dp[i - 1][0]

第i-1天持有,第i天卖出:dp[i - 1][1] + prices[i]

求最大值

dp[i][1]:第i天持有股票有两种情况

第i-1天就持有:dp[i - 1][1]

第i-1天不持有,第i天买入:- prices[i]

---这里 只买一次,第一次买股票 手头现金一定是0,所以直接-prices[i]

求最大值

3.初始化

dp[0][0] = 0

dp[0][1] = -prices[0]

4.遍历顺序

5.模拟

class Solution {
public:int maxProfit(vector<int>& prices) {if (prices.size() == 0) return 0;vector<vector<int>> dp(prices.size(),vector<int>(2));dp[0][0] = 0;dp[0][1] = -prices[0];for (int i = 1; i < prices.size(); i++) {dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);dp[i][1] = max(-prices[i], dp[i - 1][1]);}//最后一定是选择不持有股票赚的多return dp[prices.size() - 1][0];}
};

122.买卖股票的最佳时机II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

  • 示例 1:

  • 输入: [7,1,5,3,6,4]

  • 输出: 7

1.确定dp[i]数组及下标的含义

dp[i][0]:不持有股票

dp[i][1]:持有股票

2.确定递推公式

dp[i][0]:第i天不持有股票

第i-1天就不持有,dp[i-1][0]

第i-1天持有,第i天卖出:dp[i - 1][1] + prices[i]

dp[i][1]:第i天持有股票

第i-1天就持有:dp[i-1][1]

第i-1天不持有,第i天买入:dp[i - 1][0] -prices[i]

唯一不同点:买卖多次股票,买入的时候手头现金非0

class Solution {
public:int maxProfit(vector<int>& prices) {int len = prices.size();vector<vector<int>> dp(len, vector<int>(2, 0));dp[0][0] = 0;dp[0][1] = -prices[0];for (int i = 1; i < len; i++) {dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);}return dp[len - 1][0];}
};

647. 回文子串

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

  • 输入:"abc"
  • 输出:3
  • 解释:三个回文子串: "a", "b", "c"

1.确定dp[i]及下标的含义

dp[i][j]:[i,j]区间内是否是回文串

2.递推公式

s[i]==s[j]分为三种情况

a, aa都属于i-j<=1,相差一个距离

相差多个距离ababa,要根据dp[i+1][j-1]是否满足回文串了

3.初始化

初始都为false

4.遍历顺序

因为dp[i][j]依赖于dp[i+1][j-1] 所以从下往上从左往右

class Solution {
public:int countSubstrings(string s) {int len = s.size();int result = 0;vector<vector<bool>> dp(len, vector<bool>(len, false));for (int i = len - 1; i >= 0; i--) {for (int j = i; j < len; j++) {//j从i开始if (s[i] == s[j]) {if (j - i <= 1) {result++;dp[i][j] = true;} else if (dp[i + 1][j - 1]) {result++;dp[i][j] = true; }}}}return  result;}
};

516.最长回文子序列

给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。

示例 1: 输入: "bbbab" 输出: 4 一个可能的最长回文子序列为 "bbbb"。

示例 2: 输入:"cbbd" 输出: 2 一个可能的最长回文子序列为 "bb"。

回文子序列不连续

1.确定dp[i]数组和下标

dp[i][j]:字符串s在[i,j]范围里最长的回文子序列的长度是dp[i][j]

2.确定递推公式

如果s[i] == s[j] ,dp[i][j] = dp[i+1][j-1]+2

如果s[i] != s[j], dp[i][j] = max(dp[i+1][j], dp[i][j-1]);看看分别加入s[i]、s[j]哪一个可以组成最长的回文子序列。

3.初始化

i=j相同的时候,初始化为1

其他默认为0

4.遍历顺序

dp[i][j]依赖于dp[i+1][j-1],从下往上,从左往右

内层循环:for(j = i+1)原因是,j必须大于i,并且j=i已经初始化了

class Solution {
public:int longestPalindromeSubseq(string s) {vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));//i=j的时候,为1for(int i = 0; i < s.size(); i++) dp[i][i] =1;for(int i = s.size() - 1; i >= 0; i--) {for (int j = i + 1; j < s.size(); j++) {if (s[i] == s[j]) {dp[i][j] = dp[i + 1][j - 1] + 2;}else if (s[i] != s[j]) {dp[i][j] = max(dp[i+1][j], dp[i][j-1]);}}}return dp[0][s.size() - 1];}
};

相关文章:

  • 用JSZip,FileSaver 有现成cdn的http图片或者文件地址,弄成压缩包导出,解决如果文件名字都是一样的只导出一个图片或文件的方法
  • 定位OOM(Out of Memory)
  • 如何指定Microsoft Print To PDF的输出路径
  • 一键搞定长图处理:高效精准,轻松实现按固定高度像素切割
  • java TCP服务器与客户端通信示例
  • laravel对接百度智能云 实现智能机器人
  • Docker使用daocloud镜像加速
  • 基于python的随机森林回归预测+贝叶斯优化超参数前后训练效果对比
  • 1.k8s:架构,组件,基础概念
  • 和小红书一起参会! 了解大模型与大数据融合的技术趋势
  • 后台运行大师:HarmonyOS 3.0中如何轻松设置APP常驻后台
  • 左耳听风_030_29_推荐阅读分布式数据调度相关论文
  • Vue.js有哪些优点和缺点
  • Redis-实战篇-什么是缓存-添加redis缓存
  • 安卓应用开发学习:通过腾讯地图SDK实现定位功能
  • php的引用
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • AHK 中 = 和 == 等比较运算符的用法
  • Bootstrap JS插件Alert源码分析
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • JS数组方法汇总
  • laravel 用artisan创建自己的模板
  • MySQL数据库运维之数据恢复
  • oldjun 检测网站的经验
  • React Native移动开发实战-3-实现页面间的数据传递
  • SQLServer之创建数据库快照
  • ⭐ Unity 开发bug —— 打包后shader失效或者bug (我这里用Shader做两张图片的合并发现了问题)
  • Vue2.0 实现互斥
  • 番外篇1:在Windows环境下安装JDK
  • 基于 Babel 的 npm 包最小化设置
  • 简单实现一个textarea自适应高度
  • 如何使用 JavaScript 解析 URL
  • 使用common-codec进行md5加密
  • 用mpvue开发微信小程序
  • 优秀架构师必须掌握的架构思维
  • PostgreSQL之连接数修改
  • Unity3D - 异步加载游戏场景与异步加载游戏资源进度条 ...
  • ​ssh免密码登录设置及问题总结
  • !!java web学习笔记(一到五)
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • (2020)Java后端开发----(面试题和笔试题)
  • (6)设计一个TimeMap
  • (CVPRW,2024)可学习的提示:遥感领域小样本语义分割
  • (C语言)fread与fwrite详解
  • (附源码)php新闻发布平台 毕业设计 141646
  • (附源码)spring boot车辆管理系统 毕业设计 031034
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (五)IO流之ByteArrayInput/OutputStream
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)Scala的“=”符号简介
  • (转)清华学霸演讲稿:永远不要说你已经尽力了