questions S-W
This commit is contained in:
188
backend/data/questions/simplify-path.yaml
Normal file
188
backend/data/questions/simplify-path.yaml
Normal file
@@ -0,0 +1,188 @@
|
||||
title: Simplify Path
|
||||
slug: simplify-path
|
||||
difficulty: medium
|
||||
leetcode_id: 71
|
||||
leetcode_url: https://leetcode.com/problems/simplify-path/
|
||||
categories:
|
||||
- strings
|
||||
- stack
|
||||
patterns:
|
||||
- monotonic-stack
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user