栈题目:删除字符串中的所有相邻重复项 II

题目

标题和出处

标题:删除字符串中的所有相邻重复项 II

出处:1209. 删除字符串中的所有相邻重复项 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} 1s.length105
  • 2 ≤ k ≤ 10 4 \texttt{2} \le \texttt{k} \le \texttt{10}^\texttt{4} 2k104
  • 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 k1,将 sb \textit{sb} sb 的最后 k − 1 k - 1 k1 个字母删除,将 index \textit{index} index 的值减少 k − 1 k - 1 k1,以及将 count \textit{count} count k − 1 k - 1 k1 个元素出栈。

在删除 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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值