这道题是最长上升子序列的问题。
最长递增子序列问题:在一列数中寻找一些数,这些数满足:任意两个数a[i]和a[j],若i<j,必有a[i]<a[j],这样最长的子序列称为最长递增子序列。
设dp[i]表示以i为结尾的最长递增子序列的长度,则状态转移方程为:
dp[i] = max{dp[j]+1}, 1<=j<i,a[j]<a[i].
常规思路是用动态规划,时间复杂度是n ^ 2,结果会超时。
思路:
input 1 7 3 5 9 4 8
dp[1]=1;
因为7>1dp[2]=dp[1]+1=2;
因为3>1, 3<7,dp[3]=dp[1]+1=2;
因为5>3, 5<7dp[4]=dp[3]+1=3;
因为9>5,dp[5]=dp[4]+1=4;
因为4<9, 4>3dp[6]=dp[3]+1=3;
因为8>4dp[7]=dp[6]+1=4;
dp的思路主要是当i>j时,若a[i]>a[j],就把i前面得到最大子序列长度找出来并加上1,就得到当前i最长的子序列长度
由上述分析可知,dp数组的值并不是严格递增的,所以最大解并不是dp[n],所以需要用一个循环来查找最大解。耗时较多。
先附上dp算法代码。
#include <iostream>
using namespace std;
const int MAX = 40001;
int dp[MAX], num[MAX];
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int n;
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%d", &num[i]);
memset(dp, 0, sizeof(dp)); //初始化dp数组
dp[1] = 1;
for(int i = 2; i <= n; i++) //每次求以第i个数为终点的最长子序列的长度
{
int temp = 0; //记录满足条件的,第i个数左边的上升子序列的最大长度
for(int j = 1; j < i; j++) //查看以第j个数为终点的最长上升子序列
{
if(num[i] > num[j])
if(temp < dp[j])
temp = dp[j];
}
dp[i] = temp + 1; //在得到前i-1个数的最长子序列后把长度+1
}
int nMAX = -1;
for(int i = 1; i <= n; i++) //寻找dp数组中记录长度最长的子序列的长度
if(nMAX < dp[i])
nMAX = dp[i];
printf("%d\n", nMAX);
}
return 0;
}
dp算法会超时,所以要换一种思路。用二分法+dp。
思路: dp[len]记录着长度为len的最小子序列的末尾数
5 3 4 8 6 7 2
dp[1]=5
5>3;此时长度为1的子序列的最小末尾数是3,更新dp[1]
dp[1]=3
3<4;此时长度+1,dp[2]=4
dp[2]=4
4<8;此时长度+1,dp[3]=8
dp[3]=8
8>6;此时长度为3的子序列的最小末尾数是6,更新dp[3]
dp[3]=6
6<7;此时长度+1
dp[4]=7
2<7;
dp[1]=2
最后得到的dp[4],4是最长上升子序列的长度,7是最长上升子序列的末尾数
从上面的分析中,我们可以得带一个解题思路:用dp[len]记录着长度为len的最小子序列的末尾数,注意dp中元素是单调递增的
#include<iostream>
using namespace std;
const int MAXN = 40002;
int a[MAXN],dp[MAXN];
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
int len = 1;
dp[1] = a[1];
for(int i = 2; i <= n; i++)
{
if(dp[len] < a[i])
{
dp[++len] = a[i];
continue;
}
int left = 1, right = len, mid; //二分法查找a[i]应该对应的dp[j]的位置,满足dp[j-1]<a[i]<dp[j]。
while (left <= right)
{
mid = (left + right) / 2;
if(dp[mid] < a[i])
left = mid + 1;
else
right = mid - 1;
}
dp[left] = a[i];
}
printf("%d\n", len);
}
return 0;
}