题目描述
化简 Unix 绝对路径:. 表示当前目录、.. 表示上一级、多个斜杠合并、结尾不留斜杠。
思路解析
直觉是「找到 a/.. 这种模式就把两段一起抹掉」,但删完可能又冒出新的可消模式,要反复扫;连续斜杠、结尾斜杠、根目录的 .. 还要各种特判。难点是回退要精确作用到"最近进入的那一层",纯字符串替换抓不住这个"最近"。
转折:目录是「进一层 / 退一层」的层级结构,而栈天生管层级——栈顶永远是「最近进入的目录」。先按 / 切成几段:普通目录名就入栈(深入一层);遇 .. 就弹出栈顶(退回最近那层);遇 . 或空串就忽略。.. 该退谁?正是栈顶,所以不用满世界找。
准备 · 按 / 切分:"/a/./b/../c" 按 / 切开,得到上面这串段:a、.、b、..、c(开头的空段已忽略)。下面是空栈,逐段处理。
读 "a" · 入栈:第一段是普通目录 a,深入一层——入栈。栈:[a]。
读 "." · 忽略:读到 .,表示「当前目录」,原地不动,啥也不做。栈仍是 [a]。
读 "b" · 入栈:普通目录 b,再深入一层——入栈。栈:[a, b],栈顶是 b(最近进入的)。
读 ".." · 先看栈顶:读到 ..,要退回上一级。先确认栈非空:栈顶 b 正是刚进入的最近一层,退回上级就是把它弹掉。
读 ".." · 弹出 b:弹出栈顶 b,退回到 a。栈变回 [a]。这一步演示了 .. 为什么只动栈顶——它精确退掉「最近进入的那层」。
读 "c" · 入栈:普通目录 c,入栈。栈:[a, c]。注意刚才的 .. 只退掉了 b,没动到 a——这就是为什么结果是 a、c 两层。
拼接结果:所有段处理完,把栈里 [a, c] 用 / 连起来、前面补根 /,得 "/a/c"。
反例 · 空栈遇 "..":负例提醒:如果路径开头就是 "/.."(栈还空着),.. 没有上一级可退——根目录的上一级还是根。这时必须直接跳过,不能 pop 空栈,否则报错。
凡是「进入一层 / 退回一层」的结构(目录、括号、撤销操作),栈都是最自然的工具:入栈深入,弹栈回退,栈顶永远是「最近那一层」。
参考代码(Python)
1stack = []
2for part in path.split(class="cl-str">"/"): # 按 / 切开
3 if part == class="cl-str">"" or part == class="cl-str">".": # 空段 / 当前目录 → 跳过
4 continue
5 elif part == class="cl-str">"..": # 回上级
6 if stack: stack.pop() # 栈空就别弹
7 else:
8 stack.append(part) # 普通目录入栈
9return class="cl-str">"/" + class="cl-str">"/".join(stack)复杂度分析
- 时间复杂度:O(n) —— n 为路径长度,切分加每段一次进出栈
- 空间复杂度:O(n) —— 栈最多存下所有目录段
套路模板
记住骨架:切分 → 忽略项跳过 / 回退项弹栈(空栈不弹) / 其余入栈 → 拼接。括号匹配、撤销重做都是同一类栈应用。
1stack = []
2for token in 切分(input):
3 if 忽略(token): continue # 如 class="cl-str">"" / class="cl-str">"."
4 elif 回退(token): # 如 class="cl-str">".."
5 if stack: stack.pop() # 空栈别弹
6 else: stack.append(token) # 深入一层
7return 拼接(stack)易错点
- 错误写法:遇 ".." 不判栈空就 stack.pop() → 正确写法:if stack: stack.pop()(像 "/.." 开头时栈是空的,pop 空栈会抛 IndexError;根的上一级仍是根)
- 错误写法:忘了跳过连续 // 产生的空段 → 正确写法:空串 "" 也要 continue(split("/") 对 "//a" 会切出空字符串,不跳过会把它当目录入栈)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。