Medium

题目描述

假设我们有一个文件系统,它同时存储文件和目录。以下图片展示了一个文件系统的例子:

这里,我们有 dir 作为根目录下的唯一目录。dir 包含两个子目录 subdir1subdir2subdir1 包含一个文件 file1.ext 和子目录 subsubdir1subdir2 包含一个子目录 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 由字母、数字和/或空格组成。

给定一个表示上述格式文件系统的字符串输入,返回抽象文件系统中文件的最长绝对路径的长度。如果系统中没有文件,返回 0。

注意测试用例保证文件系统是有效的,并且没有文件或目录名的长度为 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
解释:我们没有任何文件,只有一个名为 "a" 的目录。

提示:

  • 1 <= input.length <= 10^4
  • input 可能包含小写或大写英文字母、换行符 ‘\n’、制表符 ‘\t’、点号 ‘.’、空格 ’ ’ 和数字。
  • 所有文件和目录名都有正长度。

解题思路

这道题的核心思路是使用栈模拟文件系统的层级结构

问题分析:

  1. 通过制表符 \t 的数量判断当前文件/目录的深度层级
  2. 只有文件(包含 . 的名称)才需要计算路径长度,目录不算
  3. 需要维护从根目录到当前位置的路径长度

解题步骤:

  1. 按换行符分割输入字符串,得到每一行
  2. 对每一行:
    • 统计前导制表符数量,确定层级深度
    • 获取去除制表符后的名称
  3. 使用栈维护每一层的累计路径长度:
    • 当前层级小于等于栈大小时,需要弹出多余层级
    • 将当前层级的累计长度压入栈中
  4. 如果当前名称是文件(包含 ‘.’),更新最大路径长度

关键点:

  • 栈中存储的是到达每一层的累计路径长度
  • 路径长度包括分隔符 / 的长度
  • 最终答案是所有文件路径长度的最大值

这种方法时间复杂度为 O(n),空间复杂度为 O(深度),是最优解法。

代码实现

class Solution {
public:
    int lengthLongestPath(string input) {
        vector<int> stack;
        int maxLen = 0;
        
        // 按换行符分割字符串
        stringstream ss(input);
        string line;
        
        while (getline(ss, line)) {
            // 计算层级深度(制表符数量)
            int level = 0;
            while (level < line.size() && line[level] == '\t') {
                level++;
            }
            
            // 获取名称(去除制表符)
            string name = line.substr(level);
            
            // 调整栈大小到当前层级
            while (stack.size() > level) {
                stack.pop_back();
            }
            
            // 计算当前路径长度
            int currentLen = name.length();
            if (!stack.empty()) {
                currentLen += stack.back() + 1; // +1 for '/'
            }
            
            // 如果是文件,更新最大长度
            if (name.find('.') != string::npos) {
                maxLen = max(maxLen, currentLen);
            } else {
                // 如果是目录,压入栈
                stack.push_back(currentLen);
            }
        }
        
        return maxLen;
    }
};
class Solution:
    def lengthLongestPath(self, input: str) -> int:
        stack = []
        max_len = 0
        
        # 按换行符分割
        lines = input.split('\n')
        
        for line in lines:
            # 计算层级深度(制表符数量)
            level = 0
            while level < len(line) and line[level] == '\t':
                level += 1
            
            # 获取名称(去除制表符)
            name = line[level:]
            
            # 调整栈大小到当前层级
            while len(stack) > level:
                stack.pop()
            
            # 计算当前路径长度
            current_len = len(name)
            if stack:
                current_len += stack[-1] + 1  # +1 for '/'
            
            # 如果是文件,更新最大长度
            if '.' in name:
                max_len = max(max_len, current_len)
            else:
                # 如果是目录,压入栈
                stack.append(current_len)
        
        return max_len
public class Solution {
    public int LengthLongestPath(string input) {
        var stack = new List<int>();
        int maxLen = 0;
        
        // 按换行符分割
        string[] lines = input.Split('\n');
        
        foreach (string line in lines) {
            // 计算层级深度(制表符数量)
            int level = 0;
            while (level < line.Length && line[level] == '\t') {
                level++;
            }
            
            // 获取名称(去除制表符)
            string name = line.Substring(level);
            
            // 调整栈大小到当前层级
            while (stack.Count > level) {
                stack.RemoveAt(stack.Count - 1);
            }
            
            // 计算当前路径长度
            int currentLen = name.Length;
            if (stack.Count > 0) {
                currentLen += stack[stack.Count - 1] + 1; // +1 for '/'
            }
            
            // 如果是文件,更新最大长度
            if (name.Contains('.')) {
                maxLen = Math.Max(maxLen, currentLen);
            } else {
                // 如果是目录,压入栈
                stack.Add(currentLen);
            }
        }
        
        return maxLen;
    }
}
var lengthLongestPath = function(input) {
    const stack = [];
    let maxLen = 0;
    
    // 按换行符分割
    const lines = input.split('\n');
    
    for (const line of lines) {
        // 计算层级深度(制表符数量)
        let level = 0;
        while (level < line.length && line[level]

复杂度分析

复杂度类型复杂度说明
时间复杂度O(n)n 是输入字符串的长度,需要遍历每个字符一次
空间复杂度O(d)d 是文件系统的最大深度,栈最多存储 d 个层级的路径长度