学习笔记:删除相邻重复字符
一、题目概述
给定一个字符串 ss,可以进行以下操作:选择两个相邻且相同的字符并删除它们。需要反复进行此操作,直到无法再删除任何字符。任务是返回最终删除重复项后的字符串。
示例
样例1:
输入:s = "abbaca"
输出:"ca"
样例2:
输入:s = "azxxzy"
输出:"ay"
样例3:
输入:s = "a"
输出:"a"
二、思路解析
1. 使用栈来模拟删除操作
我们可以使用栈来解决这个问题。栈的特性非常适合用来处理相邻重复字符的删除问题。具体步骤如下:
-
遍历字符串中的每个字符:
- 如果栈顶元素与当前字符相同,说明找到了一个相邻重复字符,执行删除操作(即弹出栈顶元素)。
- 如果栈顶元素与当前字符不同,说明当前字符没有重复,直接将它压入栈中。
-
最终栈中的元素就是处理过所有重复字符删除后的结果。
2. 时间复杂度与空间复杂度
- 时间复杂度: O(n)O(n),其中 nn 是字符串的长度。我们只遍历一次字符串,对于每个字符,进行入栈或出栈操作,时间复杂度是常数级别的。
- 空间复杂度: O(n)O(n),最坏情况下栈中需要存储所有字符,即当没有重复字符时,栈的空间复杂度为 O(n)O(n)。
3. 算法的核心思想
栈的先进后出(LIFO)特性使得每遇到相邻重复字符时,我们能迅速删除前一个字符,直到没有可删除的字符为止。这个过程模拟了从左到右的删除操作。
三、代码实现
#include <iostream>
#include <stack>
#include <string>
std::string removeDuplicates(std::string s) {
std::stack<char> stk;
for (char c : s) {
if (!stk.empty() && stk.top() == c) {
stk.pop(); // 删除栈顶的重复字符
} else {
stk.push(c); // 将当前字符压入栈
}
}
// 将栈中的字符拼接成最终的字符串
std::string result = "";
while (!stk.empty()) {
result = stk.top() + result;
stk.pop();
}
return result;
}
int main() {
std::string s;
std::cin >> s;
std::cout << removeDuplicates(s) << std::endl;
return 0;
}
四、代码解析
1. 遍历字符串:
通过遍历字符串中的每个字符,我们根据栈的当前状态来决定是否进行删除操作:
- 栈为空或栈顶与当前字符不同:将当前字符压入栈中。
- 栈顶元素与当前字符相同:弹出栈顶元素,表示删除相邻的相同字符。
2. 构建最终字符串:
栈中存储的是最终的字符序列。我们可以从栈顶开始逐一弹出字符,构建结果字符串。
3. 输入与输出:
程序通过 cin 接受输入字符串 s,然后通过 removeDuplicates 函数返回处理后的字符串,最后输出结果。
五、优化与总结
优化
此算法的时间复杂度已达到最优 O(n)O(n),并且空间复杂度为 O(n)O(n),其中 nn 是字符串的长度。每个字符最多入栈一次和出栈一次,因此时间和空间复杂度无法进一步优化。
栈的应用:
本题展示了栈在处理“逆向”操作时的强大能力。在处理相邻重复字符删除时,栈的先进后出特性恰好能有效模拟这一过程。
总结
这个问题通过栈的方式高效地模拟了“删除相邻相同字符”的过程,避免了暴力的字符遍历和多次删除操作。栈的使用简洁而高效,适合解决类似的字符匹配和删除问题。
六、实际应用
-
栈的应用:
- 栈常用于需要逆序处理或匹配成对元素的情况,典型的例子包括括号匹配、撤销操作、表达式求值等问题。
-
字符串处理:
- 本题解决了一个经典的字符串去重问题,实际上可以扩展为处理更复杂的字符串操作,如括号匹配、反转字符串等问题。
七、扩展思考
-
不同字符集的扩展:
- 本题仅处理小写字母,但如果字符集扩展为更复杂的情况(如大写字母、数字、特殊字符等),栈的思路依然适用。
-
优化空间复杂度:
- 可以考虑使用双指针法来优化空间复杂度,通过直接操作字符串来实现字符的删除,而不使用额外的栈空间。但栈方法更直观且易于理解。