概览
一个问题必须有重叠子问题和最优子结构(这个问题的最优解可以由其子问题的最优解有效的构造出来)才可以用动态规划解决。
解决方式:
- 递推:自底向上
- 递归自顶向下
贪心与动态规划的区别
贪心是局部最优解,在每一次选择中,选了就不后悔。动态规划则考虑所有的子问题,选择能笑到最后的。
1) 确定递推量。 这一步需要确定递推过程中要保留的历史信息数量和具体含义, 同时也会定下动态规划的维度;
2) 推导递推式。 根据确定的递推量, 得到如何利用存储的历史信息在有效时间(通常是常量或者线性时间)内得到当前的信息结果;
3) 计算初始条件。 有了递推式之后, 我们只需要计算初始条件, 就可以根据递推式得到我们想要的结果了。 通常初始条件都是比较简单的情况, 一般来说直接赋值即可;
4) (可选)考虑存储历史信息的空间维度。 这一步是基于对算法优化的考虑, 一般来说几维动态规划我们就用几维的存储空间是肯定可以实现的。 但是有时我们对于历史信息的要求不高, 比如这一步只需要用到上一步的历史信息, 而不需要更早的了, 那么我们可以只存储每一步的历史信息, 每步覆盖上一步的信息, 这样便可以少一维的存储空间, 从而优化算法的空间复杂度。 动态规划的时间复杂度是O((维度)×(每步获取当前值所用的时间复杂度))。 基本上按照上面的思路, 动态规划的题目都可以解决, 不过最难的一般是在确定递推量, 一个好的递推量可以使得动态规划的时间复杂度尽量低。
数塔问题
//数塔问题,将一些数字排成塔状,第n层有n个数字,现在从第一层走到第n层
//最后路径上最大的数字之和为多少
//用DFS做复杂度O(2^n),存在大量的重复子问题
//状态转移方程:dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+f[i][j]
#include<cstdio>
#include<algorithm>
using namespace std;
int f[1000][1000] = {0};
int dp[1000][1000] = {0};
int main(){
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i++){
for(int j = 1; j <= i;j++){
scanf("%d",&f[i][j]);
}
}
for(int j = 1;j <= n;j++){
dp[n][j] = f[n][j];
}
for(int i = n-1;i >=1;i--){
for(int j = 1;j <= i;j++){
dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+f[i][j];
}
}
printf("%d",dp[1][1]);
}
最大连续子列和
//最大连续子序列和
//dp[i]表示以i结尾的数列的最大值
//状态转移方程 dp[i] = max(arr[i],dp[i-1]+arr[i])
#include<cstdio>
#include<algorithm>
using namespace std;
int main(){
int arr[] = {-2,11,-4,13,-5,-2};
int dp[6];
dp[0] = arr[0];
for(int i = 1;i < 6;i++){
dp[i] = max(arr[i],dp[i-1]+arr[i]);
}
int maxSum = -1;
for(int i = 0;i < 6;i++){
if(dp[i] > maxSum){
maxSum = dp[i];
}
}
printf("%d",maxSum);
}
最长不下降子序列
//最长不下降子序列
//状态转移方程dp[i] = max(1,dp[j]+1),条件:arr[j] <= arr[i] && dp[j]+1>dp[i]
//时间复杂度O(n^2)
#include<cstdio>
#include<algorithm>
using namespace std;
int main(){
int n;
scanf("%d",&n);
int arr[1000],dp[1000];
for(int i = 0;i < n;i++){
scanf("%d",&arr[i]);
}
int ans = -1;
for(int i = 0;i < n;i++){
dp[i] = 1;
for(int j = 0;j < i;j++){
if(arr[j] <= arr[i] && dp[j]+1>dp[i]){
dp[i] = dp[j]+1;
}
}
ans = max(ans,dp[i]);
}
printf("%d",ans);
}
最长公共子序列
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
//最长公共子序列
//d[i][j]表示A的i号位和B的j号位之前的最长公共子序列
//d[i][j] = dp[i-1][j-1]+1;A[i] = b[i]
//d[i][j] = max(dp[i-1][j],dp[i][j-1]);A[i] != b[i]
int main() {
int dp[100][100];
string A = " sadstory";
string B = " adminsorry";
int lenA = A.size()-1;
int lenB = B.size()-1;
for(int i = 0;i <= lenA;i++){
dp[i][0] = 0;
}
for(int i = 0;i <= lenB;i++){
dp[0][i] = 0;
}
for(int i = 1;i <= lenA;i++){
for(int j = 1;j <= lenB;j++){
if(A[i] == B[j]){
dp[i][j] = dp[i-1][j-1]+1;
}else{
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
}
printf("%d",dp[lenA][lenB]);
}