questions S-W

This commit is contained in:
2025-05-30 19:18:33 +01:00
parent 68699f35ec
commit f7e491f1e8
46 changed files with 9696 additions and 0 deletions

View 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
&nbsp;
**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
&nbsp;
**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)
&nbsp;
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.