2022-11-19 00:09阅读: 272评论: 0推荐: 0

【动态规划】最长递增子序列

最长递增子序列

简介

最长递增子序列(Longest Increasing Subsequence)是指找到一个给定序列的最长子序列的长度,使得子序列中的所有元素单调递增

例如,nums=[357128]LIS[3578],长度为 4

分析

方法一: 转换为LCS

我们可以把 求最长递增子序列问题 转化为求 最长公共子序列 的问题。

例如,A=[357128] 的最长递增子序列,那么可以将数组 A 排序,排序之后的数组为 B=[1,2,3,5,7,8],只需求数组 A 与数组 B 的最长公共子序列即可。
数组 A 与数组 B 最长公共子序列就是 [3578],就是最长递增子序列。

参考:最长公共子序列

方法二:动态规划

设数组 nums={a0, a1, a2, ..., an1}dp[i]表示以元素 ai 结尾的最长递增子序列。

初始条件

每一个字符都是长度为 1 的递增子序列,即:

L[i]=1

状态转移方程

每一个较大的元素结尾的子序列,都可以由以它前一个小于它的元素结尾的子序列转移而来。

这里,我们直接给出状态转移方程,当 j<i<n  aj<ai 时,有:

dp[i]=max(dp[j]+1, dp[i])

例如,A=[357128],由于 a1>a0,所以 dp[1]=dp[0]+1=2

代码实现

from typing import List
class Solution:
@staticmethod
def lengthOfLIS(nums: List[int]) -> int:
n = len(nums)
dp = [1] * n
for i in range(1, n):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[j] + 1, dp[i])
return max(dp)

方法三:二分法

tails[i] 表示长度为 i+1 的递增子序列的末尾元素的最小值

也就是说,我们要保证 tails 数组中的每个位置的元素都是尽可能地小,同时,我们将 tail 全都初始化为 0

例如,假设 nums=[4,5,6,3] ,那么:

  • i=0 时,所有的递增子序列:[4][5][6][3],此时 tails[0]=3
  • i=1 时, 所有的递增子序列:[4,5], [5,6], [4,6],此时 tails[1]=5
  • i=2 时, 所有的递增子序列:[4,5,6],此时 tails[2]=6
  • i=3 时,不存在递增子序列。

容易看出,tails 是一个递增序列,所以,我们只需要遍历数组 nums ,对于每一个元素,通过二分查找,找到该元素可以被插入tails 数组中的位置即可,数组 nums 中的任意一个元素,在插入数组 tails[i] 中时,都有两种情况:

  • 要么,它能找到一个位置,并 替换 掉已有元素;
  • 或者,它大于当前 tails 中所有的元素,这时将其 追加 tails 已有元素的最后。

遍历完数组后,最终,tails 数组中不为零的元素个数,就是最长递增子序列的长度

例如,以 nums=[3,9,4,7,4,12] 为例:

  • i=0 时,在 tails 的位置 0 插入元素 3tails=[3,0,0,0,0,0]
  • i=1 时,在 tails 的位置 1 插入元素 9tails=[3,9,0,0,0,0]
  • i=2 时,在 tails 的位置 1 覆盖元素 4tails=[3,4,0,0,0,0]
  • i=3 时,在 tails 的位置 2 插入元素 7tails=[3,4,7,0,0,0]
  • i=4 时,在 tails 的位置 1 覆盖元素 4tails=[3,4,7,0,0,0]
  • i=5 时,在 tails 的位置 3 追加元素 12tails=[3,4,7,12,0,0]

最终,tails 数组中不为零的元素为 [3,4,7,12],就是最长递增子序列的长度,所以,该数组的最长递增子序列长度为 4

代码实现

from typing import List
class Solution:
@staticmethod
def lengthOfLIS(nums: List[int]) -> int:
""" 求最长递增子序列 """
tails = [0] * len(nums)
size = 0
for num in nums:
left, right = 0, size
# 通过二分法,为num在tail数组中一个插入位置
while left != right:
mid = (left + right) // 2
if tails[mid] < num:
left = mid + 1
else:
right = mid
tails[left] = num
# 记录tail数组中不为零的元素的长度
if left == size:
size += 1
return size

应用

应用1:Leetcode.300

题目

300. 最长递增子序列

分析

参考前面的算法分析。

代码实现

class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
dp = [1] * n
for i in range(1, n):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[j] + 1, dp[i])
return dp[n - 1]

应用2:Leetcode.354

题目

354. 俄罗斯套娃信封问题

分析

由于能嵌套的信封的长和宽必须严格递减,所以我们将原有的信封尺寸,先按照 w 升序排列,如果 w 相同的时候,将 h 降序排列,这样就得到一个排序的二维数组。

然后,再取排序后的h最长递增子序列,就是能嵌套的信封个数,即可得到答案。

代码实现

from typing import List
class Solution:
def maxEnvelopes(self, envelopes: List[List[int]]) -> int:
# 基于w升序,h降序排序
envelopes = sorted(envelopes, key=lambda x: (x[0], -x[1]))
# 寻找以h升序的最长子序列
heights = [envelope[1] for envelope in envelopes]
return self.lengthOfLIS(heights)
@staticmethod
def lengthOfLIS(nums: List[int]) -> int:
""" 求最长递增子序列 """
tails = [0] * len(nums)
size = 0
for num in nums:
left, right = 0, size
while left != right:
mid = (left + right) // 2
if tails[mid] < num:
left = mid + 1
else:
right = mid
tails[left] = num
if left == size:
size += 1
return size

本文作者:LARRY1024

本文链接:https://www.cnblogs.com/larry1024/p/16901322.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LARRY1024  阅读(272)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.