删除系统中的重复文件夹
难度:
标签:
题目描述
由于一个漏洞,文件系统中存在许多重复文件夹。给你一个二维数组 paths
,其中 paths[i]
是一个表示文件系统中第 i
个文件夹的绝对路径的数组。
- 例如,
["one", "two", "three"]
表示路径"/one/two/three"
。
如果两个文件夹(不需要在同一层级)包含 非空且相同的 子文件夹 集合 并具有相同的子文件夹结构,则认为这两个文件夹是相同文件夹。相同文件夹的根层级 不 需要相同。如果存在两个(或两个以上)相同 文件夹,则需要将这些文件夹和所有它们的子文件夹 标记 为待删除。
- 例如,下面文件结构中的文件夹
"/a"
和"/b"
相同。它们(以及它们的子文件夹)应该被 全部 标记为待删除:/a
/a/x
/a/x/y
/a/z
/b
/b/x
/b/x/y
/b/z
- 然而,如果文件结构中还包含路径
"/b/w"
,那么文件夹"/a"
和"/b"
就不相同。注意,即便添加了新的文件夹"/b/w"
,仍然认为"/a/x"
和"/b/x"
相同。
一旦所有的相同文件夹和它们的子文件夹都被标记为待删除,文件系统将会 删除 所有上述文件夹。文件系统只会执行一次删除操作。执行完这一次删除操作后,不会删除新出现的相同文件夹。
返回二维数组 ans
,该数组包含删除所有标记文件夹之后剩余文件夹的路径。路径可以按 任意顺序 返回。
示例 1:

输入:paths = [["a"],["c"],["d"],["a","b"],["c","b"],["d","a"]] 输出:[["d"],["d","a"]] 解释:文件结构如上所示。 文件夹 "/a" 和 "/c"(以及它们的子文件夹)都会被标记为待删除,因为它们都包含名为 "b" 的空文件夹。
示例 2:

输入:paths = [["a"],["c"],["a","b"],["c","b"],["a","b","x"],["a","b","x","y"],["w"],["w","y"]] 输出:[["c"],["c","b"],["a"],["a","b"]] 解释:文件结构如上所示。 文件夹 "/a/b/x" 和 "/w"(以及它们的子文件夹)都会被标记为待删除,因为它们都包含名为 "y" 的空文件夹。 注意,文件夹 "/a" 和 "/c" 在删除后变为相同文件夹,但这两个文件夹不会被删除,因为删除只会进行一次,且它们没有在删除前被标记。
示例 3:

输入:paths = [["a","b"],["c","d"],["c"],["a"]] 输出:[["c"],["c","d"],["a"],["a","b"]] 解释:文件系统中所有文件夹互不相同。 注意,返回的数组可以按不同顺序返回文件夹路径,因为题目对顺序没有要求。
示例 4:

输入:paths = [["a"],["a","x"],["a","x","y"],["a","z"],["b"],["b","x"],["b","x","y"],["b","z"]] 输出:[] 解释:文件结构如上所示。 文件夹 "/a/x" 和 "/b/x"(以及它们的子文件夹)都会被标记为待删除,因为它们都包含名为 "y" 的空文件夹。 文件夹 "/a" 和 "/b"(以及它们的子文件夹)都会被标记为待删除,因为它们都包含一个名为 "z" 的空文件夹以及上面提到的文件夹 "x" 。
示例 5:

输入:paths = [["a"],["a","x"],["a","x","y"],["a","z"],["b"],["b","x"],["b","x","y"],["b","z"],["b","w"]] 输出:[["b"],["b","w"],["b","z"],["a"],["a","z"]] 解释:本例与上例的结构基本相同,除了新增 "/b/w" 文件夹。 文件夹 "/a/x" 和 "/b/x" 仍然会被标记,但 "/a" 和 "/b" 不再被标记,因为 "/b" 中有名为 "w" 的空文件夹而 "/a" 没有。 注意,"/a/z" 和 "/b/z" 不会被标记,因为相同子文件夹的集合必须是非空集合,但这两个文件夹都是空的。
提示:
1 <= paths.length <= 2 * 104
1 <= paths[i].length <= 500
1 <= paths[i][j].length <= 10
1 <= sum(paths[i][j].length) <= 2 * 105
path[i][j]
由小写英文字母组成- 不会存在两个路径都指向同一个文件夹的情况
- 对于不在根层级的任意文件夹,其父文件夹也会包含在输入中
代码结果
运行时间: 261 ms, 内存: 45.4 MB
/*
* 思路:
* 1. 使用Stream API构建文件系统树。
* 2. 使用Stream API遍历和序列化文件夹树。
* 3. 使用Collectors和Map计算重复的文件夹。
* 4. 使用Stream API收集剩余的文件夹路径。
*/
import java.util.*;
import java.util.stream.*;
class Solution {
class TrieNode {
Map<String, TrieNode> children = new HashMap<>();
String path;
boolean toDelete = false;
TrieNode(String path) {
this.path = path;
}
}
public List<List<String>> deleteDuplicateFolder(List<List<String>> paths) {
TrieNode root = new TrieNode("");
// 构建文件系统树
paths.forEach(path -> {
TrieNode curr = root;
path.forEach(folder -> curr.children.putIfAbsent(folder, new TrieNode(folder)));
});
Map<String, Integer> count = new HashMap<>();
// 序列化和计数
serialize(root, count);
List<List<String>> result = new ArrayList<>();
// 获取剩余文件夹
collectRemainingFolders(root, new ArrayList<>(), result);
return result;
}
private String serialize(TrieNode node, Map<String, Integer> count) {
if (node.children.isEmpty()) {
return "";
}
String serialized = node.children.values().stream()
.map(child -> child.path + "(" + serialize(child, count) + ")")
.sorted()
.collect(Collectors.joining());
count.merge(serialized, 1, Integer::sum);
if (count.get(serialized) > 1) {
node.toDelete = true;
}
return serialized;
}
private void collectRemainingFolders(TrieNode node, List<String> path, List<List<String>> result) {
if (node.toDelete) {
return;
}
if (!path.isEmpty()) {
result.add(new ArrayList<>(path));
}
node.children.values().forEach(child -> {
path.add(child.path);
collectRemainingFolders(child, path, result);
path.remove(path.size() - 1);
});
}
}
解释
方法:
此题解使用字典树(Trie)和序列化的方法来识别和删除重复的文件夹。首先,利用Trie树将每个路径按照文件夹的层级结构构建成树形结构。然后,使用深度优先搜索(DFS)对每个节点进行后序遍历,将每个节点的子树结构序列化成字符串形式。通过这种序列化,可以轻易地比较不同节点的子树结构是否相同。使用一个哈希表来记录每种序列化表示的出现频率。最后,再次使用DFS遍历Trie树,此次遍历时,根据序列化字符串的出现次数判断该节点是否应被删除。如果某个节点的序列化表示出现超过一次,则其对应的文件夹及子文件夹都标记为删除。最终,将所有未被标记删除的文件夹路径收集并返回。
时间复杂度:
O(N*L + N*S*log(S))
空间复杂度:
O(N*L)
代码细节讲解
🦆
在构建Trie树的过程中,每个节点是如何确保不会重复添加相同的文件夹名,从而避免错误地构建树结构?
▷🦆
为什么在序列化节点时需要对子节点名称进行排序?这一步骤对比较不同节点的子树结构有何作用?
▷🦆
在实现`construct`函数时,序列化字符串是如何从子节点构建并累加到父节点的?能否详细解释这一过程?
▷🦆
在使用哈希表记录序列化字符串的出现频率时,如果两个完全不同的路径导致相同的序列化输出,这会对结果有什么影响?
▷