问题
最长递增子序列
•子序列:是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
•给你一个整数数组 nums ,找到其中最长严格递增子序列的长度,并输出该子序列。
•输入:nums = [10,9,2,5,3,7,101,18]
•输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
测试用例:
•(1)输入:nums = [10,9,2,5,3,7,101,18]
•输出:子序列: [2,3,7,101],长度: 4 。
(2)输入:nums = [0,1,0,3,2,3]
输出:子序列: [0,1,2,3],长度: 4 。
(3)输入:nums = [2,2,2,2,2,2]
输出:子序列:[ 2],长度:1
请先在工程目录里建立input.txt文档,因为程序使用txt文档输入来接收数据
输入:
8
10 9 2 5 3 7 101 18
输出:
代码
#include<iostream>
#include<fstream>
#define maxsize 10000 //所容纳数组的最大长度
using namespace std;
int dp[maxsize];
int q = 10000;
int maxLen = 0;
int endIndex[maxsize] = { 0 }; 记下该数字前一个较小的数的下标的数组
int nums[maxsize];
int index = 0; //选择字典序最小的数字的下标
int main()
{
void print(int index, int maxLength);
int fun(int nums[], int size);
int i;
int size;
ifstream fp_read("input.txt");
ofstream fp_write("output.txt");
fp_read >> size;
for (i = 0; i < size; i++)
fp_read>> nums[i];
int lislength = fun(nums, size);
fp_write << lislength << endl;
cout << "最长递增子序列长度为: " << lislength << endl;
cout << "最长递增子序列为: "; //先输出最长的长度,再输出递增子序列
print(index, maxLen);
cout << endl;
return 0;
}
int fun(int nums[], int size) //计算最长递增子序列长度
{
int i, j;
for (i = 0; i < size; i++)
{
dp[i] = 1; // 每个元素本身构成一个子序列,长度为1
for (j = 0; j < i; j++)
if (nums[j] < nums[i] && dp[i] < dp[j] + 1)
{
dp[i] = dp[j] + 1;
endIndex[i] = j; //记下该数字前一个较小的数的下标,
}
if (dp[i] > maxLen)
maxLen = dp[i];
}
for (i = 0; i < size; i++)
if (dp[i] == maxLen && nums[i] < q)
{
q = nums[i];
index = i;
}
return maxLen;
}
void print(int index, int maxLength) //递归打印最长递增子序列
{
if (maxLength <= 0)
return;
print(endIndex[index], --maxLength); //根据最长递增子序列长度来决定递归次数,每次递归减一
cout << nums[index] << ' ';
}
代码分析
1.递归方程为dp[i]=max(dp[j])+1,其中0≤j<i且num[j]<num[i],
dp[i]这个数组代表以nums[i]结尾的数的子系列的长度。
关键代码如下:
for (i = 0; i < size; i++)
{
dp[i] = 1; // 每个元素本身构成一个子序列,长度为1
for (j = 0; j < i; j++)
if (nums[j] < nums[i] && dp[i] < dp[j] + 1)
{
dp[i] = dp[j] + 1;
endIndex[i] = j; //记下该数字前一个较小的数的下标,
}
if (dp[i] > maxLen)
maxLen = dp[i];
}
当nums[j]小于nums[i]的时候,还要继续判断当前dp[i]的值是否比dp[j]+1的值小,如果小于就更新dp[i]的值。这个递推具有最优子结构性质,从前往后地推,只要前面的数确定了它的最长子序列的长度,后面的数就能一一推出来。最终的结果就是将每一个数的dp[i]比较选出最大的那个dp[i]。
计算出最长的长度,还要将这个子序列输出,这里我添加一个数组index代表所标记的该数以该数结尾的数,它的前一个较小的数下标j记录为endIndex[i] = j。
2.
for (i = 0; i < size; i++)
if (dp[i] == maxLen && nums[i] < q)
{
q = nums[i];
index = i;
}
对于上面的代码
由于当最长的系列递增的长度相同的时候,可能有多种情况,这里我只输出字典序最小的,只需判断所有dp[i]最大的值的数,它的nums[i]数值是最小的,记录它的下标index=i这个数即可。
3.
void print(int index, int maxLength) //递归打印最长递增子序列
{
if (maxLength <= 0)
return;
print(endIndex[index], --maxLength); //根据最长递增子序列长度来决定递归次数,每次递归减一
cout << nums[index] << ' ';
}
递归的打印这里,一开始函数中确定了最大的那个dp[i]所对应的数的下标,将它输入到index参数里面,然后再把最长的子序列的该长度maxlength输入到参数里面,然后进行定位输出,每次通过endIndex数组确定它前一个较小的数的下标。当maxLength小于等于0的时候就返回,这样就可以一次输出最终的子序列个数。
4.时间复杂度
对于输入大小为size的数组,存在两层嵌套for循环。外层循环用于遍历原始数组,内层循环用于比较前面的元素与当前元素的大小关系。
因此,该算法的时间复杂度为O(n^2)。