- 什么是动态规划?
- 动态规划的核心思想
- 动态规划的解题套路
- 一个例子走进动态规划
什么是动态规划?
动态规划(英语:Dynamic programming,简称 DP),通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。简单来说,动态规划其实就是,给定一个问题,我们把它拆成一个个子问题,直到子问题可以直接解决。然后呢,把子问题答案保存起来,以减少重复计算。再根据子问题答案反推,得出原问题解的一种方法(类似于递推)。
动态规划核心思想
动态规划最核心的思想,就在于拆分子问题,记住过往,减少重复计算。
动态规划的解题套路
什么样的问题可以考虑使用动态规划解决呢?
★ 如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。
比如一些求最值的场景,如最长递增子序列、最小编辑距离、背包问题、凑零钱问题等等,都是动态规划的经典应用场景。
动态规划的解题思路
动态规划的核心思想就是拆分子问题,记住过往,减少重复计算。 并且动态规划一般都是自底向上的,因此我总结了一下我做动态规划的思路:
- 穷举分析
- 确定边界
- 找出规律,确定最优子结构
- 写出状态转移方程
例如:最长上升子序列
题目描述
这是一个简单的动规板子题。
给出一个由n(n≤5000) 个不超过 10^6 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。
最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。
输入格式
第一行,一个整数 n,表示序列长度。
第二行有 n 个整数,表示这个序列。
输出格式
一个整数表示答案。
输入 #1复制
6 1 2 4 1 3 4
输出 #1复制
4
1.穷举分析
寻找数组a[i]的最长递增子序列长度跟什么有关系,显然a【i】的长度与比i下标小的数有关。我们用dp数组存放最长递增子序列长度,由案例分析,当n=1时,数组只有1,那么dp【1】=1,当n=2时,数组为1 2,那么dp【2】=2,当n=3时,dp【3】=3,当n=4时,dp【4】=1,当n=5时,dp【5】=3,当n=6时,dp【6】=4。
分析找规律,拆分子问题
通过上面分析,我们可以发现一个规律:如果新加入一个元素a[i],最长递增子序列要么是以a[i]结尾的递增子序列,要么就是a[j]的最长递增子序列在加上元素a【i】(即加1),其中j就是下标比i小的下标,并且a【j】也要比a【i】小。
最简单的边界情况
当a数组只有一个元素时,最长递增子序列的长度dp(1)=1,当a数组有两个元素时,dp(2) =2或者1, 因此边界就是dp(1)=1。
确定最优子结构
从穷举分析,我们可以得出,以下的最优结构:
dp(i) =max(dp(j))+1,存在j属于区间[0,i-1],并且num[i]>num[j]。
max(dp(j)) 就是最优子结构。
状态转移方程
数组a[i]的最长递增子序列就是:
最长递增子序列 =max(dp[i])
实现代码:
#include<bits/stdc++.h>
using namespace std;
int a[5010],dp[5010],maxn=0;
int main(){
ios::sync_with_stdio(false);
int n;
cin >> n;
for(int i=1;i<=n;++i)
{
cin >> a[i];
}
for(int i=1;i<=n;++i)
{
dp[i]=1;//边界都为1
for(int j=1;j<i;++j){//j必须比i小
if(a[j]<a[i]){//当a【j】比a【i】小时,dp【i】最大递增子序列就是dp【j】+1
dp[i]=max(dp[i],dp[j]+1);//比a【i】小的数有多个,我们只要最大的序列,因此需要用max函数和自身比较,求出最大子序列
}
maxn=max(maxn,dp[i]);//另开一个变量存储最大子序列长度
}
}
cout << maxn << endl;
return 0;
}