LeetCode 之 子序列问题

f6761c452567e3dcd2b9a8f37811f5f7.jpg

674. 最长连续递增序列

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

示例 1:

输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。

示例 2:

输入:nums = [2,2,2,2,2]
输出:1
解释:最长连续递增序列是 [2], 长度为1。

1. 确定 dp 以及下标的含义

dp[i]以下标i为结尾的数组的连续递增的子序列长度为 dp[i]

2. 确定递推公式

如果 nums[i + 1] > nums[i],那么以 i+1 为结尾的数组的连续递增的子序列长度一定等于以i为结尾的数组的连续递增的子序列长度 + 1 。

即:dp[i + 1] = dp[i] + 1

3. dp 数组初始化

以下标i为结尾的数组的连续递增的子序列长度最少也应该是1,即就是nums[i]这一个元素。所以dp[i]应该初始1。

4. 确定遍历顺序

从递推公式上可以看出, dp[i + 1] 依赖 dp[i],所以一定是从前向后遍历。

for (let i = 1; i < nums.length; i++) {
  if (nums[i - 1] < nums[i]) {
    dp[i] = dp[i - 1] + 1
  }
}

完整代码

let findLengthOfLCIS = function (nums) {
  if (nums.length === 1) return 1
  let dp = new Array(nums.length)
  let max = 0
  dp[0] = 1

  for (let i = 1; i < nums.length; i++) {
    if (nums[i - 1] < nums[i]) {
      dp[i] = dp[i - 1] + 1
    } else {
      dp[i] = 1
    }
    max = Math.max(max, dp[i])
  }
  return max
}

300. 最长递增子序列

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

1. 确定 dp 以及下标的含义

dp[i] 表示i之前包括i的最长上升子序列。

2. 状态转移方程

位置 i 的最长升序子序列等于 j0i-1 各个位置的最长升序子序列 + 1 的最大值。

所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

这里不是要 dp[i]dp[j] + 1进行比较,而是要取dp[j] + 1的最大值

3. dp[i] 的初始化

每一个i,对应的 dp[i](即最长上升子序列)起始大小至少都是是1。

4. 确定遍历顺序

dp[i] 是有0到i-1各个位置的最长升序子序列推导而来,那么遍历i一定是从前向后遍历。

j其实就是0到i-1,遍历i的循环里外层,遍历j则在内层,代码如下:

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

完整代码

var lengthOfLIS = function (nums) {
  if (nums.length == 0) {
    return 0
  }
  let dp = new Array(nums.length)
  dp[0] = 1
  let maxans = 1

  for (let i = 1; i < nums.length; i++) {
    dp[i] = 1
    for (let j = 0; j < i; j++) {
      if (nums[i] > nums[j]) {
        dp[i] = Math.max(dp[i], dp[j] + 1)
      }
    }
    maxans = Math.max(maxans, dp[i])
  }
  return maxans
};

673. 最长递增子序列的个数

给定一个未排序的整数数组,找到最长递增子序列的个数。

示例 1:

输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。

示例 2:

输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。

完整代码

var findNumberOfLIS = function (nums) {
  let n = nums.length, maxLen = 0, ans = 0;
  const dp = new Array(n).fill(0);
  const cnt = new Array(n).fill(0);
  for (let i = 0; i < n; ++i) {
    dp[i] = 1;
    cnt[i] = 1;
    for (let j = 0; j < i; ++j) {
      if (nums[i] > nums[j]) {
        if (dp[j] + 1 > dp[i]) {
          dp[i] = dp[j] + 1;
          cnt[i] = cnt[j]; // 重置计数
        } else if (dp[j] + 1 === dp[i]) {
          cnt[i] += cnt[j];
        }
      }
    }
    if (dp[i] > maxLen) {
      maxLen = dp[i];
      ans = cnt[i]; // 重置计数
    } else if (dp[i] === maxLen) {
      ans += cnt[i];
    }
  }
  return ans;
};

1027. 最长等差数列

给定一个整数数组 A,返回 A 中最长等差子序列的长度。

回想一下,A 的子序列是列表 A[i_1], A[i_2], ..., A[i_k] 其中 0 <= i_1 < i_2 < ... < i_k <= A.length - 1。并且如果 B[i+1] - B[i]( 0 <= i < B.length - 1) 的值都相同,那么序列 B 是等差的。

示例 1:

输入:[3,6,9,12]
输出:4
解释:
整个数组是公差为 3 的等差数列。

示例 2:

输入:[9,4,7,2,10]
输出:3
解释:
最长的等差子序列是 [4,7,10]。

示例 3:

输入:[20,1,15,3,10,5,8]
输出:4
解释:
最长的等差子序列是 [20,15,10,5]。

var longestArithSeqLength = function(nums) {
    const len = nums.length
    const dp = Array.from({ length: len }, () => new Array(len).fill(2))
    let ans = 2
    for (let i = 1; i < len; i++) { // 中间的数字
      for (let j = i + 1; j < len; j++) { // 后面的数字
         for (let k = 0; k < i; k++) { // 前面的数字
             if (nums[j] - nums[i] === nums[i] - nums[k]) { // 构成等差数列
                dp[i][j] = Math.max(dp[k][i] + 1, dp[i][j])
                ans = Math.max(dp[i][j], ans)
              }
            }
          }
        }
    return ans
};

334. 递增的三元子序列

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。

如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。

示例 1:

输入:nums = [1,2,3,4,5]
输出:true
解释:任何 i < j < k 的三元组都满足题意

示例 2:

输入:nums = [5,4,3,2,1]
输出:false
解释:不存在满足题意的三元组

示例 3:

输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6

这道题是 300. 最长递增子序列 变种过来的,思路差不多,只有处理一些小细节就可以了。当然肯定有更加优秀的解法,这里就留给读者发挥了。

1. 确定 dp 以及下标的含义

dp[i] 表示i之前包括i的最长上升子序列。

2. 状态转移方程

位置 i 的最长升序子序列等于 j0i-1 各个位置的最长升序子序列 + 1 的最大值。

所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

这里不是要 dp[i]dp[j] + 1进行比较,而是要取dp[j] + 1的最大值

3. dp[i] 的初始化

每一个i,对应的 dp[i](即最长上升子序列)起始大小至少都是是1。

4. 确定遍历顺序

dp[i] 是有0到i-1各个位置的最长升序子序列推导而来,那么遍历i一定是从前向后遍历。

j其实就是0到i-1,遍历i的循环里外层,遍历j则在内层,代码如下:

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

完整代码

var increasingTriplet = function (nums) {
  if (nums.length == 0) {
    return 0
  }
  if (Array.from(new Set(nums)).length === 2) {
      return false
  }
  let dp = new Array(nums.length)
  dp[0] = 1
  let maxans = 1

  for (let i = 1; i < nums.length; i++) {
    dp[i] = 1
    for (let j = 0; j < i; j++) {
      if (nums[i] > nums[j]) {
        dp[i] = Math.max(dp[i], dp[j] + 1)
      }
    }
    maxans = Math.max(maxans, dp[i])
    if (maxans >= 3) {
        return true
    }
  }
  return false
};

646. 最长数对链

给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。

现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。

给定一个数对集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。

示例:

输入:[[1,2], [2,3], [3,4]]
输出:2
解释:最长的数对链是 [1,2] -> [3,4]

这道题是 300. 最长递增子序列 变种过来的,思路差不多,只有处理一些小细节就可以了。当然肯定有更加优秀的解法,这里就留给读者发挥了。

先对 envelopes 进行排序,之后进行套模板就行了。

1. 确定 dp 以及下标的含义

dp[i] 表示i之前包括i的最长上升子序列。

2. 状态转移方程

位置 i 的最长升序子序列等于 j0i-1 各个位置的最长升序子序列 + 1 的最大值。

所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

这里不是要 dp[i]dp[j] + 1进行比较,而是要取dp[j] + 1的最大值

3. dp[i] 的初始化

每一个i,对应的 dp[i](即最长上升子序列)起始大小至少都是是1。

4. 确定遍历顺序

dp[i] 是有0到i-1各个位置的最长升序子序列推导而来,那么遍历i一定是从前向后遍历。

j其实就是0到i-1,遍历i的循环里外层,遍历j则在内层,代码如下:

for (let i = 1; i < nums.length; i++) {
  for (let j = 0; j < i; j++) {
    if (envelopes[i][0] > envelopes[j][1]) {
      dp[i] = Math.max(dp[i], dp[j] + 1)
    }
  }
}

完整代码

var findLongestChain = function (envelopes) {
  let n = envelopes.length;
  if (n < 1) return 0;
  let max = 1;
  let dp = new Array(n).fill(1);
  dp[0] = 1;
  envelopes.sort((a, b) => a[0] - b[0])

  for (let i = 1; i < n; i++) {
    for (let j = 0; j < i; j++) {
      if (envelopes[i][0] > envelopes[j][1]) {
        dp[i] = Math.max(dp[i], dp[j] + 1)
      }
    }
    max = Math.max(max, dp[i]);
  }

  return max;
};

354. 俄罗斯套娃信封问题

给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。

当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

注意:不允许旋转信封。

示例 1:

输入:envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:3
解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。

示例 2:

输入:envelopes = [[1,1],[1,1],[1,1]]
输出:1

这道题是 300. 最长递增子序列 变种过来的,思路差不多,只有处理一些小细节就可以了。当然肯定有更加优秀的解法,这里就留给读者发挥了。

先对 envelopes 进行排序,之后进行套模板就行了。

1. 确定 dp 以及下标的含义

dp[i] 表示i之前包括i的最长上升子序列。

2. 状态转移方程

位置 i 的最长升序子序列等于 j0i-1 各个位置的最长升序子序列 + 1 的最大值。

所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

这里不是要 dp[i]dp[j] + 1进行比较,而是要取dp[j] + 1的最大值

3. dp[i] 的初始化

每一个i,对应的 dp[i](即最长上升子序列)起始大小至少都是是1。

4. 确定遍历顺序

dp[i] 是有0到i-1各个位置的最长升序子序列推导而来,那么遍历i一定是从前向后遍历。

j其实就是0到i-1,遍历i的循环里外层,遍历j则在内层,代码如下:

for (let i = 1; i < nums.length; i++) {
  for (let j = 0; j < i; j++) {
    if (envelopes[i][0] > envelopes[j][0] && envelopes[i][1] > envelopes[j][1]) {
      dp[i] = Math.max(dp[i], dp[j] + 1)
    }
  }
}

完整代码

var maxEnvelopes = function (envelopes) {
  let n = envelopes.length;
  if (n < 1) return 0;
  let max = 1;
  let dp = new Array(n).fill(1);
  dp[0] = 1;
  envelopes.sort((a, b) => a[0] - b[0])

  for (let i = 1; i < n; i++) {
    for (let j = 0; j < i; j++) {
      if (envelopes[i][0] > envelopes[j][0] && envelopes[i][1] > envelopes[j][1]) {
        dp[i] = Math.max(dp[i], dp[j] + 1)
      }
    }
    max = Math.max(max, dp[i]);
  }

  return max;
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,820评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,648评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,324评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,714评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,724评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,328评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,897评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,804评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,345评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,431评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,561评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,238评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,928评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,417评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,528评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,983评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,573评论 2 359

推荐阅读更多精彩内容