今天也是为了cc,努力奋斗的一天ヾ(≧▽≦*)o
问题描述
暴力解决方案
时间复杂度为
O
(
2
n
)
O(2^n)
O(2n)
动态规划解决方案
事实上,上述的枚举过程包含了大量重复计算。通过学习下面的动态规划解法应该就会很容易理解为什么会有重复计算产生了。
步骤1——数据结构定义
令dp[i]
表示以A[i]
结尾的最长不下降子序列长度。
这样对A[i]
来说就会有两种可能:
下面是一个更有趣的例子:
步骤2——状态转移方程
通过步骤1的分析,可以得到下面的状态转移方程:
dp[i] = max{1,dp[j]+1}(j=1,2,..,i-1&&A[j]<=A[i])
上面的状态转移方程隐含了边界:dp[i]=1(1≤i≤n)
。整体复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
到此就可以想象究竟重复计算出现在哪里了:每次碰到子问题“以A[i]
结尾的最长不下降子序列”时,都去重新遍历所有子序列,而不是直接记录这个子问题的结果。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 100;
int A[N],dp[N];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&A[i]);
}
int ans = -1; //记录最大的dp[i]
for(int i=1;i <= n;++i){
dp[i] = 1; //边界初始条件(即先假设每个元素自成一个子序列)
for(int j=1;j<i;++j){
if(A[i] >= A[j] && (dp[j]+1>dp[i])){
dp[i] = dp[j] + 1; //状态转移方程,用以更新dp[i]
}
}
ans = max(ans,dp[i]);
}
printf("%d",ans);
return 0;
}
题型训练
- 拦截导弹
- 合唱队形
比较抽象一定的类LIS问题,需要进行一些排序操作,确保遍历时,子问题一定在原问题前面: - LeetCode 1048. Longest String Chain
- LeetCode 435. Non-overlapping Intervals
- LeetCode 674. Longest Continuous Increasing Subsequence