27. 最长递增子序列【LC300】DP

86 阅读2分钟

题目:

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

需要对「子序列」和「子串」这两个概念进行区分; 子序列(subsequence):子序列并不要求连续,例如:序列 [4, 6, 5] 是 [1, 2, 4, 3, 7, 6, 5] 的一个子序列; 子串(substring、subarray):子串一定是原始字符串的连续子串。

  • 首要问题,定义状态

首先考虑题目问什么,就把什么定义成状态。题目问最长上升子序列的长度,其实可以把「子序列的长度」定义成状态,但是发现「状态转移」不好做。

基于「动态规划」的状态设计需要满足「无后效性」的设计思想,可以将状态定义为「以 nums[i] 结尾 的「上升子序列」的长度」

「无后效性」的设计思想:让不确定的因素确定下来,以保证求解的过程形成一个逻辑上的有向无环图。这题不确定的因素是某个元素是否被选中,而我们设计状态的时候,让 nums[i] 必需被选中,这一点是「让不确定的因素确定下来」,也是我们这样设计状态的原因。

dp[i] 表示:以 nums[i] 结尾 的「上升子序列」的长度。注意:这个定义中 nums[i] 必须被选取,且必须是这个子序列的最后一个元素

  • 状态转移方程: 如果一个较大的数接在较小的数后面,就会形成一个更长的子序列。只要 nums[i] 严格大于在它位置之前的某个数,那么 nums[i] 就可以接在这个数后面形成一个更长的上升子序列。

image.png

S[i] = max{ 1, S[j]+1} (0 < j < i Loop),会把所有的S[j]进行Loop

image.png

  • 初始化

dp[i] = 1,11 个字符显然是长度为 11 的上升子序列。

  • 输出: 不能返回最后一个状态值,最后一个状态值只表示以 nums[len - 1] 结尾的「上升子序列」的长度,状态数组 dp 的最大值才是题目要求的结果。

解:

var lengthOfLIS = function (nums) {
  let max = 1;
  if (!nums || !nums.length || nums.length === 1) {
    return max;
  }
  const dp = Array(nums.length).fill(1);
  for (let i = 0; i < nums.length; i++) {
    for (j = 0; j < i; j++) {
      if (nums[i] > nums[j]) {
        dp[i] = Math.max(dp[i], dp[j] + 1) //前面几位(0~j)状态的更新 
      }
    }
    max = Math.max(dp[i], max)
  }
  return max;
};