文章目录


题目描述
题目链接:力扣 844. 比较含退格的字符串
题目描述:

示例 1:
输入:s = "ab#c", t = "ad#c"
输出:true
解释:s 和 t 都会变成 "ac"。
示例 2:输入:s = "ab##", t = "c#d#"
输出:true
解释:s 和 t 都会变成 ""。
示例 3:输入:s = "a#c", t = "b"
输出:false
解释:s 会变成 "c",t 仍然是 "b"。
提示:
- 1 <= s.length, t.length <= 200
- s 和 t 只含有小写字母以及字符 '#'
为什么这道题值得我们花几分钟能懂?
继昨天用栈解决"相邻重复项删除"后,这道题是栈在 "撤销/回退类场景" 的又一经典应用!它不仅延续了"用字符串模拟栈"的核心技巧,还进一步拓展了栈的适用边界------从"消除相邻相同元素"升级到"按规则删除前序元素"。
更关键的是,这道题能帮我们强化两个核心思维:
- 问题转化能力:把"带退格的字符串比较"转化为"先处理字符串得到最终形态,再比较",这是算法中"先化简、后求解"的经典思路;
- 工具复用能力:昨天刚掌握的"字符串模拟栈"技巧,今天可以直接复用,验证"栈思维"的通用性。
算法原理
这道题的核心是模拟文本编辑器处理退格键的过程,而栈的"后进先出"特性恰好匹配退格键"删除最近输入字符"的逻辑------就像我们日常按"Ctrl+Z"撤销最后一步操作一样。
核心思路
- 字符串预处理:分别处理 s 和 t 两个字符串,得到它们经过退格后的最终形态;
- 栈模拟编辑过程 :
- 遍历字符串的每个字符;
- 如果当前字符是普通小写字母 → 压入"栈"(用字符串模拟);
- 如果当前字符是
#(退格键)→ 若"栈"非空,则弹出栈顶字符(删除最近输入的字符),若栈空则无操作;
- 结果比较:将两个字符串处理后的结果对比,相等则返回 true,否则返回 false。
为什么还是用字符串模拟栈?
昨天我们已经验证过,相比标准 stack<char>,用字符串模拟栈有两大优势:
- 无额外空间开销:字符串本身就是连续内存,无需栈容器的额外控制块;
- 操作更高效 :
back()(取栈顶)、pop_back()(弹栈)都是内联操作,无需函数调用开销,且无需最后反转(栈版需要)。
对于这道题,退格操作本质是"删除上一个有效字符",字符串的 pop_back() 正好精准匹配这个需求,逻辑上完全等价于栈,且实现更简洁。
代码实现
cpp
class Solution {
public:
bool backspaceCompare(string s, string t) {
// 提前判断,减少不必要的处理
if(s == t)
return true;
// 分别处理两个字符串,得到退格后的结果
string s1 = check(s);
string s2 = check(t);
// 比较处理后的结果
return s1 == s2;
}
// 核心函数:处理带退格的字符串,返回最终形态
string check(string s)
{
string ret; // 用字符串模拟栈
for(auto ch : s)
{
if(ch != '#')
ret += ch; // 普通字符,入栈
else
{
if(!ret.empty())
ret.pop_back(); // 退格,弹出栈顶(非空时)
}
}
return ret;
}
};
代码解读
- 提前剪枝:先判断 s 和 t 是否完全相等,避免后续无意义的处理;
- 复用逻辑 :把字符串处理逻辑封装成
check函数,代码更简洁、可复用; - 边界处理 :遇到
#时先判断栈是否为空,避免对空字符串执行pop_back()导致越界; - 核心逻辑 :
check函数中,普通字符入栈,退格字符弹栈(非空时),最终返回的字符串就是处理后的结果。
复杂度分析
| 维度 | 分析结果 | 细节说明 |
|---|---|---|
| 时间复杂度 | O(n + m) | n 是 s 的长度,m 是 t 的长度;处理每个字符串只需一次遍历,比较结果是 O(min(n,m)),整体为线性时间 |
| 空间复杂度 | O(n + m) | 最坏情况下(无退格键),处理后的字符串需要存储所有字符,空间与原字符串等长 |
进阶思考(空间优化)
如果想把空间复杂度优化到 O(1),可以采用双指针从后往前遍历的思路:
- 从字符串末尾开始,统计需要跳过的字符数(遇到
#则跳过数+1,遇到普通字符且跳过数>0则跳过数-1,否则记录该字符); - 同时遍历 s 和 t,对比每一个有效字符,若出现不一致则返回 false。
这种方法虽然空间更优,但逻辑稍复杂,对于本题的输入规模(长度≤200),字符串模拟栈的方式已经足够高效,且代码更易理解、不易出错------算法选择要兼顾性能和可读性。
总结
通过这道题,我们进一步巩固了栈在"撤销/回退"类问题中的应用,核心收获:
- 栈思维的延伸:从"消除相邻重复"到"删除前序元素",栈的"后进先出"始终适配"最近操作优先处理"的场景;
- 代码复用与封装:将重复的字符串处理逻辑封装成函数,提升代码可读性和可维护性;
- 问题化简思路:复杂问题(带退格的比较)→ 拆解为两个简单问题(处理字符串 + 比较字符串),降低解题难度。
核心技巧
- 遇到"退格、撤销、删除最近元素"类问题 → 优先考虑栈;
- 用字符串模拟栈是处理字符类栈问题的"最优解"(时间/空间常数更小);
- 写代码时先做简单剪枝(如本题提前判断 s==t),减少无效计算;
- 边界条件必须处理(如栈空时的退格操作)。
下题预告
我们下次练习 力扣 227. 基本计算器 II ,这是栈在"表达式计算"场景的经典应用------面对包含 +、-、*、/ 的四则运算表达式,如何利用栈处理运算符的优先级?
继"相邻消除""退格撤销"后,栈将帮我们解决更复杂的"优先级处理"问题。表达式计算是面试高频考点,掌握这道题,你对栈的理解会再上一个台阶~
Doro又又又带着小花🌸来啦!🌸奖励🌸看到这里的你!如果这篇「比较含退格的字符串」的博客帮你巩固了栈的应用场景,别忘了点赞收藏~关注我,持续更新算法解题思路,从基础题一步步吃透数据结构!有任何想法或疑问,评论区一起讨论呀~
