LeetCode 300.最长上升子序列 分析理解

题目描述:
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

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

一、DP 时间复杂度为O(n²)

动态规划的两种实现
1.自顶向下(带有备忘录的递归)
2.自底向上(记录过程中求得的结果)

最优子结构 + 重叠子问题 -> 动态规划

核心:将问题分解成若干个小问题,并记住求过的解来节省时间。
Those who cannot remember the past are condemned to repeat it.

算法步骤:
1.描述最优解的结构特征(列出状态转移方程)
2.自底向上计算最优解的值(利用递归或循环)

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = (int)nums.size();
        if (n < 2)
            return n;
        int* dp = new int[n];	//创建dp数组       
        for (int i = 0; i < n; i++)
        {
            dp[i] = 1;			//初始最短长度序列中只包含自身
         //对于每新加入的nums[i],需要遍历其前面的所有元素
            for (int j = 0; j < i; j++)	//遍历找到最长上升子序列的长度
                if (nums[i] > nums[j])	//如果满足单调增则带入如下方程
                {	//状态转移方程
                    dp[i] = max(dp[i], dp[j]+1);
                }
         }
        int result = dp[0];
        for (int i = 0; i < n; i++)
            if (dp[i] > result)
                result = dp[i];
        return result;
    }
};

二、贪心算法 + 二分查找 时间复杂度为O(nlogn)

核心思想: 为了上升序列尽可能的长,应该使每次加的值尽可能小。

故引入两个参数:
1.数组d[i]:记录长度为i的上升序列末尾的元素。
2.长度len:记录序列的长度。

步骤:
1.遍历nums[i],若nums[i] > d[len],则长度len++,d[len] = nums[i]。
2.否则,用二分查找找到d[pos] < nums[i] < d[pos+1] 的这样一个位置pos,更新d[pos+1] = nums[i]。(d数组里的元素是递增的)

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = (int)nums.size();
        if (n < 2)
            return n;
        int* d = new int[n+1]();	//d数组各个元素初始化为0
        int len = 1;				//长度初始化为1元素为nums[0]
        d[1] = nums[0];
        for (int i = 1; i < n; i++)	//O(n)遍历nums各个元素
        {
            if (nums[i] > d[len])	//步骤1的代码实现
            {
                d[++len] = nums[i];
            }
            else					//O(logn)二分查找位置
            {
                int left = 1, right = len, pos = 0;
                while (left <= right)
                {
                    int mid = (left + right) / 2;
                    if (d[mid] < nums[i])
                    {
                        pos = mid;
                        left = mid + 1;
                    }
                    else
                    {
                        right = mid - 1;
                    }
                }
                d[pos+1] = nums[i];
            }
        }
        return len;
    }
};

总结:需要注意的是,d[len]代表的是长度为len的上升子序列末尾值的最小值,加入一个元素时考虑的只有d[len],不需要考虑d的其他元素。步骤二的更新d[len]是贪心算法的核心体现。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值