删除子文件夹
难度:中等
你是一位系统管理员,手里有一份文件夹列表 folder
,你的任务是要删除该列表中的所有 子文件夹,并以 任意顺序 返回剩下的文件夹。
如果文件夹 folder[i]
位于另一个文件夹 folder[j]
下,那么 folder[i]
就是 folder[j]
的 子文件夹 。
文件夹的「路径」是由一个或多个按以下格式串联形成的字符串:'/'
后跟一个或者多个小写英文字母。
- 例如,
"/leetcode"
和"/leetcode/problems"
都是有效的路径,而空字符串和"/"
不是。
示例 1:
输入:folder = ["/a","/a/b","/c/d","/c/d/e","/c/f"]
输出:["/a","/c/d","/c/f"]
解释:"/a/b" 是 "/a" 的子文件夹,而 "/c/d/e" 是 "/c/d" 的子文件夹。
示例 2:
输入:folder = ["/a","/a/b/c","/a/b/d"]
输出:["/a"]
解释:文件夹 "/a/b/c" 和 "/a/b/d" 都会被删除,因为它们都是 "/a" 的子文件夹。
示例 3:
输入: folder = ["/a/b/c","/a/b/ca","/a/b/d"]
输出: ["/a/b/c","/a/b/ca","/a/b/d"]
排序
思路:
我们可以将字符串数组 folder \textit{folder} folder按照字典序进行排序。在排序完成后,对于每一个 folder [ i ] \textit{folder}[i] folder[i],如果 folder [ i − 1 ] \textit{folder}[i-1] folder[i−1] 恰好是它的前缀,并且 folder [ i ] \textit{folder}[i] folder[i] 第一个多出的字符是 / \text{/} /,那么我们就可以把 folder [ i ] \textit{folder}[i] folder[i] 删除。
注意当 folder [ i ] \textit{folder}[i] folder[i] 被删除后,后续的所有字符串都需要向前移动一个位置。例如
[“/a”,“/a/b”,“/a/c”] \text{[``/a'',``/a/b'',``/a/c'']} [“/a”,“/a/b”,“/a/c”]中, “/a/b” \text{``/a/b''} “/a/b” 被删除后,数组会变为 [“/a”,“/a/c”] \text{[``/a'',``/a/c'']} [“/a”,“/a/c”], “/a/c” \text{``/a/c''} “/a/c”也会被删除。
这样做的必要性是显然的,因为如果上述条件满足,就说明 folder [ i ] \textit{folder}[i] folder[i] 是 folder [ i − 1 ] \textit{folder}[i-1] folder[i−1] 的子文件夹。对于充分性,我们可以使用反证法:
假设 folder [ i ] \textit{folder}[i] folder[i] 是某个 folder [ j ] ( j ≠ i − 1 ) \textit{folder}[j]~(j \neq i-1) folder[j] (j=i−1) 的子文件夹但不是 folder [ i − 1 ] \textit{folder}[i-1] folder[i−1] 的子文件夹,那么在排序后, folder [ j ] \textit{folder}[j] folder[j] 一定出现在 folder [ i ] \textit{folder}[i] folder[i] 的前面,也就是有 j < i j < i j<i。如果有多个满足要求的 j j j,我们选择最早出现的那个。这样就保证了 folder [ j ] \textit{folder}[j] folder[j] 本身不会是其它文件夹的子文件夹。
由于 “/” \text{``/''} “/” 的字典序小于所有的小写字母,并且 folder [ i ] \textit{folder}[i] folder[i] 是由 folder [ j ] \textit{folder}[j] folder[j] 加上 “/” \text{``/''} “/” 再加上后续字符组成,因此在 folder [ i ] \textit{folder}[i] folder[i] 和 folder [ j ] \textit{folder}[j] folder[j] 之间的所有字符串也都一定是由
folder [ j ] \textit{folder}[j] folder[j] 加上 “/” \text{``/''} “/” 再加上后续字符组成。这些字符串都是 folder [ i ] \textit{folder}[i] folder[i] 的子文件夹,它们会依次被删除。当遍历到 folder [ i ] \textit{folder}[i] folder[i] 时,它的上一个元素恰好是 folder [ j ] \textit{folder}[j] folder[j],因此它一定会被删除。
复杂度分析:
- 时间复杂度: O ( n l ⋅ log n ) O(nl \cdot \log n) O(nl⋅logn),其中 n n n 和 l l l 分别是数组 folder \textit{folder} folder 的长度和文件夹的平均长度。 O ( n l ⋅ log n ) O(nl \cdot \log n) O(nl⋅logn) 为排序需要的时间,后续构造答案需要的时间为 O ( n l ) O(nl) O(nl),在渐进意义下小于前者。
- 空间复杂度: O ( l ) O(l) O(l)。在构造答案比较前缀时,我们使用了字符串的截取子串操作,因此需要 O ( l ) O(l) O(l) 的临时空间。我们也可以使用一个递增的指针依次对两个字符串的每个相同位置进行比较,省去这一部分的空间,使得空间复杂度降低至排序需要的栈空间 O ( log n ) O(\log n) O(logn)。但空间优化并不是本题的重点,因此上述的代码中仍然采用空间复杂度为 O ( l ) O(l) O(l) 的写法。注意这里不计入返回值占用的空间。
class Solution:
def removeSubfolders(self, folder: List[str]) -> List[str]:
folder.sort()
res = [folder[0]]
for i in range(1, len(folder)):
if folder[i][:len(res[-1]) + 1] != res[-1] + '/':
res.append(folder[i])
return res
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/remove-sub-folders-from-the-filesystem