动态规划之最长递增子序列

873 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

动态规划(Dynamic Programming)是一种分阶段求解决策问题的数学思想,它通过把原问题分解为简单的子问题来解决复杂问题。

最长递增子序列

给你一个整数数组 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

首先需要注意的是题目要求的是子序列而不是子串子串一定是连续的,而子序列不一定是连续的。

暴力解法

先得到输入数组的所有子序列,然后遍历这些子序列判断是否严格上升,并且找出最长的哪一个,不过这种方法在穷举所有子序列的时候时间复杂度非常高。

动态规划

  • 首先定义状态,明确 dp 数组的定义,即dp[i] 表示:以 nums[i] 结尾 的最长严格递增子序列的长度。
  • 然后是找出状态转移方程,本题中,只有nums[i]大于它前面的数字,才能拼接在后面形成一个更长的上升子序列。
  • 最后是base case,对于每个字符来说,它自己一个字符也属于上升子序列。所以dp[i] = 1。

代码如下:

int lengthOfLIS(int[] nums) {
    int[] dp = new int[nums.length];
    Arrays.fill(dp, 1);
    for (int i = 0; i < nums.length; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j])
                dp[i] = Math.max(dp[i], dp[j] + 1);
        }
    }

    int res = 0;
    for (int i = 0; i < dp.length; i++) {
        res = Math.max(res, dp[i]);
    }
    return res;
}

复杂度分析

  • 时间复杂度:O(N^2)
  • 空间复杂度:O(N),只是额外使用了一个dp数组。