71. 简化路径

71. 简化路径

✨核心逻辑

本题采用 栈(双端队列实现)与字符串分割 的策略:

  1. 路径分割:利用 Unix 路径以 / 为分隔符的特性,使用 split("/") 将路径字符串切割成若干个字符串组件。
  2. 栈结构维护:利用栈(或双端队列模拟栈)来维护路径层级。
  3. 遍历组件并处理
    • 遇到 "." 或者空字符串(由连续斜杠产生),说明是当前目录或分隔符,不做任何处理,直接跳过。
    • 遇到 "..",说明要返回上一级目录。如果栈不为空,则弹出栈顶元素(丢弃上一级目录)。
    • 遇到其他正常目录名,将其压入栈中
  4. 拼接结果:最后如果栈为空,说明路径在根目录,直接返回 "/";如果不为空,则从栈底到栈顶依次弹出元素,拼接上 / 形成规范的简化路径。

🔥代码实现(含详细变量注释)

class Solution {
    public String simplifyPath(String path) {
        // stack:使用双端队列 ArrayDeque 作为一个栈来保存有效路径的目录名。
        // ArrayDeque 比 Stack 类性能更好,且支持 FIFO 和 LIFO 操作。
        Deque<String> stack = new ArrayDeque<>();
        
        // split:将原路径按照 "/" 进行分割,分割后数组可能包含空字符串、"."、".." 和真实的目录名
        String[] split = path.split("/");
        
        // st:遍历数组中的每一个拆分出来的字符串组件
        for (String st : split) {
            // 如果遇到的是 "." 或者是空字符串(由多个连续斜杠 "/" 产生),则忽略,直接进入下一次循环
            if (st.equals(".") || st.equals("")) {
                continue;
            }
            // 如果遇到的是 "..",代表返回上一级目录
            if (st.equals("..")) {
                if (!stack.isEmpty()) {
                    // 出栈操作:丢弃栈顶的元素(即上一级目录)
                    stack.pollLast();
                }
            } else {
                // 如果不是 "." 和 "..",说明是合法的目录名,将其压入栈中
                stack.offerLast(st);
            }
        }
        
        // 额外判断:如果遍历完成后栈是空的,说明路径被简化到了根目录,直接返回 "/"
        if (stack.isEmpty()) {
            return "/";
        }
        
        // sb:用于构建最终的规范路径字符串
        StringBuilder sb = new StringBuilder();
        // 从队列头部依次取出元素(保证了目录本身从根到叶子的正确顺序),前缀加上斜杠拼成规范路径
        while (!stack.isEmpty()) {
            sb.append("/").append(stack.pollFirst());
        }
        // 返回最终拼接好的简化路径字符串
        return sb.toString();
    }
}

  • ⏱️复杂度分析
    • 时间复杂度:O(N),其中 N 是字符串 path 的长度。分割字符串需要遍历一次,处理分割后的数组也需要遍历一次,均为线性时间。

    • 空间复杂度:O(N),主要消耗在分割出的字符串数组 split 和用于存储目录名的双端队列 stack 上,最坏情况下路径中全是单级目录,栈的大小接近 N。

🔍总结一下:

目录的层级结构天然具有“先进后出”的特点(进入子目录 => 压栈,返回上级目录 => 弹栈),所以用栈来存储路径中的有效目录名

需要注意栈为空的情况,也就是没有有效路径,返回""