【leetcode】300 最长上升子序列(动态规划,贪心,二分查找)

题目链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/

题目描述

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

示例:

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

说明:

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

思路

蛮力法找出所有子序列,共有 2 n 2^n 2n 个子序列;如果每个子序列都进行检查,则总的时间复杂度为 O ( n ∗ 2 n ) O(n*2^n) O(n2n)。复杂度太高,下面考虑动态规划的解法。

1 动态规划O(n^2)

【1】定义状态
dp[i]表示以i元素结尾的最长上升子序列的长度。即在 [0, ..., i] 的范围内,选择以数字 nums[i] 结尾可以获得的最长上升子序列的长度。注意:以第 i 个数字为结尾,即要求 nums[i] 必须被选取。反正一个子序列一定会以一个数字结尾,那我就将状态这么定义,这一点是常见的。
【2】状态转移方程
遍历索引是i的元素时,需要检查[0,i-1]的所有dp,如果nums[i]严格大于之前的某个nums[j],则将nums[i]接到这个数后必然能够形成一个更长的上升子序列;此时dp[i]为他们的最大值+1。
状态转移方程:dp(i) = max( 1 + dp(j) if j < i and dp[i] > dp[j])

复杂度分析
时间复杂度:O(n^2)
空间复杂度:O(n)

/*
 * 最长上升子序列:动态规划
 * 时间复杂度O(N^2) 空间复杂度O(N)
 */
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size() <= 1) return nums.size();
        vector<int> dp(nums.size(),1);  // 表示以当前元素结尾的最长上升子序列长度;当前元素必须使用
        int maxLen = 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);    // 如果当前元素i大于j,则序列长度加一
            }
            maxLen = max(dp[i],maxLen);
        }

        return maxLen;
    }
};
2 动态规划+贪心+二分查找

维护一个数组tail,当出现的数大于这个数组直接append,否则替换掉数组中大于等于这个数的最小值。
最后tail的长度就是最长上升子序列的长度
tail数组是一个有序数组,使用二分查找复杂度为O(log n)

每一次来一个新的数 num,就找 tail 数组中第一个大于等于 num 的那个数,试图让它变小,以致于新来的数有更多的可能性接在它后面,成为一个更长的“上升子序列”,这是“贪心算法”的思想。

复杂度分析
时间复杂度:O(nlogn)
空间复杂度:O(n)

在这里插入图片描述
在这里插入图片描述

/*
 * 最长上升子序列:动态规划+贪心+二分查找
 * 时间复杂度O(NlogN) 空间复杂度O(N)
 */
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size() <= 1) return nums.size();
        vector<int> dp = {nums[0]}; // 第i个位置上的元素是长度为i+1的最长上升子序列的末尾元素最小值

        for (int i = 1; i < nums.size(); ++i) {
            int left = 0, right = dp.size();
            // 二分查找第一个大于等于num[i]的数的索引
            while (left<right){
                int mid = (left+right) /2;
                if(dp[mid] < nums[i])
                    left = mid +1;
                else
                    right = mid;
            }
            if (left == dp.size())
                dp.push_back(nums[i]);  // 比最末尾元素大,加入数组
            else
                dp[left] = nums[i];     // 替换
        }

        return dp.size();
    }
};

在这里插入图片描述

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值