动态规划的特点:
需要在给定约束条件下优化某种指标时,动态规划很有用。
问题可分解为离散子问题时,可使用动态规划来解决。
每种动态规划解决方案都涉及网格。
单元格中的值通常就是你要优化的值。
每个单元格都是一个子问题,因此你需要考虑如何将问题分解为子问题。
没有放之四海皆准的计算动态规划解决方案的公式。
因此,划分子问题的方式不同,得到的网格就不同,递推公式自然也就不同。
本博客记录一些常见的动态规划问题,方便查询。
描述
有两个字符串(可能包含空格),请找出其中最长的公共连续子串,输出其长度。(长度在1000以内)
例如:
输入:acbcbcef 和abcbced
输出:5
划分1
递推公式为:
//二维数组遍历循环
if(A[i]==B[j]){
if(i==0||j==0);
dp[i][j]=1;
else
dp[i][j] = dp[i - 1][j - 1] + 1;
}
划分2
字符串 ADCDADB 和 ABDCDBABB
递推公式为:
//二维数组遍历循环 从第二行,第二列开始遍历,第一行和第一列不遍历。
if(A[i-1]==B[j-1]){//
dp[i][j] = dp[i - 1][j - 1] + 1;
}
例题1
题目标题:
计算两个字符串的最大公共字串的长度,字符不区分大小写
输入描述:
输入两个字符串
输出描述:
输出一个整数
示例
输入
asdfas werasdfaswer
输出
6
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
string str1,str2;
while(cin>>str1>>str2){
int len1=str1.size();
int len2=str2.size();
int result=0;
vector<vector<int>>dp(len1,vector<int>(len2,0));
for(int i=0;i<len1;i++){
for(int j=0;j<len2;j++){
if(str1[i]==str2[j]){
if(i==0||j==0){
dp[i][j]=1;
}
else{
dp[i][j]=dp[i-1][j-1]+1;
result=max(dp[i][j],result);
}
}
}
}
cout<<result<<endl;
}
return 0;
}
例题2
题目描述
查找两个字符串a,b中的最长公共子串。若有多个,输出在较短串中最先出现的那个。
输入描述:
输入两个字符串
输出描述:
返回重复出现的字符
示例
输入
abcdefghijklmnop
abcsafjklmnopqrstuvw
输出
jklmnop
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
string str1,str2;
while(cin>>str1>>str2){
if(str1.length()>str2.length())
swap(str1,str2);//若有多个,输出在较短串中最先出现的那个
int len1=str1.size();
int len2=str2.size();
int maxcom=0,pos=0;
vector<vector<int>>dp(len1+1,vector<int>(len2+1,0));
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(str1[i-1]==str2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
if(dp[i][j]>maxcom){
maxcom=dp[i][j];
pos=i;
}
}
}
}
cout<<str1.substr(pos-maxcom,maxcom)<<endl;
}
return 0;
}
01背包问题
例题1:
有N件物品和一个容量为V的背包。第i件物品的价值是C[i],重量是W[i]。求解将哪些物品装入背包可使价值总和最大。
输入描述:
输入第一行数 N V (1 <=N <=500) (1<= V <= 10000)
输入 N行 两个数字 代表 C W (1 <= C <= 50000, 1 <= W <=10000)
输出描述:
输出最大价值
示例1
输入
5 10
8 6
10 4
4 2
5 4
5 3
输出
19
//参考一楼的 做了一点小小的改动
#include<iostream>
#include<utility>
#include<vector>
using namespace std;
int main()
{
int N,V;
while(cin >> N >> V)
{
vector<pair<int,int>>goods;
for(int i = 0; i < N; ++i)
{
pair<int,int>tem;//第一个int是价值,第二个是重量
cin >> tem.first >> tem.second;
goods.push_back(tem);
}
vector<vector<int>> dp(N+1,vector<int>(V+1,0));//划分网格
for(int i = 1; i < N+1; ++i)//网格的行代表物品
{
for(int j = 1; j < V+1; ++j)//网格的列代表子背包容量
{
if( goods[i-1].second> j)//如果不下,那就等于上次的最优存储
dp[i][j] = dp[i-1][j];
else{//如果放得下 就取较大值
int tmp_best = goods[i-1].first + dp[i-1][j-goods[i-1].second];
dp[i][j] = max(tmp_best,dp[i-1][j]);
}
}
}
//返回最后一个元素就是最优的方案
cout << dp[N][V] << endl;
}
return 0;
}
例题2:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<pair<int, int>>task = { {20,300},{30,500},{50,620},{30,370},{50,400},{30,450},{40,380},{10,150} };
const int len1 = task.size();
int x;
while (cin >> x) {
int len2 = x / 10 ;
vector<vector<int>>dp(len1+1, vector<int>(len2+1, 0));//dp网格
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (j*10< task[i - 1].first) {
dp[i][j] = dp[i - 1][j];
}
else {
int pos =(j * 10 - task[i - 1].first)/ 10 ;
int tem = task[i-1].second+ dp[i - 1][pos];
dp[i][j] = max(tem, dp[i - 1][j]);
}
}
}
cout << dp[len1][len2] << endl;
}
return 0;
}
最大递增子序列
描述
给定数组arr,返回arr最长递增子序列长度。比如arr=[2,1,5,3,6,4,8,9,7],最长递增子序列为[1,3,4,8,9],所以返回5
思路
生成与arr长度相等的dp,dp[i]表示在必须以arr[i]这个数结尾的情况下,arr[0…i]中的最大递增子序列长度。
arr: 2 1 5 3 6 4 8 9 7
dp: 1 1 2 2 3 3 4 5 4
求解以i为结尾的最大递增子序列的长度,那么在arr[0]-arr[i-1]中,所有比arr小的数,都有可能成为该最大递增子序列的倒数第二个数,那么在这么多选择中,以哪个数结尾的最大递增子序列最大,那么就选择那个数作为倒数第二个数。
例题1
题目描述
Redraiment是走梅花桩的高手。Redraiment总是起点不限,从前到后,往高的桩子走,但走的步数最多,不知道为什么?你能替Redraiment研究他最多走的步数吗?
输入描述:
输入多行,先输入数组的个数,再输入相应个数的整数
输出描述:
输出结果
样例输入
6
2 5 1 5 4 5
样例输出
3
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
int cnt;
while(cin>>cnt){
int result=0;//记录结果
vector<int>arr(cnt,0);//数据数组,初始化
for(int i=0;i<cnt;i++){
cin>>arr[i];
}
vector<int>dp(cnt,1);//动态规划网格数组
for(int i=1;i<cnt;i++){
for(int j=0;j<=i-1;j++){
if(arr[i]>arr[j]){
dp[i]=max(dp[j]+1,dp[i]);//更新dp[i]
}
}
result=max(result,dp[i]);//记录最大值
}
cout<<result<<endl;
}
return 0;
}
最小编辑距离
描述
放个传送门link
例题1
Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由俄国科学家Levenshtein提出的,故又叫Levenshtein Distance。
Ex:
字符串A:abcdefg
字符串B: abcdef
通过增加或是删掉字符”g”的方式达到目的。这两种方案都需要一次操作。把这个操作所需要的次数定义为两个字符串的距离。
要求:
给定任意两个字符串,写出一个算法计算它们的编辑距离。
输入描述:
输入两个字符串
输出描述:
得到计算结果
示例1
输入
abcdefg
abcdef
输出
1
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
string s1,s2;
while(cin>>s1>>s2){
int len1=s1.size();
int len2=s2.size();
vector<vector<int>>dp(len1+1,vector<int>(len2+1,0));
for(int i=1;i<=len1;i++){
dp[i][0]=i;
}
for(int j=1;j<=len2;j++){
dp[0][j]=j;
}
dp[0][0]=0;
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(s1[i-1]==s2[j-1])
dp[i][j]=dp[i-1][j-1];
else{
int tem=min(dp[i-1][j],dp[i][j-1]);
dp[i][j]=min(dp[i-1][j-1],tem)+1;
}
}
}
cout<<dp[len1][len2]<<endl;
}
return 0;
}
其他可以用动态规划解决的问题
例题1
题目描述
请编写一个函数(允许增加子函数),计算n x m的棋盘格子(n为横向的格子数,m为竖向的格子数)沿着各自边缘线从左上角走到右下角,总共有多少种走法,要求不能走回头路,即:只能往右和往下走,不能往左和往上走。
输入描述:
输入两个正整数
输出描述:
返回结果
示例1
输入
2
2
输出
6
//先看这个问题的递归解决思路,了解递推公式后,更容易理解这个问题的动态规划方法
/*用递归来做,将右下角看做原点(0, 0),左上角看做坐标(m, n),下图所示:
从(m, n)—>(0, 0)就分两步走:
往右走一步:f(m, n - 1)—>(0, 0) 加上下走一步:f(m - 1, n)—>(0, 0)
注意:但凡是触碰到边界,也就是说f(x, 0)或者f(0,x)都只有一条直路可走了,这里的x是变量哈。
f(m, n) = f(m, n - 1) + f(m - 1, n)
按照这种思想,算法就很简单了,代码如下:*/
#include <iostream>
using namespace std;
int fun(int m,int n){
if(m==0||n==0)
return 1;
else
return fun(m-1,n)+fun(m,n-1);
}
int main(){
int m,n;
while(cin>>m>>n){
cout<<fun(m,n)<<endl;
}
return 0;
}
下面是动态规划解决这个问题的代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
int m, n;
while (cin >> m >> n) {
vector<vector<int> > dp(n + 1, vector<int>(m + 1, 0));
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
if(i==0||j==0)
dp[i][j]=1;
else
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
cout << dp[n][m] << endl;
}
return 0;
}