【LeetCode笔记】300. 最长递增子序列(Java、动态规划、二分法、贪心)

题目描述

  • 难点在于时间复杂度 O(n * logn)的做法
    在这里插入图片描述
    在这里插入图片描述

思路 & 代码

动态规划 O( n 2 n^2 n2)

  • 先抛砖引玉啦~
  • dp[i]:以 nums[i] 结尾的子序列,能达到的最大长度
  • 对于 dp[i] 来说,只要找到前面的比 nums[i] 小的 nums[j] 中最大的 dp[j] 即可
class Solution {
    public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        int ans = 1;
        // dp[i]:以 nums[i] 结尾的子序列,能达到的最长长度
        int[] dp = new int[len];
        // 边界
        dp[0] = 1;
        // 复杂度 O(n^2)
        for(int i = 1; i < len; i++){
            // 找到前面结点里,比 nums[i] 小的中,dp[] 最长的值
            int maxPre = 0;
            for(int j = i - 1; j >= 0; j--){
                if(nums[i] > nums[j]){
                	// dp[j] 最优子结构
                    maxPre = Math.max(dp[j], maxPre);
                }
            }
            // 状态转移方程
            dp[i] = maxPre + 1;
            ans = Math.max(ans, dp[i]);
        }
        return ans;
    }
}

动态规划 + 二分法 + 贪心 O( n l o g n nlogn nlogn)

  • 新定义 tail[i]:代表长度为 i + 1 的递增子序列的最小尾值
  • 详见注释。建议还是看看这篇题解,结合里面的动图会更好地理解这道题。
class Solution {
    public int lengthOfLIS(int[] nums) {
        // dp + 二分 + 贪心
        int len = nums.length;
        // tail[i]:长度为 i + 1 的递增子序列的最小尾值
        int[] tail = new int[len];
        // 边界
        tail[0] = nums[0];
        // 尾值下标
        int endIndex = 0;
        for(int i = 1; i < len; i++){
            // nums[i] > tail[endIndex] 的情况:加入尾,更新 endIndex
            if(nums[i] > tail[endIndex]){
                tail[++endIndex] = nums[i];
            }
            // <= 的情况,二分法找到第一个 >= nums[i] 的值,并更新
            else{
                int left = 0, right = endIndex;
                // 区间选择:[left, mid] [mid + 1, right]
                while(left < right){
                    // mid 向下取整,防止 mid == right 的情况导致死循环
                    int mid = left + (right - left) / 2;
                    // 直接舍弃掉这部分,mid 是用不到的
                    if(tail[mid] < nums[i]){
                        left = mid + 1;
                    }
                    // mid 是可能用得到的
                    else{
                        right = mid;
                    }
                }
                // 更新所取值
                tail[left] = nums[i];
            }
        }
        // 由下标获取长度
        int ans = endIndex + 1;
        return ans;
    }
}
/**
    * Q:为什么可以直接更新到前方?比如 [2,3,8,18]遇到4,更新成[2,3,4,18]
    * 1. 这个数组在 endIndex 不变的维护过程中,只有[0, left]是满足题目条件的序列,[left + 1, endIndex]则不保证
    * 2. 但是在 endIndex 增加的维护过程,可以保证“正确性”:因为之前肯定出现过满足题目条件的[0, endIndex]的,
    *    1.的维护过程是建立于此的基础上进行更新的,因此 endIndex 值的正确性是可以保证的。
    * 3. 小值更新到前方:贪心,这是为了之后遇到较小值也可以增加 endIndex 而服务的
*/

二刷

  • 写不出 O(logN) 的,我还写不出你 O(n^2) 的吗!
  • 10+ 行代码,还是挺清晰的,记得 dp[nums.length - 1] 不一定是最终答案噢~
class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = 1;
        int res = 1;
        for(int i = 1; i < nums.length; i++) {
            int max = 0;
            for(int j = 0; j < i; j++) {
                if(nums[j] < nums[i]) {
                    max = Math.max(max, dp[j]);
                }
            }
            dp[i] = max + 1;
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值