title: Simplify Path slug: simplify-path difficulty: medium leetcode_id: 71 leetcode_url: https://leetcode.com/problems/simplify-path/ categories: - strings - stack patterns: - slug: monotonic-stack is_optimal: true function_signature: "def simplify_path(path: str) -> str:" test_cases: visible: - input: { path: "/home/" } expected: "/home" - input: { path: "/home//foo/" } expected: "/home/foo" - input: { path: "/home/user/Documents/../Pictures" } expected: "/home/user/Pictures" hidden: - input: { path: "/../" } expected: "/" - input: { path: "/.../a/../b/c/../d/./" } expected: "/.../b/d" - input: { path: "/a/./b/../../c/" } expected: "/c" - input: { path: "/" } expected: "/" - input: { path: "/a//b////c/d//././/.." } expected: "/a/b/c" - input: { path: "/..hidden" } expected: "/..hidden" description: | You are given an *absolute* path for a Unix-style file system, which always begins with a slash `'/'`. Your task is to transform this absolute path into its **simplified canonical path**. The *rules* of a Unix-style file system are as follows: - A single period `'.'` represents the current directory. - A double period `'..'` represents the previous/parent directory. - Multiple consecutive slashes such as `'//'` and `'///'` are treated as a single slash `'/'`. - Any sequence of periods that does **not match** the rules above should be treated as a **valid directory or file name**. For example, `'...'` and `'....'` are valid directory or file names. The simplified canonical path should follow these *rules*: - The path must start with a single slash `'/'`. - Directories within the path must be separated by exactly one slash `'/'`. - The path must not end with a slash `'/'`, unless it is the root directory. - The path must not have any single or double periods (`'.'` and `'..'`) used to denote current or parent directories. Return the **simplified canonical path**. constraints: | - `1 <= path.length <= 3000` - `path` consists of English letters, digits, period `'.'`, slash `'/'` or `'_'`. - `path` is a valid absolute Unix path. examples: - input: 'path = "/home/"' output: '"/home"' explanation: "The trailing slash should be removed." - input: 'path = "/home//foo/"' output: '"/home/foo"' explanation: "Multiple consecutive slashes are replaced by a single one." - input: 'path = "/home/user/Documents/../Pictures"' output: '"/home/user/Pictures"' explanation: 'A double period ".." refers to the directory up a level (the parent directory).' - input: 'path = "/../"' output: '"/"' explanation: "Going one level up from the root directory is not possible." - input: 'path = "/.../a/../b/c/../d/./"' output: '"/.../b/d"' explanation: '"..." is a valid name for a directory in this problem.' explanation: intuition: | Think of navigating a file system in a terminal. When you type `cd ..`, you go up one directory. When you type `cd .`, you stay in the same directory. And when you type `cd foldername`, you enter that folder. The key insight is that a **stack** perfectly models directory navigation: - Entering a directory = **push** the directory name onto the stack - Going up a level (`..`) = **pop** from the stack (if not empty) - Staying in place (`.`) = do nothing Imagine the stack as your "breadcrumb trail" of directories from root to your current location. Each valid directory name adds a breadcrumb; `..` removes the last breadcrumb; `.` keeps things unchanged. After processing all parts of the path, the stack contains exactly the directories in the canonical path, from root to deepest level. Join them with `'/'` and prepend a `'/'` to get the answer. approach: | We solve this using a **Stack-Based Path Simplification**: **Step 1: Split the path into components** - Split the input path by `'/'` to get individual components - This automatically handles multiple consecutive slashes — they produce empty strings which we'll filter out   **Step 2: Process each component with a stack** - Initialise an empty stack to track valid directory names - For each component: - If it's `'..'`: pop from the stack (if not empty) — go up one level - If it's `'.'` or empty string: skip — stay in current directory - Otherwise: push onto the stack — it's a valid directory name   **Step 3: Reconstruct the canonical path** - Join all elements in the stack with `'/'` - Prepend `'/'` to ensure the path starts with root - If the stack is empty, return `'/'` (the root directory)   Example walkthrough with `"/home/user/Documents/../Pictures"`: - Split: `['', 'home', 'user', 'Documents', '..', 'Pictures']` - Process: `''` skip → `'home'` push → `'user'` push → `'Documents'` push → `'..'` pop → `'Pictures'` push - Stack: `['home', 'user', 'Pictures']` - Result: `'/home/user/Pictures'` common_pitfalls: - title: Treating All Periods as Special description: | Only `'.'` (single period) and `'..'` (double period) have special meaning. Sequences like `'...'`, `'....'`, or `'.hidden'` are **valid directory names**. Don't use regex like `/\.+/` to match all period sequences — check for exact matches. wrong_approach: "Treating '...' or '....' as special navigation" correct_approach: "Only '.' and '..' are special; others are valid names" - title: Popping from Empty Stack description: | When encountering `'..'` at the root level, there's nothing to pop. Trying to go above root is a no-op in Unix. For example, `'/../'` should return `'/'`, not cause an error. wrong_approach: "stack.pop() without checking if stack is empty" correct_approach: "if stack: stack.pop()" - title: Forgetting the Leading Slash description: | The canonical path must start with `'/'`. After joining stack elements, don't forget to prepend it. `['home', 'foo']` should become `'/home/foo'`, not `'home/foo'`. wrong_approach: "'/'.join(stack)" correct_approach: "'/' + '/'.join(stack)" - title: Trailing Slash in Result description: | The canonical path must not end with a slash (unless it's just `'/'`). Be careful not to add a trailing slash when reconstructing the path. wrong_approach: "'/' + '/'.join(stack) + '/'" correct_approach: "'/' + '/'.join(stack)" key_takeaways: - "**Stack for hierarchical navigation**: Stacks naturally model push/pop operations like directory traversal" - "**Split to simplify**: Splitting by delimiter handles multiple consecutive delimiters elegantly" - "**Only exact matches are special**: `'.'` and `'..'` are special; everything else (including `'...'`) is a valid name" - "**Real-world application**: This is exactly how `os.path.normpath()` or `realpath` work under the hood" time_complexity: "O(n). We iterate through the path once to split it, then process each component once." space_complexity: "O(n). In the worst case, the stack stores all directory names from the path." solutions: - approach_name: Stack-Based Simplification is_optimal: true code: | def simplify_path(path: str) -> str: # Split by '/' — consecutive slashes produce empty strings components = path.split('/') stack = [] for component in components: if component == '..': # Go up one level (if possible) if stack: stack.pop() elif component == '.' or component == '': # Current directory or empty — skip continue else: # Valid directory name — add to path stack.append(component) # Reconstruct canonical path with leading slash return '/' + '/'.join(stack) explanation: | **Time Complexity:** O(n) — We process each character of the path a constant number of times (split + iteration). **Space Complexity:** O(n) — The stack and split result can each hold up to O(n) elements in the worst case. We split the path, filter components using a stack (push for valid names, pop for `..`, skip for `.` and empty), then join with slashes. The leading `'/'` ensures proper format. - approach_name: Using Built-in (Python) is_optimal: false code: | import os def simplify_path(path: str) -> str: # os.path.normpath handles path normalisation # But it uses OS-specific separators, so ensure Unix style normalised = os.path.normpath(path) # On Windows, replace backslashes with forward slashes return normalised.replace('\\', '/') explanation: | **Time Complexity:** O(n) — `normpath` processes the path linearly. **Space Complexity:** O(n) — Creates a new string for the result. This leverages Python's built-in path normalisation. However, it's OS-dependent (uses `\\` on Windows) and may not match LeetCode's expected behaviour exactly. The manual stack approach is more portable and demonstrates the algorithm. Included here to show the real-world equivalent.