题目

题目来源:力扣(LeetCode)

给你一个整数数组 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[i] ,表示在nums数组的前 i 个元素中,以 nums[i] 结尾的最长上升子序列的长度。注意:这个定义中 nums[i] 必须被选取,且必须是这个子序列的最后一个元素;

  • 状态转移方程

我们从小到大计算 dp 数组的值,在计算dp[i] 之前,我们已经计算出 dp[0 … i - 1] 的值,则状态转移方程为:

dp[i] = max(dp[i], dp[j] + 1), 其中0 ≤ j < i且 num[j] < num[i]

即考虑往 dp[0 … i - 1] 中最长的上升子序列后面再加一个 nums[i]。由于 dp[j] 代表 nums[0, … , j] 中以 nums[j] 结尾的最长上升子序列,所以如果能从 dp[j] 这个状态转移过来,那么 nums[i] 必然要大于 nums[j],才能将 nums[i] 放在 nums[j] 后面以形成更长的上升子序列。

  • 初始状态

dp[i] = 1,其含义是nums的每个元素都至少可以单独成为子序列,此时长度都为1

  • 返回值

返回dp列表中的最大值,即可得到全局的最长上升子序列的长度

代码实现

  1. /**
  2. * @param {number[]} nums
  3. * @return {number}
  4. */
  5. var lengthOfLIS = function (nums) {
  6. let dp = new Array(nums.length);
  7. let ans = 0;
  8. for (let i = 0; i < nums.length; i++) {
  9. // 初始化 dp[i] 为 1,表示 nums 的每个元素都至少可以单独成为子序列,此时长度都为 1
  10. dp[i] = 1;
  11. for (let j = 0; j < i; j++) {
  12. // 当 nums[i] <= nums[j] 时: nums[i] 无法接在 nums[j] 之后,
  13. // 此情况上升子序列不成立,跳过
  14. if (nums[j] >= nums[i]) continue;
  15. // nums[i]>nums[j], nums[i] 可以接在 nums[j] 之后(此题要求严格递增),
  16. // 此情况下最长上升子序列长度为 dp[j] + 1
  17. dp[i] = Math.max(dp[i], dp[j] + 1);
  18. }
  19. // dp 列表中的最大值即为 全局最长上升子序列的长度
  20. ans = Math.max(dp[i], ans);
  21. }
  22. return ans;
  23. };