子序列

  • ★402. 移掉 K 位数字
  • 60. 排列序列
  • 115. 不同的子序列
  • 128. 最长连续序列
  • 187. 重复的DNA序列
  • 297. 二叉树的序列化与反序列化
  • 298. 二叉树最长连续序列
  • 300. 最长递增子序列
  • 334. 递增的三元子序列
  • 364. 加权嵌套序列和 II
  • 376. 摆动序列
  • 428. 序列化和反序列化 N 叉树
  • 444. 序列重建
  • 446. 等差数列划分 II - 子序列
  • 449. 序列化和反序列化二叉搜索树
  • 459. 重复的子字符串
  • 516. 最长回文子序列
  • 521. 最长特殊序列 Ⅰ
  • 522. 最长特殊序列 II
  • 647. 回文子串
  • 659. 分割数组为连续子序列
  • 674. 最长连续递增序列
  • 696. 计数二进制子串
  • 708. 循环有序列表的插入
  • 727. 最小窗口子序列
  • 730. 统计不同回文子序列
  • 761. 特殊的二进制序列
  • 792. 匹配子序列的单词数
  • 795. 区间子数组个数
  • 801. 使序列递增的最小交换次数
  • 811. 子域名访问计数
  • 846. 一手顺子
  • 873. 最长的斐波那契子序列的长度
  • 891. 子序列宽度之和
  • 903. DI 序列的有效排列
  • 936. 戳印序列
  • 940. 不同的子序列 II
  • 946. 验证栈序列
  • 1081. 不同字符的最小子序列
  • 1092. 最短公共超序列
  • 1121. 将数组分成几个递增序列
  • 1220. 统计元音字母序列的数目
  • 1233. 删除子文件夹
  • 1248. 统计「优美子数组」
  • 1258. 近义词句子
  • 1332. 删除回文子序列
  • 1359. 有效的快递序列数目
  • 1403. 非递增顺序的最小子序列
  • 1425. 带限制的子序列和
  • 1458. 两个子序列的最大点积
  • 1476. 子矩形查询
  • 1498. 满足条件的子序列数目
  • 1622. 奇妙序列
  • 1630. 等差子数组
  • 1673. 找出最具竞争力的子序列
  • 1682. 最长回文子序列 II
  • 1713. 得到子序列的最少操作次数
  • 1718. 构建字典序最大的可行序列
  • 1755. 最接近目标值的子序列和
  • 1771. 由子序列构造的最长回文串的长度
  • 1819. 序列中不同最大公约数的数目
  • 1905. 统计子岛屿
  • 1911. 最大子序列交替和
  • 1930. 长度为 3 的不同回文子序列
  • 1940. 排序数组之间的最长公共子序列
  • 1955. 统计特殊子序列的数目
  • 1987. 不同的好子序列数目
  • 2002. 两个回文子序列长度的最大乘积
  • 2014. 重复 K 次的最长子序列
  • 2030. 含特定字母的最小子序列
  • 392. 判断子序列
  • 方法一:双指针
  • 方法二:动态规划
  • 方法三:
  • re 超时
  • 491. 递增子序列
  • 方法一:动态规划 + 哈希表
  • 方法二:深度优先搜索+哈希表
  • 方法三:广度优先搜索+哈希表
  • 594. 最长和谐子序列
  • 方法一:双指针 滑动窗口
  • 方法二:哈希表
  • 673. 最长递增子序列的个数
  • 方法一:动态规划
  • 方法二:贪心 + 前缀和 + 二分查找
  • 1143. 最长公共子序列
  • 方法一:动态规划
  • 二维数组到一维数组的优化
  • 二维数组到一维数组的优化
  • 1218. 最长定差子序列
  • 方法一:从前往后找 34 / 39 个通过测试用例
  • 方法二:动态规划


★402. 移掉 K 位数字

Leetcode

class Solution(object):
    def removeKdigits(self, num, k):
        stack = []
        remain = len(num) - k
        for digit in num:
            while k and stack and stack[-1] > digit:
                stack.pop()
                k -= 1
            stack.append(digit)
            
        return ''.join(stack[:remain]).lstrip('0') or '0'

60. 排列序列

115. 不同的子序列

128. 最长连续序列

187. 重复的DNA序列

297. 二叉树的序列化与反序列化

298. 二叉树最长连续序列

300. 最长递增子序列

334. 递增的三元子序列

364. 加权嵌套序列和 II

376. 摆动序列

428. 序列化和反序列化 N 叉树

444. 序列重建

446. 等差数列划分 II - 子序列

449. 序列化和反序列化二叉搜索树

459. 重复的子字符串

516. 最长回文子序列

521. 最长特殊序列 Ⅰ

522. 最长特殊序列 II

647. 回文子串

659. 分割数组为连续子序列

674. 最长连续递增序列

696. 计数二进制子串

708. 循环有序列表的插入

727. 最小窗口子序列

730. 统计不同回文子序列

761. 特殊的二进制序列

792. 匹配子序列的单词数

795. 区间子数组个数

801. 使序列递增的最小交换次数

811. 子域名访问计数

846. 一手顺子

873. 最长的斐波那契子序列的长度

891. 子序列宽度之和

903. DI 序列的有效排列

936. 戳印序列

940. 不同的子序列 II

946. 验证栈序列

1081. 不同字符的最小子序列

1092. 最短公共超序列

1121. 将数组分成几个递增序列

1220. 统计元音字母序列的数目

1233. 删除子文件夹

1248. 统计「优美子数组」

1258. 近义词句子

1332. 删除回文子序列

1359. 有效的快递序列数目

1403. 非递增顺序的最小子序列

1425. 带限制的子序列和

1458. 两个子序列的最大点积

1476. 子矩形查询

1498. 满足条件的子序列数目

1622. 奇妙序列

1630. 等差子数组

1673. 找出最具竞争力的子序列

1682. 最长回文子序列 II

1713. 得到子序列的最少操作次数

1718. 构建字典序最大的可行序列

1755. 最接近目标值的子序列和

1771. 由子序列构造的最长回文串的长度

1819. 序列中不同最大公约数的数目

1905. 统计子岛屿

1911. 最大子序列交替和

1930. 长度为 3 的不同回文子序列

1940. 排序数组之间的最长公共子序列

1955. 统计特殊子序列的数目

1987. 不同的好子序列数目

2002. 两个回文子序列长度的最大乘积

2014. 重复 K 次的最长子序列

2030. 含特定字母的最小子序列

剑指 Offer 26. 树的子结构
剑指 Offer 31. 栈的压入、弹出序列
剑指 Offer 33. 二叉搜索树的后序遍历序列
剑指 Offer 37. 序列化二叉树
剑指 Offer 42. 连续子数组的最大和
剑指 Offer 44. 数字序列中某一位的数字
剑指 Offer 48. 最长不含重复字符的子字符串
剑指 Offer 57 - II. 和为s的连续正数序列
剑指 Offer 61. 扑克牌中的顺子
剑指 Offer II 008. 和大于等于 target 的最短子数组
剑指 Offer II 009. 乘积小于 K 的子数组
剑指 Offer II 010. 和为 k 的子数组
剑指 Offer II 011. 0 和 1 个数相同的子数组
剑指 Offer II 012. 左右两边子数组的和相等
剑指 Offer II 013. 二维子矩阵的和
剑指 Offer II 020. 回文子字符串的个数
剑指 Offer II 048. 序列化与反序列化二叉树
剑指 Offer II 086. 分割回文子字符串
剑指 Offer II 095. 最长公共子序列
剑指 Offer II 097. 子序列的数目
剑指 Offer II 115. 重建序列
剑指 Offer II 119. 最长连续序列

392. 判断子序列

Leetcode

方法一:双指针

当从前往后匹配,可以发现每次贪心地匹配靠前的字符是最优决策。

假定当前需要匹配字符 c,而字符 c 在 t 中的位置 x1 和 x2 出现(x1 < x2),那么贪心取 x1 是最优解,因为 x2 后面能取到的字符,x1 也都能取到,并且通过 x1 与 x2 之间的可选字符,更有希望能匹配成功。

初始化两个指针 i 和 j,分别指向 s 和 t 的初始位置。每次贪心地匹配,匹配成功则 i 和 j 同时右移,匹配 s 的下一个位置,匹配失败则 j 右移,i 不变,尝试用 t 的下一个字符匹配 s。

最终如果 i 移动到 s 的末尾,就说明 s 是 t 的子序列。

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        n, m = len(s), len(t)
        i = j = 0
        while i < n and j < m:
            if s[i] == t[j]:
                i += 1
            j += 1
        return i == n

方法二:动态规划

双指针,大量的时间用于在 t 中找下一个匹配字符。

定义状态: dp[i][j] 表示 s[:i] 是 t[:j] 的子序列。

dp 初始化:

m, n = len(s), len(t)
dp = [[False] * (n +1) for _ in range(m + 1)]
for i in range(n+1): # s = ""
    dp[0][i] = True

转移方程:
Leetcode 子序列_算法

返回: dp[m][n]

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        m, n = len(s), len(t)
        dp = [[False] * (n +1) for _ in range(m + 1)]
        for i in range(n+1): # s = ""
            dp[0][i] = True
        
        for i in range(m):
            for j in range(n):                
                if s[i] == t[j]:
                    dp[i + 1][j + 1] = dp[i][j]
                else:
                    dp[i + 1][j + 1] = dp[i + 1][j]
        return dp[m][n]

方法三:

对于 s 的每一个字符,从 t 中找,找到了,记录位置,下一个字符,从该位置后面找。

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        temp = -1 
        for i in range(len(s)):
            for j in range(temp+1, len(t)):
                if s[i] == t[j]:
                    temp = j
                    break
            else:
                return False
        return True

re 超时

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool: 
        return bool(re.findall('.*?'.join(list(s)), t))

491. 递增子序列

Leetcode

方法一:动态规划 + 哈希表

简单分析一下为了理解:
状态定义:dp[i] 表示以 nums[i] 结尾的所有递增子序列集合
初始化:dp[0] = {(num[0], )}
转移方程:dp[i] = dp[i-1].update({k + (nums[i], ) for k in dp[i-1] if k[-1] <= num})

因为 dp[i] 之于 dp[i-1] 有关系,所以简化以后用一个集合 pres 来保存状态:
初始 pres = {(num[0], )}
转移方程:

# 原来的基础上进行了扩充,update() 添加新的元素或集合到当前集合中。
pres.update({j+(i, ) for j in pres if j[-1] <= i}) 
pres.add((i, ))

返回符合要求的递增子序列。

class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        if not nums: return []
        pres = {(nums[0],)}
        for num in nums[1:]:
            pres.update({x+(num, ) for x in pres if x[-1] <= num})
            pres.add((num,))
            
        return [list(e) for e in pres if len(e) > 1]

方法二:深度优先搜索+哈希表

class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        res = []
        def dfs(nums: List[int], tmp: List[int]) -> None:
            if len(tmp) > 1: res.append(tmp)
            curPres = set()
            for inx, x in enumerate(nums):
                if x in curPres: continue
                if not tmp or x >= tmp[-1]:
                    curPres.add(x)
                    dfs(nums[inx+1:], tmp+[x])

        dfs(nums, [])
        return res

方法三:广度优先搜索+哈希表

class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        res = []
        d = deque([(nums, [])])
        while d:
            cur, new = d.popleft()
            if len(new) > 1:
                res.append(new)
            curPres = set()
            for inx, i in enumerate(cur):
                if i in curPres:
                    continue
                if not new or i >= new[-1]:
                    curPres.add(i)
                    d.append((cur[inx+1:], new+[i]))
        return res

594. 最长和谐子序列

Leetcode

方法一:双指针 滑动窗口

排序后使用双指针,只要窗口内的数差最大为 1,就扩大窗口,如果差为 1 开始更新 res;反之缩小窗口。

class Solution:
    def findLHS(self, nums: List[int]) -> int:
        nums.sort()
        res = i = 0
        for j in range(len(nums)):
            while nums[j] - nums[i] > 1: # 收缩窗口,直到窗口内差为 1
                i += 1
            if nums[j] - nums[i] == 1: # 逐步扩大右边界,同时更新 res。
                res = max(res, j - i + 1)
                
        return res

记录差一的个数,更新左指针及 res。

class Solution:
    def findLHS(self, nums: List[int]) -> int:
        nums.sort()
        nums.append(10 ** 9 + 2)
        res = i = slow = k = 0
  
        # for i in range(1, len(nums)): # 不知错在那里
        while i < len(nums):        
            if nums[i] - nums[slow] > 1: # 如果窗口差一,更新 res
                if k:
                    res = max(res, i - slow)
                slow = i - k
                k = 0               
            else:
                if nums[i] > nums[slow]:
                    k += 1 # k 记录差一的个数
                i += 1
            
        return res

方法二:哈希表

class Solution:
    def findLHS(self, nums: List[int]) -> int:
        hash = Counter(nums)
        return max((val + hash[key + 1] for key, val in hash.items() if key + 1 in hash), default=0)

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

Leetcode300. 最长递增子序列

方法一:动态规划

Leetcode 子序列_动态规划_02Leetcode 子序列_算法_03 分别表示以 Leetcode 子序列_动态规划_04

状态转移方程为:

Leetcode 子序列_leetcode_05

对于 Leetcode 子序列_算法_03,其等于所有满足 Leetcode 子序列_leetcode_07Leetcode 子序列_leetcode_08

class Solution:
    def findNumberOfLIS(self, nums: List[int]) -> int:
        n, max_len, ans = len(nums), 0, 0
        dp = [1] * n
        cnt = [1] * n
        for i, x in enumerate(nums):
            for j in range(i):
                if x > nums[j]:
                    if dp[j] + 1 > dp[i]:
                        dp[i] = dp[j] + 1
                        cnt[i] = cnt[j]  # 重置计数
                    elif dp[j] + 1 == dp[i]:
                        cnt[i] += cnt[j]
            if dp[i] > max_len:
                max_len = dp[i]
                ans = cnt[i]  # 重置计数
            elif dp[i] == max_len:
                ans += cnt[i]
                
        return ans

方法二:贪心 + 前缀和 + 二分查找

将数组 d 扩展成一个二维数组,其中 d[i] 数组表示所有能成为长度为 i 的最长上升子序列的末尾元素的值。具体地,将更新 Leetcode 子序列_leetcode_09 这一操作替换成将 Leetcode 子序列_子序列_10 置于 Leetcode 子序列_List_11 数组末尾。这样 Leetcode 子序列_List_11 中就保留了历史信息,且 Leetcode 子序列_List_11

类似地,也可以定义一个二维数组 Leetcode 子序列_leetcode_14,其中 Leetcode 子序列_子序列_15 记录了以 Leetcode 子序列_List_16 为结尾的最长上升子序列的个数。为了计算 Leetcode 子序列_子序列_15,可以考察 Leetcode 子序列_算法_18Leetcode 子序列_动态规划_19,将所有满足 Leetcode 子序列_leetcode_20Leetcode 子序列_leetcode_21 累加到 Leetcode 子序列_子序列_15,这样最终答案就是 Leetcode 子序列_动态规划_23

在代码实现时,由于 Leetcode 子序列_List_11 中的元素是有序的,可以二分得到最小的满足 Leetcode 子序列_leetcode_20 的下标 k。另一处优化是将 Leetcode 子序列_leetcode_14 改为其前缀和,并在开头填上 0,此时 Leetcode 子序列_List_16 对应的最长上升子序列的个数就是 Leetcode 子序列_List_28,这里 Leetcode 子序列_leetcode_29

class Solution:
    def findNumberOfLIS(self, nums: List[int]) -> int:
        d, cnt = [], []
        for v in nums:
            i = bisect(len(d), lambda i: d[i][-1] >= v)
            c = 1
            if i > 0:
                k = bisect(len(d[i - 1]), lambda k: d[i - 1][k] < v)
                c = cnt[i - 1][-1] - cnt[i - 1][k]
            if i == len(d):
                d.append([v])
                cnt.append([0, c])
            else:
                d[i].append(v)
                cnt[i].append(cnt[i][-1] + c)
        return cnt[-1][-1]

def bisect(n: int, f: Callable[[int], bool]) -> int:
    l, r = 0, n
    while l < r:
        mid = (l + r) // 2
        if f(mid):
            r = mid
        else:
            l = mid + 1
    return l

1143. 最长公共子序列

Leetcode583. 两个字符串的删除操作

方法一:动态规划

最长公共子序列:Longest Common Subsequence 简称 LCS

状态定义: dp[i][j] 表示 text1[:i] 与 text2[:j] 的 LCS 的长度

初始化: dp = [[0] * (n + 1) for _ in range(m + 1)]
注意 行 m, 列 n,dp[0][0] 表示 text1 = text2 = “”。

转移方程:
Leetcode 子序列_动态规划_30

为了避免判断 i - 1,j - 1 合法性,直接定义 dp 数组 (n+1) * (m+1)。注意 dp 与 text 的索引

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        m, n = len(text1), len(text2)
        dp = [[0] * (n + 1) for _ in range(m + 1)] # 行 m, 列 n
        
        for i in range(m):
            for j in range(n):
                if text1[i] == text2[j]:
                    dp[i+1][j+1] = dp[i][j] + 1
                else:
                    dp[i+1][j+1] = max(dp[i+1][j],dp[i][j+1])

        return dp[m][n]

二维数组到一维数组的优化

对动态规划进行了优化,因为只涉及到两行,分别定义两个变量记录。

j

j + 1

a

……

a[j]

a[j+1]

b

……

b[j]

b[j+1]

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int: 
        m, n = len(text1), len(text2)
        
        if text1 in text2: return m
        if text2 in text1: return n 
       
        a, b = [0] * (n + 1),  [0] * (n + 1)

        for i in range(m):
            for j in range(n):
                if text1[i] == text2[j]: b[j+1] = a[j] + 1
                else: b[j+1] = max(b[j],a[j+1])
                    
            a, b = b, a                
        
        return a[n]

二维数组到一维数组的优化

对动态规划进行了优化,因为只涉及到两行,用到第一行与第二行前面数据,所以可以优化到一维数组。

Leetcode 子序列_List_31

比如计算 dp[2][2] 的时候, 可能用到 dp[1][1], dp[1][2], 和dp[2][1]。 左侧数据在同一行,上方数据还未被覆盖,注意左上方的数据,因为在计算前一列的时候可能会被覆盖,需要保存。

Leetcode 子序列_动态规划_32

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int: 
        m, n = len(text1), len(text2)        
        if text1 in text2: return m
        if text2 in text1: return n        
        dp = [0] * (n + 1)

        for i in range(m):
            upLeft = dp[0] # 每一行的开始都是 0 
            for j in range(n):
                tmp = dp[j + 1] # 缓存 dp[j + 1], 后面会被覆盖。
                if text1[i] == text2[j]: 
                    dp[j+1] = upLeft + 1
                else: 
                    dp[j+1] = max(dp[j],dp[j+1])
                    
                upLeft = tmp  # 下一次作为 dp[i][j] 用           
        
        return dp[n]

1218. 最长定差子序列

Leetcode

方法一:从前往后找 34 / 39 个通过测试用例

class Solution:
    def longestSubsequence(self, arr: List[int], difference: int) -> int:
        res, n = 0, len(arr)
        for i in range(n):
            cnt, x, j = 1, arr[i], i
   
            while j+1 < n:
                y = difference + x
                if y in arr[j + 1:]:
                    idx = arr.index(y, j + 1)               
                    cnt += 1
                    x = arr[idx]
                    j = idx
                else:break                
                
            res = max(res, cnt)
            
        return res

方法二:动态规划

dp[i] 表示以 arr[i] 为结尾的最长的等差子序列的长度
在 arr[i] 左侧找等差数列的前一项,即 arr[i] − d 的元素,如果存在,将 arr[i] 加到以 arr[j] 为结尾的最长的等差子序列的末尾。

转移方程: dp[v] = dp[v − d] + 1

class Solution:
    def longestSubsequence(self, arr: List[int], difference: int) -> int:
        dp = defaultdict(int) 
        for v in arr:
            dp[v] = dp[v - difference] + 1
        return max(dp.values())