题目描述
给定N个数,求这N个数的最长上升子序列的长度。
样例输入
7
2 5 3 4 1 7 6
样例输出
4
思路:动态规划
一.惯性思维版(多此一举版)
1.状态与定义
状态1:面对第i个数字
状态2:上一次选择的数字位置 j
dp[ i ][ j ]: 面向第i位数字,以第j位数字结尾时的最长上升子序列长度2.状态转移方程
对于每个数字位置i可以选择或者不选择该数字:
选择i:
处于第i位数字并以k结尾时,必然从k前面的第j位作为序列结尾使得序列最长并+1即可,在第j位选择的基础上,序列尾部再接上第k位,序列长度+1
dp[i][k] = max(dp[i][j]+1) 0<i<=n , 0<j<i , 0<k<j 且 a[ j ]>a[ k ]
不选择i:
不选择时,默认长度为1即可
dp[i][j] = 1
3.初值
dp[ i ][ j ] = 1 0<i,j<=n
代码
/***
状态冗余版:
1.状态
状态1:面对第i个数字
状态2:上一次选择的数字位置
dp[i][j]:面对第i个数字,且序列以第j位为末尾时的最长序列长度
2.状态转移方程
选择i:
dp[i][j] = max(dp[i][k])+1 ,0<j<i,0<k<j 且a[j]>a[k]
(注意:这里容易出错,很容易觉得是 dp[i][j] = max(dp[i-1][j])+1,0<j<i即在面对i-1个数字决策时最长的一个,
实际上位置i与i-1在最长上升序列长度关系上没有任何关系
例如序列:3 4 3 ,i = 3 时若选择3,此时的最长长度为1即 dp[3][j] = 1,0<j<3
而不是max(dp[i-1][j]) = max(dp[3-1][1],dp[3-1][2])=2.产生这个误解的原因是没有理解dp[i][j]的含义,
dp[i][j]已经明确了是以j结尾的最长子序列长度,这个长度应该是max(上次序列长度)+1
因为本次长度只在上一个序列长度基础上+1,而不是在位置i-1 上 +1,正是因为i冗余导致了混淆)
不选i:
dp[i][j] = 1
3.初值
dp[i][j] = 1 1<=i,j<=n
**/
#include <bits/stdc++.h>
#define maxlen 1001
using namespace std;
int a[maxlen];
int dp[maxlen][maxlen];
int main() {
int n,maxn = 1;
cin>>n;
for(int i = 1 ;i<=n; i++){
cin>>a[i];
//赋予初值
for(int k = 1 ;k<=n ;k++)
dp[i][k] = 1;
}
//枚举每个数字
for(int i = 1 ;i<= n;i++){
//本次:枚举序列所有结尾的数字位置k
for(int k = 1 ;k<i ;k++)
//上一次:枚举上一次结尾数字的位置j,本次 = max(上一次)+1
for(int j = 1 ;j<k ;j++){
if(a[k]>a[j])
dp[i][k] = max(dp[i][j]+1,dp[i][k]);
maxn = max(dp[i][k],maxn);
}
}
cout<<maxn<<endl;
return 0;
}
二.正确的动态规划 O(n^2)
1.状态与定义
实际上,当前所在数字位置 i 对问题没有意义,因为序列的长度是在上一个选择的末尾再加一个新的数字,而不是在第i-1位数字上进行决策。(假设:上次最长序列的末尾是第j位,这次应该是在第j位的基础上新增当前的第i位数字,当然 j 可以等于i-1但不一定是i-1)
————————————————————————————————
状态1:以【第i位数字】结尾
dp[ i ]: 以第i位数字结尾时的最长上升子序列长度2.状态转移方程
本次序列最长长度 = 上次最长序列长度+1
dp[i] = max(dp[ j ])+1 , 0<j<i 且 a[ i ]>a[ j ]
3.初值
dp[ i ]= 1 ,0<i<=n
代码
#include <bits/stdc++.h>
#define maxlen 1001
using namespace std;
/***
动态规划1
1.定义数组含义
序列末尾数字位置
dp[i]:以第i个数字结尾时的最长上升子序列长度
2.状态转移方程
选当前:
dp[i] = max(dp[j])+1 0<j<i 且 a[i]>a[j]
不选当前:
dp[i] = 1
3.初值
dp[i] = 1
**/
/****
int a[maxlen];
int dp[maxlen];
int main() {
int n,maxn = 1;
cin>>n;
for(int i = 1 ;i<=n; i++){
cin>>a[i];
dp[i] = 1;
}
// a 1 2 3 1 1 1
//1: dp 1 1 1 1 1 1
//2: dp 1 2
//3: dp 1 2 3 1 1 1
for(int i = 2;i<=n; i++){
for(int j = 1 ;j<i ;j++){
if(a[i]>a[j])
dp[i] = max(dp[i],dp[j]+1);
}
maxn = max(maxn,dp[i]);
}
cout<<maxn<<endl;
return 0;
}
三、蜘蛛纸牌法(二分法) O(nlogn)
思路介绍:
类似蜘蛛纸牌的规则,对于每个数字,如果在堆中无法放置接龙,则新建一个堆放置。最后堆的数量就是最长子序列长度。(每个堆的放置规则为:新加入该堆的数字必须比原来的大,否则不能放置。由于堆头的数字有序,所以可以使用二分查找)