DP:简单来说就是寻找最终状态与前面状态的关系式,将最初的状态叠加到最后状态,从而得出答案
下面是一些经典DP题,建议先搞懂背包问题中的各种优化,可见我另一篇博客:背包问题
题目目录
斐波那契序列
题目:后一个数等于前两个数的和,1,1,2,3,5,8,13,21······
说明:可以说是最经典的DP题了,f(n)=f(n-1)+f(n-2);
long long f(int n)
{
if(n==1||n==2) return 1;
return f(n-1)+f(n-2);
}
cout<<f(n)<<endl;
进阶:这种递归写法时间复杂度很大,这是因为每次都要重新计算f(n),可以优化成有记忆性的递归,即每次都记录下已经算过的f(n)
long long f[1005];
long long dp(int n)
{
if(f[n]!=0) return f[n];
if(n==1||n==2) return 1;
f[n]=dp(n-1)+dp(n-2);
return f[n];
}
cout<<dp(n)<<endl;
三角形数
题目
输入n行数,第n行有n个数,从上往下走最长的路径,注意给的数有可能是负数
输入
4
1
2 4
15 5 8
7 6 9 15
输出28
题目链接:数字三角形
思路: 从下往上递最优的状态
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
#define M 1005
int n;
int a[M][M];
int dp[M]; //一维优化
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
for(int i=n;i>=1;i--)
for(int j=1;j<=i;j++)
dp[j]=max(dp[j],dp[j+1])+a[i][j];
cout<<dp[1]<<endl;
return 0;
}
进阶:1.求最小路径
思路:将max改为min即可
for(int i=n;i>=1;i--)
for(int j=1;j<=i;j++)
dp[j]=min(dp[j],dp[j+1])+a[i][j];
cout<<dp[1]<<endl;
2.从上往下实现(将状态递到最后一行,MAX筛选最优的)
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
#define M 1005
int n,MAX=0;
int a[M][M];
int dp[M];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
memset(dp,-inf,sizeof(dp));
dp[1]=0;
for(int i=1;i<=n;i++)
for(int j=i;j>=1;j--)
dp[j]=max(dp[j],dp[j-1])+a[i][j];
for(int j=1;j<=n;j++)
MAX=max(dp[j],MAX);
cout<<MAX<<endl;
return 0;
}
最小路径和
题目:给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
输入
3 4
1 3 1 2
1 5 10 5
2 13 3 20
输出27
解释: 因为路径 1→3→1→2→5→20 的总和最小。
题目链接:最小路径和
思路:(n,m)等于左边的(n-1,m)和上面的(n,m-1)中最小的+a[n][m]
AC代码:
#include<iostream>
#include<algorithm>
using namespace std;
#define M 1005
int n,m;
int a[M][M];
int dp[M][M];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+a[i][j];
cout<<dp[n][m]<<endl;
return 0;
}
进阶:一维优化
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) //要用左边dp和上边dp的来判断该dp,而左边的dp是同行,故正序
dp[j]=min(dp[j],dp[j-1])+a[i][j];
cout<<dp[m]<<endl;
将字符串翻转到单调递增
题目
如果一个由 ‘0’ 和 ‘1’ 组成的字符串,是以一些 ‘0’(可能没有 ‘0’)后面跟着一些 ‘1’(也可能没有 ‘1’)的形式组成的,那么该字符串是单调递增的。
我们给出一个由字符 ‘0’ 和 ‘1’ 组成的字符串 S,我们可以将任何 ‘0’ 翻转为 ‘1’ 或者将 ‘1’ 翻转为 ‘0’。
返回使 S 单调递增的最小翻转次数。
样例 1:
输入:“01110”
输出:1
解释:翻转最后一位得到 01111.
样例 2:
输入:“010110”
输出:2
解释:翻转得到 011111,或者是 000111。
样例 3:
输入:“00011100”
输出:2
解释:翻转得到 00011111。
题目链接:将字符串翻转到单调递增
思路:考虑加新的数与不加的关系,加的是1一定不变,加的是0就考虑前面最优是全部翻转成0,或最后有1两种情况
LeetCode代码:
class Solution {
public:
int minFlipsMonoIncr(string s) {
int n=s.size();
int dp[20005];
int sum[20005]={0};
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+s[i-1]-'0'; ///前缀和记录前面1的个数
for(int i=1;i<=n;i++){
if(s[i-1]=='1') dp[i]=dp[i-1];
else dp[i]=min(sum[i],dp[i-1]+1); ///要么全部翻转成0(sum[1]),要么最后有1(dp[i-1]+1)
}
return dp[n];
}
};
AC代码:
#include <iostream>
using namespace std;
#define M 20005
int dp[M];
string s;
int main()
{
cin>>s;
int n=s.size();
int sum[20005]={0};
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+s[i-1]-'0'; ///前缀和记录前面1的个数
for(int i=1;i<=n;i++){
if(s[i-1]=='1') dp[i]=dp[i-1];
else dp[i]=min(sum[i],dp[i-1]+1); ///要么全部翻转成0(sum[1]),要么最后有1(dp[i-1]+1)
}
cout<<dp[n]<<endl;
return 0;
}
进阶:使用背包问题的优化思想,将上面的代码从一维优化到单变量
核心代码:
for(int i=1;i<=n;i++)
if(s[i-1]=='0') dp=min(sum[i],dp+1);
cout<<dp<<endl;
最长匹配括号
题目:
输入一个只包含" ( " " ) " " [ " " ] " 的字符串,输出可以成功匹配的括号数量,注意中间有其他括号也可以进行匹配(如果必须中间没有其他括号才能匹配就是用栈来做的)
输入
((()))
()()()
([]])
)[)(
([][][)
end输出
6
6
4
0
6
题目链接:最长匹配括号
思路:枚举左右区间下标,区间间距逐渐变大,dp[i][j]是区间 i到j 匹配的括号数, 所以dp[i][j]至少=中间所有拆成两部分的小区间之和的最大值
核心DP:如果匹配成功,dp[左边界][右边界]=dp[左边界+1][右边界-1]+2;
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<string>
using namespace std;
#define inf 0x3f3f3f3f;
#define M 1005
string s;
int dp[M][M];
int main()
{
while(cin>>s&&s!="end") //s下标是从0到len-1
{
memset(dp,0,sizeof(dp));
int len=s.size();
for(int i=1;i<len;i++){
for(int j=0;j+i<len;j++){
int k=j+i; ///j是左下标,k是右下标,i是左右下标的间距
if((s[j]=='('&&s[k]==')')||(s[j]=='['&&s[k]==']'))
dp[j][k]=dp[j+1][k-1]+2; //核心DP
for(int o=j; o<k; o++)
dp[j][k]=max(dp[j][k],dp[j][o]+dp[o+1][k]);
}
}
cout<<dp[0][len-1]<<endl;
}
}