题目描述
思路解析动画文字版
记住「排序让父在子前 → 只和上一个保留项比 + 斜杠前缀」。那个斜杠是关键:它把 /a/b(子目录)和 /ab(同级别的另一个目录)区分开。
先看原始输入:[/c/d, /a, /a/b, /c/d/e, /ab],顺序是乱的,/a 和它的子目录 /a/b 没挨着。直接两两比会很费劲。
第一步先按字典序排序。字典序里,前缀更短的字符串排在前,所以每个父目录都会落到它子目录的前面。
按字典序排序,得到 [/a, /a/b, /ab, /c/d, /c/d/e]。注意 /a 排在 /a/b 前、/c/d 排在 /c/d/e 前——父一定在子之前,这正是后面只比「上一个保留项」就够的底气。
结果列表 ans 先清空。接下来从左往右扫每个路径,只和 ans 里最后一个保留项比。
轮到第 0 个 /a(紫)。此刻 ans 还是空的,没有可比的保留项。
先看 ans:还是空的,没有上一个保留项可比,那它直接就该留下。
ans 是空的,/a 当然要保留(标绿),它成为新的「上一个保留项」。
轮到第 1 个 /a/b(紫)。ans 里最后一个保留项是 /a(蓝),拿它来比。
把 /a/b 和「/a/」(上一个保留项 /a 再加个斜杠)逐字符比一比,看它是不是以这个前缀开头。
判定:/a/b 以「/a/」开头(连斜杠一起),说明它就在 /a 这个目录里面,是子文件夹,跳过(标灰),不进 ans。
轮到第 2 个 /ab(紫)。ans 里最后一个保留项是 /a(蓝),拿它来比。
把 /ab 和「/a/」(上一个保留项 /a 再加个斜杠)逐字符比一比,看它是不是以这个前缀开头。
判定:/ab 虽然也以 /a 这几个字打头,但下一个字符不是斜杠(是「b」),所以它不是 /a 的子目录,而是另一个目录,保留(标绿)。这就是那个斜杠的作用。
轮到第 3 个 /c/d(紫)。ans 里最后一个保留项是 /ab(蓝),拿它来比。
把 /c/d 和「/ab/」(上一个保留项 /ab 再加个斜杠)逐字符比一比,看它是不是以这个前缀开头。
判定:/c/d 不以「/ab/」开头,它是个新的目录,保留(标绿),成为新的「上一个保留项」。
轮到第 4 个 /c/d/e(紫)。ans 里最后一个保留项是 /c/d(蓝),拿它来比。
把 /c/d/e 和「/c/d/」(上一个保留项 /c/d 再加个斜杠)逐字符比一比,看它是不是以这个前缀开头。
判定:/c/d/e 以「/c/d/」开头(连斜杠一起),说明它就在 /c/d 这个目录里面,是子文件夹,跳过(标灰),不进 ans。
扫完全程:绿色 [/a, /ab, /c/d] 是保留下来的,灰色 /a/b、/c/d/e 是被删的子文件夹。回头看,排序加一遍扫描就搞定,关键全在「排序让父在子前」和那个分隔斜杠。
再回顾一遍:排序让每个父目录排到子目录前,于是扫描时只需拿当前路径和「上一个保留项 + 斜杠」做一次前缀判断,就能定出它是不是子文件夹,省掉了和所有保留项逐个比较。绿色 [/a, /ab, /c/d] 即答案。
边界:斜杠区分同名前缀、互不包含则全留、深层子目录也只看上一个保留项。
两个高频追问:可用 Trie 免排序;排序让父在子前是因为父是子的前缀。
参考代码
from typing import Listclass Solution: def removeSubfolders(self, folder: List[str]) -> List[str]: folder.sort() ans = [] for path in folder: if not ans or not path.startswith(ans[-1] + '/'): ans.append(path) return ans复杂度
- 时间:O(L·n·log n),排序是 n·log n 次比较、每次比较最坏要看 L 个字符(L 为路径平均长度);再加一遍扫描、每次前缀判定 O(L)
- 空间:O(L·n),结果列表与排序所需空间,数量级是所有路径的总字符数
易错点
面试追问把动画讲成自己的话
追问不排序能不能做?
追问为什么排序能保证父在子前?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
搜索推荐系统
LeetCode 1268 · 中等 · 沿着 字典树套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题