LeetCode 1047简单栈
删除字符串中的所有相邻重复项 图解题解
这道题到底在问什么
给一个字符串 s,反复删除两个相邻且相同的字母,直到删不动为止,返回最后剩下的字符串。
- s
- "abbaca"
- 输出
- "ca"
- 过程
- abbaca → aaca → ca
最优解:一步一步想明白
- 3能做对,但每删一次就要重头再扫。像 aaaa… 这种,每趟只消掉一点,最坏要扫很多趟 → O(n²)。重复扫描就是慢的根源。
- 4栈就是为「最近一个还没了结的」而生:和栈顶相同就 弹栈(消一对),否则压栈。从左到右扫一遍,连锁反应被栈自动接住,你不用回头。
- 5栈为空,指针还没出发换条更长的串 cabccbaddxyyxez 把过程演透——它里头藏了两段连锁消除。上面是输入流,下面是空栈。指针从左到右逐字符扫,盯住栈怎么涨怎么消。
- 6压栈 → [c]第 1 个字符 c,栈是空的,没人能和它配对,压栈,栈变 [c]。
- 7压栈 → [c, a]第 2 个是 a,栈顶是 c,不一样,消不掉,压栈,栈变 [c, a]。
- 8压栈 → [c, a, b]第 3 个是 b,栈顶是 a,不同,继续压栈,栈一路涨到 [c, a, b]。
- 9压栈 → [c, a, b, c]第 4 个又是 c,栈顶是 b,不同,压栈,栈涨到 [c, a, b, c]——注意这串 cabc 一个都没消,全堆着。
- 10弹栈(消一对) → [c, a, b]第 5 个是 c,栈顶正好也是 c——凑成一对!弹栈消掉,栈回到 [c, a, b]。消完后新露出的栈顶是 b,连锁就要开始了。
- 11弹栈(消一对) → [c, a]第 6 个是 b,和刚露出的栈顶 b 又凑一对!弹栈消掉,栈剩 [c, a],新栈顶变成 a。
- 12弹栈(消一对) → [c]第 7 个是 a,又和栈顶 a 配对,再弹栈——一口气消了三对 cc、bb、aa!栈塌回 [c]。这就是栈替你自动接住的连锁反应。
- 13压栈 → [c, d]第 8 个是 d,栈顶是 c,不同,压栈,栈变 [c, d]。
- 14弹栈(消一对) → [c]第 9 个又是 d,和栈顶 d 配对,弹栈消掉,栈回到 [c]。
- 15压栈 → [c, x]第 10 个是 x,栈顶是 c,不同,压栈,栈变 [c, x]——第二段连锁正在埋伏笔。
- 16压栈 → [c, x, y]第 11 个是 y,栈顶是 x,不同,压栈,栈涨到 [c, x, y]。
- 17弹栈(消一对) → [c, x]第 12 个又是 y,和栈顶 y 配对,弹栈消掉,栈剩 [c, x]——新栈顶 x 露出来了。
- 18弹栈(消一对) → [c]第 13 个是 x,消掉 yy 后新露出的栈顶正好是 x——再凑一对!弹栈,栈又塌回 [c]。第二段连锁也被栈接住了。
- 19压栈 → [c, e]第 14 个是 e,栈顶是 c,不同,压栈,栈变 [c, e]。
- 20压栈 → [c, e, z]第 15 个是 z,栈顶是 e,不同,压栈,栈变 [c, e, z],到头也没人能再和它配对。
- 21栈 [c, e, z] = 答案字符串走完了。栈里从底到顶拼起来就是答案 = cez。一趟扫完,全程没回过头,两段连锁全靠栈自动接住。
- 22刚才整条 cabccbaddxyyxez 走下来,指针只从左滑到右一遍,没有任何回头扫描——这就是栈把 O(n²) 压到 O(n) 的关键。
- 25为什么是栈不是队列?因为消除只跟最近一个较劲——后进先出。凡是「当前元素只跟最近一个较劲」的题,第一反应就该想到栈。
- 27stack 为空 → 读 top 崩!假如来第一个 a 时不先判空,直接拿 stack[-1] 去比,程序当场越界报错。所以条件里 stack 非空 必须写在前面。
- 28栈空 → 直接压栈 [a]把判空写在前:栈空时短路、不去读栈顶,直接压栈 [a],安全。这一行顺序就是这题的命门。
- 29两两消光 → 栈空 []全相同的 aaaa:a 压、第二个 a 消,第三个 a 压、第四个 a 又消——两两抵消,栈最终清空。偶数全消、奇数会剩一个。
⚠️ 容易写错的地方
✗ 错:不判栈空就读栈顶
✓ 对:先判 stack 非空,再比 stack[-1]
栈空时读栈顶会越界/报错
✗ 错:消一对后忘了「连锁」
✓ 对:用栈天然处理连锁,无需额外回扫
新栈顶会和下一个字符自动再比
完整代码(Python / C++ / Java)
Python
class Solution:
def removeDuplicates(self, s):
stack = [] # 栈:存还没被消掉的字符
for ch in s:
if stack and stack[-1] == ch: # 和栈顶相同
stack.pop() # 消掉一对
else:
stack.append(ch) # 否则压栈
return "".join(stack)C++
class Solution {
public:
string removeDuplicates(string s) {
string stk; // 用 string 当栈
for (char ch : s) {
if (!stk.empty() && stk.back() == ch)
stk.pop_back(); // 消掉一对
else stk.push_back(ch); // 否则压栈
}
return stk;
}
};Java
class Solution {
public String removeDuplicates(String s) {
StringBuilder sb = new StringBuilder(); // 当栈用
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
int len = sb.length();
if (len > 0 && sb.charAt(len - 1) == ch)
sb.deleteCharAt(len - 1); // 消掉一对
else sb.append(ch); // 否则压栈
}
return sb.toString();
}
}复杂度
时间复杂度
O(n)
每个字符最多入栈一次、出栈一次,共 ≤ 2n 次操作 → O(n)
空间复杂度
O(n)
最坏没有任何相邻重复(如 abcdef),栈里要存下整串 → O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 删除字符串中的所有相邻重复项 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么一趟就够,不会漏消?+
弹栈后新的栈顶就是新的「前一个」,下一个字符会自动和它比,连锁被栈接住。
能否不开额外栈、原地做?+
能。用一个写指针 top 在原数组上模拟栈,空间 O(1)(不计返回)。
复杂度?+
时间 O(n),空间 O(n)(栈);原地写法空间 O(1)。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 删除字符串中的所有相邻重复项 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。