文章目录
子序列(不连续)
<span id = “最长上升子序列">最长上升子序列
LIS(longest increasing subsequence)是最经典的动态规划题目之一,因为它的递推公式很有条理,可以很清楚的告诉我们什么状态可以递推出当前的状态。
首先搞清楚几个概念:
上升:本题中的上升指的是严格的上升,即{1, 2, 3}
而不是{1, 2, 2}
。
子序列和子串:子序列是数组中可以不连续的子段序列,而子串是数组中一个连续的一段序列。
(暴力回溯)
可以将所有的数组中的所有子集(所有组合)求出来,然后判断是否为严格上升的序列,最后取最长的序列的长度。
有时间复杂度太高,所以就不写了。
(动规)
1.dp数组的含义
要求出最长上升子序列,只有一个限制条件,即要满足是上升的序列,所以只需要一个一维数组来控制即可。
定义dp[i]
表示:以第i个数字结尾的最长上升子序列的长度为dp[i]
。
注意:答案不一定是dp[nums.size() - 1]
,因为不一定是以最后一个数字结尾就是最长的上升子序列,例如nums = {1, 2, 3, 1, 1}
,所以最后遍历一遍以nums[i]
结尾的所有数dp[i]
,取一个最大值。(也可以在算出dp[i]的时候就更新ans,见下面的代码)
2.递推公式
一般递推公式都是推出最后一个最具有一般性的dp数组,即dp[i]
。所以要考虑什么状态可以推出dp[i]
,即dp[i]
前一个满足要求的dp数组,也就是说哪一个dp数组满足nums[i]
是它的最长上升子序列中的数。
可以发现想要推出nums[i]
是否为最长上升子序列中的数,就要看nums[j] j = 1,2 ... i - 1
是否小于nums[i]
,if (nums[i] > nums[j])
这时dp[i] = dp[j] + 1
,但是j = 1,2,...i - 1
,所以dp[i]
是其中最大的一个,所以dp[i] = max(dp[i], dp[j] + 1)
。
3.初始化
因为每一个子序列本身都是一个上升的子序列,所以dp
数组中的所有数初始化为1。
根据递推公式中dp[i] = max(dp[i], dp[j] + 1)
,可知dp[i]
有dp[j]
也就是dp[0 ... i - 1]
推出,所以不用特判初始化。
4.遍历顺序
有递推公式可以知道,前面状态推出后面的状态,所以从前往后遍历即可。
5.举例
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
vector<int> dp(nums.size(), 1);
int ans = 0;
for (int i = 1; i < nums.size(); i ++) {
for (int j = 0; j < i; j ++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
}
if (ans < dp[i]) ans = dp[i];
}
return ans;
}
};
(动规 + 二分 + 贪心)
tail[i]
表示:长度为i + 1的所上升子序列的结尾的最小值。所以tail
数组的长度就是上升子序列的长度。
如何更新tail
数组:
1.如果从前往后遍历数组,nums[i] > tail[end]
的话,就将nums[i]
插入到tail
数组的最后。
2.如果从前往后遍历数组,nums[i] <= tail[end]
的话,就将tail
数组中>=nums[i]
的第一个元素,替换成nums[i]
,这个过程可以用二分来解决。
为什么要用nums[i]
替换tail
数组中>= nums[i]
的第一个元素?
数组是从前往后遍历的去求出上升子序列的最后一个最小元素的。
如果nums[i]>tail[end]
的话就不说明了,因为要满足上升子序列的要求,所以只能将nums[i]
插入到tail
的后面。
但是如果nums[i]<=tail[end]
,就说明tail
数组中一定存在一个数>=nums[i]
,而>=nums[i]
的第一个数,可能是tail[end]
,也可能是tail
数组中前面的数。这里就要用到贪心的思想了,就是不论如何,要想求出最长的上升子序列,就要使得if(nums[i] <= tail[end])
中的nums[i]
一定是tail
数组中相应位置上最小的数,因为小数字后面接的大数字的个数一定要比小数字大的数后面接的大数字的个数要多。
这样说太抽象了,举个栗子:
tail = {1, 3, 6}
,nums[i] = 5
,nums[i + 1] = 6
如果不替换nums[i]
的话,因为nums[i]
不大于tail[end] == 6
nums[i + 1]
也不大于tail[end] == 6
,所以tail
的长度就是3.
但是因为nums[i] < tail[end]
,所有nums[i]
替换>= nums[i]
的第一个元素tail[2] == 6
,所以tail = {1, 3, 5}
, 因为nums[i + 1] > tail[end]
,所以nums[i + 1]
到tail
数组后面,所以tail = {1, 3, 5, 6}
,这时tail
数组的长度为4,所以最长上升子序列的长度为4。这显然比上面一种情况的长度要大。
class Solution {
public:
int lengthOfLIS(vector<int>& nums