算法题二:最长递增子序列
一、问题描述、最长递增子序列
输入一个无序的整数数组,请你找到其中最长递增子序列的长度。函数签名如下:
int lengthofLTS(int[] nums);
例如,输入nums[10,9,2,5,3,7,101,18],其中最长递增子序列是[2,3,7,101],长度为4。所以算法的输出应该为4.
在此,本文中对该问题进行修改,不仅要求得到其最长递增子序列的长度,还要得到该序列。序列以数组下标形式输出。
则上例中的输出应为{2,4,5,6},长度为4。
注:下文示例所用数组都使用nums[10,9,2,5,3,7,101,18];
二、解题思路
1、示例分析
1)nums[0]=10,nums[1]=9;故nums的前两个数所组成序列的最长递增子序列为{0}或{1},其长度均为1。
2)nums[0]=10,nums[1]=9,nums[2]=2;故nums的前三个数所组成序列的最长递增子序列为{0}或{1}或{2},其长度均为1。
3)nums[0]=10,nums[1]=9,nums[2]=2,nums[3]=5;故nums的前四个数所组成序列的最长递增子序列为{2,3},其长度为2。
4)以此推出该序列的最长递增子序列为{2,4,5,6},长度为4。
2、方法总结
可以看出该方法与数学归纳法相似,即假设k<n时条件成立,根据该假设证明k=n时条件依然让成立。类似的,可以设计动态规划算法,建立一个dp数组,假设dp[0…i-1]都已经被算出,根据结果求出dp[i]。
3、dp数组的定义
根据题目描述可以得知算法最后需要输出最长递增子序列和该序列的长度。长度使用int型变量保存,子序列可以使用int型数组来记录,鉴于子序列的不确定性(子序列是未知的,其长度自然也是未知的,无法得知数组大小),故使用vector(可变大小数组)来记录递增子序列,当然也可使用deque(双端队列)。本题中使用vector。
dp[i]表示以nums[i]这个数结尾的最长递增子序列,包含序列长度和序列情况。
dp数组的设计如下:
struct DP
{
int len=1;// 子序列长度(默认为1)
//deque<int> sequence;//记录递增序列
vector<int> sequence;//记录递增序列
};
以nums[i]这个数结尾的最长递增子序列包含它自己,因此 len默认为1。并且根据上面对dp数组的定义可以知道只要找到dp数组中len最大的那一项就可以得到答案。
注:当有多个最长递增子序列时,sequence只记录其中一个。
三、算法设计
1、对dp数组的初始处理
在声明dp数组后,需要将各个元素的下标输入到相应的sequence中,来表示当前的递增序列只有其自身。
DP *dp=new DP[len];
for (int i = 0;i < len;i++)
{
dp[i].sequence.push_back(i);
}
其中len表示待检测的数组的长度,即nums的长度。
2、求取dp[i]
假设已知dp[0…i-1]的数据,求出dp[i]。
因为是求递增序列,所以当找到前面结尾比nums[i]小的子序列,只需将nums[i]接在最后,就形成一条新的子序列,且这个新的子序列长度加一。由于可能会得到多条新的子序列,只需要取出其中长度最大的一条即可。
由已有条件我们可以将nums[i]的值与nums[0…i-1]的值进行比较即可。假设j<i。当nums[i]<=nums[j];dp[i]不做任何变化;当nums[i]>nums[j]且dp[i].len<dp[j].len+1时,dp[i]的数据做出相应的改变。代码如下:
for (int i = 0;i < len;i++)
{
for (int j = i - 1;j >= 0;j--)
{
if (a[i] > a[j] && (dp[j].len + 1) > dp[i].len)
{
//dp[i].len = max(dp[i].len, dp[j].len + 1);
dp[i].len = dp[j].len + 1;
dp[i].sequence = dp[j].sequence;//获取以序号j结尾的递增子序列
dp[i].sequence.push_back(i);//输入当前元素序号
}
}
}
3、确定dp数组中的最大项
由上面可以得知最终结果就是dp数组中len最大的那一项。
DP temp;
for (int i = 0;i < len;i++)
{
if (temp.len < dp[i].len)
{
temp.len = dp[i].len;
temp.sequence = dp[i].sequence;
}
}
return temp;
4、最终完整代码
//Dynamic programming动态规划解法
DP DPLTS(vector<int> nums)
{
//为dp数组申请空间
//vector<DP> dp;
int len = nums.size();
DP *dp=new DP[len];
for (int i = 0;i < len;i++)
{
dp[i].sequence.push_back(i);
}
for (int i = 0;i < len;i++)
{
for (int j = i - 1;j >= 0;j--)
{
if (a[i] > a[j] && (dp[j].len + 1) > dp[i].len)
{
//dp[i].len = max(dp[i].len, dp[j].len + 1);
dp[i].len = dp[j].len + 1;
dp[i].sequence = dp[j].sequence;//获取以序号j结尾的递增子序列
dp[i].sequence.push_back(i);//输入当前元素序号
}
}
}
DP temp;
for (int i = 0;i < len;i++)
{
if (temp.len < dp[i].len)
{
temp.len = dp[i].len;
temp.sequence = dp[i].sequence;
}
//temp.len = max(temp.len, dp[i].len);
}
delete[] dp;
dp = nullptr;
return temp;
}