300. Longest Increasing Subsequence

题目描述和难度

  • 题目描述:

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

示例:

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

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

思路分析

求解关键:

思路1:使用动态规划。 状态的定义:以 num[i] 结尾的最长上升子序列的长度。 状态转移方程:之前的数中比 num[i] 小的最长上升子序列的长度 + 1。

思路2:使用贪心选择 + 二分查找算法。

参考解答

参考解答1

import java.util.Arrays;

// 最长上升子序列问题
// 300. 最长上升子序列
// https://leetcode-cn.com/problems/longest-increasing-subsequence/description/
public class Solution {

    //【关键】将 dp 数组定义为:以 nums[i] 结尾的最长上升子序列的长度
    // 那么题目要求的,就是这个 dp 数组中的最大者
    // 以数组  [10, 9, 2, 5, 3, 7, 101, 18] 为例:
    // dp 的值: 1  1  1  2  2  3  4    4
    // 注意实现细节。
    public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        if (len == 0) {
            return 0;
        }
        // 状态的定义是:以 i 结尾的最长上升子序列的长度
        // 状态转移方程:之前比最后那个数字小的最长上升子序列的长度 + 1
        int[] dp = new int[len];
        Arrays.fill(dp, 1); // 如果只有 1 个元素,那么这个元素自己就构成了最长上升子序列,所以设置为 1 是合理的
        for (int i = 1; i < len; i++) { // 从第 2 个元素开始,逐个写出 dp 数组的元素的值
            int curVal = nums[i];
            for (int j = 0; j < i; j++) { // 找出比当前元素小的哪些元素的最小值
                if (curVal > nums[j]) {
                    dp[i] = Integer.max(dp[j] + 1, dp[i]);
                }
            }
        }
        // 最后要全部走一遍,看最大值
        int res = dp[0];
        for (int i = 0; i < len; i++) {
            res = Integer.max(res, dp[i]);
        }
        return res;
    }

    public static void main(String[] args) {
        int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
        Solution solution = new Solution();
        int lengthOfLIS = solution.lengthOfLIS(nums);
        System.out.println(lengthOfLIS);
    }
}

参考解答2

import java.util.Arrays;

public class Solution4 {

    // 有贪心选择的意思
    public int lengthOfLIS(int[] nums) {
        int len = nums.length;
        // 先考虑极端输入
        if (len <= 1) {
            return len;
        }
        // tail 数组的定义:长度为 i+1 的上升子序列的末尾最小是几
        int[] tail = new int[len];
        // 遍历一遍整个数组,使用二分查找算法
        tail[0] = nums[0];
        int res = 0;
        for (int i = 1; i < len; i++) {
            // 比 tail 数组实际有效的末尾的那个元素还大
            if (nums[i] > tail[res]) {
                // 直接添加在那个元素的后面
                tail[++res] = nums[i];
            } else {
                // 二分查找到第 1 个比 nums[i] 还大的元素,更新到那个位置
                int l = 0;
                int r = res;
                while (l < r) {
                    int mid = l + (r - l) / 2;
                    // 有就啥都不做了
                    if (tail[mid] == nums[i]) {
                        l = mid;
                        break;
                    } else if (tail[mid] >= nums[i]) {
                        r = mid;
                    } else {
                        l = mid + 1;
                    }
                }
                tail[l] = nums[i];
            }
            printArray(nums[i], tail);
        }
        return ++res;
    }

    // 调试方法,以观察是否运行正确
    private void printArray(int num, int[] tail) {
        System.out.print("当前数字:" + num);
        System.out.print("\t当前 tail 数组:");
        int len = tail.length;
        for (int i = 0; i < len; i++) {
            if (tail[i] == 0) {
                break;
            }
            System.out.print(tail[i] + ", ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int[] nums = new int[]{3, 5, 6, 2, 5, 4, 19, 5, 6, 7, 12};
        Solution4 solution4 = new Solution4();
        int lengthOfLIS = solution4.lengthOfLIS(nums);
        System.out.println("最长上升子序列的长度:" + lengthOfLIS);
    }
}

本篇文章的地址为 https://liweiwei1419.github.io/leetcode-solution/leetcode-0300-longest-increasing-subsequence ,如果我的题解有错误,或者您有更好的解法,欢迎您告诉我 liweiwei1419@gmail.com