questions D-E
This commit is contained in:
200
backend/data/questions/decode-string.yaml
Normal file
200
backend/data/questions/decode-string.yaml
Normal file
@@ -0,0 +1,200 @@
|
||||
title: Decode String
|
||||
slug: decode-string
|
||||
difficulty: medium
|
||||
leetcode_id: 394
|
||||
leetcode_url: https://leetcode.com/problems/decode-string/
|
||||
categories:
|
||||
- strings
|
||||
- stack
|
||||
- recursion
|
||||
patterns:
|
||||
- monotonic-stack
|
||||
|
||||
description: |
|
||||
Given an encoded string, return its decoded string.
|
||||
|
||||
The encoding rule is: `k[encoded_string]`, where the `encoded_string` inside the square brackets is being repeated exactly `k` times. Note that `k` is guaranteed to be a positive integer.
|
||||
|
||||
You may assume that the input string is always valid; there are no extra white spaces, square brackets are well-formed, etc. Furthermore, you may assume that the original data does not contain any digits and that digits are only for those repeat numbers, `k`. For example, there will not be input like `3a` or `2[4]`.
|
||||
|
||||
The test cases are generated so that the length of the output will never exceed `10^5`.
|
||||
|
||||
constraints: |
|
||||
- `1 <= s.length <= 30`
|
||||
- `s` consists of lowercase English letters, digits, and square brackets `'[]'`
|
||||
- `s` is guaranteed to be a valid input
|
||||
- All the integers in `s` are in the range `[1, 300]`
|
||||
|
||||
examples:
|
||||
- input: 's = "3[a]2[bc]"'
|
||||
output: '"aaabcbc"'
|
||||
explanation: "The pattern 3[a] expands to 'aaa' and 2[bc] expands to 'bcbc', giving 'aaabcbc'."
|
||||
- input: 's = "3[a2[c]]"'
|
||||
output: '"accaccacc"'
|
||||
explanation: "The inner 2[c] expands to 'cc', making 3[acc], which expands to 'accaccacc'."
|
||||
- input: 's = "2[abc]3[cd]ef"'
|
||||
output: '"abcabccdcdcdef"'
|
||||
explanation: "2[abc] becomes 'abcabc', 3[cd] becomes 'cdcdcd', plus 'ef' at the end."
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
Think of this problem like unpacking nested boxes. Each `k[...]` is a box containing a message that needs to be repeated `k` times. The tricky part is that boxes can be nested inside other boxes — you need to unpack the innermost boxes first before you can complete the outer ones.
|
||||
|
||||
This "last opened, first closed" pattern is exactly what a **stack** excels at. When you encounter an opening bracket `[`, you're entering a new nested level. When you hit a closing bracket `]`, you've completed the innermost pattern and need to expand it before continuing with the outer context.
|
||||
|
||||
Imagine reading `3[a2[c]]`:
|
||||
- You see `3[` — push your current work aside, start fresh for what's inside
|
||||
- You see `a2[` — push again, start fresh for the innermost part
|
||||
- You see `c]` — you've completed `c`, multiply by `2` to get `cc`, pop back to the previous context
|
||||
- Now you have `acc`, hit `]` — multiply by `3` to get `accaccacc`
|
||||
|
||||
The stack lets you "pause" your current string-building work, dive into a nested pattern, resolve it, and then resume where you left off.
|
||||
|
||||
approach: |
|
||||
We solve this using a **Stack-Based Approach**:
|
||||
|
||||
**Step 1: Initialise data structures**
|
||||
|
||||
- `stack`: Holds pairs of (previous_string, repeat_count) when we enter a new `[`
|
||||
- `current_string`: The string we're building at the current nesting level
|
||||
- `current_num`: Accumulates multi-digit numbers like `12` or `300`
|
||||
|
||||
|
||||
|
||||
**Step 2: Iterate through each character**
|
||||
|
||||
- **If digit**: Accumulate into `current_num` (handle multi-digit numbers with `current_num = current_num * 10 + int(char)`)
|
||||
- **If `[`**: Push `(current_string, current_num)` onto the stack, then reset both to start fresh for the nested content
|
||||
- **If `]`**: Pop from stack, multiply `current_string` by the popped count, and prepend the popped string
|
||||
- **If letter**: Simply append to `current_string`
|
||||
|
||||
|
||||
|
||||
**Step 3: Return the result**
|
||||
|
||||
- After processing all characters, `current_string` contains the fully decoded string
|
||||
|
||||
|
||||
|
||||
The key insight is that we save our "context" (what we've built so far and how many times to repeat the upcoming section) whenever we enter a new nesting level, then restore and combine when we exit.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Forgetting Multi-Digit Numbers
|
||||
description: |
|
||||
The repeat count `k` can be more than one digit! For example, `12[a]` means repeat `a` twelve times, not `1` followed by `2[a]`.
|
||||
|
||||
When you encounter a digit, you need to accumulate: `current_num = current_num * 10 + int(char)`. This handles numbers like `123` correctly by building up `1` → `12` → `123`.
|
||||
wrong_approach: "Treating each digit as a separate number"
|
||||
correct_approach: "Accumulate digits: current_num = current_num * 10 + digit"
|
||||
|
||||
- title: Not Resetting State After Opening Bracket
|
||||
description: |
|
||||
When you push to the stack upon seeing `[`, you must reset `current_string` to empty and `current_num` to `0`. Otherwise, you'll mix content from different nesting levels.
|
||||
|
||||
For `3[a2[c]]`, after pushing at the first `[`, you need a fresh start to build `a` before encountering `2[c]`.
|
||||
wrong_approach: "Continuing to append to the same string after ["
|
||||
correct_approach: "Reset current_string and current_num after pushing to stack"
|
||||
|
||||
- title: String Concatenation in Wrong Order
|
||||
description: |
|
||||
When popping from the stack after `]`, the order matters:
|
||||
- `prev_string + (current_string * count)` is correct
|
||||
- `(current_string * count) + prev_string` is wrong
|
||||
|
||||
The `prev_string` is what came *before* the `k[...]` pattern, so it should stay at the front.
|
||||
wrong_approach: "Appending prev_string after the repeated content"
|
||||
correct_approach: "Prepend prev_string: result = prev_string + current_string * count"
|
||||
|
||||
- title: Using Recursion Without Tracking Position
|
||||
description: |
|
||||
A recursive approach is valid but requires careful handling of the current position. You need to return both the decoded string *and* the index where you stopped, so the caller knows where to resume.
|
||||
|
||||
The iterative stack approach avoids this complexity by maintaining state explicitly.
|
||||
|
||||
key_takeaways:
|
||||
- "**Stack for nested structures**: Whenever you see nested patterns with matching pairs (brackets, parentheses), think stack"
|
||||
- "**Save and restore context**: Push your current state when entering a new level, pop and combine when exiting"
|
||||
- "**Multi-digit number handling**: Always accumulate digits with `num = num * 10 + digit` for robustness"
|
||||
- "**Pattern recognition**: This stack technique applies to many parsing problems — balanced parentheses, expression evaluation, nested HTML tags"
|
||||
|
||||
time_complexity: "O(n * m) where `n` is the length of the encoded string and `m` is the maximum length of the decoded string. Each character is processed once, but string concatenation may involve copying characters multiple times due to repetition."
|
||||
space_complexity: "O(n + m) for the stack storing intermediate strings and the output string. In the worst case of deeply nested patterns, the stack depth approaches `n`."
|
||||
|
||||
solutions:
|
||||
- approach_name: Stack
|
||||
is_optimal: true
|
||||
code: |
|
||||
def decodeString(s: str) -> str:
|
||||
stack = [] # Stores (prev_string, repeat_count) pairs
|
||||
current_string = ""
|
||||
current_num = 0
|
||||
|
||||
for char in s:
|
||||
if char.isdigit():
|
||||
# Accumulate multi-digit numbers
|
||||
current_num = current_num * 10 + int(char)
|
||||
|
||||
elif char == '[':
|
||||
# Save current context and start fresh
|
||||
stack.append((current_string, current_num))
|
||||
current_string = ""
|
||||
current_num = 0
|
||||
|
||||
elif char == ']':
|
||||
# Pop context and apply repetition
|
||||
prev_string, repeat_count = stack.pop()
|
||||
current_string = prev_string + current_string * repeat_count
|
||||
|
||||
else:
|
||||
# Regular letter - just append
|
||||
current_string += char
|
||||
|
||||
return current_string
|
||||
explanation: |
|
||||
**Time Complexity:** O(n * m) — We process each character once, but string operations may copy characters multiple times based on repeat counts.
|
||||
|
||||
**Space Complexity:** O(n + m) — Stack space for nested levels plus the decoded output string.
|
||||
|
||||
The stack elegantly handles nested patterns by saving context when entering brackets and restoring when exiting. Each `]` triggers a "resolve and combine" operation that builds up the final string from the inside out.
|
||||
|
||||
- approach_name: Recursion
|
||||
is_optimal: false
|
||||
code: |
|
||||
def decodeString(s: str) -> str:
|
||||
def decode(index: int) -> tuple[str, int]:
|
||||
result = ""
|
||||
num = 0
|
||||
|
||||
while index < len(s):
|
||||
char = s[index]
|
||||
|
||||
if char.isdigit():
|
||||
# Accumulate the repeat count
|
||||
num = num * 10 + int(char)
|
||||
|
||||
elif char == '[':
|
||||
# Recurse to decode the nested content
|
||||
decoded, index = decode(index + 1)
|
||||
result += decoded * num
|
||||
num = 0
|
||||
|
||||
elif char == ']':
|
||||
# End of current level - return to caller
|
||||
return result, index
|
||||
|
||||
else:
|
||||
# Regular letter
|
||||
result += char
|
||||
|
||||
index += 1
|
||||
|
||||
return result, index
|
||||
|
||||
decoded_string, _ = decode(0)
|
||||
return decoded_string
|
||||
explanation: |
|
||||
**Time Complexity:** O(n * m) — Similar to the stack approach, processing each character with potential string repetition.
|
||||
|
||||
**Space Complexity:** O(n) — Recursion depth proportional to nesting level, plus O(m) for output.
|
||||
|
||||
The recursive approach mirrors the stack solution but uses the call stack implicitly. Each `[` triggers a recursive call, and each `]` returns to the caller. The key is returning both the decoded string *and* the current index so the caller knows where to resume parsing.
|
||||
Reference in New Issue
Block a user