LeetCode 题解工作台
可移除字符的最大数目
给你两个字符串 s 和 p ,其中 p 是 s 的一个 子序列 。同时,给你一个元素 互不相同 且下标 从 0 开始 计数的整数数组 removable ,该数组是 s 中下标的一个子集( s 的下标也 从 0 开始 计数)。 请你找出一个整数 k ( 0 ),选出 removable 中的 前 k…
4
题型
8
代码语言
3
相关题
当前训练重点
中等 · 二分·搜索·答案·空间
答案摘要
我们注意到,如果移除 前 个下标对应的字符后,满足 仍然是 的一个子序列,那么移除 $k \lt k' \leq \textit{removable.length}$ 个下标对应的字符后,依然满足条件,这存在着单调性。因此,我们可以使用二分查找,找到最大的 。 我们定义二分查找的左边界 $l = 0$,右边界 $r = \textit{removable.length}$,然后进行二分查找…
Interview AiBoxInterview AiBox 实时 AI 助手,陪你讲清 二分·搜索·答案·空间 题型思路
题目描述
给你两个字符串 s 和 p ,其中 p 是 s 的一个 子序列 。同时,给你一个元素 互不相同 且下标 从 0 开始 计数的整数数组 removable ,该数组是 s 中下标的一个子集(s 的下标也 从 0 开始 计数)。
请你找出一个整数 k(0 <= k <= removable.length),选出 removable 中的 前 k 个下标,然后从 s 中移除这些下标对应的 k 个字符。整数 k 需满足:在执行完上述步骤后, p 仍然是 s 的一个 子序列 。更正式的解释是,对于每个 0 <= i < k ,先标记出位于 s[removable[i]] 的字符,接着移除所有标记过的字符,然后检查 p 是否仍然是 s 的一个子序列。
返回你可以找出的 最大 k ,满足在移除字符后 p 仍然是 s 的一个子序列。
字符串的一个 子序列 是一个由原字符串生成的新字符串,生成过程中可能会移除原字符串中的一些字符(也可能不移除)但不改变剩余字符之间的相对顺序。
示例 1:
输入:s = "abcacb", p = "ab", removable = [3,1,0] 输出:2 解释:在移除下标 3 和 1 对应的字符后,"abcacb" 变成 "accb" 。 "ab" 是 "accb" 的一个子序列。 如果移除下标 3、1 和 0 对应的字符后,"abcacb" 变成 "ccb" ,那么 "ab" 就不再是 s 的一个子序列。 因此,最大的 k 是 2 。
示例 2:
输入:s = "abcbddddd", p = "abcd", removable = [3,2,1,4,5,6] 输出:1 解释:在移除下标 3 对应的字符后,"abcbddddd" 变成 "abcddddd" 。 "abcd" 是 "abcddddd" 的一个子序列。
示例 3:
输入:s = "abcab", p = "abc", removable = [0,1,2,3,4] 输出:0 解释:如果移除数组 removable 的第一个下标,"abc" 就不再是 s 的一个子序列。
提示:
1 <= p.length <= s.length <= 1050 <= removable.length < s.length0 <= removable[i] < s.lengthp是s的一个 子字符串s和p都由小写英文字母组成removable中的元素 互不相同
解题思路
方法一:二分查找
我们注意到,如果移除 前 个下标对应的字符后,满足 仍然是 的一个子序列,那么移除 个下标对应的字符后,依然满足条件,这存在着单调性。因此,我们可以使用二分查找,找到最大的 。
我们定义二分查找的左边界 ,右边界 ,然后进行二分查找。在每次查找中,我们取中间值 ,然后检查移除 的前 个下标对应的字符后,是否满足 仍然是 的一个子序列。如果满足,我们更新左边界 ,否则更新右边界 。
二分查找结束后,返回左边界 即可。
时间复杂度 ,空间复杂度 。其中 是字符串 的长度,而 是 的长度。
class Solution:
def maximumRemovals(self, s: str, p: str, removable: List[int]) -> int:
def check(k: int) -> bool:
rem = [False] * len(s)
for i in removable[:k]:
rem[i] = True
i = j = 0
while i < len(s) and j < len(p):
if not rem[i] and p[j] == s[i]:
j += 1
i += 1
return j == len(p)
l, r = 0, len(removable)
while l < r:
mid = (l + r + 1) >> 1
if check(mid):
l = mid
else:
r = mid - 1
return l
复杂度分析
| 指标 | 值 |
|---|---|
| 时间 | Depends on the final approach |
| 空间 | Depends on the final approach |
面试官常问的追问
外企场景- question_mark
They expect you to notice monotonicity in the answer, not binary search on string positions.
- question_mark
They want a subsequence checker that skips removed indices without physically rebuilding s.
- question_mark
They are testing whether you can separate the search over k from the validation logic.
常见陷阱
外企场景- error
Rebuilding the filtered string on every check adds unnecessary overhead and obscures the two-pointer logic.
- error
Using binary search without proving why a larger removable prefix cannot recover once a smaller one already fails.
- error
Forgetting that only the first k indices in removable count, not any arbitrary set of k deletions.
进阶变体
外企场景- arrow_right_alt
Return the minimum k that makes p stop being a subsequence instead of the maximum k that keeps it valid.
- arrow_right_alt
Allow multiple queries with different p values, which pushes you to rethink repeated subsequence checks.
- arrow_right_alt
Replace subsequence matching with substring matching, which breaks the simple two-pointer validation used here.