LeetCode 第71题:简化路径

97 阅读5分钟

LeetCode 第71题:简化路径

题目描述

给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格绝对路径(以 '/' 开头),请你将其转化为更加简洁的规范路径。

在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点(..)表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,//)都被视为单个斜杠 / 。 对于此问题,任何其他格式的点(例如,...)均被视为文件/目录名称。

请注意,返回的规范路径必须遵循下述格式:

  • 始终以斜杠 / 开头。
  • 两个目录名之间必须只有一个斜杠 /
  • 最后一个目录名(如果存在)不能以 / 结尾。
  • 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 ...)。

返回简化后得到的规范路径。

难度

中等

题目链接

点击在LeetCode中查看题目

示例

示例 1:

输入:path = "/home/" 输出:"/home" 解释:注意,最后一个目录名后面没有斜杠。

示例 2:

输入:path = "/../" 输出:"/" 解释:从根目录向上一级是不可行的,因为根目录是你可以到达的最高级。

示例 3:

输入:path = "/home//foo/" 输出:"/home/foo" 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。

示例 4:

输入:path = "/a/./b/../../c/" 输出:"/c"

提示

  • 1 <= path.length <= 3000
  • path 由英文字母,数字,./_ 组成。
  • path 是一个有效的 Unix 风格绝对路径。

解题思路

栈方法

这道题目本质上是对路径字符串的解析和处理,非常适合使用栈来解决。

关键点:

  1. 使用栈来存储路径中的有效目录名
  2. 遇到 . 时忽略(当前目录)
  3. 遇到 .. 时弹出栈顶元素(返回上一级)
  4. 遇到其他有效目录名时入栈
  5. 最后将栈中元素拼接成规范路径

具体步骤:

  1. 将路径按 / 分割成多个部分
  2. 遍历这些部分:
    • 如果是空字符串或 .,跳过
    • 如果是 ..,且栈不为空,弹出栈顶元素
    • 如果是其他有效目录名,入栈
  3. 最后将栈中元素用 / 连接,并在前面加上 /

图解思路

算法步骤分析表

步骤操作栈的状态说明
初始输入[]栈为空
分割"/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

代码亮点

  1. 🎯 使用栈结构高效处理路径
  2. 💡 巧妙处理各种特殊情况
  3. 🔍 代码逻辑清晰,易于理解
  4. 🎨 各语言实现保持一致的思路

常见错误分析

  1. 🚫 没有处理连续斜杠的情况
  2. 🚫 没有正确处理 ...
  3. 🚫 忘记在路径前添加 /
  4. 🚫 没有处理栈为空的情况

解法对比

解法时间复杂度空间复杂度优点缺点
栈方法O(n)O(n)直观易懂需要额外空间
双指针O(n)O(n)一次遍历实现复杂
正则表达式O(n)O(n)代码简洁效率较低

相关题目