388. Longest Absolute File Path
Problem Description
This problem asks you to find the length of the longest absolute file path in a file system represented as a string.
The file system is represented in a special text format where:
- Each line represents either a directory or a file
- The depth level is indicated by tab characters (
\t
) at the beginning of each line - Files are distinguished from directories by containing a dot (
.
) in their name - The actual string uses
\n
for newlines and\t
for tabs
For example, the string "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdir1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext"
represents:
dir subdir1 file1.ext subsubdir1 subdir2 subsubdir2 file2.ext
The absolute path to any file is formed by concatenating all parent directories with /
separators. In the example above:
- Path to
file1.ext
is"dir/subdir1/file1.ext"
(length 20) - Path to
file2.ext
is"dir/subdir2/subsubdir2/file2.ext"
(length 32)
You need to return the length of the longest absolute path to any file (not directory) in the system. If no files exist, return 0.
The solution uses a stack-based approach:
- Parse each line to determine its indentation level (number of tabs) and content
- Maintain a stack that tracks the cumulative path length at each directory level
- When processing each item:
- Pop directories from the stack until matching the current indentation level
- Calculate the current path length (including parent directories and
/
separators) - If it's a directory, push its length to the stack
- If it's a file (contains
.
), update the maximum path length
- Return the maximum file path length found
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The file system structure is inherently a tree/graph structure where directories are nodes and the parent-child relationships form edges. Each directory can contain multiple subdirectories and files, creating a hierarchical tree structure.
Is it a tree?
- Yes: The file system is specifically a tree structure - it has a root directory, each directory/file has exactly one parent (except the root), and there are no cycles. The indentation levels in the input string represent the parent-child relationships in this tree.
DFS
- Yes: We should use DFS (Depth-First Search) to traverse this tree structure.
Conclusion: The flowchart suggests using DFS for this problem.
The solution implements DFS through a stack-based approach:
- The stack maintains the current path from root to the current directory level
- As we parse each line, we're essentially doing a depth-first traversal of the file system tree
- When we encounter deeper indentation (more tabs), we go deeper into the tree (push to stack)
- When we encounter shallower indentation (fewer tabs), we backtrack (pop from stack)
- This mimics the recursive nature of DFS where we explore as deep as possible before backtracking
The DFS pattern is perfect here because:
- We need to explore each complete path from root to file
- We maintain the current path state as we traverse
- We backtrack when moving to sibling branches
- The stack naturally handles the path construction and backtracking operations
Intuition
When we look at the file system representation, we notice that the indentation level (number of tabs) tells us exactly where we are in the directory hierarchy. Each tab represents going one level deeper into the tree structure.
The key insight is that as we parse the input line by line, we're essentially performing a depth-first traversal of the file system tree. When we see more tabs than before, we're going deeper into subdirectories. When we see fewer tabs, we're backtracking to a parent directory.
This naturally suggests using a stack to keep track of our current path. Why a stack? Because:
- When we go deeper (more tabs), we need to remember where we came from - we push the current directory onto the stack
- When we backtrack (fewer tabs), we need to "forget" the directories we're leaving - we pop from the stack
- The stack always represents our current path from root to the current level
For calculating path lengths efficiently, instead of storing directory names in the stack, we can store cumulative path lengths. This way:
- If the stack has
[3, 7, 12]
, it means:- First directory has length 3
- Path to second level is length 7 (including the
/
separator) - Path to third level is length 12
When we encounter a file (identified by having a .
in its name), we calculate its full path length by taking the parent's cumulative length from the stack and adding the file's name length plus one for the /
separator.
The beauty of this approach is that we don't need to build actual path strings or traverse the tree multiple times. We process each line exactly once, maintaining just enough information (cumulative lengths) to calculate the answer efficiently. The stack naturally handles the tree traversal pattern - growing when we go deeper and shrinking when we backtrack - making this a clean DFS solution.
Solution Approach
The solution implements a stack-based DFS traversal of the file system. Let's walk through the implementation step by step:
1. Initialize Variables:
i
andn
: Track current position and total length of input stringans
: Stores the maximum file path length foundstk
: Stack to maintain cumulative path lengths at each directory level
2. Main Processing Loop: The algorithm processes the input character by character, parsing each line to extract:
- Indentation level (number of tabs)
- Content (directory or file name)
- Whether it's a file (contains a
.
)
3. Parse Indentation Level:
while input[i] == '\t':
ident += 1
i += 1
Count consecutive tab characters to determine the depth level of the current item.
4. Parse Content and Detect Files:
cur, isFile = 0, False
while i < n and input[i] != '\n':
cur += 1
if input[i] == '.':
isFile = True
i += 1
cur
: Counts the length of the current directory/file nameisFile
: Set toTrue
if we encounter a.
(indicating a file)
5. Stack Management - Backtracking:
while len(stk) > 0 and len(stk) > ident:
stk.pop()
This is the backtracking step. If the current indentation level is less than or equal to the stack size, we pop directories from the stack. This happens when we move to a sibling or ancestor directory in the tree.
6. Calculate Cumulative Path Length:
if len(stk) > 0:
cur += stk[-1] + 1
If there's a parent directory (stack not empty), add its cumulative length plus 1 for the /
separator to get the full path length to the current item.
7. Handle Directories vs Files:
if not isFile:
stk.append(cur)
continue
ans = max(ans, cur)
- For directories: Push the cumulative path length onto the stack and continue
- For files: Update the maximum path length if current is longer
Example Walkthrough:
For input "dir\n\tfile.txt"
:
- Process "dir":
ident=0
,cur=3
, not a file → push 3 to stack:[3]
- Process "file.txt":
ident=1
,cur=8
, is a file- Stack has 1 element, which equals
ident
, so no popping - Add parent length:
cur = 8 + 3 + 1 = 12
- Update answer:
ans = 12
- Stack has 1 element, which equals
The algorithm efficiently handles the tree traversal in O(n)
time where n
is the input string length, using O(d)
space where d
is the maximum directory depth.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Input: "dir\n\tsubdir1\n\t\tfile1.ext\n\tsubdir2\n\t\tfile2.ext"
This represents:
dir subdir1 file1.ext subdir2 file2.ext
Step-by-step execution:
Initial state: i=0
, ans=0
, stk=[]
Line 1: "dir"
- Parse indentation:
ident=0
(no tabs) - Parse content:
cur=3
(length of "dir"),isFile=False
(no dot) - Stack management: Stack is empty and equals
ident
, so no popping - Since it's a directory, push to stack:
stk=[3]
Line 2: "\tsubdir1"
- Parse indentation:
ident=1
(one tab) - Parse content:
cur=7
(length of "subdir1"),isFile=False
- Stack management:
len(stk)=1
equalsident
, so no popping - Calculate path:
cur = 7 + stk[-1] + 1 = 7 + 3 + 1 = 11
(adds parent + separator) - Push to stack:
stk=[3, 11]
Line 3: "\t\tfile1.ext"
- Parse indentation:
ident=2
(two tabs) - Parse content:
cur=10
(length of "file1.ext"),isFile=True
(contains dot) - Stack management:
len(stk)=2
equalsident
, so no popping - Calculate path:
cur = 10 + stk[-1] + 1 = 10 + 11 + 1 = 22
- It's a file, so update answer:
ans = max(0, 22) = 22
- Don't push to stack (it's a file)
Line 4: "\tsubdir2"
- Parse indentation:
ident=1
(one tab) - Parse content:
cur=7
(length of "subdir2"),isFile=False
- Stack management:
len(stk)=2
>ident=1
, so pop once:stk=[3]
- Calculate path:
cur = 7 + stk[-1] + 1 = 7 + 3 + 1 = 11
- Push to stack:
stk=[3, 11]
Line 5: "\t\tfile2.ext"
- Parse indentation:
ident=2
(two tabs) - Parse content:
cur=10
(length of "file2.ext"),isFile=True
- Stack management:
len(stk)=2
equalsident
, so no popping - Calculate path:
cur = 10 + stk[-1] + 1 = 10 + 11 + 1 = 22
- Update answer:
ans = max(22, 22) = 22
Result: The longest absolute file path has length 22
The actual paths are:
- "dir/subdir1/file1.ext" → length 22
- "dir/subdir2/file2.ext" → length 22
Notice how the stack grows and shrinks as we traverse the directory tree, always maintaining the cumulative path lengths up to the current level. When we backtrack from subdir1
to process subdir2
, we pop from the stack, effectively "forgetting" the subdir1
branch and preparing to explore the subdir2
branch.
Solution Implementation
1class Solution:
2 def lengthLongestPath(self, input: str) -> int:
3 i, n = 0, len(input)
4 max_length = 0
5 # Stack to store cumulative lengths at each directory level
6 path_stack = []
7
8 while i < n:
9 # Count the indentation level (number of tabs)
10 indent_level = 0
11 while i < n and input[i] == '\t':
12 indent_level += 1
13 i += 1
14
15 # Get the current file/directory name length and check if it's a file
16 current_length = 0
17 is_file = False
18 while i < n and input[i] != '\n':
19 current_length += 1
20 if input[i] == '.':
21 is_file = True
22 i += 1
23
24 # Skip the newline character
25 if i < n:
26 i += 1
27
28 # Pop directories from stack that are deeper than current indent level
29 while len(path_stack) > indent_level:
30 path_stack.pop()
31
32 # Calculate cumulative path length including parent directories
33 if path_stack:
34 # Add parent path length plus 1 for the '/' separator
35 current_length += path_stack[-1] + 1
36
37 # If it's a directory, push its cumulative length to stack
38 if not is_file:
39 path_stack.append(current_length)
40 else:
41 # If it's a file, update the maximum path length
42 max_length = max(max_length, current_length)
43
44 return max_length
45
1class Solution {
2 public int lengthLongestPath(String input) {
3 int currentIndex = 0;
4 int inputLength = input.length();
5 int maxPathLength = 0;
6
7 // Stack stores cumulative lengths at each directory level
8 Deque<Integer> directoryLengthStack = new ArrayDeque<>();
9
10 while (currentIndex < inputLength) {
11 // Count the indentation level (number of tabs)
12 int indentationLevel = 0;
13 while (currentIndex < inputLength && input.charAt(currentIndex) == '\t') {
14 indentationLevel++;
15 currentIndex++;
16 }
17
18 // Calculate current file/directory name length and check if it's a file
19 int currentNameLength = 0;
20 boolean isFile = false;
21 while (currentIndex < inputLength && input.charAt(currentIndex) != '\n') {
22 currentNameLength++;
23 if (input.charAt(currentIndex) == '.') {
24 isFile = true;
25 }
26 currentIndex++;
27 }
28
29 // Skip the newline character
30 currentIndex++;
31
32 // Pop directories from stack that are at same or deeper level
33 // This happens when we move to a sibling or parent directory
34 while (!directoryLengthStack.isEmpty() && directoryLengthStack.size() > indentationLevel) {
35 directoryLengthStack.pop();
36 }
37
38 // Calculate cumulative path length including parent directories
39 int cumulativePathLength = currentNameLength;
40 if (!directoryLengthStack.isEmpty()) {
41 // Add parent's cumulative length plus 1 for the '/' separator
42 cumulativePathLength += directoryLengthStack.peek() + 1;
43 }
44
45 // If it's a directory, push its cumulative length to stack for future use
46 if (!isFile) {
47 directoryLengthStack.push(cumulativePathLength);
48 } else {
49 // If it's a file, update the maximum path length
50 maxPathLength = Math.max(maxPathLength, cumulativePathLength);
51 }
52 }
53
54 return maxPathLength;
55 }
56}
57
1class Solution {
2public:
3 int lengthLongestPath(string input) {
4 int index = 0;
5 int inputLength = input.size();
6 int maxPathLength = 0;
7 stack<int> pathLengthStack; // Stack to store cumulative path lengths at each directory level
8
9 while (index < inputLength) {
10 // Count the indentation level (number of tabs)
11 int indentLevel = 0;
12 while (index < inputLength && input[index] == '\t') {
13 indentLevel++;
14 index++;
15 }
16
17 // Calculate the length of current file/directory name
18 int currentNameLength = 0;
19 bool isFile = false;
20 while (index < inputLength && input[index] != '\n') {
21 currentNameLength++;
22 // Check if current entry is a file (contains a dot)
23 if (input[index] == '.') {
24 isFile = true;
25 }
26 index++;
27 }
28
29 // Skip the newline character
30 index++;
31
32 // Pop directories from stack that are deeper than current indent level
33 // This happens when we move back up in the directory tree
34 while (!pathLengthStack.empty() && pathLengthStack.size() > indentLevel) {
35 pathLengthStack.pop();
36 }
37
38 // Calculate cumulative path length including parent directories
39 int cumulativePathLength = currentNameLength;
40 if (!pathLengthStack.empty()) {
41 // Add parent path length plus 1 for the separator '/'
42 cumulativePathLength += pathLengthStack.top() + 1;
43 }
44
45 // If it's a directory, push its cumulative length to stack for future use
46 if (!isFile) {
47 pathLengthStack.push(cumulativePathLength);
48 continue;
49 }
50
51 // If it's a file, update the maximum path length
52 maxPathLength = max(maxPathLength, cumulativePathLength);
53 }
54
55 return maxPathLength;
56 }
57};
58
1function lengthLongestPath(input: string): number {
2 let index = 0;
3 const inputLength = input.length;
4 let maxPathLength = 0;
5 const pathLengthStack: number[] = []; // Stack to store cumulative path lengths at each directory level
6
7 while (index < inputLength) {
8 // Count the indentation level (number of tabs)
9 let indentLevel = 0;
10 while (index < inputLength && input[index] === '\t') {
11 indentLevel++;
12 index++;
13 }
14
15 // Calculate the length of current file/directory name
16 let currentNameLength = 0;
17 let isFile = false;
18 while (index < inputLength && input[index] !== '\n') {
19 currentNameLength++;
20 // Check if current entry is a file (contains a dot)
21 if (input[index] === '.') {
22 isFile = true;
23 }
24 index++;
25 }
26
27 // Skip the newline character
28 index++;
29
30 // Pop directories from stack that are deeper than current indent level
31 // This happens when we move back up in the directory tree
32 while (pathLengthStack.length > 0 && pathLengthStack.length > indentLevel) {
33 pathLengthStack.pop();
34 }
35
36 // Calculate cumulative path length including parent directories
37 let cumulativePathLength = currentNameLength;
38 if (pathLengthStack.length > 0) {
39 // Add parent path length plus 1 for the separator '/'
40 cumulativePathLength += pathLengthStack[pathLengthStack.length - 1] + 1;
41 }
42
43 // If it's a directory, push its cumulative length to stack for future use
44 if (!isFile) {
45 pathLengthStack.push(cumulativePathLength);
46 continue;
47 }
48
49 // If it's a file, update the maximum path length
50 maxPathLength = Math.max(maxPathLength, cumulativePathLength);
51 }
52
53 return maxPathLength;
54}
55
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the input string.
The algorithm iterates through each character in the input string exactly once using the while loop with index i
. For each line in the input:
- Counting tabs:
O(t)
wheret
is the number of tabs (indentation level) - Reading the file/directory name:
O(m)
wherem
is the length of the name - Stack operations (pop): In the worst case, we might pop all elements, but each element is pushed and popped at most once throughout the entire execution
Since each character is visited once and each stack operation is amortized O(1)
over the entire execution, the total time complexity is O(n)
.
Space Complexity: O(d)
where d
is the maximum depth of the directory structure.
The space is primarily used by the stack stk
which stores the cumulative lengths at each directory level. In the worst case, the stack size equals the maximum depth of the nested directory structure. Additional variables (i
, n
, ans
, ident
, cur
, isFile
) use O(1)
space.
Therefore, the overall space complexity is O(d)
, which in the worst case could be O(n)
if the input represents a deeply nested structure with single-character names.
Common Pitfalls
1. Incorrectly Handling Stack Comparison with Indentation Level
One of the most common mistakes is misunderstanding when to pop from the stack. The condition while len(stk) > indent_level
is crucial but often implemented incorrectly.
Incorrect Implementation:
# Wrong: using >= instead of >
while len(stk) >= indent_level:
stk.pop()
Why it's wrong: The stack size represents the number of parent directories, and the indent level represents the depth of the current item. If indent_level = 1
, it means the current item is at depth 1 (has one parent), so we should keep exactly 1 item in the stack.
Correct Implementation:
while len(stk) > indent_level:
stk.pop()
2. Forgetting to Add Path Separator Length
Another frequent error is forgetting to account for the /
separator when calculating cumulative path lengths.
Incorrect Implementation:
if len(stk) > 0:
cur += stk[-1] # Missing the +1 for '/'
Correct Implementation:
if len(stk) > 0:
cur += stk[-1] + 1 # Add 1 for the '/' separator
3. Not Handling Edge Cases Properly
Edge Case 1: No files in the system
# Input: "dir\n\tsubdir1\n\tsubdir2" # Should return 0, not some directory length
Edge Case 2: Files at root level (no indentation)
# Input: "file.txt" # Should return 8, not 0
Edge Case 3: Multiple dots in filename
# Input: "file.name.ext" # Should be recognized as a file (any dot counts)
4. Incorrect File Detection
Incorrect Implementation:
# Wrong: Only checking if name ends with common extensions if name.endswith('.txt') or name.endswith('.ext'): isFile = True
Correct Implementation:
# Any dot in the name indicates a file if '.' in name: isFile = True
5. Off-by-One Errors in String Parsing
Be careful with index management when parsing:
Incorrect Implementation:
# Forgetting to skip the newline character
while i < n and input[i] != '\n':
cur += 1
i += 1
# Missing: i += 1 to skip '\n'
Correct Implementation:
while i < n and input[i] != '\n':
cur += 1
i += 1
# Skip the newline if it exists
if i < n:
i += 1
Complete Corrected Solution:
class Solution:
def lengthLongestPath(self, input: str) -> int:
i, n = 0, len(input)
max_length = 0
path_stack = []
while i < n:
# Count indentation
indent_level = 0
while i < n and input[i] == '\t':
indent_level += 1
i += 1
# Parse name and check for file
current_length = 0
is_file = False
while i < n and input[i] != '\n':
current_length += 1
if input[i] == '.':
is_file = True
i += 1
# Skip newline
if i < n:
i += 1
# Maintain stack at correct level
while len(path_stack) > indent_level:
path_stack.pop()
# Add parent path length if exists
if path_stack:
current_length += path_stack[-1] + 1
# Update based on type
if not is_file:
path_stack.append(current_length)
else:
max_length = max(max_length, current_length)
return max_length
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!