Leetcode#300 最长上升子序列

题目:

给定一个无序的整数数组,找到其中最长上升子序列的长度。

输入:

一个无序整数数组,

输出:

该整数数组的最长上升子序列的长度

示例:

输入: [10, 9, 2, 5, 3, 7, 101, 18]
输出: 4
解释: 最长的上升子序列是 [2, 3, 7, 101],它的长度是 4。

说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。你算法的时间复杂度应该为 O(n2)O(n2)
进阶: 你能将算法的时间复杂度降低到 O(nlogn)O(nlogn) 吗?

思路:

分析:

  1. 首先仔细审题,明确题目中的条件。
    子序列:不要求连续子序列,只要保证元素前后顺序一致即可;
    上升:这里的“上升”是“严格上升”,类似于 [2, 3, 3, 6, 7] 这样的子序列是不符合要求的;
  2. 一个序列可能有多个最长上升子序列,题目中只要我们求这个最长的长度。如果使用回溯搜索,选择所有的子序列进行判断,时间复杂度为 O((2n)n)O( (2^n) * n )
  3. 观察输入的规模,以第 i 个数字结尾的最长上升子序列长度取决于前 i 个数字,与其后的数字无关。可以使用动态规划解决该问题,用dp数组记录以每个数字结尾时的最长子序列的长度,即我们要求 nums[i] 必须被选取。反正一个子序列一定要以一个数字结尾,那我就将状态这么定义,这一点是重要且常见的

方法一:DP解法
子问题: 以第 ii 个元素结尾的最长子序列的长度
最优子结构: dp[i]
决策: 以第 ii 个元素结尾
递推关系式:
只要数组 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[i]={1,initialize dp[i] for each i=0,1...n;max(dp[j]+1),if j<i and nums[i]>nums[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.
示例:
数组:   [10,9,2,5,3,7,101,18]\;[10, 9, 2, 5, 3, 7, 101, 18]
dp 的值:1    1    1  2    2    3441 \;\; 1 \;\; 1 \; 2 \;\; 2 \;\; 3 \quad 4 \quad 4
时间复杂度: O(n2)O(n^2)
空间复杂度: 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(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(nlogn)O(n*logn)
空间复杂度: 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;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章