ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

leetcode1/leetcode 中的 Crawler Log Folder 双解法:用栈与常数空间计数器建模文件系统深度

leetcode1/leetcode 中的 Crawler Log Folder 双解法:用栈与常数空间计数器建模文件系统深度 leetcode1/leetcode 中的 Crawler Log Folder 双解法用栈与常数空间计数器建模文件系统深度【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode本篇技术文章基于仓库中的 crawler-log-folder.md完整讲解 LeetCode 1428 Crawler in Log文件夹中的爬虫这道题的两类标准解法栈模拟与深度计数器迭代。读完之后你不仅能掌握文件系统路径深度这类层次化状态的建模方式、两种解法在 9 种语言下的完整可复制实现还能借助仓库中同构题目的源码理解边界钳制深度/索引不得越过根节点这一关键防御性写法。题目与前置知识题目的核心设定是一个文件管理器从主文件夹main folder出发按序执行一组日志操作logs每条操作有三种形态../移动到上一级父文件夹./停留在当前文件夹不改变位置其他字符串移动到一个以该字符串命名的子文件夹。要求返回回到主文件夹所需的最少操作数。原仓库文档列出了动手前应掌握的两项前置能力栈数据结构Stack Data Structure理解 push/pop 操作以及栈如何建模嵌套或层次化状态字符串比较String Comparison通过比较字符串相等性来区分不同的文件夹操作。需要说明的是仓库的各语言目录python/、java/、cpp/等中并没有 1428 对应的独立解答文件该仓库的命名规范是题号-题目标题例如 1472-design-browser-history.py 对应 LeetCode 1472这道题仅由文章系列覆盖符合 articles/README.md 中文章需覆盖尽可能多的相关解法并给出时间/空间复杂度的写作约定。解法一栈Stack直觉文件系统路径可以用栈自然地建模进入子文件夹就是压栈push回到父文件夹就是弹栈pop./什么都不做。处理完全部日志后栈的大小恰好表示当前距离主文件夹的深度也就是回到主文件夹所需的最少操作数——每弹出一个元素对应一次../操作。算法步骤初始化一个空栈对每条日志操作若是../栈非空则弹栈移动到父文件夹若是./不做任何事停留在当前文件夹否则将该文件夹名压栈移动到子文件夹返回栈的大小即距离主文件夹的深度。多语言实现以下实现完整继承自原文档覆盖 Python、Java、C、JavaScript、C#、Go、Kotlin、Swift、Rust 九种语言。Pythonclass Solution: def minOperations(self, logs: List[str]) - int: stack [] for log in logs: if log ../: if stack: stack.pop() elif log ! ./: stack.append(log) return len(stack)Javapublic class Solution { public int minOperations(String[] logs) { StackString stack new Stack(); for (String log : logs) { if (log.equals(../)) { if (!stack.isEmpty()) { stack.pop(); } } else if (!log.equals(./)) { stack.push(log); } } return stack.size(); } }Cclass Solution { public: int minOperations(vectorstring logs) { stackstring st; for (auto log : logs) { if (log ../) { if (!st.empty()) { st.pop(); } } else if (log ! ./) { st.push(log); } } return st.size(); } };JavaScriptclass Solution { /** * param {string[]} logs * return {number} */ minOperations(logs) { let stack []; for (let log of logs) { if (log ../) { if (stack.length 0) { stack.pop(); } } else if (log ! ./) { stack.push(log); } } return stack.length; } }C#public class Solution { public int MinOperations(string[] logs) { Stackstring stack new Stackstring(); foreach (string log in logs) { if (log ../) { if (stack.Count 0) { stack.Pop(); } } else if (log ! ./) { stack.Push(log); } } return stack.Count; } }Gofunc minOperations(logs []string) int { stack : []string{} for _, log : range logs { if log ../ { if len(stack) 0 { stack stack[:len(stack)-1] } } else if log ! ./ { stack append(stack, log) } } return len(stack) }Kotlinclass Solution { fun minOperations(logs: ArrayString): Int { val stack ArrayDequeString() for (log in logs) { if (log ../) { if (stack.isNotEmpty()) { stack.removeLast() } } else if (log ! ./) { stack.addLast(log) } } return stack.size } }Swiftclass Solution { func minOperations(_ logs: [String]) - Int { var stack [String]() for log in logs { if log ../ { if !stack.isEmpty { stack.removeLast() } } else if log ! ./ { stack.append(log) } } return stack.count } }Rustimpl Solution { pub fn min_operations(logs: VecString) - i32 { let mut stack Vec::new(); for log in logs { if log ../ { if !stack.is_empty() { stack.pop(); } } else if log ! ./ { stack.push(log); } } stack.len() as i32 } }时间与空间复杂度时间复杂度O(n)每条日志只做一次 O(1) 的压栈/弹栈空间复杂度O(n)最坏情况下所有操作都是进入子文件夹栈中保存 n 个元素。解法二迭代深度计数器直觉进一步观察可以发现我们其实并不需要保存文件夹名因为最终只关心深度。用一个简单的计数器跟踪当前处于第几层即可——进入子文件夹则计数器加 1移动到父文件夹则减 1但不能低于 0因为不可能越过主文件夹./保持不变。算法步骤将深度计数器初始化为0对每条日志操作若是./跳过深度不变若是../计数器减 1但确保不低于0否则计数器加 1进入子文件夹返回计数器的值即回到主文件夹的最少操作数。多语言实现Pythonclass Solution: def minOperations(self, logs: List[str]) - int: res 0 for log in logs: if log ./: continue if log ../: res max(0, res - 1) else: res 1 return resJavapublic class Solution { public int minOperations(String[] logs) { int res 0; for (String log : logs) { if (log.equals(./)) { continue; } if (log.equals(../)) { res Math.max(0, res - 1); } else { res; } } return res; } }Cclass Solution { public: int minOperations(vectorstring logs) { int res 0; for (auto log : logs) { if (log ./) { continue; } if (log ../) { res max(0, res - 1); } else { res; } } return res; } };JavaScriptclass Solution { /** * param {string[]} logs * return {number} */ minOperations(logs) { let res 0; for (let log of logs) { if (log ./) { continue; } if (log ../) { res Math.max(0, res - 1); } else { res; } } return res; } }C#public class Solution { public int MinOperations(string[] logs) { int res 0; foreach (string log in logs) { if (log ./) { continue; } if (log ../) { res Math.Max(0, res - 1); } else { res; } } return res; } }Gofunc minOperations(logs []string) int { res : 0 for _, log : range logs { if log ./ { continue } if log ../ { if res 0 { res-- } } else { res } } return res }Kotlinclass Solution { fun minOperations(logs: ArrayString): Int { var res 0 for (log in logs) { if (log ./) { continue } if (log ../) { res maxOf(0, res - 1) } else { res } } return res } }Swiftclass Solution { func minOperations(_ logs: [String]) - Int { var res 0 for log in logs { if log ./ { continue } if log ../ { res max(0, res - 1) } else { res 1 } } return res } }Rustimpl Solution { pub fn min_operations(logs: VecString) - i32 { let mut res 0; for log in logs { if log ./ { continue; } if log ../ { res 0i32.max(res - 1); } else { res 1; } } res } }时间与空间复杂度时间复杂度O(n)单次遍历空间复杂度O(1)只使用一个整型计数器这是该解法相对栈解法的主要优势。常见陷阱Common Pitfalls原文档专门总结了两个高频错误这里完整保留并加以展开。陷阱一允许深度变为负数处理../时必须保证深度不低于 0。越过主文件夹是不可能的因此当深度已经为0时再执行减 1 会产生错误结果。# 错误深度可能变为负数 if log ../: depth - 1 # 正确钳制在 0 if log ../: depth max(0, depth - 1)栈解法天然免疫这个陷阱——它对栈是否为空做了显式判断再弹栈而计数器解法则必须依赖max(0, ...)或等价的条件判断如 Go 版中的if res 0来钳制边界。两种写法本质上是同一约束的不同表达。陷阱二忘记处理当前目录操作././的语义是停留在当前文件夹不应改变深度。若把它当作普通文件夹名而增加深度就是典型错误。# 错误把 ./ 也当作文件夹处理 if log ! ../: depth 1 # 这会对 ./ 错误地加一 # 正确显式跳过 ./ if log ./: continue elif log ../: depth max(0, depth - 1) else: depth 1仓库佐证同一个边界钳制模式在 Design Browser History 中的复现从源码结构看操作不能越过根节点/首页这一约束是本仓库中一类反复出现的防御性写法。同仓库的 1472-design-browser-history.py对应 LeetCode 1472 Design Browser History提供了两种实现恰好可以和本题对照理解数组实现back方法用self.i max(self.i - steps, 0)把当前索引钳制在 0保证不会回退到首页之前见 python/1472-design-browser-history.py#L49-L51链表实现back方法通过while self.cur.prev and steps 0的循环条件隐式完成同样的边界保护——链表头节点即首页的prev为None自然停止回退见 python/1472-design-browser-history.py#L18-L22。这与本文 Crawler Log Folder 的对应关系非常直接对比维度Crawler in Log栈Crawler in Log计数器Browser History数组Browser History链表状态载体文件夹名栈深度整数历史记录数组 索引双向链表 当前指针边界保护方式弹栈前判断stack非空max(0, res - 1)钳制max(self.i - steps, 0)钳制self.cur.prev为空即停止空间复杂度O(n)O(1)O(n)O(n)可以看出无论是栈非空才弹、深度钳制在 0还是索引钳制在 0、前驱为空即停止底层逻辑都是同一条层次化状态的回退操作必须被根节点截断。掌握这一模式后面对任何前进/回退/层级类题目都能快速写出正确的边界处理。两种解法的取舍栈解法语义上更直观栈内容真实还原了当前路径如[documents, snapshot]调试与扩展例如需要输出完整路径时更有优势代价是 O(n) 额外空间计数器解法在只需要深度数值的本题中更精简O(1) 空间是生产代码中只关心层级数场景的首选两者时间复杂度均为 O(n)实际面试或刷题中推荐先写计数器版本再说明若需保留路径则退化为栈的推广思路。小结本文基于 articles/crawler-log-folder.md 完整继承并展开了 Crawler in Log 的两条解题路径栈模拟O(n) 空间与深度计数器O(1) 空间保留了 9 种语言的全部可复制实现、复杂度分析与两个常见陷阱的对照代码并结合仓库中 python/1472-design-browser-history.py 的同构实现把回退操作必须被根节点截断这一通用模式落到具体的源码证据上。相关代码可通过仓库根目录下的语言子目录如 python/、java/以及 README.md 中的题解完成度表格继续索引到同系列题目。【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表