1910. 删除一个字符串中所有出现的给定子字符串
来源第 55 场双周赛 Q2难度中等分数1460
题目描述
给你两个字符串 s 和 part ,请你对 s 反复执行以下操作直到 所有 子字符串 part 都被删除:
- 找到
s中 最左边 的子字符串part,并将它从s中删除。
请你返回从 s 中删除所有 part 子字符串以后得到的剩余字符串。
一个 子字符串 是一个字符串中连续的字符序列。
示例 1:
输入:s = "daabcbaabcbc", part = "abc" 输出:"dab" 解释:以下操作按顺序执行: - s = "daabcbaabcbc" ,删除下标从 2 开始的 "abc" ,得到 s = "dabaabcbc" 。 - s = "dabaabcbc" ,删除下标从 4 开始的 "abc" ,得到 s = "dababc" 。 - s = "dababc" ,删除下标从 3 开始的 "abc" ,得到 s = "dab" 。 此时 s 中不再含有子字符串 "abc" 。
示例 2:
输入:s = "axxxxyyyyb", part = "xy" 输出:"ab" 解释:以下操作按顺序执行: - s = "axxxxyyyyb" ,删除下标从 4 开始的 "xy" ,得到 s = "axxxyyyb" 。 - s = "axxxyyyb" ,删除下标从 3 开始的 "xy" ,得到 s = "axxyyb" 。 - s = "axxyyb" ,删除下标从 2 开始的 "xy" ,得到 s = "axyb" 。 - s = "axyb" ,删除下标从 1 开始的 "xy" ,得到 s = "ab" 。 此时 s 中不再含有子字符串 "xy" 。
提示:
1 <= s.length <= 10001 <= part.length <= 1000s 和part只包小写英文字母。
解法
方法一:暴力替换
思考
\(s\) 与 \(\textit{part}\) 长度分别不超过 \(10^3\),反复寻找并删除一次出现的代价可接受,不必先构造自动机。
每次用库函数替换最左侧的 \(\textit{part}\),直到不再出现。删除后可能拼出新的 \(\textit{part}\),因此必须循环而非只扫一遍。
总长度每次至少缩短 \(|\textit{part}|\),循环次数有上界,实现与题意一致。
我们循环判断 \(s\) 中是否存在字符串 \(part\),是则进行一次替换,继续循环此操作,直至 \(s\) 中不存在字符串 \(part\),返回此时的 \(s\) 作为答案字符串。
时间复杂度 \(O(n^2 + n \times m)\),空间复杂度 \(O(n + m)\)。其中 \(n\) 和 \(m\) 分别是字符串 \(s\) 和字符串 \(part\) 的长度。
1 2 3 4 5 | |
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 9 10 | |
1 2 3 4 5 6 | |
1 2 3 4 5 6 | |
方法二:栈
思考
方法一每删掉一次就要重新扫描整串。 \(|s|\) 每次至少减少 \(1\),轮数可达 \(O(n)\),总时间是 \(O(n^2)\)。
从左往右读时,当前结果里若还有 \(\textit{part}\),最左侧的那一次一定贴在当前结果的末尾:更早的出现在读到它的时候就已经删掉了。
因此用一个栈保存还没被删掉的字符。每压入一个字符,若末尾的 \(m\) 个字符等于 \(\textit{part}\) 就弹出。一遍扫描即按题意完成所有删除。
用字符串 \(st\) 模拟栈,从左到右扫描 \(s\)。把当前字符追加到 \(st\) 末尾;若 \(st\) 的长度至少为 \(m = |\textit{part}|\),且末尾 \(m\) 个字符恰好是 \(\textit{part}\),则删掉这 \(m\) 个字符。扫描结束后, \(st\) 就是答案。
这样做与方法一等价:任意时刻 \(st\) 中都不含 \(\textit{part}\),下一次能匹配上的 \(\textit{part}\) 必然以当前字符结尾,也就是剩余字符串中最左侧的一次出现。
时间复杂度 \(O(n \times m)\),空间复杂度 \(O(n)\)。其中 \(n\) 和 \(m\) 分别是 \(s\) 和 \(\textit{part}\) 的长度。
1 2 3 4 5 6 7 8 9 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 6 7 8 9 10 11 | |