1898. 可移除字符的最大数目

题目解析:最大可移除字符数使得子序列依旧存在


题目描述

给定两个字符串 sp,其中 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,满足移除 removablek 个字符后 p 仍是子序列。

难点:

  • 如何高效判断 p 是否是 s 的子序列?
  • 如何快速找到最大的 k

解题方法

1. 判断子序列的函数

设计一个辅助函数 isSubsequence(s, p, removed_set),判断在从 s 移除下标集合 removed_set 的字符后,p 是否仍然是 s 的子序列。

思路:

  • 用两个指针 i 遍历 sj 遍历 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

总结与思考

  • 该问题本质是对子序列判断的灵活应用。
  • 利用二分查找缩小搜索范围,将复杂度降低。
  • 判断子序列时,巧妙跳过被移除的字符,是关键点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值