力扣388(java)-文件的最长绝对路径(中等)

题目:

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

 

这里将 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 ,返回文件系统中 指向 文件 的 最长绝对路径 的长度 。 如果系统中没有文件,返回 0。

 示例 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 <= 104
  • input 可能包含小写或大写的英文字母,一个换行符 '\n',一个制表符 '\t',一个点 '.',一个空格 ' ',和数字。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/longest-absolute-file-path
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路:用栈来解决该问题,栈中元素为已遍历路的长度,栈的长度(元素的个数)为当前路径的深度,栈顶元素为当前路径的长度。

 从示例中可以发现:

1.每个文件或目录的开头,都是以一个 \n 来划分;

2.文件或目录的层级,可以通过 \t 的个数来得到;

3.通过 “.” 来区分是文件还是目录。

注意给定的字符串中,\n , \ t都算一个字符。

具体步骤如下:

1.循环字符串中的每个文件或目录,如果当前字符为 \t,则当前深度level++,跳入下一个字符 i++;

2.如果当前字符既不是\n又不是\t,则需要判断是文件还是目录,需要统计出当前文件或者目录的长度,跳过当前字符挨着的\n;

3.如果当前目录的深度 < 当前路径的深度,说明当前节点并不是上一个节点的父节点,需要回退到当前节点的父节点;

举个例子:

遍历到subsubdir1时,当前路径的深度为stack.size()=3,遍历到subdir2时,当前目录的深度为level=1,说明subsubdir1并不是subdir2的父节点,则需要回退到subdir2的父节点dir。

4.判断栈是否为空,不为空,则算出当前路径的总长度,然后如果当前路径为文件,则更新最大长度,否则就代表是目录,暂时将目录的长度压入栈中,最后将最大长度返回即可。

模拟一下:

input = "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdir1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext"

①:dir ==> strLen = 3, i = 3,跳过挨着的\n,i = 4,当前level 和 stack.size()都为0,跳过,则入栈<3>;

②:\t ==>level = 1,i = 5;

③:subdir1 ==>strLen = 7, i = 12,跳过挨着的\n,i = 13,当前level 和 stack.size()都为1,跳过,栈不为空,更新strLen = 10,不是文件则入栈<3,10>;

④:\t\t ==>level = 2,i =15;

⑤:file1.ext ==>strLen = 9,i =24,跳过挨着的\n,i = 25,当前level 和 stack.size()都为2,跳过,栈不为空,更新strLen = 19,当前字符串为文件,则更sum = Math.max(sum, strLen + level)=Math.max(0, 21) = 21;

⑥: \t\t ==>level = 2,i =27;

⑦:subsubdir1 ==>strLen = 10,i =37,跳过挨着的\n,i = 38,当前level 和 stack.size()都为2,跳过,栈不为空,更新strLen = 20,不是文件则入栈<3,10,20>;

⑧:\t ==>level = 1,i = 39;

⑨:subdir2 ==>strLen = 7,i =46,跳过挨着的\n,i = 47,当前level = 1 <  stack.size()=3,故stack.pop(),故栈为<3>,栈不为空,更新strLen = 10,不是文件则入栈<3,10>;

⑩:\t\t ==>level = 2,i =49;

①①:subsubdir2 ==>strLen = 10,i =59,跳过挨着的\n,i = 60,当前level 和stack.size都为2,跳过,栈不为空,更新strLen = 20,不是文件则入栈<3,10,20>;

①②:\t\t\t ==>level = 3,i =63;

①③:file2.ext ==> strLen = 9,i =72,i++ = 73,当前level 和stack.size都为3,跳过,栈不为空,更新strLen = 29,当前字符串为文件,则更sum = Math.max(sum, strLen + level)=Math.max(21, 32) = 32;

①④: i > n结束循环,返回sum = 32

代码:

 1 class Solution {
 2     public int lengthLongestPath(String input) {
 3         //统计最后的结果
 4         int sum = 0;
 5         //算出输入字符串的长度
 6         int n = input.length();
 7         //用栈来存放已遍历的路径长度
 8         Deque<Integer> stack = new ArrayDeque<Integer>();
 9         //循环输入的每个文件或目录
10         //i代表每个文件或目录的下标
11         int i = 0;
12         while(i < n){
13             //通过\t统计出当前目录或文件的层数
14             int level = 0;
15             while(i < n && input.charAt(i) == '\t'){
16                 level++;
17                 i++;
18             }
19             //统计当前文件名或目录的长度
20             Boolean isFile = false;
21             int strLen = 0;
22             while(i < n && input.charAt(i) != '\n'){
23                 if(input.charAt(i) == '.'){
24                     isFile = true;
25                 }
26                 strLen++;
27                 i++;
28             }
29             //跳过当前字符串接着的\n
30             i++;
31             //当前节点的深度 < 当前路径的深度,需要返回上一级目录
32             while(level < stack.size()){
33                 stack.pop();
34             }
35 
36             //如果栈不为空,则就算出该条路的总长度(不包含/)
37             if(!stack.isEmpty()){
38                 strLen += stack.peek();
39             }
40             if(isFile){
41                 //更新最大长度,level代表 / 的个数
42                 sum = Math.max(sum, strLen + level);
43             }else{
44                 //如果是目录,则先入栈
45                 stack.push(strLen);
46             }    
47         }
48         return sum;
49     }
50 }

小知识:

Java中的栈Stack、Deque、ArrayDeque、LinkedList:来源@liberg

 三种定义:
1 Deque<Integer> stack = new ArrayDeque<>();
2 Deque<Integer> stack = new LinkedList<>();
3 Stack<Integer> stack = new Stack<>();

栈是一种后进先出(LIFO)的容器,常用的操作push/pop/peekJava中Stack类从Vector类继承,底层是用数组实现的线程安全的栈。Java中用来表达栈的功能(push/pop/peek)更适用的是使用双端队列接口Deque,并用实现类 ArrayDeque/LinkedList 来进行初始化。

不使用Stack的原因:

1.从性能上来说应该使用Deque代替Stack。

2.JDK官方已建议优先使用Deque的实现类来代替Stack。

关于ArrayDeque和LinkedList的选择(这两者既可当成栈(仅支持在尾部加入或移除元素)使用,也可当成双端队列使用,即可以在队列的两端(头或尾)将元素加入或移除):

  • ArrayDeque和LinkedList这两者底层,一个采用数组存储,一个采用链表存储;
  • 数组存储,容量不够时需要扩容和数组拷贝,通常容量不会填满,会有空间浪费;
  • 链表存储,每次push都需要new Node节点,并且node节点里面有prev和next成员,也会有额外的空间占用。

ArrayDeque用作栈时比Stack快,用作队列的时候也会比LinkedList快。在用作栈时,更倾向于使用ArrayDeque。

posted on 2022-09-01 10:42  我不想一直当菜鸟  阅读(72)  评论(0编辑  收藏  举报