经典题目
以打家劫舍这个例子开始
其实这个题目的意思就是:给你一个数组,拿取不相邻的数组的值,求所有可能拿取的值中的最大值。
题目分析:
递归方式
就是自己调用自己,一种暴力枚举的方式。所谓暴力是没有经过任何的优化,枚举了所有的情况的方式。
//从后向前遍历做法
class Solution {
public:
int help(int index){
if(index==0)return nums[0];
if(index==1)return max(nums[0],nums[1]);
int use=help(index-2)+nums[index];//选了当前这个数
int ofuse=help(index-1);//不选当前这个数
return max(use,ofuse);
}
int rob(vector<int>& nums) {
if(nums.size()==0)return 0;
return help(nums.size()-1);
}
};
记忆化递归
开始真正的动态规划之前,先记录下记忆化搜索的这种方式。
所谓记忆化搜索,就是对于会重复遍历或者说重复计算的部分,直接用数组或者其他集合去存起来,这样当下一次如果计算到相同的值的时候,就可以免去计算直接拿出这个想要的值了。
//从后向前遍历做法
class Solution {
public:
int help(vector<int>nums,int index,vector<int>&vis){
if(index==0)return nums[0];
if(index==1)return max(nums[0],nums[1]);
if(vis[index]!=-1)return vis[index];//如果不是-1,说明递归计算过了
int use=help(nums,index-2,vis)+nums[index];//选了当前这个数
int ofuse=help(nums,index-1,vis);//不选当前这个数
int m=max(use,ofuse);
vis[index]=m;//记忆化存储
return m;
}
int rob(vector<int>& nums) {
vector<int>ans(nums.size(),-1);//初始值为-1,如果递归节点的值已经
if(nums.size()==0)return 0;
return help(nums,nums.size()-1,ans);
}
};
DP
自底向上。将大规模的问题转换为小规模的问题。基本上所有的动态规划都可以使用递归的式子来转换过来。
要素:
- 初始化:可以根据状态方程式来设置初始值。
- 状态转移方程式:想一下递归式子,要根据题意推导。
- 缓存中间结果:用数组或者其他集合来缓存。一般数组大小为原数组大小。
- 顺序问题:要找到依赖关系,当前的对象由谁推导出来的。
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size()==0)return 0;
if(nums.size()==1)return nums[0];
int dp[110];
dp[0]=nums[0];
dp[1]=max(nums[0],nums[1]);
for(int i=2;i<nums.size();i++){
dp[i]=max(dp[i-1],dp[i-2]+nums[i]);//可以状态压缩
}
return dp[nums.size()-1];
}
};
动态规划详解
介绍
拿到一个题目,如何知道这个题目能用动态规划?
状态是什么要如何判断?如何准确定义?
基本题型
线性DP
子序列和子串
首先是几个需要熟记的点:
- 子序列:子序列并不要求连续
- 子串:子串一定是原始字符串的连续子串
- 回文串:一个字符串,正着读和反着读是一样的。
入门题目:
经典题目:
背包
0-1背包问题:
问题描述:现在有N件物品,一个背包容量为M,每个物品的体积为v,每个物品的价值是w,现在要求出这N件物品的最大价值。
状态方程分析:像这种先确定是使用一维还是二维,首先物品肯定要算上,另外还有限制条件就是物体的体积,同时考虑问题要求出什么,现在是求出这总共的N件物品的最大价值,可以先考虑第一件物品,第一和第二…同时标注出前i件物品使用多少体积,因此得到dp定义:dp[i][j]:前i件物品在使用体积不超过j的情况下的最大价值。
代码实现:
#include<iostream>
using namespace std;
const int N = 1010;
int n, m;//n物品个数,m是背包的容量
int f[N][N];//最大价值
int v[N], w[N];//V是每个物品的体积,w是每个物品的价值
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
}
//初始化就是0件物品最大价值肯定是0
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
//如果不取出这第i件物品,那体积无变化
f[i][j] = f[i - 1][j];
//如果取出这第i件数物品放入背包,首先要保证前i件物品使用的体积大于这第i件物品的体积,不然根本放不下去。
if (j>=v[i]) {
//拿出第i件物品放入背包,那现在的就可以正常的由前面一件物品推出来,
//同时,同一个i要找出满足最大价值的j,遍历物品结束之后找到n件物品在背包容量m的约束下最大的价值
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
}
int res = 0;
//已经算出了不同的i最合适的j了,直接固定n在0-m里面找最合适的i就行了,其实这里直接写f[n][m]就是可以的
for (int i = 0; i <= m; i++) {
res=max(res,f[n][i]);
}
cout << res << endl;
return 0;
}
状态压缩
#include<iostream>
using namespace std;
const int N = 1010;
int n, m;//n物品个数,m是背包的容量
int f[N];//最大价值
int v[N], w[N];//V是每个物品的体积,w是每个物品的价值
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
}
//初始化就是0件物品最大价值肯定是0
for (int i = 1; i <= n; i++) {//物品个数
for (int j = m; j >= v[i]; j--) {//背包体积变化
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
}
//已经算出了不同的i最合适的j了,直接固定n在0-m里面找最合适的i就行了,其实这里直接写f[n]就是可以的
cout << f[m]<< endl;
return 0;
}
遍历顺序:关于内循环遍历背包的容量是从后向前遍历的原因是:如果从前向后遍历,那更新的dp值就会覆盖以前的dp值,当下一步要利用以前的状态去推导的时候就会发生错误,而从后向前更新,不会覆盖以前的dp值。
经典题目:
- List item
完全背包问题:
打家劫舍
股票
- List item
待补: