每日一题:LeetCode:71.简化路径:栈 & 双指针

1,414 阅读4分钟
  • 本文已参与「新人创作礼」活动, 一起开启掘金创作之路

  • 每日一题:LeetCode:71.简化路径

    • 时间:2022-01-06
    • 力扣难度:Medium
    • 个人难度:Easy
    • 数据结构:字符串、栈
    • 算法:模拟、双指针

2022-01-06:LeetCode:71.简化路径

1. 题目描述

  • 题目:原题链接

    • 给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格的绝对路径 ,以/开头,请你将其转化为更加简洁的规范路径
    • 在 Unix 风格的文件系统中,一个点.表示当前目录本身
    • 此外,两个点..表示将目录切换到上一级即父目录;两者都可以是复杂相对路径的组成部分
    • 任意多个连续的斜杠,如://都被视为单个斜杠/, 对于此问题,任何其他格式的点(例如,...)均被视为文件或目录名称
    • 请注意,返回的规范路径必须遵循下述格式
      • 始终以斜杠/开头
      • 两个目录名之间必须只有一个斜杠/
      • 最后一个目录名(如果存在)不能 以/结尾。
      • 此外,路径仅包含从根目录到目标文件或目录的路径上的目录,即不含 ...
    • 返回简化后得到的 规范路径
  • 输入输出规范

    • 输入:字符串path
    • 输出:简化后的字符串
  • 输入输出示例

    • 输入:"/../I/./love//996/../you/"
    • 输出:"/I/love/you"
    • 输入:"/a/./b/../../c/"
    • 输出:"/c"

2. 方法一:字符串模拟+栈

  • 思路

    • 本题首先可以根据题目对简化后字符串的要求入手,遍历整个字符串,对/...等分别进行处理
    • 最终将满足的子目录路径拼接到StringBuilder
    • 注意对于..切换到父目录时,需要判断是否到了根目录,已到根目录的话输出/
  • 复杂度分析:n是path字符串的长度

    • 时间复杂度:O(n)O(n),遍历了整个path字符串,且最后输出时也遍历了存放子目录的List
    • 空间复杂度:O(n)O(n),使用了一个List集合存放子路径
  • 题解

    public String simplifyPath(String path) {
        if (path == null || path.length() == 0 || path.charAt(0) != '/') return null;
        List<String> pathList = new ArrayList<>();
        int n = path.length();
        for (int i = 0; i < n; ) {
            if (path.charAt(i) == '/') { // 处理 [//]
                i++; 
            } else {
                // 记录当前位置
                int index = i;
                while (i < n && path.charAt(i) != '/') {
                    i++;
                }
                // 截取一个子目录
                String subPath = path.substring(index, i);
                // 处理 [..]
                if ("..".equals(subPath)) {
                    if(!pathList.isEmpty()) {
                        pathList.remove(pathList.size() - 1);
                    }
                    // 处理 [.] [//]
                }else if (!".".equals(subPath) && !"".equals(subPath))
                    // 存入子目录
                    pathList.add(subPath);
            }
        }
        StringBuilder sb = new StringBuilder();
        if (!pathList.isEmpty()) {
            for(String subPath : pathList)
                sb.append("/").append(subPath);
        }
        return "".equals(sb.toString()) ? "/" : sb.toString();
    }
    

3. 方法二:字符串分割+栈

  • 思路

    • 和方法一类似,除了使用List集合,也可以使用栈结构来存放子目录
    • 使用字符串分割方法split(),无需遍历整个path字符串,只需要遍历分割后的字符串数组即可
    • 由于栈是先进后出(FILO)的,所以使用双端队列,如LinkedList,从而方便在最后拼接简化后字符串时的操作
  • 复杂度分析:n是path字符串的长度

    • 时间复杂度:O(n)O(n),分割方法split()实际上也是遍历了整个path字符串
    • 空间复杂度:O(n)O(n),使用了一个栈结构存放子路径
  • 题解

    public String simplifyPath(String path) {
        if (path == null || path.length() == 0 || path.charAt(0) != '/') return null;
        Deque<String> stack = new LinkedList<>();
        String[] strs = path.split("/");
        for (String str : strs) {
            if ("..".equals(str)) {
                if (!stack.isEmpty()) {
                    stack.removeLast();
                }
            } else if (!".".equals(str) && !"".equals(str)) {
                stack.offerLast(str);
            }
        }
        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty()) {
            sb.append("/").append(stack.removeFirst());
        }
        return "".equals(sb.toString()) ? "/" : sb.toString();
    }
    
  • 思考与优化

    • 思考:如何直接在一个循环中完成对简化后字符串的拼接,而不是将所有子目录保存到一个栈或者集合中,最后在遍历集合来拼接字符串
    • 优化:使用两个指针startend来记录每次拼接的字符串的起始和结尾位置,如果后面遇到了..,则根据指针来删除字符串的内容
    • 由于可能存在多个..,所以两个指针startend也分别需要表示为集合的类型
  • 题解:优化版本

    public String simplifyPath(String path) {
        if (path == null || path.length() == 0 || path.charAt(0) != '/') return null;
        StringBuilder sb = new StringBuilder();
        // 起始和结尾指针
        Stack<Integer> start = new Stack<>();
        Stack<Integer> end = new Stack<>();
        // 初始化
        start.push(0);
        end.push(1);
        String[] strs = path.split("/");
        for (String str : strs) {
            // 处理 [..]
            if ("..".equals(str)) { 
                if (!start.isEmpty()) {
                    sb.delete(start.pop(), end.pop());
                } else {
                    sb.delete(0, 1);
                }
                // 处理 [.]  [//]
            } else if (!".".equals(str) && !"".equals(str)) {  
                start.push(sb.length());
                sb.append("/").append(str);
                end.push(sb.length());
            }
        }
        return "".equals(sb.toString()) ? "/" : sb.toString();
    }
    

最后

新人LeetCoder,发布的题解有些会参考其他大佬的思路(参考资料的链接会放在最下面),欢迎大家关注我 ~ ~ ~ 如果本文有所帮助的话,希望大家可以给个三连「点赞」&「收藏」&「关注」 ~ ~ ~ 也希望大家有空的时候光临我的「个人博客」。