题目大意
给定一个长度为N的序列数组,输出最长的严格递增(如果i < j,则a[i] < a[j])的子序列长度
解题思路
动态规划的经典问题之最长上升子序列问题。
1.状态集合:dp[i]
表示以第i
个数字结尾的最长的严格递增子序列的长度
2.状态转移方程式:假设L0~Lk
表示一个严格递增序列,如果x>Lk
,则可将x
接到原递增序列后,构成一个新的严格递增序列。
所以,如果j < i
且 a[j] < a[i]
,则可有两种可能情况
–>a.将a[i]
接到以a[j]
为结尾的严格递增子序列后,即dp[i] = dp[j] + 1
–>b.不做任何处理,当前子序列表示不变,即dp[i] = dp[i]
综上,对于以第i
个元素为结尾的严格递增子序列长度来说,dp[i] = max{a, b}, j = 0 ~ i
#include <iostream>
using namespace std;
const int MAXN = 1005;
int value[MAXN]; //存放输入序列
int dp[MAXN]; //dp[i]表示以第i个元素(value[i])为结尾的最长递增子序列长度
int main() {
int n;
while(cin >> n) {
for(int i = 0; i < n; i++) {
cin >> value[i];
}
int res = -1; //保存答案
//状态转移
for(int i = 0; i < n; i++) {
dp[i] = 1;
for(int j = 0; j < i; j++) {
if(value[j] < value[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
res = max(res, dp[i]); //更新最长长度大小
}
cout << res << endl;
}
return 0;
}
更优解法(时间复杂度由O(N^2) 优化到O(N * logN))
这种解法其实更像是一种贪心算法。先用如图中样例举个例子(手动遍历寻找最长递增子序列):
第一步,将1
加入子序列中[1]
第二步,将7
加入子序列中[1, 7]
第三步(重点),当移动到3
这个元素时,比7
要小,所以不能加入子序列尾部中,但此时如果子序列中的7
更改为3
,将会使当前子序列更优。通俗的解释,>7
的元素显然>3
,而后面的操作中,数字出现>3
的可能性肯定不低于>7
的可能性,所以序列改变为[1, 3]
更优。
所以贪心策略为:遍历到value[i]
时,先在子序列中寻找第一个大于value[i]
的元素值,并更改其为value[i]
以使当前子序列更优,如果未找到,则将value[i]
加入子序列尾部。而在寻找的过程中,因为子序列是严格递增的序列,所以可以采用二分查找。
第四步,[1,3,5]
第五步,[1,3,5,9]
第六步,[1,3,4,9]
第七步,[1,3,4,8]
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 1005;
const int INF = 0x3f;
int value[MAXN];
int sub[MAXN]; //存放严格递增子序列
int main() {
int n;
while(cin >> n) {
memset(sub, -INF, sizeof(sub));
for(int i = 0; i < n; i++) {
cin >> value[i];
}
int len = 0;
for(int i = 0; i < n; i++) {
//二分查找子序列种第一个大于于value[i]的值
int l = 0, r = len;
while(l < r) {
int mid = (l + r + 1) >> 1;
if(sub[mid] < value[i]) {
l = mid;
}else {
r = mid - 1;
}
}
sub[r + 1] = value[i];
len = max(len, r + 1);
}
cout << len << endl;
}
return 0;
}
调用C++库函数实现二分查找(lower_bound)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 1005;
const int INF = 0x3f;
int value[MAXN];
int sub[MAXN]; //存放严格递增子序列
int main() {
int n;
while(cin >> n) {
memset(sub, INF, sizeof(sub));
for(int i = 0; i < n; i++) {
cin >> value[i];
}
for(int i = 0; i < n; i++) {
*lower_bound(sub, sub + n, value[i]) = value[i]; //更新最长上升子序列
}
//需要注意memset是按照字节初始化,所以sub每一位元素的初始化值应为0x3f3f3f3f
int res = lower_bound(sub, sub + n, 0x3f3f3f3f) - sub;
cout << res << endl;
}
return 0;
}