目录删除
问题背景
我们需要处理一种特殊的字符串格式,它能表示一个目录树的层级结构,类似于 Windows 系统中 tree /f 命令的输出。我们的任务是根据一系列规则,删除这个目录树中的特定目录,并统计被删除目录的总数。
目录结构字符串格式
-
内容: 输入的字符串仅包含数字、字母(大小写敏感)和特殊符号
|-。 -
层次表示:
- 符号
|-用于表示目录的层级深度。一个目录前的|-序列数量决定了它在树中的层级。 - 没有
|-前缀的是根目录(第1层)。一个|-表示第2层,两个|-表示第3层,以此类推。
- 符号
-
父子关系:
- 每个子目录都挂载在其上方最近的一个、层级比它浅一层的目录之下。
删除规则
一个目录能够被删除,当且仅当它同时满足以下两个条件:
- 名称匹配: 该目录的名称必须在给定的待删除目录名列表
delDirs中。 - 叶子节点: 该目录必须是一个叶子目录(即没有任何子目录)。
特别注意(连锁反应):
当一个目录的所有子目录都被删除后,它自身也会变成一个叶子目录。此时,如果它的名称也恰好在 delDirs 列表中,那么它也符合被删除的条件,删除操作会继续向上进行。
任务要求
给定一个表示目录树的字符串列表 dirTreeLines 和一个待删除目录名的列表 delDirs,请重复应用上述删除规则,直到没有任何目录可以被删除为止。最终,返回被删除的不同路径的目录总数。
输入格式
-
delDirs: 第一个参数,一个字符串列表,包含所有待删除的目录名。1 <= delDirs.length <= 1001 <= delDirs[i].length <= 100
-
dirTreeLines: 第二个参数,一个字符串列表,表示目录树的结构。1 <= dirTreeLines.length <= 501 <= dirTreeLines[i].length <= 100
-
用例保证:
- 输入的目录结构是合法的。
- 有且仅有一个根目录。
- 除根目录外,所有目录都有对应的父目录。
输出格式
- 一个整数,表示最终被删除的目录的总个数。如果没有任何目录被删除,则输出
0。
样例说明
样例 1
-
输入:
delDirs = ["B", "Cpp"]dirTreeLines = ["A", "|-B", "|-|-Cpp", "|-|-B", "|-|-|-B", "|-lib32"]
-
输出:
4 -
解释:
-
构建目录树:
/A(根)/A/B/A/B/Cpp/A/B/B/A/B/B/B/A/lib32
-
模拟删除过程 (多轮进行) :
-
第 1 轮: 寻找初始的、符合删除条件的叶子目录。
/A/B/Cpp: 是叶子目录,名称Cpp在delDirs中 -> 删除 (计数: 1)。/A/B/B/B: 是叶子目录,名称B在delDirs中 -> 删除 (计数: 2)。/A/lib32: 是叶子目录,但名称lib32不在delDirs中 -> 不删除。
-
第 2 轮: 因
/A/B/B/B被删除,/A/B/B现已成为叶子目录。/A/B/B: 是新的叶子目录,名称B在delDirs中 -> 删除 (计数: 3)。
-
第 3 轮: 因
/A/B/Cpp和/A/B/B都被删除,/A/B现已成为叶子目录。/A/B: 是新的叶子目录,名称B在delDirs中 -> 删除 (计数: 4)。
-
结束:
/A仍然有子目录/A/lib32,不是叶子目录,流程结束。
-
-
最终结果: 共删除了 4 个目录。
-
样例 2
-
输入:
delDirs = ["A", "Java"]dirTreeLines = ["A", "|-a", "|-|-A", "|-A", "|-|-A"]
-
输出:
3 -
解释:
-
构建目录树:
/A(根)/A/a/A/a/A/A/A/A/A/A
-
模拟删除过程:
-
第 1 轮:
/A/a/A: 是叶子,名称A在delDirs中 -> 删除 (计数: 1)。/A/A/A: 是叶子,名称A在delDirs中 -> 删除 (计数: 2)。
-
第 2 轮:
/A/a/A和/A/A/A被删除后,/A/a和/A/A成为新的叶子目录。/A/a: 是叶子,但名称a不在delDirs中(大小写敏感)-> 不删除。/A/A: 是叶子,名称A在delDirs中 -> 删除 (计数: 3)。
-
结束: 根目录
/A仍有子目录/A/a,不是叶子目录,无法删除。
-
-
最终结果: 共删除了 3 个目录。
-
import java.util.*;
import java.util.stream.Collectors;
public class Solution {
/**
* 内部静态类,用于表示目录树中的一个节点。
*/
static class DirNode {
String name; // 目录名
DirNode parent; // 指向父节点的引用
List<DirNode> children = new ArrayList<>(); // 存储所有直接子节点
boolean isDeleted = false; // 标记该节点是否已被删除
DirNode(String name, DirNode parent) {
this.name = name;
this.parent = parent;
}
/**
* 判断当前节点是否为叶子节点。
* 一个节点是叶子,当且仅当它的所有子节点都已经被标记为删除。
* 如果一个节点原本就没有子节点,它也是叶子。
* @return 如果是叶子节点,返回 true;否则返回 false。
*/
boolean isLeaf() {
for (DirNode child : children) {
// 只要有一个子节点尚未被删除,当前节点就不是叶子节点
if (!child.isDeleted) {
return false;
}
}
return true; // 所有子节点都已被删除(或没有子节点)
}
}
/**
* 主函数,实现删除逻辑并返回删除的目录数。
* @param delDirs 要删除的目录名列表
* @param dirTreeLines 代表目录树结构的字符串列表
* @return 被删除的目录总数
*/
public int deleteDirectories(String[] delDirs, String[] dirTreeLines) {
// --- 1. 准备工作 ---
// 将要删除的目录名放入 HashSet,以便进行 O(1) 的快速查找
Set<String> namesToDelete = new HashSet<>(Arrays.asList(delDirs));
// 处理边界情况
if (dirTreeLines == null || dirTreeLines.length == 0 || namesToDelete.isEmpty()) {
return 0;
}
// --- 2. 构建目录树 ---
// allNodes 列表用于存储树中所有的节点,方便后续进行多轮遍历
List<DirNode> allNodes = new ArrayList<>();
// pathStack 数组像一个路径栈,pathStack[d] 存储深度为 d 的最新节点
DirNode[] pathStack = new DirNode[dirTreeLines.length]; // 深度不可能超过行数
// 处理根节点(总是在第一行,深度为 0)
String rootName = dirTreeLines[0];
DirNode root = new DirNode(rootName, null);
allNodes.add(root);
pathStack[0] = root;
// 遍历剩余的行,构建树的其余部分
for (int i = 1; i < dirTreeLines.length; i++) {
String line = dirTreeLines[i];
// 计算当前行的深度。"|-" 长度为2。
// lastIndexOf 找到最后一个 "|-" 的起始位置。
int depth = line.lastIndexOf("|-") / 2 + 1;
// 截取掉前缀,得到目录名
String name = line.substring(depth * 2);
// 父节点是上一深度的最新节点
DirNode parent = pathStack[depth - 1];
// 创建新节点
DirNode newNode = new DirNode(name, parent);
// 建立父子关系
parent.children.add(newNode);
// 更新路径栈和节点列表
pathStack[depth] = newNode;
allNodes.add(newNode);
}
// --- 3. 迭代删除 ---
int deletedCount = 0; // 记录删除的目录总数
boolean deletedInThisPass; // 标记在一轮完整的遍历中是否发生了删除
// 使用 do-while 循环,至少执行一次,只要上一轮有删除操作就继续下一轮
do {
deletedInThisPass = false; // 每轮开始前重置标志
// 遍历所有节点,尝试删除
for (DirNode node : allNodes) {
// 检查是否满足所有删除条件:
// 1. 节点尚未被删除
// 2. 节点的名字在待删除列表中
// 3. 节点当前是一个叶子节点(即其所有子节点都已被删除)
if (!node.isDeleted && namesToDelete.contains(node.name) && node.isLeaf()) {
node.isDeleted = true; // 标记为已删除
deletedCount++; // 总数加 1
deletedInThisPass = true; // 标记本轮发生了删除
}
}
} while (deletedInThisPass); // 如果本轮有删除,则可能导致新的叶子节点产生,需要再来一轮
// 4. 返回结果
return deletedCount;
}
}
public class Main {
public static void main(String[] args) {
Solution solver = new Solution();
// 样例1
System.out.println("--- 样例 1 ---");
String[] delDirs1 = {"B", "Cpp"};
String[] dirTreeLines1 = {
"A",
"|-B",
"|-|-Cpp",
"|-|-B",
"|-|-|-B",
"|-lib32"
};
int result1 = solver.deleteDirectories(delDirs1, dirTreeLines1);
System.out.println("输出: " + result1); // 预期: 4
System.out.println();
// 样例2
System.out.println("--- 样例 2 ---");
String[] delDirs2 = {"A", "Java"};
String[] dirTreeLines2 = {
"A",
"|-a",
"|-|-A",
"|-A",
"|-|-A"
};
int result2 = solver.deleteDirectories(delDirs2, dirTreeLines2);
System.out.println("输出: " + result2); // 预期: 3
}
}