LeetCode 71. 简化路径
题目描述
✅ 71. 简化路径



题意分析
输入是以
/开头的 Unix 风格绝对路径,要求按题目规则化简成规范路径。多个连续斜杠视为一个,完整片段.表示停在当前目录,完整片段..表示返回父目录,根目录不能继续向上。输出必须以一个
/开头,目录之间只保留一个/,除根目录外不以斜杠结尾。只有完整的.、..才是特殊片段,其他包含点的名称都要作为普通名称保留;本题只处理路径字符串,无需访问文件系统。
解法:栈模拟目录进出
核心思路
[!blue]
返回父目录,就是撤销最近一次尚未被撤销的有效目录进入操作,正好符合栈的后进先出特点。按
/分段后,用栈从底到顶保存当前从根目录出发的有效目录顺序。每个片段只属于三种情况:空片段或
.不改变当前位置,直接跳过;..在栈非空时弹出最后一个目录,空栈时保持在根目录;其余片段表示进入一个普通目录,追加到栈尾。处理完任意路径前缀后,栈都表示该前缀对应的规范目录链。继续读一个片段只需根据上述规则更新最后一层,已经抵消的目录不必保留,也不会影响后面的处理。
最后按栈底到栈顶的顺序用
/连接目录名,再统一补上开头的/。栈里不存分隔符,拼接时自然不会产生连续斜杠或尾斜杠;空栈拼接后也正好得到根目录/。
解题步骤
- 按
/切分路径,创建保存有效目录名的栈。- 遇到空片段或完整的
.时跳过。- 遇到完整的
..时,栈非空就移除末尾目录,栈为空则不做处理。- 其余片段作为普通目录名追加到栈尾。
- 从栈底到栈顶拼接目录,在开头加上
/并返回。
代码实现
class Solution {
public String simplifyPath(String path) {
Deque<String> stack = new ArrayDeque<>();
for (String part : path.split("/")) {
if (part.isEmpty() || part.equals(".")) {
continue;
}
if (part.equals("..")) {
if (!stack.isEmpty()) {
// 回到上一级只删除最后保留的目录,根目录不能再向上。
stack.removeLast();
}
} else {
stack.addLast(part);
}
}
return "/" + String.join("/", stack);
}
}
import "strings"
func simplifyPath(path string) string {
stack := []string{}
for _, part := range strings.Split(path, "/") {
if part == "" || part == "." {
continue
}
if part == ".." {
if len(stack) > 0 {
// 回到上一级只删除最后保留的目录,根目录不能再向上。
stack = stack[:len(stack)-1]
}
} else {
stack = append(stack, part)
}
}
return "/" + strings.Join(stack, "/")
}
复杂度分析
设路径字符串长度为 $n$。
- 时间复杂度:$O(n)$。切分、比较片段和拼接的总字符处理量为线性,每个目录最多入栈、出栈一次。
- 辅助空间复杂度:$O(n)$,切分结果和目录栈的规模均不超过输入长度。
关键点总结
[!green]
- 栈维护当前有效目录链,
..只撤销末尾的一层。- 根目录由空栈表示,向上操作不能再弹出任何内容。
- 特殊含义根据完整片段判断,输出分隔符在最后统一添加。
易错点总结
[!yellow]
- 弹栈前必须判空,根目录上的
..应被忽略,不能越界。- 不能用“以点开头”识别特殊片段,只有完整的
.和..才改变导航行为。- 空片段不应入栈,否则连续斜杠会被错误保留。
- 目录输出顺序是栈底到栈顶,不能通过连续弹栈倒序拼接。
- 空栈对应根目录
/,不能返回空字符串;普通路径也不能再追加尾斜杠。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 388. 文件的最长绝对路径 | 中等 | 同样解析目录层级,本题通过栈处理点目录与父目录,原题通过缩进层级累计文件路径长度。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!