文章目录
- 最长上升子序列(LIS)的定义:
- 三种求解方法:
- 题目1:求最长连续递增序列
- 题目2:找到其中最长上升子序列的长度
- 感性思路:简单DP,不一样的配方,一样的套路。想要求得最长上升子序列的长度,来源只有一种——**前一个最长上升子序列的长度+1** ,据此就得到了状态转移方程 。
- 如何实现:用F[i] 来表示该位置最长上升子序列的长度,那么我们就扫描i之前的元素,如果前面的元素比nums[i]小,我们就**让该位置的长度=前面最大的最长上升子序列的长度加1**,即“F[i]=max{F[j]+1}( 0< j < i, A[j] < A[i] )” 。需要注意的是必须是前面的最大的最长上升子序列的长度加1 !!! 比如:
- 题目3:求最长上升子序列的个数
最长上升子序列(LIS)的定义:
一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
三种求解方法:
1. O(n^2)的DP
2. O(nlogn)的二分+贪心法
3. O(nlogn)的树状数组优化的DP
题目1:求最长连续递增序列
给定一个未经排序的整数数组,找到最长且连续的的递增序列。
示例 1:
输入: [1,3,5,4,7]
输出: 3
解释: 最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。
示例 2:
输入: [2,2,2,2,2]
输出: 1
解释: 最长连续递增序列是 [2], 长度为1。
注意:数组长度不会超过10000。
思路:
dp[]
数组表示以i
结尾的最长连续递增序列的长度!!所以后面的元素如果比前面的元素大的话,那么他的dp
值就是前面的dp
值加一.不用管前面的值是如何计算出来的.只是拿来用就行了
注意这是连续的!!!!所以之需要一个for
循环就行了
class Solution
{
public:
int findLengthOfLCIS(vector<int> &nums)
{
int len = nums.size();
if (len <= 0)
return 0;
vector<int> dp(len, 1);
int res = 1;
for (int i = 1; i < len; i++)
{
if (nums[i - 1] < nums[i])
dp[i] = dp[i - 1] + 1;
if (dp[i] > res)
res = dp[i];
}
return res;
}
};
题目2:找到其中最长上升子序列的长度
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
感性思路:简单DP,不一样的配方,一样的套路。想要求得最长上升子序列的长度,来源只有一种——前一个最长上升子序列的长度+1 ,据此就得到了状态转移方程 。
如何实现:用F[i] 来表示该位置最长上升子序列的长度,那么我们就扫描i之前的元素,如果前面的元素比nums[i]小,我们就让该位置的长度=前面最大的最长上升子序列的长度加1,即“F[i]=max{F[j]+1}( 0< j < i, A[j] < A[i] )” 。需要注意的是必须是前面的最大的最长上升子序列的长度加1 !!! 比如:
1, 3, 6, 7, 9, 4, 10, 5, 6
对应的 F[ i ] 是:
1,2,3,4,5,3, x
当算到 x 的时候,如果不是前面的最大的最长上升子序列的长度加1的话x的位置就会变成4,这是我们所不希望的 。这也就是不连续的最长递增子序列的特点,也就是需要两个for循环!!
class Solution
{
public:
int lengthOfLIS(vector<int> &nums)
{
if (nums.size() == 0)
return 0;
/**
定义记录最长子序列长度的数组 array
array[i] 表示以 nums[i] 结尾的最长子序列的长度值,对于每个元素,初始化都为 1
*/
vector<int> array = vector<int>(nums.size(), 1);
int res = array[0];
for (int i = 1; i < nums.size(); i++)
{
for (int j = 0; j < i; j++)
{
//后面的元素比前面的元素值大,那么后面的元素可以放在前面的元素后面形成一个新的子序列
if (nums[i] > nums[j])
{
//更新记录的长度值
array[i] = max(array[i], 1 + array[j]);
}
}
res = max(res,array[i]);
}
return res;
}
};
题目3:求最长上升子序列的个数
给定一个未排序的整数数组,找到最长递增子序列的个数。
示例 1:
输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。
示例 2:
输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。
注意: 给定的数组长度不超过 2000 并且结果一定是32位有符号整数。
思路:
- 对于最长子序列的个数,需要我们在处理dp的时候来记录,我们设count[i]为以第i个数结尾的最长序列的个数,与dp同理,count初值也都是1;
- 状态转移的时候,如果dp更新了,也就是说(
dp[j]+1>dp[i])说明这个长度的序列是新出现的,我们需要将count[i]设置为count[j]
,因为新序列中,最新的数提供了序列的尾巴,数量是由前面积累的(或者说转移);举例序列[1 1 3 7]我们易得数字3对应的dp=2,count=2,因为可以构成两个[1 3]那么我们操作到数字7的时候,发现接在3后面最长,就可以转移count来作为初始数量; - 当
dp[j]+1==dp[i]的时候,如同样例,操作7的时候,我们最先发现了可以接在5后面,最长序列[1 3 5 7],然后发现可以接在4后面,[1 3 4 7],长度也是4,这时候就同样需要转移count,加上去 count[i]+=count[j]
; - 最后我们需要遍历dp,找到dp[i]=我们记录的最大值的时候,累加我们得到的count[i],即为所求结果,时间复杂度是O(n^2)
class Solution
{
public:
int findNumberOfLIS(vector<int> &nums)
{
if (nums.size() <= 0)
return 0;
vector<int> dp(nums.size(), 1); //表示以i结尾的子序列的长度
vector<int> count(nums.size(), 1); //表示以i结尾的子序列的个数
int res = 0;
int max_len = 0;
for (int i = 0; i < nums.size(); i++)
{
for (int j = 0; j < i; j++)
{
if (nums[j] < nums[i])
{
//说明这个长度的序列是新出现的
if (dp[j] + 1 > dp[i])
{
count[i] = count[j];
dp[i] = dp[j] + 1;
}
//个数是递增的,后面的状态与上一个状态有关
else if (dp[j] + 1 == dp[i])
count[i] += count[j];
}
}
max_len = std::max(max_len, dp[i]); //最长子序列的长度
}
for (int i = 0; i < nums.size(); i++)
{
if (dp[i] == max_len)
res += count[i];
}
return res;
}
};
参考链接:
https://blog.csdn.net/George__Yu/article/details/75896330