题目
标题和出处
标题:删除字符串中的所有相邻重复项 II
难度
6 级
题目描述
要求
给你一个字符串 s \texttt{s} s 和一个整数 k \texttt{k} k, k \texttt{k} k 重复项删除操作将会从 s \texttt{s} s 中选择 k \texttt{k} k 个相邻且相同的字母并删除它们,被删去的子串的左侧和右侧变成相邻。
我们对 s \texttt{s} s 重复进行 k \texttt{k} k 重复项删除操作,直到无法继续为止。
在执行完所有删除操作后,返回最终得到的字符串。答案保证唯一。
示例
示例 1:
输入:
s
=
"abcd",
k
=
2
\texttt{s = "abcd", k = 2}
s = "abcd", k = 2
输出:
"abcd"
\texttt{"abcd"}
"abcd"
解释:没有要删除的内容。
示例 2:
输入:
s
=
"deeedbbcccbdaa",
k
=
3
\texttt{s = "deeedbbcccbdaa", k = 3}
s = "deeedbbcccbdaa", k = 3
输出:
"aa"
\texttt{"aa"}
"aa"
解释:
先删除
"eee"
\texttt{"eee"}
"eee" 和
"ccc"
\texttt{"ccc"}
"ccc",得到
"ddbbbdaa"
\texttt{"ddbbbdaa"}
"ddbbbdaa"。
再删除
"bbb"
\texttt{"bbb"}
"bbb",得到
"dddaa"
\texttt{"dddaa"}
"dddaa"。
最后删除
"ddd"
\texttt{"ddd"}
"ddd",得到
"aa"
\texttt{"aa"}
"aa"。
示例 3:
输入:
s
=
"pbbcggttciiippooaais",
k
=
2
\texttt{s = "pbbcggttciiippooaais", k = 2}
s = "pbbcggttciiippooaais", k = 2
输出:
"ps"
\texttt{"ps"}
"ps"
数据范围
- 1 ≤ s.length ≤ 10 5 \texttt{1} \le \texttt{s.length} \le \texttt{10}^\texttt{5} 1≤s.length≤105
- 2 ≤ k ≤ 10 4 \texttt{2} \le \texttt{k} \le \texttt{10}^\texttt{4} 2≤k≤104
- s \texttt{s} s 只包含小写英语字母
解法
思路和算法
这道题是「删除字符串中的所有相邻重复项」的进阶。「删除字符串中的所有相邻重复项」要求删除两个相邻且相同的字母,这道题要求删除 k k k 个相邻且相同的字母。
可以使用栈实现删除字符串中的所有相邻重复项。由于题目要求返回执行完所有删除操作后的字符串,因此需要创建 StringBuffer \texttt{StringBuffer} StringBuffer 或 StringBuilder \texttt{StringBuilder} StringBuilder 类型的可变字符串对象实现字符串的拼接和删除操作。该可变字符串可以看成栈,可变字符串的左端为栈底,右端为栈顶,拼接和删除字符都在栈顶操作。将该可变字符串命名为 sb \textit{sb} sb。用 index \textit{index} index 表示栈顶元素下标,初始时 index = − 1 \textit{index} = -1 index=−1,表示栈为空。
由于只有出现 k k k 个相邻且相同的字母时才需要删除,因此还需要维护一个栈存储连续相同字母个数。将存储连续相同字母个数的栈命名为 counts \textit{counts} counts。
从左到右遍历字符串 s s s,对于每个字符 c c c,进行如下操作:
-
如果 counts \textit{counts} counts 为空或者 sb \textit{sb} sb 的下标 index \textit{index} index 处的字符不等于 c c c,则字符 c c c 的前面没有相邻重复项,连续相同字母个数是 1 1 1,将 c c c 拼接到 sb \textit{sb} sb 的末尾,将 index \textit{index} index 的值加 1 1 1,以及将 1 1 1 入栈 counts \textit{counts} counts;
-
否则,字符 c c c 和前面的相邻字符相同,需要判断连续相同字母个数,当前字符 c c c 处的连续相同字母个数 count \textit{count} count 等于 counts \textit{counts} counts 的栈顶元素加 1 1 1,比较 count \textit{count} count 和 k k k 的值:
-
如果 count < k \textit{count} < k count<k,则连续相同字母个数小于 k k k,将 c c c 拼接到 sb \textit{sb} sb 的末尾,将 index \textit{index} index 的值加 1 1 1,以及将 count \textit{count} count 入栈 counts \textit{counts} counts;
-
如果 count = k \textit{count} = k count=k,则连续相同字母个数等于 k k k,需要删除以当前字符 c c c 结尾的连续 k k k 个字母,由于 c c c 未拼接到 sb \textit{sb} sb 且 count \textit{count} count 未入栈 counts \textit{counts} counts,因此需要删除的字母个数是 k − 1 k - 1 k−1,将 sb \textit{sb} sb 的最后 k − 1 k - 1 k−1 个字母删除,将 index \textit{index} index 的值减少 k − 1 k - 1 k−1,以及将 count \textit{count} count 的 k − 1 k - 1 k−1 个元素出栈。
-
在删除 k k k 个相邻且相同的字母之后,如果被删除的字母前后都有字母,则被删除的字母的前后字母从不相邻变成相邻,可能产生新的 k k k 个相邻且相同的字母,在后续遍历过程中继续删除 k k k 个相邻且相同的字母。例如,对于 s = “aabbba" s = \text{``aabbba"} s=“aabbba", k = 3 k = 3 k=3,遍历到最后一个 ‘b’ \text{`b'} ‘b’ 时会将 “bbb" \text{``bbb"} “bbb" 删除,此时剩下的字母是 “aa" \text{``aa"} “aa",遍历到下一个 ‘a’ \text{`a'} ‘a’ 时就会遇到新的 3 3 3 个相邻且相同的字母,也需要删除。
遍历结束之后,将栈内的字符按照从栈底到栈顶的顺序依次拼接,即可得到结果字符串。由于上述做法用可变字符串对象代替栈,因此该可变字符串的内容即为结果字符串,将该可变字符串转换成 String \texttt{String} String 类型然后返回。
代码
class Solution {
public String removeDuplicates(String s, int k) {
StringBuffer sb = new StringBuffer();
int index = -1;
Deque<Integer> counts = new ArrayDeque<Integer>();
int length = s.length();
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (counts.isEmpty() || sb.charAt(index) != c) {
sb.append(c);
index++;
counts.push(1);
} else {
int count = counts.peek() + 1;
if (count < k) {
sb.append(c);
index++;
counts.push(count);
} else {
for (int j = 1; j < k; j++) {
sb.deleteCharAt(index);
index--;
counts.pop();
}
}
}
}
return sb.toString();
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。需要遍历字符串 s s s 一次,每次更新结果字符串的时间都是 O ( 1 ) O(1) O(1)。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 s s s 的长度。空间复杂度主要取决于存储结果字符串的 StringBuffer \texttt{StringBuffer} StringBuffer 或 StringBuilder \texttt{StringBuilder} StringBuilder 类型的对象以及存储连续相同字母个数的栈空间,空间复杂度是 O ( n ) O(n) O(n)。