子序列问题
- 一般指“不连续的序列”
- 1个序列,2个序列,。。。
- 最长,最少,。。。
- DP: 复杂度一般是 或
- 经典题目
- LIS 单串
- LCS 双串
- 编辑距离
300. 最长递增子序列
- 子序列:不需要连续
- 单串DP
dp[i]: 以ith结尾
- 复杂度. 时间:, 空间:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
dp = [0] * len(nums)
dp[0] = 1
for i in range(1, len(nums)):
dp[i] = 1
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
dp[i]: 长度为i的LIS的最后一个元素的最小可能值。这样就能构造尽可能长的递增子序列。
初始化dp[0] = float('-inf')
dp是严格递增的 (单调栈?)
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
def BinarySearch_insert(dp, target):
left, right = 0, len(dp)-1
while left <= right:
mid = left + (right - left) // 2
if dp[mid] > target:
right = mid - 1
elif dp[mid] < target:
left = mid + 1
else: # nums[mid] == target
right = mid - 1
return left # it's possible to have left = len(dp) + 1
n = len(nums)
dp = [float('-inf')]
res = 0
for i in range(n):
# 在dp中找最小的index使得dp[index] >= nums[i], 二分找左边界
insert_idx = BinarySearch_insert(dp, nums[i])
if insert_idx == len(dp):
dp.append(nums[i])
res += 1
else:
dp[insert_idx] = nums[i]
return res
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
def BinarySearch(nums, target):
n = len(nums)
left, right = 0, n-1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return left
n = len(nums)
stack = [nums[0]]
for i in range(1, n):
idx = BinarySearch(stack, nums[i])
if idx == len(stack):
stack.append(nums[i])
else:
stack[idx] = nums[i]
return len(stack)
1143. 最长公共子序列
- 思路
- example
- Longest Common Subsequence (LCS)
- 双串,二维DP, 不需要连续
dp[i][j]: text1 前i个,text2前j个 结果,i.e., text1: 0,...,i-1; text2: 0,...,j-1
注意对dp[i][j], 对应的最长公共子序列并不一定以i-1th 和 j-1th 结尾。
方便初始化处理, i.e., dp[0][j] = dp[i][0] = 0
Goal: dp[m][n]
- 复杂度. 时间:O(mn), 空间: O(mn)
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)]
for i in range(1, m+1):
for j in range(1, n+1):
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
72. 编辑距离
dp[i][j]
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
dp = [[0 for _ in range(n+1)] for _ in range(m+1)]
for j in range(1, n+1):
dp[0][j] = j
for i in range(1, m+1):
dp[i][0] = i
for i in range(1, m+1):
for j in range(1, n+1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1
return dp[m][n]
其它类似问题
583. 两个字符串的删除操作
- 思路
- example
- 最小步数
dp[i][j]: word1 前i个,word2前j个 (word1: 0, ..., i-1; word2: 0, ..., j-1)
- 复杂度. 时间:O(mn), 空间: O(mn)
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
m, n = len(word1), len(word2)
dp = [[0] * (n+1) for _ in range(m+1)]
for j in range(n+1):
dp[0][j] = j
for i in range(m+1):
dp[i][0] = i
for i in range(1, m+1):
for j in range(1, n+1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1] # 不需删除
else:
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + 1 # 删除word1的i-1th或word2的j-1th
return dp[m][n]
712. 两个字符串的最小ASCII删除和
- 思路
- example
- 类似上题
- 复杂度. 时间:O(mn), 空间: O(mn)
class Solution:
def minimumDeleteSum(self, s1: str, s2: str) -> int:
m, n = len(s1), len(s2)
dp = [[0] * (n+1) for _ in range(m+1)]
for j in range(1, n+1):
dp[0][j] = dp[0][j-1] + ord(s2[j-1])
for i in range(1, m+1):
dp[i][0] = dp[i-1][0] + ord(s1[i-1])
for i in range(1, m+1):
for j in range(1, n+1):
if s1[i-1] == s2[j-1]:
dp[i][j] = dp[i-1][j-1] # 不需删除
else:
dp[i][j] = min(dp[i-1][j] + ord(s1[i-1]), dp[i][j-1] + ord(s2[j-1])) # 删除s1的i-1th或s2的j-1th
return dp[m][n]
1312. 让字符串成为回文串的最少插入次数
- 单串,区间DP (二维)
dp[i][j]: 使 s[i], ..., s[j] 成为回文串的 最少插入次数
注意遍历顺序 (根据递推公式)
class Solution:
def minInsertions(self, s: str) -> int:
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n-1, -1, -1):
for j in range(i, n):
if j == i:
dp[i][j] = 0 # 单个字符已经是回文
else: # i < j
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1]
else: # s[i] != s[j]
dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1
# j+1插入s[i] i-1插入s[j]
return dp[0][n-1]
class Solution:
def minInsertions(self, s: str) -> int:
n = len(s)
dp = [[0 for _ in range(n)] for _ in range(n)]
for i in range(n-1, -1, -1):
for j in range(i+1, n):
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1]
else:
dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1
return dp[0][n-1]
516. 最长回文子序列
- 不连续子序列
- 单串,区间DP (二维)
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
dp = [[0]*n for _ in range(n)]
for i in range(n-1, -1, -1):
for j in range(i, n):
if i == j:
dp[i][j] = 1
else: # i < j
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1] + 2
else: # s[i] != s[j]
dp[i][j] = max(dp[i+1][j], dp[i][j-1]) # 最长
return dp[0][n-1]