【LeetCode】最长递增子序列基础DP优化Java题解

127 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情

题目描述

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路分析

  • 今天的算法题目是数组题目,题目要求找出其中 最长严格递增子序列的长度
  • 这个题目是经典问题,有多种解法,首先可以使用基础动态规划方法解决,思路的核心是以 nums[i] 为结尾的最长上升子序列。定义 DP 数组,初始化 DP[i] = 1, 然后采用 for 双重循环, 定义变量 j 且 j < i, 在 [j, i] 区间,动态更新递增的最大值。基础的DP的时间复杂度是O(n * n), 空间复杂度是O(n)。
  • 首先得学会上面的解法,上面的解法是最常见的思路。我们需要求的是最长的上升子序列,可以假设已经得到上升子序列的结尾数越小,便利后面街上一个数,则会有可能得到一个更大的最长上升子序列。
  • 定义新状态,tail[i] 表示:长度为 i + 1 的 所有 上升子序列的结尾的最小值。初始化 tail[0] = nums[0]。
  • 继续遍历 nums, 如果 nums[i] > tail[end], 则 end++, tail[end] = nums[i], 找到了下一个更大的数。否则在有序数组 tail 中查找第一个大于 nums[i] 的数,让他变小。这里采用二分查找提升效率。
  • 最终,有序数组 tail 的长度,就是所求的「最长上升子序列」的长度。下列实现代码是大神 liweiwei1419 实现,看了诸多高票题解,个人认为这个是写的最好的。向大佬致敬!供参考。

通过代码

public class Solution {
    public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        if (len <= 1) {
            return len;
        }

        // tail 数组的定义:长度为 i + 1 的上升子序列的末尾最小是几
        int[] tail = new int[len];
        // 遍历第 1 个数,直接放在有序数组 tail 的开头
        tail[0] = nums[0];
        // end 表示有序数组 tail 的最后一个已经赋值元素的索引
        int end = 0;

        for (int i = 1; i < len; i++) {
            // 【逻辑 1】比 tail 数组实际有效的末尾的那个元素还大
            if (nums[i] > tail[end]) {
                // 直接添加在那个元素的后面,所以 end 先加 1
                end++;
                tail[end] = nums[i];
            } else {
                // 使用二分查找法,在有序数组 tail 中
                // 找到第 1 个大于等于 nums[i] 的元素,尝试让那个元素更小
                int left = 0;
                int right = end;
                while (left < right) {
                    int mid = left + ((right - left) >>> 1);
                    if (tail[mid] < nums[i]) {
                        left = mid + 1;
                    } else {
                        right = mid;
                    }
                }
                tail[left] = nums[i];
            }
        }
        end++;
        return end;
    }
}


总结

  • 在算法学习过程中,自己常常感到还有这种解法的神奇,还需要不断的刷题,思考,不断学习完善自己的知识体系。更好的完善思路,提升自己的能力!
  • 坚持算法每日一题,加油!