• 概述

    经典的最长上升子序列问题,是求在一个序列中,最长的单增子序列的长度。通过修改一些边界条件,也可以求非严格单增、单减的子序列长度。同时很多问题也可以转化为最长上升子序列问题。
    最长上升子序列有两种经典的求解方法,一种基于动态规划,时间复杂度最长上升子序列 - 图1,另一种基于二分查找优化,时间复杂度最长上升子序列 - 图2
    读者可以先练习Leetcode第300号问题。下面给出参考代码。
    动态规划

    1. class Solution:
    2. def lengthOfLIS(self, nums: List[int]) -> int:
    3. dp = [1] * len(nums)
    4. for i in range(1, len(nums)):
    5. for j in range(i):
    6. if nums[i] > nums[j]: # 严格单增,如果非严格单增,可以改为 >=
    7. dp[i] = max(dp[i], dp[j] + 1)
    8. return max(dp)

    二分查找

    1. class Solution:
    2. def lengthOfLIS(self, nums: List[int]) -> int:
    3. d = []
    4. for a in nums:
    5. i = bisect.bisect_left(d, a) # 严格单增,如果非严格单增,可改为bisect_right
    6. if i < len(d):
    7. d[i] = a
    8. else:
    9. d.append(a)
    10. return len(d)
    • 最长公共子序列与最长上升子序列的相互转化

    给你一个数组 target ,包含若干 互不相同 的整数,以及另一个整数数组 arr ,arr 可能 包含重复元素。
    每一次操作中,你可以在 arr 的任意位置插入任一整数。比方说,如果 arr = [1,4,1,2] ,那么你可以在中间添加 3 得到 [1,4,3,1,2] 。你可以在数组最开始或最后面添加整数。
    请你返回 最少 操作次数,使得 target 成为 arr 的一个子序列。
    一个数组的 子序列 指的是删除原数组的某些元素(可能一个元素都不删除),同时不改变其余元素的相对顺序得到的数组。比方说,[2,7,4] 是 [4,2,3,7,2,1,4] 的子序列(加粗元素),但 [2,4,2] 不是子序列。

    数据规模:
    最长上升子序列 - 图3
    target 不包含任何重复元素。

    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/minimum-operations-to-make-a-subsequence
    分析:
    本题很容易通过最长公共子序列建模,单最长公共子序列的时间复杂度为最长上升子序列 - 图4,本题所给的数据规模下会超时。因为target中不包含重复元素。所以可以将target中的元素进行编号,然后在arr中寻找最长上升子序列,最长上升子序列的长度就对应着最长公共子序列的长度。对于最长上升子序列问题,使用最长上升子序列 - 图5的算法进行求解。
    参考解答:

    1. class Solution:
    2. def minOperations(self, target: List[int], A: List[int]) -> int:
    3. def LIS(A):
    4. d = []
    5. for a in A:
    6. i = bisect.bisect_left(d, a)
    7. if i < len(d):
    8. d[i] = a
    9. else:
    10. d.append(a)
    11. return len(d)
    12. B = []
    13. target = { t:i for i, t in enumerate(target)}
    14. for a in A:
    15. if a in target:
    16. B.append(target[a])
    17. return len(target) - LIS(B)