题目描述
这是 LeetCode 上的 388. 文件的最长绝对路径 ,难度为 中等。
Tag : 「模拟」、「哈希表」
假设有一个同时存储文件和目录的文件系统。下图展示了文件系统的一个示例:
这里将 dir
作为根目录中的唯一目录。dir
包含两个子目录 subdir1
和 subdir2
。
subdir1
包含文件 file1.ext
和子目录 subsubdir1
;subdir2
包含子目录 subsubdir2
,该子目录下包含文件 file2.ext
。
在文本格式中,如下所示(⟶表示制表符):
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
的格式,其中 name
和 extension
由字母、数字和/或空格组成。
给定一个以上述格式表示文件系统的字符串 input
,返回文件系统中 指向 文件 的 最长绝对路径 的长度 。 如果系统中没有文件,返回 00。
示例 1:
输入:input = "dir\n\tsubdir1\n\tsubdir2\n\t\tfile.ext" 输出:20 解释:只有一个文件,绝对路径为 "dir/subdir2/file.ext" ,路径长度 20 复制代码
示例 2:
输入: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:
输入:input = "a" 输出:0 解释:不存在任何文件 复制代码
示例 4:
输入:input = "file1.txt\nfile2.txt\nlongfile.txt" 输出:12 解释:根目录下有 3 个文件。 因为根目录中任何东西的绝对路径只是名称本身,所以答案是 "longfile.txt" ,路径长度为 12 复制代码
提示:
- 1 <= input.length <= 10^41<=input.length<=104
input
可能包含小写或大写的英文字母,一个换行符'\n'
,一个制表符'\t'
,一个点'.'
,一个空格' '
,和数字。
模拟 + 哈希表
为了方便,我们将 input
替换为 s
。
对于每一个文件或文件夹而言,我们可以通过访问到结尾(\n
)的方式取得,记为 cur
,然后根据 cur
前面有多少个 \t
得知其所在的层级,假设当前其所在层级为 level
,那么它自然归属到最新一个层级为 level - 1
的文件夹中,因此我们可以使用哈希表记录每个层级最新的文件夹路径,通过字符串拼接的方式得到 cur
所在的完整路径 path
,并在处理整个 s
过程中,统计长度最大的文件路径。
代码:
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)
- 空间复杂度:O(n)O(n)
优化
上述做法只是为了方便我们输出具体方案。
实际上,我们只关心最终的路径长度,而不关心具体路径,因此容易将解法一修改为只记录长度,而不记录路径的做法,从而避免掉字符串拼接带来的消耗,同时利用 s
的长度数据范围,使用数组来替代常数较大的哈希表。
代码:
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(n)
- 空间复杂度:O(C)O(C)
最后
这是我们「刷穿 LeetCode」系列文章的第 No.388
篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour… 。
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。