最长递增子序列:
即给一个长度为N的数组,求得其中的递增的子序列的最大长度,具体问题为:
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N。
第二行包含 N 个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
对于N的规模较小的时候(<1000)时我们当然可以利用动态规划的来实现,代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int a[N];
//dp[i]即为以a[i]为尾数的最长递增子序列的数量
int dp[N];
int n;
int main() {
int ans = 0;
cin >> n;
for (int i = 1; i <= n; i++)cin >> a[i];
for (int i = 1; i <= n; i++) {
//每个数字都有自己本身的长度为1
dp[i] = 1;
//枚举在这之前的所有数
for (int j = 1; j < i; j++) {
//接着获取到以该数为尾数的这个子序列的长度,再+1即为当前dp[i]
if (a[j] < a[i])dp[i] = max(dp[i], dp[j] + 1);//当然得取他们的最大值
}
//最后获取以这些数字为尾数的递增子序列中最长的一个就是答案
if (dp[i] > ans)ans = dp[i];
}
cout << ans << endl;
return 0;
}
不难发现当前算法的复杂度为O(n^2),当N达到足够大时,该程序就会超时。
算法优化:
其实在分析该算法时不难发现,该算法在进行第二层循环时,是有冗余判断的,假设:
以4为尾数的最长子序列长度为1,(我们简单将其记作dp[4]=1,接下来同理),dp[4] = dp[5] =dp[6] = 1,那么当我判断dp[i]的时候,其实我只需要将i与4进行即可,因为如果i<4,那么一定会小于5和6,接下来就不必判断;如果i>4,因为4,5,6,的dp相同我也不需要去判断i与5,6的大小。
由此可得出优化的思路,可以将dp相同的尾数的最小值存储到一个数组里,这样当i再与其进行判断时,就无需进行如同上文一样的冗余判断。至此我们可以将算法的优化转换为存储dp相同且尾数最小的数组的实现。
数组的实现与分析:
我们可以创建一个数组q[N],其下标为dp的值,也就是递增子序列的长度,其对应的值也就是,当前长度下对应的最小尾数,我们可以发现该数组的值是递增的,证明如下:
假设存在q[i]<q[j],i>j;既然q[i]<q[j],则q[i]可以作为尾数为q[j]的子序列的前一个子序列,而q[i]的子序列的长度为i,所以j = i + 1 > i,与前式矛盾,证毕。
巧合的是,就最开始的代码而言,我们就是通过找到比a[j]要大的a[i]进行dp长度的增加的,所以我们在增加长度的同时也可以实现对数组的维护(也就是始终保证数组的值是相同长度下最小的),实现代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int a[N], q[N];
int n;
int main() {
cin >> n;
for (int i = 0; i < n; i++)cin >> a[i];
int len = 0;//记录q的长度,也就是记录递增子序列的长度
for (int i = 0; i < n; i++) {
//遍历a数组时,对于每一个数,要找到一个小于他的最大值,小于是因为我们要找递增子序列,最大值是因为我们要保证该子序列是最长的
int l = 0, r = len;
//二分法找a[i]对应的上述分析的位置
while (l < r) {
int mid = l + r + 1 >> 1;
if (q[mid] < a[i])l = mid;
else r = mid - 1;
}
//如果a[i]比q[len]里所有数都要大,r势必会指向q的末尾也就是len,此时就会更新len的长度
len = max(len, r + 1);
//维护q数组,如果a[i]是一个相同长度但尾数比q[i]小的数字的话,经过二分,会查找到q[i]的前一个位置:因为相同长度所以一定比q[i-1]大。然后通过r+1找到q[i],永尾数更小的a[i]更新q[i];
q[r + 1] = a[i];
}
//最后得到的长度即为答案
cout << len << endl;
return 0;
}
此时时间复杂度就转变为一层遍历循环(n)加一个二分(logn)O() .