LeetCode 第71题:简化路径
题目描述
给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格绝对路径(以 '/' 开头),请你将其转化为更加简洁的规范路径。
在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点(..)表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,//)都被视为单个斜杠 / 。 对于此问题,任何其他格式的点(例如,...)均被视为文件/目录名称。
请注意,返回的规范路径必须遵循下述格式:
- 始终以斜杠
/开头。 - 两个目录名之间必须只有一个斜杠
/。 - 最后一个目录名(如果存在)不能以
/结尾。 - 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含
.或..)。
返回简化后得到的规范路径。
难度
中等
题目链接
示例
示例 1:
输入:path = "/home/" 输出:"/home" 解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:path = "/../" 输出:"/" 解释:从根目录向上一级是不可行的,因为根目录是你可以到达的最高级。
示例 3:
输入:path = "/home//foo/" 输出:"/home/foo" 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:
输入:path = "/a/./b/../../c/" 输出:"/c"
提示
1 <= path.length <= 3000path由英文字母,数字,.,/或_组成。path是一个有效的 Unix 风格绝对路径。
解题思路
栈方法
这道题目本质上是对路径字符串的解析和处理,非常适合使用栈来解决。
关键点:
- 使用栈来存储路径中的有效目录名
- 遇到
.时忽略(当前目录) - 遇到
..时弹出栈顶元素(返回上一级) - 遇到其他有效目录名时入栈
- 最后将栈中元素拼接成规范路径
具体步骤:
- 将路径按
/分割成多个部分 - 遍历这些部分:
- 如果是空字符串或
.,跳过 - 如果是
..,且栈不为空,弹出栈顶元素 - 如果是其他有效目录名,入栈
- 如果是空字符串或
- 最后将栈中元素用
/连接,并在前面加上/
图解思路
算法步骤分析表
| 步骤 | 操作 | 栈的状态 | 说明 |
|---|---|---|---|
| 初始 | 输入 | [] | 栈为空 |
| 分割 | "/home//foo/" | [] | 分割后得到 ["", "home", "", "foo", ""] |
| 遍历1 | "" | [] | 空字符串,跳过 |
| 遍历2 | "home" | ["home"] | 有效目录名,入栈 |
| 遍历3 | "" | ["home"] | 空字符串,跳过 |
| 遍历4 | "foo" | ["home", "foo"] | 有效目录名,入栈 |
| 遍历5 | "" | ["home", "foo"] | 空字符串,跳过 |
| 拼接 | 结果 | "/home/foo" | 将栈中元素用 / 连接 |
状态/情况分析表
| 情况 | 输入 | 输出 | 说明 |
|---|---|---|---|
| 基本路径 | "/home/" | "/home" | 去掉末尾斜杠 |
| 根目录 | "/" | "/" | 保持根目录 |
| 当前目录 | "/a/./b/" | "/a/b" | 忽略 . |
| 上级目录 | "/a/../" | "/" | 处理 .. |
| 复杂路径 | "/a/./b/../../c/" | "/c" | 综合处理 |
代码实现
C# 实现
public class Solution {
public string SimplifyPath(string path) {
// 使用栈来存储路径中的有效目录名
Stack<string> stack = new Stack<string>();
// 将路径按 / 分割
string[] parts = path.Split('/');
// 遍历分割后的部分
foreach (string part in parts) {
// 如果是空字符串或 .,跳过
if (string.IsNullOrEmpty(part) || part == ".") {
continue;
}
// 如果是 ..,且栈不为空,弹出栈顶元素
else if (part == "..") {
if (stack.Count > 0) {
stack.Pop();
}
}
// 如果是其他有效目录名,入栈
else {
stack.Push(part);
}
}
// 如果栈为空,返回根目录 /
if (stack.Count == 0) {
return "/";
}
// 将栈中元素拼接成规范路径
List<string> result = new List<string>(stack);
result.Reverse(); // 栈是后进先出,需要反转
return "/" + string.Join("/", result);
}
}
Python 实现
class Solution:
def simplifyPath(self, path: str) -> str:
# 使用栈来存储路径中的有效目录名
stack = []
# 将路径按 / 分割
parts = path.split('/')
# 遍历分割后的部分
for part in parts:
# 如果是空字符串或 .,跳过
if not part or part == '.':
continue
# 如果是 ..,且栈不为空,弹出栈顶元素
elif part == '..':
if stack:
stack.pop()
# 如果是其他有效目录名,入栈
else:
stack.append(part)
# 将栈中元素拼接成规范路径
return '/' + '/'.join(stack)
C++ 实现
class Solution {
public:
string simplifyPath(string path) {
// 使用栈来存储路径中的有效目录名
vector<string> stack;
// 将路径按 / 分割
stringstream ss(path);
string part;
// 使用 getline 和 '/' 作为分隔符来分割字符串
while (getline(ss, part, '/')) {
// 如果是空字符串或 .,跳过
if (part.empty() || part == ".") {
continue;
}
// 如果是 ..,且栈不为空,弹出栈顶元素
else if (part == "..") {
if (!stack.empty()) {
stack.pop_back();
}
}
// 如果是其他有效目录名,入栈
else {
stack.push_back(part);
}
}
// 如果栈为空,返回根目录 /
if (stack.empty()) {
return "/";
}
// 将栈中元素拼接成规范路径
string result;
for (const string& dir : stack) {
result += "/" + dir;
}
return result;
}
};
执行结果
- C# 执行用时:88 ms
- C# 内存消耗:39.8 MB
- Python 执行用时:36 ms
- Python 内存消耗:15.1 MB
- C++ 执行用时:4 ms
- C++ 内存消耗:8.2 MB
代码亮点
- 🎯 使用栈结构高效处理路径
- 💡 巧妙处理各种特殊情况
- 🔍 代码逻辑清晰,易于理解
- 🎨 各语言实现保持一致的思路
常见错误分析
- 🚫 没有处理连续斜杠的情况
- 🚫 没有正确处理
.和.. - 🚫 忘记在路径前添加
/ - 🚫 没有处理栈为空的情况
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 栈方法 | O(n) | O(n) | 直观易懂 | 需要额外空间 |
| 双指针 | O(n) | O(n) | 一次遍历 | 实现复杂 |
| 正则表达式 | O(n) | O(n) | 代码简洁 | 效率较低 |
相关题目
- LeetCode 第20题:有效的括号 - 简单
- LeetCode 第224题:基本计算器 - 困难
- LeetCode 第388题:文件的最长绝对路径 - 中等