题目解析:最大可移除字符数使得子序列依旧存在
题目描述
给定两个字符串 s
和 p
,其中 p
是字符串 s
的一个子序列。同时,给定一个整数数组 removable
,这个数组是 s
的下标子集,且元素互不相同,数组下标从 0 开始计数。
你需要找到一个整数 k
(满足 0 <= k <= removable.length
),选出 removable
中的前 k
个下标,然后从字符串 s
中移除这些下标对应的字符。要求移除后,字符串 p
仍然是字符串 s
的一个子序列。
换句话说,对于每个 0 <= i < k
,将下标 removable[i]
所指向的字符标记为删除,然后移除所有标记字符,检查移除后的字符串中是否还包含 p
作为子序列。你需要返回满足条件的最大 k
。
关键概念:子序列
字符串的 子序列 是由原字符串通过删除一些字符(可能不删除)并保持剩余字符相对顺序不变形成的新字符串。
例如:
"abc"
是"ahbgdc"
的子序列(删除'h'
,'g'
,'d'
,'c'
后剩余"abc"
)。"axc"
不是"ahbgdc"
的子序列(顺序不对)。
解题分析
题目核心是判断,在移除某些字符后,p
是否依然是 s
的子序列。
- 由于字符移除顺序是固定的(必须从
removable
的头部开始移除),k
只能从 0 逐步增加。 - 需要找最大
k
,满足移除removable
前k
个字符后p
仍是子序列。
难点:
- 如何高效判断
p
是否是s
的子序列? - 如何快速找到最大的
k
?
解题方法
1. 判断子序列的函数
设计一个辅助函数 isSubsequence(s, p, removed_set)
,判断在从 s
移除下标集合 removed_set
的字符后,p
是否仍然是 s
的子序列。
思路:
- 用两个指针
i
遍历s
,j
遍历p
。 - 遍历时跳过所有被移除的字符(
i in removed_set
), - 遇到
s[i] == p[j]
时,j
向后移动。 - 最终判断
j == len(p)
,若成立,说明p
是子序列。
2. 二分搜索最大 k
由于 k
的取值范围是 [0, len(removable)]
,且判断是否满足条件是单调递增(k
小,满足;k
大,可能不满足),可以用二分查找快速定位最大 k
。
步骤:
- 设
left = 0, right = len(removable)
。 - 计算中间值
mid = (left + right) // 2
。 - 构造被移除字符集合
removed_set = set(removable[:mid])
。 - 调用
isSubsequence
判断p
是否仍是子序列。 - 如果是,更新结果
result = mid
,尝试增大k
,即left = mid + 1
。 - 如果否,减小
k
,即right = mid - 1
。 - 最终
result
即为最大k
。
代码实现(Python)
from typing import List
class Solution:
def maximumRemovals(self, s: str, p: str, removable: List[int]) -> int:
def isSubsequence(s, p, removed_set):
i, j = 0, 0
while i < len(s) and j < len(p):
if i not in removed_set and s[i] == p[j]:
j += 1
i += 1
return j == len(p)
left, right = 0, len(removable)
result = 0
while left <= right:
mid = (left + right) // 2
removed_set = set(removable[:mid])
if isSubsequence(s, p, removed_set):
result = mid
left = mid + 1
else:
right = mid - 1
return result
复杂度分析
- 判断子序列复杂度是 O(n),其中
n = len(s)
,因为最多遍历一次字符串s
。 - 二分查找执行约 O(log m) 次,其中
m = len(removable)
。 - 因此总体复杂度是 O(n log m),非常高效。
示例说明
以示例来说明算法流程:
s = "abcacb"
p = "ab"
removable = [3, 1, 0]
- 初始时,
p = "ab"
是s
的子序列。 - 尝试移除前 0 个字符(不移除),
p
显然是子序列。 - 尝试移除前 1 个字符,移除下标 3(字符
'a'
),剩余字符串s
仍包含子序列"ab"
。 - 尝试移除前 2 个字符,移除下标 3 和 1(字符
'a'
和'b'
),剩余字符串为"c c b"
,此时p = "ab"
不再是子序列。
最终最大 k = 1
。
代码执行示例
sol = Solution()
s = "abcacb"
p = "ab"
removable = [3, 1, 0]
print(sol.maximumRemovals(s, p, removable)) # 输出: 1
总结与思考
- 该问题本质是对子序列判断的灵活应用。
- 利用二分查找缩小搜索范围,将复杂度降低。
- 判断子序列时,巧妙跳过被移除的字符,是关键点。