LeetCode 388中等栈 · 单调栈
文件的最长绝对路径 图解题解
这道题到底在问什么
输入是一个字符串:换行符分隔每一行,每行开头的制表符个数表示这一项在目录树里的深度,名字带点号的是文件、否则是目录。求最长的「目录/.../文件」绝对路径有多长(含中间的斜杠),没有文件返回 0。
- 输入
- dir 换行 制表符 subdir1 换行 制表符 subdir2 换行 制表符制表符 file.ext
- 输出
- 20
- 输入
- 本节演示(含两个文件的目录树)
- 输出
- 32
最优解:一步一步想明白
- 3记牢「栈存每层累计长、栈高等于深度、弹栈对齐父层、加一是斜杠、目录入栈文件刷答案」,下面每一帧都在套它。
- 4先把整段文本按换行符拆成 7 行摆在上方,前面的「·」点表示这一行的缩进深度(制表符个数)。栈一开始是空的,我们从第一行开始,逐行往栈里维护「每层累计路径长」。
- 5扫到第 0 行「dir」。它前面有 0 个制表符,所以深度是 0;名字本身 3 个字符。名字里没有点号,它是个目录。先看看栈要不要弹。
- 6栈现在是空的,说明「dir」就在根目录下,没有父层,前面也不用加斜杠。它的累计长度就等于名字本身 3。
- 7累计 = 名字 3 = 3。「dir」是目录,把这个累计 3 压进栈,它就成了下一层更深目录的父层。继续看下一行。
- 8扫到第 1 行「subdir1」。它前面有 1 个制表符,所以深度是 1;名字本身 7 个字符。名字里没有点号,它是个目录。先看看栈要不要弹。
- 9这一行深度 1,栈高正好是 1,不满足「栈高 > 深度」,所以一个都不弹。栈顶的父层是「dir」,累计 3。
- 10累计 = 名字 7 + 父层 3 + 斜杠 1 = 11。「subdir1」是目录,把这个累计 11 压进栈,它就成了下一层更深目录的父层。继续看下一行。
- 11扫到第 2 行「file1.ext」。它前面有 2 个制表符,所以深度是 2;名字本身 9 个字符。名字里带点号,它是个文件。先看看栈要不要弹。
- 12这一行深度 2,栈高正好是 2,不满足「栈高 > 深度」,所以一个都不弹。栈顶的父层是「subdir1」,累计 11。
- 13「file1.ext」是文件,它对应一条完整绝对路径,候选路径长 = 名字 9 + 父层 11 + 斜杠 1 = 21。21 比旧答案 0 大,刷新答案为 21。文件不入栈,继续往下扫。
- 14扫到第 3 行「subsubdir1」。它前面有 2 个制表符,所以深度是 2;名字本身 10 个字符。名字里没有点号,它是个目录。先看看栈要不要弹。
- 15这一行深度 2,栈高正好是 2,不满足「栈高 > 深度」,所以一个都不弹。栈顶的父层是「subdir1」,累计 11。
- 16累计 = 名字 10 + 父层 11 + 斜杠 1 = 22。「subsubdir1」是目录,把这个累计 22 压进栈,它就成了下一层更深目录的父层。继续看下一行。
- 17扫到第 4 行「subdir2」。它前面有 1 个制表符,所以深度是 1;名字本身 7 个字符。名字里没有点号,它是个目录。先看看栈要不要弹。
- 18这一行深度 1,可栈里还压着更深的层。规则是「栈高 > 深度就弹」,于是连弹 2 个,把栈高对齐成 1。现在栈顶就是它的父层「dir」,累计 3。
- 19累计 = 名字 7 + 父层 3 + 斜杠 1 = 11。「subdir2」是目录,把这个累计 11 压进栈,它就成了下一层更深目录的父层。继续看下一行。
- 20扫到第 5 行「subsubdir2」。它前面有 2 个制表符,所以深度是 2;名字本身 10 个字符。名字里没有点号,它是个目录。先看看栈要不要弹。
- 21这一行深度 2,栈高正好是 2,不满足「栈高 > 深度」,所以一个都不弹。栈顶的父层是「subdir2」,累计 11。
- 22累计 = 名字 10 + 父层 11 + 斜杠 1 = 22。「subsubdir2」是目录,把这个累计 22 压进栈,它就成了下一层更深目录的父层。继续看下一行。
- 23扫到第 6 行「file2.ext」。它前面有 3 个制表符,所以深度是 3;名字本身 9 个字符。名字里带点号,它是个文件。先看看栈要不要弹。
- 24这一行深度 3,栈高正好是 3,不满足「栈高 > 深度」,所以一个都不弹。栈顶的父层是「subsubdir2」,累计 22。
- 25「file2.ext」是文件,它对应一条完整绝对路径,候选路径长 = 名字 9 + 父层 22 + 斜杠 1 = 32。32 比旧答案 21 大,刷新答案为 32。文件不入栈,继续往下扫。
- 26全部扫完。两个文件里,file1.ext 那条是 21,file2.ext 那条最长,正是 dir/subdir2/subsubdir2/file2.ext,长度 3+1+7+1+10+1+9 = 32。栈始终只维护「当前这条路径」上的每层累计,一遍扫描就锁定了答案 32。
⚠️ 容易写错的地方
✗ 错:把斜杠的长度漏掉
✓ 对:父层累计后要 +1 再加自身
绝对路径里每多一层就多一个斜杠分隔符,长度要算进去
✗ 错:用「是否含点号」判目录还是文件时把目录名里的点也当文件
✓ 对:本题保证目录名不含点、文件名形如 name.extension
题目约束下含点号即文件,无需更复杂判断
✗ 错:弹栈条件写成「栈高 ≥ 深度」
✓ 对:应是「栈高 > 深度」才弹
要保留与本行同深度的父层位置,弹过头会丢父层累计
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def lengthLongestPath(self, input: str) -> int:
i, n = 0, len(input)
ans = 0
stk = []
while i < n:
ident = 0
while input[i] == '\t':
ident += 1
i += 1
cur, isFile = 0, False
while i < n and input[i] != '\n':
cur += 1
if input[i] == '.':
isFile = True
i += 1
i += 1
# popd
while len(stk) > 0 and len(stk) > ident:
stk.pop()
if len(stk) > 0:
cur += stk[-1] + 1
# pushd
if not isFile:
stk.append(cur)
continue
ans = max(ans, cur)
return ansC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int lengthLongestPath(string input) {
int i = 0, n = input.size();
int ans = 0;
stack<int> stk;
while (i < n) {
int ident = 0;
for (; input[i] == '\t'; ++i) {
++ident;
}
int cur = 0;
bool isFile = false;
for (; i < n && input[i] != '\n'; ++i) {
++cur;
if (input[i] == '.') {
isFile = true;
}
}
++i;
// popd
while (!stk.empty() && stk.size() > ident) {
stk.pop();
}
if (stk.size() > 0) {
cur += stk.top() + 1;
}
// pushd
if (!isFile) {
stk.push(cur);
continue;
}
ans = max(ans, cur);
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int lengthLongestPath(String input) {
int i = 0;
int n = input.length();
int ans = 0;
Deque<Integer> stack = new ArrayDeque<>();
while (i < n) {
int ident = 0;
for (; input.charAt(i) == '\t'; i++) {
ident++;
}
int cur = 0;
boolean isFile = false;
for (; i < n && input.charAt(i) != '\n'; i++) {
cur++;
if (input.charAt(i) == '.') {
isFile = true;
}
}
i++;
// popd
while (!stack.isEmpty() && stack.size() > ident) {
stack.pop();
}
if (stack.size() > 0) {
cur += stack.peek() + 1;
}
// pushd
if (!isFile) {
stack.push(cur);
continue;
}
ans = Math.max(ans, cur);
}
return ans;
}
}复杂度
时间
O(n)
n 为字符串长度,每个字符只被扫一次
空间
O(d)
d 为最大目录深度,栈最多存一条路径上的各层
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 文件的最长绝对路径 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么用栈,而不是把每条路径都拼成字符串再求长?+
拼字符串会重复拷贝公共前缀,最坏会退化到平方级。用栈只维护「每层的累计长度」这一个数字,公共前缀天然复用,读到文件时一次加法就得到整条路径长,整体只需一遍 O(n) 扫描。
如果还要返回那条最长路径本身(而不只是长度),怎么改?+
把栈里同时存「累计长 + 这一层的名字」,或者再维护一个名字栈。读到刷新答案的文件时,把当前名字栈用斜杠拼起来记下来即可。核心结构不变,只是多存了名字。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 文件的最长绝对路径 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。