1. 问题背景与需求分析
在处理字符串比较的场景中,我们经常会遇到一种特殊需求:需要比较两个包含退格符(通常用'#'表示)的字符串是否相等。这类问题在实际开发中并不少见,比如:
- 文本编辑器中的撤销操作模拟
- 命令行输入时的退格处理
- 密码输入框的字符删除逻辑
假设我们有两个字符串:"ab#c"和"ad#c",其中'#'表示前一个字符应该被删除。处理后的结果都是"ac",因此这两个字符串应该被认为是相等的。
关键点:退格字符的处理会改变字符串的最终呈现形式,直接比较原始字符串无法得到正确结果
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 栈的解决方案原理
2.1 栈的特性与适用性
栈(Stack)是一种LIFO(后进先出)的数据结构,特别适合处理这类需要"撤销"或"回退"操作的场景。其核心操作包括:
- push:将元素压入栈顶
- pop:弹出栈顶元素
- peek:查看栈顶元素而不弹出
在处理含退格的字符串时,我们可以:
- 遇到普通字符时压入栈
- 遇到退格符时弹出栈顶字符
- 最终比较两个栈的内容
2.2 算法步骤详解
以字符串"s = "ab#c""为例:
- 初始化空栈
- 遍历字符:
- 'a' → 压入 → 栈:['a']
- 'b' → 压入 → 栈:['a', 'b']
- '#' → 弹出 → 栈:['a']
- 'c' → 压入 → 栈:['a', 'c']
- 最终栈内容即为处理后的字符串"ac"
3. 代码实现与优化
3.1 基础实现(Python示例)
python复制def backspace_compare(s: str, t: str) -> bool:
def build(s):
stack = []
for c in s:
if c != '#':
stack.append(c)
elif stack: # 避免空栈时pop
stack.pop()
return ''.join(stack)
return build(s) == build(t)
时间复杂度:O(m+n),空间复杂度:O(m+n)
3.2 空间优化方案
对于内存敏感的场景,可以采用双指针从后往前遍历:
python复制def backspace_compare(s: str, t: str) -> bool:
i, j = len(s)-1, len(t)-1
skip_s = skip_t = 0
while i >= 0 or j >= 0:
# 处理s的退格
while i >= 0:
if s[i] == '#':
skip_s += 1
i -= 1
elif skip_s > 0:
skip_s -= 1
i -= 1
else:
break
# 处理t的退格
while j >= 0:
if t[j] == '#':
skip_t += 1
j -= 1
elif skip_t > 0:
skip_t -= 1
j -= 1
else:
break
# 比较当前字符
if i >= 0 and j >= 0 and s[i] != t[j]:
return False
# 一个字符串已遍历完
if (i >= 0) != (j >= 0):
return False
i -= 1
j -= 1
return True
优化后空间复杂度:O(1)
4. 边界条件与测试用例
4.1 必须考虑的边界情况
- 连续多个退格符:"a###b"
- 退格符数量多于前面字符数:"###a"
- 空字符串输入:""
- 只有退格符的字符串:"####"
- 退格符在开头:"#abc"
- 退格符在末尾:"abc#"
4.2 测试用例设计
python复制test_cases = [
("ab#c", "ad#c", True),
("a##c", "#a#c", True),
("a#c", "b", False),
("###", "##", True),
("", "", True),
("xy#z", "xzz#", False),
("bxj##tw", "bxj###tw", False)
]
5. 实际应用中的经验技巧
5.1 性能优化建议
- 对于已知最大退格次数的场景,可以预分配栈空间
- 在嵌入式系统中,可以用固定大小的循环数组实现栈
- 多语言环境下注意Unicode字符的处理
5.2 常见错误排查
-
空栈弹出问题:
python复制# 错误写法 if c == '#': stack.pop() # 可能抛出IndexError # 正确写法 if c == '#' and stack: stack.pop() -
字符串拼接效率:
python复制# 低效写法 result = '' for c in stack: result += c # 高效写法 result = ''.join(stack) -
边界条件遗漏:
- 忘记处理连续退格
- 忽略字符串长度不一致的情况
6. 扩展应用场景
6.1 文本编辑器实现
现代文本编辑器通常使用更复杂的数据结构(如Piece Table或Rope),但基本原理类似:
python复制class TextEditor:
def __init__(self):
self.text = []
self.undo_stack = []
def type(self, char):
self.text.append(char)
self.undo_stack.append(('add', len(self.text)-1))
def backspace(self):
if self.text:
char = self.text.pop()
self.undo_stack.append(('del', len(self.text), char))
6.2 命令行历史记录
Shell处理命令行历史时也使用类似机制:
python复制def process_command(cmd):
stack = []
for c in cmd:
if c == '^H': # 退格键
if stack:
stack.pop()
else:
stack.append(c)
return ''.join(stack)
6.3 密码输入验证
处理密码输入时的退格逻辑:
python复制def get_password():
password = []
while True:
ch = getch() # 获取单个字符输入
if ch == '\r': # 回车结束
break
elif ch == '\x08': # 退格
if password:
password.pop()
print('\b \b', end='', flush=True)
else:
password.append(ch)
print('*', end='', flush=True)
return ''.join(password)
7. 算法选择与比较
7.1 不同实现方式对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 双栈法 | O(m+n) | O(m+n) | 代码简洁,易理解 |
| 双指针法 | O(m+n) | O(1) | 内存受限环境 |
| 字符串重建 | O(m+n) | O(m+n) | 需要中间结果 |
7.2 语言特性利用
在支持生成器的语言中,可以更优雅地实现:
python复制def backspace_generator(s):
skip = 0
for c in reversed(s):
if c == '#':
skip += 1
elif skip > 0:
skip -= 1
else:
yield c
def compare(s, t):
return all(a == b for a, b in zip(
backspace_generator(s),
backspace_generator(t)
))
8. 进阶挑战与变种问题
8.1 支持多字符删除
假设"⌫"表示删除前一个单词(直到遇到空格):
python复制def process_advanced(s):
stack = []
i = 0
while i < len(s):
if s[i] == '⌫':
# 删除前一个单词
while stack and stack[-1] != ' ':
stack.pop()
if stack: # 删除空格
stack.pop()
else:
stack.append(s[i])
i += 1
return ''.join(stack)
8.2 实时比较场景
对于需要实时比较的场景(如协同编辑),可以使用增量处理:
python复制class RealTimeComparator:
def __init__(self):
self.stack1 = []
self.stack2 = []
def update(self, s_idx, char):
target = self.stack1 if s_idx == 0 else self.stack2
if char == '#':
if target:
target.pop()
else:
target.append(char)
def is_equal(self):
return self.stack1 == self.stack2
8.3 支持撤销/重做
完整的历史记录支持:
python复制class TextProcessor:
def __init__(self):
self.text = []
self.history = []
self.redo_stack = []
def execute(self, op, *args):
if op == 'insert':
pos, char = args
self.text.insert(pos, char)
elif op == 'delete':
pos = args[0]
char = self.text.pop(pos)
args = (pos, char)
self.history.append((op, args))
self.redo_stack.clear()
def undo(self):
if not self.history:
return
op, args = self.history.pop()
if op == 'insert':
self.text.pop(args[0])
self.redo_stack.append(('insert', args))
elif op == 'delete':
pos, char = args
self.text.insert(pos, char)
self.redo_stack.append(('delete', (pos,)))
