力扣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/peek。Java中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。