题目:
给定一个无序的整数数组,找到其中最长上升子序列的长度。
输入:
一个无序整数数组,
输出:
该整数数组的最长上升子序列的长度
示例:
输入: [10, 9, 2, 5, 3, 7, 101, 18]
输出: 4
解释: 最长的上升子序列是 [2, 3, 7, 101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。你算法的时间复杂度应该为 。
进阶: 你能将算法的时间复杂度降低到 吗?
思路:
分析:
- 首先仔细审题,明确题目中的条件。
子序列:不要求连续子序列,只要保证元素前后顺序一致即可;
上升:这里的“上升”是“严格上升”,类似于 [2, 3, 3, 6, 7] 这样的子序列是不符合要求的; - 一个序列可能有多个最长上升子序列,题目中只要我们求这个最长的长度。如果使用回溯搜索,选择所有的子序列进行判断,时间复杂度为 。
- 观察输入的规模,以第 i 个数字结尾的最长上升子序列长度取决于前 i 个数字,与其后的数字无关。可以使用动态规划解决该问题,用dp数组记录以每个数字结尾时的最长子序列的长度,即我们要求 nums[i] 必须被选取。反正一个子序列一定要以一个数字结尾,那我就将状态这么定义,这一点是重要且常见的。
方法一:DP解法
子问题: 以第 个元素结尾的最长子序列的长度
最优子结构: dp[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)。
示例:
数组:
dp 的值:
时间复杂度:
空间复杂度:
实现代码:
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;
}
};
方法二:贪心 + 二分查找
进阶部分,若要将算法复杂度降到,看到这个复杂度一般思路就是使用二分查找。
贪心思想:如果要使上升子序列尽可能的长,则需要让序列上升得尽可能慢,因此希望每次在上升子序列最后加上的那个数尽可能的小。
维护一个数组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]
时间复杂度:
空间复杂度:
实现代码:
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;
}
};