常见dp总结

----------------01背包(在约束条件下,一个物品选一件,求达到的目的最大/最小,从右向左)------------------------

一共有n件物品,第i(i从0开始)件物品的重量为w[i],价值为v[i]。在总重量不超过背包承载上限W的情况下,能够装入背包的最大价值是多少?
暴力:复杂度达到O(2^n)
注意到:前i件物品放入背包所获得最大价值可由前前i-1件物品放入背包所获得最大价值得到,因此可以用DP来解决

DP(时间复杂度:O(n×W), 空间复杂度:O(W))
目的:求最大价值
变量:物品 背包限重
因此定义dp[i][j] 代表限重为j,放前i个物品,能获得的最大价值
不放第i个物品: dp[i][j]=dp[i-1][j]
放第i个物品(j >=w[i]):dp[i][j]=dp[i-1][j-w[i]]+v[i]
综合:状态转移方程为两者最大值:dp[i][j]=max(dp[i-1][j], dp[i-1][j-w[i]]+v[i])
使用滚动数组进行空间优化后:
dp[i]=max(dp[i],dp[j-w[i]+v[i]);
初始化:全0,表示什么都不放,在背包限重为任何值的情况下,能够获得的最大价值均是0.
注意:
在二维数组中使用的是前一行上面和前一行上面左边的位置,为了防止[0,j-1]的值被覆盖,空间优化后,只能从后面进行遍历

//当是混乱时
vector<int> dp(W + 1);
for (int i = 0; i < n; ++i) {
	vector<int> pre(dp); 
	for (int j = 0; j <= W; ++j) {
		if (j >= w[i]) dp[j] = max(pre[j], pre[j - w[i]] + v[i]);
	}
}
return dp[W];
//一维
vector<int> dp(W + 1);
for (int i = 0; i < n; ++i) {
	for(int j = W; j >= w[i]; --j) {
		dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
	}
}
return dp[W];

----------------完全背包(在约束条件下,一个物品选n件,求达到的目的最大/最小,从左向右)------------------------
完全背包与01背包不同就是每种物品可以有无限多个:一共有n种物品,每种物品有无限多个,第i(i从0开始)种物品的重量为w[i],价值为v[i]。在总重量不超过背包承载上限W的情况下,能够装入背包的最大价值是多少?
与上面转移方程不同的是:
装入第i种物品,此时和01背包不太一样,因为每种物品有无限个,所以此时不应该转移到dp[i−1][j−w[i]]而应该转移到dp[i][j−w[i]],即装入第i种商品后还可以再继续装入第种商品。

状态转移方程: dp[i][j]=max(dp[i-1][j], dp[i][j-w[i]] + v[i]);
边界是一样的。
使用滚动数组进行空间优化后:
由于dp[i][j]使用的是上面和左边的值,所以应该把[0,j-1]覆盖掉,只能从前面进行遍历

//一维
vector<int> dp(W + 1);
for (int i = 0; i < n; ++i) {
	for (int j = w[i]; j <= W; ++j) {
		dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
	}
}
return dp[W];

题型
1:每一步有多种选择,问到达最后一步有多少种选择:
leetcode70.爬楼梯(简单)
https://blog.csdn.net/zhangjiaji111/article/details/121801918
leetcode62. 不同路径(中等)
https://blog.csdn.net/zhangjiaji111/article/details/121802934
leetcode63. 不同路径 II(中等)
https://blog.csdn.net/zhangjiaji111/article/details/124578905
leetcode120. 三角形最小路径和(中等)
https://blog.csdn.net/zhangjiaji111/article/details/124595666

2:累计,当前完了什么状态(题目限制)
leetcode198.打家劫舍(中等)
https://blog.csdn.net/zhangjiaji111/article/details/121110704
leetcode213. 打家劫舍 II(中等)(加了环)
https://blog.csdn.net/zhangjiaji111/article/details/124615422
leetcode121 122 309 714 123 188股票买卖问题
https://blog.csdn.net/zhangjiaji111/article/details/120779081
leetcode1770. 执行乘法运算的最大分数(中等)
https://blog.csdn.net/zhangjiaji111/article/details/124498775

3:(bfs->dp) 向右和下访问时,[i][j]的状态可以由[i-1][j]和[i][j-1]得到
剑指offer13.机器人的运动范围(中等)
https://blog.csdn.net/zhangjiaji111/article/details/120710116

5.0-1背包
leetcode416.分割等和子集(中等)
https://blog.csdn.net/zhangjiaji111/article/details/122225627
leetcode494.目标和(中等)
https://blog.csdn.net/zhangjiaji111/article/details/122262922
leetcode1049. 最后一块石头的重量 II(中等)
https://blog.csdn.net/zhangjiaji111/article/details/124480389
leetcode474. 一和零(中等)
https://blog.csdn.net/zhangjiaji111/article/details/124460920
leetcode956. 最高的广告牌(困难,变种题)
https://blog.csdn.net/zhangjiaji111/article/details/124536832
leetcode1751. 最多可以参加的会议数目 II(困难)
leetcode279. 完全平方数
leetcode1155. 掷骰子的N种方法

6.完全背包
leetcode322.零钱兑换(中等)
https://blog.csdn.net/zhangjiaji111/article/details/121524885
leetcode279.完全平方数(中等)
https://blog.csdn.net/zhangjiaji111/article/details/121972041
leetcode518. 零钱兑换 II
leetcode377. 组合总和 Ⅳ
leetcode638. 大礼包
leetcode1449. 数位成本和为目标值的最大数字

7.找子问题
leetcode139.单词拆分(中等)
https://blog.csdn.net/zhangjiaji111/article/details/121956114
leetcode91.解码方法(中等,代码细节)
https://blog.csdn.net/zhangjiaji111/article/details/123263247
leetcode152.乘积最大子数组(中等)
https://blog.csdn.net/zhangjiaji111/article/details/121090325
leetcode343. 整数拆分(中等)
https://blog.csdn.net/zhangjiaji111/article/details/124637443

8.最大子数和相关题目(关键字眼:子数组+个数差值)

例题(1)
在这里插入图片描述
例题(2)
在这里插入图片描述
7.区间dp(经典石子合并)
(1) 有N堆顺序放的石子,现要将所有石子合并成一堆,规定如下:每次任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的最少花费。
解决:贪心,相当于哈夫曼树的构建,哈夫曼树的特点是:带权路径长度最短==非叶子节点的和。在哈夫曼树中权值较小的结点离根节点较远,贡献的非叶子节点更多,所以每次合并时选择最小的合并即可。合并的总花费就是非叶子节点的和,即带权路径长度。
(2) 跟(1)不同的是,每次只能合并相邻的两堆
解决:区间dp
dp[i][j]表示合合并区间[i, j]的最少花费
转移方程:dp[i][j]=min(dp[i][j], dp[i][k] + dp[k+1][j] + +sums[i…j] ) k的取值为k到j-1
边界:dp[i][i] = 0;

int f(vector<int> &nums) {
	int n = nums.size();
	vector<int> sums(nums);
	for (int i = 0; i < n; ++i) {
		if (i) sums[i] += sums[i - 1];
	}
	vector<vector<int>> dp(n, vector<int>(n, INT_MAX));
	for (int i = 0; i < n; ++i) dp[i][i] = 0;
	for (int len = 2; len <= n; ++len) {
		for (int i = 0; i <= n - len; ++i) {
			//dp[i][i+len-1]
			for (int k = i; k < i + len - 1; ++k) {
				int sum = sums[i + len - 1] - (i > 0 ? sums[i - 1] : 0);
				dp[i][i + len - 1] = min(dp[i][i + len - 1], dp[i][k] + dp[k + 1][i + len - 1] + sum);
				//如果是最大花费的话,改成max即可
			}
		}
	}
	return dp[0][n - 1];
}

易错点:状态转移方程里面是 ‘+’

(3) 与(1)不同的是,石子围成一个环形,每次只能合并相邻的两堆,问合成一堆的最少/最大花费。
解决:区间dp。在(2)的基础上做就可以,唯一不同的是,将环形拷贝两份转化为线形ans为dp[0][n-1] dp[1][n] … dp[n][2n-1]中的最小值或者最大值

int f(vector<int> &nums) {
	int n = nums.size();
	vector<int> sums(nums);
	for (int i = 0; i < n; ++i) {
		if (i) sums[i] += sums[i - 1];
	}
	vector<vector<int>> dp(n, vector<int>(n, INT_MAX));
	for (int i = 0; i < n; ++i) dp[i][i] = 0;
	for (int len = 2; len <= n / 2; ++len) { //最多合并n/2堆就好了
		for (int i = 0; i <= n - len; ++i) {
			for (int k = i; k < i + len - 1; ++k) {
				int sum = sums[i + len - 1] - (i > 0 ? sums[i - 1] : 0);
				dp[i][i + len - 1] = min(dp[i][i + len - 1], dp[i][k] + dp[k + 1][i + len - 1] + sum);
				//如果是最大花费的话,改成max即可
			}
		}
	}
	int ans = INT_MAX;
	for (int i = 0; i < n / 2; ++i) { //  
		ans = min(ans, dp[i][i + n / 2 - 1]);
	}
	return ans;
}

易错点
1:最多合并的长度为n/2即可。
2:最后遍历的时候起点是0->n/2-1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值