LC 388. 文件的最长绝对路径

题目描述

这是 LeetCode 上的 388. 文件的最长绝对路径 ,难度为 中等

假设有一个同时存储文件和目录的文件系统。下图展示了文件系统的一个示例:

这里将 dir 作为根目录中的唯一目录。dir 包含两个子目录 subdir1subdir2

subdir1 包含文件 file1.ext 和子目录 subsubdir1subdir2 包含子目录 subsubdir2,该子目录下包含文件 file2.ext

在文本格式中,如下所示(⟶表示制表符):

1
2
3
4
5
6
7
dir
subdir1
⟶ ⟶ file1.ext
⟶ ⟶ subsubdir1
subdir2
⟶ ⟶ subsubdir2
⟶ ⟶ ⟶ file2.ext

如果是代码表示,上面的文件系统可以写为 "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdir1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext"'\n''\t' 分别是换行符和制表符。

文件系统中的每个文件和文件夹都有一个唯一的 绝对路径 ,即必须打开才能到达文件/目录所在位置的目录顺序,所有路径用 ‘/‘ 连接。上面例子中,指向 file2.ext 的 绝对路径 是 "dir/subdir2/subsubdir2/file2.ext" 。每个目录名由字母、数字和/或空格组成,每个文件名遵循 name.extension 的格式,其中 nameextension 由字母、数字和/或空格组成。

给定一个以上述格式表示文件系统的字符串 input ,返回文件系统中 指向 文件 的 最长绝对路径 的长度 。 如果系统中没有文件,返回 $0$。

示例 1:

1
2
3
4
5
输入:input = "dir\n\tsubdir1\n\tsubdir2\n\t\tfile.ext"

输出:20

解释:只有一个文件,绝对路径为 "dir/subdir2/file.ext" ,路径长度 20

示例 2:

1
2
3
4
5
6
7
8
输入:input = "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdir1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext"

输出:32

解释:存在两个文件:
"dir/subdir1/file1.ext" ,路径长度 21
"dir/subdir2/subsubdir2/file2.ext" ,路径长度 32
返回 32 ,因为这是最长的路径

示例 3:
1
2
3
4
5
输入:input = "a"

输出:0

解释:不存在任何文件

示例 4:
1
2
3
4
5
6
输入:input = "file1.txt\nfile2.txt\nlongfile.txt"

输出:12

解释:根目录下有 3 个文件。
因为根目录中任何东西的绝对路径只是名称本身,所以答案是 "longfile.txt" ,路径长度为 12

提示:

  • $1 <= input.length <= 10^4$
  • input 可能包含小写或大写的英文字母,一个换行符 '\n',一个制表符 '\t',一个点 '.',一个空格 ' ',和数字。

模拟 + 哈希表

为了方便,我们将 input 替换为 s

对于每一个文件或文件夹而言,我们可以通过访问到结尾(\n)的方式取得,记为 cur,然后根据 cur 前面有多少个 \t 得知其所在的层级,假设当前其所在层级为 level,那么它自然归属到最新一个层级为 level - 1 的文件夹中,因此我们可以使用哈希表记录每个层级最新的文件夹路径,通过字符串拼接的方式得到 cur 所在的完整路径 path,并在处理整个 s 过程中,统计长度最大的文件路径。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int lengthLongestPath(String s) {
Map<Integer, String> map = new HashMap<>();
int n = s.length();
String ans = null;
for (int i = 0; i < n; ) {
int level = 0;
while (i < n && s.charAt(i) == '\t' && ++level >= 0) i++;
int j = i;
boolean isDir = true;
while (j < n && s.charAt(j) != '\n') {
if (s.charAt(j++) == '.') isDir = false;
}
String cur = s.substring(i, j);
String prev = map.getOrDefault(level - 1, null);
String path = prev == null ? cur : prev + "/" + cur;
if (isDir) map.put(level, path);
else if (ans == null || path.length() > ans.length()) ans = path;
i = j + 1;
}
return ans == null ? 0 : ans.length();
}
}

  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(n)$

优化

上述做法只是为了方便我们输出具体方案。

实际上,我们只关心最终的路径长度,而不关心具体路径,因此容易将解法一修改为只记录长度,而不记录路径的做法,从而避免掉字符串拼接带来的消耗,同时利用 s 的长度数据范围,使用数组来替代常数较大的哈希表。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
static int[] hash = new int[10010];
public int lengthLongestPath(String s) {
Arrays.fill(hash, -1);
int n = s.length(), ans = 0;
for (int i = 0; i < n; ) {
int level = 0;
while (i < n && s.charAt(i) == '\t' && ++level >= 0) i++;
int j = i;
boolean isDir = true;
while (j < n && s.charAt(j) != '\n') {
if (s.charAt(j++) == '.') isDir = false;
}
Integer cur = j - i;
Integer prev = level - 1 >= 0 ? hash[level - 1] : -1;
Integer path = prev + 1 + cur;
if (isDir) hash[level] = path;
else if (path > ans) ans = path;
i = j + 1;
}
return ans;
}
}

  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(C)$

最后

这是我们「刷穿 LeetCode」系列文章的第 No.388 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!