问题描述
https://leetcode-cn.com/problems/longest-increasing-subsequence/
动态规划 O(n ^ 2)
动态规划的话,所使用的数组为 dp[len]
dp[i]
,表示从 原数组的 0 — 》 i 的所有数中,以nums[i]
结尾的最长递增子序列的长度
状态转移方程:
dp[i] = max(dp[ j ] + 1,dp [ i ]);
,j —》 (0 --> i-1)
因为以每一个数为递增子序列的结尾数字时,当计算的时候,他的最后结果一定是由前面比他小的数字(n)
,以n结尾的最长递增子序列的长度 + 1
得到的,也就是 dp[ j ] + 1
在初始化的时候,我们需要把每一个位置的长度都初始化为1,也就是以他本身结尾的最长递增子序列的长度最小是1
int lengthOfLIS1(vector<int>& nums)
{
int len = nums.size();
if(len < 2) return len;
vector<int> dp(len,1);
for(int i = 1; i < len; i++)
for(int j = 0; j < i; j++)
if(nums[j] < nums[i])
{
//当前的最长递增序列,一定是前面的一个比他小的数的长度 + 1变来的
dp[i] = max(dp[j]+1,dp[i]);
}
int ans = dp[0];
for(auto& n : dp)
ans = max(ans,n);
return ans;
}
贪心 + 二分查找 O(n * log n)
在贪心的策略中,我们可以维护一个递增的数组,我们只要保证每次这个数组的末尾数字都是满足递增子序列要求的最小数字,那么我们下一次遍历的可能性就可以增加了
- 每一次遍历的过程,我们需要找到第一个比当前数字(n)大的数字(m),然后用当前数字覆盖这个位置(m = n)
- 如果当前数字比最后一个数字还大,那么直接在末尾插入就可以了
int lengthOfLIS(vector<int>& nums)
{
int len = nums.size();
if(len < 2) return len;
vector<int> tail;
int ans = 1;
tail.push_back(nums[0]);
for(auto& num : nums)
if(num > tail[ans-1]) //数组中元素就是递增顺序的,如果比末尾元素大,就尾插
{
tail.push_back(num);
ans++;
}
else
{
//找到数组中第一个大于 num 的数字下标
int le = 0;
int ri = ans - 1;
while(le < ri)
{
int mid = (le + ri) / 2;
if(tail[mid] >= num) ri = mid;
else if(tail[mid] < num) le = mid + 1;
}
//num 覆盖这个数字
tail[le] = num;
}
return ans;
}
程序的过程
- 初始时的效果, i = 0 时
- i = 1
- i = 2
- i = 3
- i = 4
- i = 5
- i = 6
- i = 7