给定一个无序的整数数组,找到其中最长上升子序列的长度。
一个无序整数数组,
该整数数组的最长上升子序列的长度
输入: [10, 9, 2, 5, 3, 7, 101, 18]
输出: 4
解释: 最长的上升子序列是 [2, 3, 7, 101],它的长度是 4。
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。你算法的时间复杂度应该为 O ( n 2 ) O(n2) O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O ( n l o g n ) O(nlogn) O(nlogn) 吗?
分析:
方法一:DP解法
子问题: 以第 i i i 个元素结尾的最长子序列的长度
最优子结构: dp[i]
决策: 以第 i i i 个元素结尾
递推关系式:
只要数组 nums 长度不为0,则最长子序列长度最小值为1,故将 dp 数组初始化为1;
计算以第 i 个数字结尾的最长子序列长度 dp[i] 时,我们应该把索引是 [0, … ,i - 1] 的最长子序列长度 dp[j] 都看一遍,如果当前的数 nums[i] 大于之前的某个数nums[j],那么 nums[i] 就可以接在 nums[j] 后形成一个更长的子序列。dp[i] 是所有 dp[j] 中的最大值加 1 (即加nums[i]本身的长度1)。
d p [ i ] = { 1 , i n i t i a l i z e d p [ i ] f o r e a c h i = 0 , 1... n ; m a x ( d p [ j ] + 1 ) , i f j < i a n d n u m s [ i ] > n u m s [ j ] . dp[i]=\left\{ \begin{aligned} 1 & , & initialize\ dp[i]\ for\ each\ i = 0,1...n ;\\ max(dp[j]+1) & , &if\ j < i\ and\ nums[i] > nums[j].\\ \end{aligned} \right. dp[i]={1max(dp[j]+1),,initialize dp[i] for each i=0,1...n;if j<i and nums[i]>nums[j].
示例:
数组: [ 10 , 9 , 2 , 5 , 3 , 7 , 101 , 18 ] \;[10, 9, 2, 5, 3, 7, 101, 18] [10,9,2,5,3,7,101,18]
dp 的值: 1 1 1 2 2 3 4 4 1 \;\; 1 \;\; 1 \; 2 \;\; 2 \;\; 3 \quad 4 \quad 4 11122344
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n ) O(n) O(n)
实现代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0) return 0;
vector<int>dp(nums.size(), 1);
int max_len = INT_MIN;
for(int i = 0; i < nums.size(); i++){
for(int j = 0; j < i; j++){
if(nums[j] < nums[i]){
dp[i] = max(dp[i], dp[j] + 1);
}
}
max_len = max_len < dp[i] ? dp[i] : max_len;
}
return max_len;
}
};
方法二:贪心 + 二分查找
进阶部分,若要将算法复杂度降到 O ( n l o g n ) O(nlogn) O(nlogn),看到这个复杂度一般思路就是使用二分查找。
贪心思想:如果要使上升子序列尽可能的长,则需要让序列上升得尽可能慢,因此希望每次在上升子序列最后加上的那个数尽可能的小。
维护一个数组dp[], dp[length]表示递增子序列长度为length时,序列最后的那个元素值。
初始化: dp[1]=nums[0]
遍历nums,如果 nums[k]>dp[length],则 dp[length+1] = nums[k], length++;否则,使用二分查找,在dp[1]~dp[length]中查找从左到右第一个大于nums[k]的元素dp[j],更新dp[j]=nums[k]
时间复杂度: O ( n ∗ l o g n ) O(n*logn) O(n∗logn)
空间复杂度: O ( n ) O(n) O(n)
实现代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0) return 0;
vector<int>dp(nums.size() + 1, 0);
dp[1] = nums[0];
int max_len = 1;
for(int i = 1; i < nums.size(); i++){
if(nums[i] > dp[max_len]){
dp[++max_len] = nums[i];
}
else{
//二分查找
int left = 1, right = max_len;
int mid;
while(left < right){
mid = left + (right - left) / 2;
if(nums[i] <= dp[mid]){
right = mid;
}
else{
left = mid + 1;
}
}
dp[left] = nums[i];
}
}
return max_len;
}
};