250 lines
11 KiB
YAML
250 lines
11 KiB
YAML
title: Ambiguous Coordinates
|
||
slug: ambiguous-coordinates
|
||
difficulty: medium
|
||
leetcode_id: 816
|
||
leetcode_url: https://leetcode.com/problems/ambiguous-coordinates/
|
||
categories:
|
||
- strings
|
||
- recursion
|
||
patterns:
|
||
- slug: backtracking
|
||
is_optimal: true
|
||
|
||
function_signature: "def ambiguous_coordinates(s: str) -> list[str]:"
|
||
|
||
test_cases:
|
||
visible:
|
||
- input: { s: "(123)" }
|
||
expected: ["(1, 2.3)", "(1, 23)", "(1.2, 3)", "(12, 3)"]
|
||
- input: { s: "(0123)" }
|
||
expected: ["(0, 1.23)", "(0, 12.3)", "(0, 123)", "(0.1, 2.3)", "(0.1, 23)", "(0.12, 3)"]
|
||
hidden:
|
||
- input: { s: "(00011)" }
|
||
expected: ["(0, 0.011)", "(0.001, 1)"]
|
||
- input: { s: "(100)" }
|
||
expected: ["(10, 0)", "(1, 0.0)"]
|
||
- input: { s: "(12)" }
|
||
expected: ["(1, 2)"]
|
||
|
||
description: |
|
||
We had some 2-dimensional coordinates, like `"(1, 3)"` or `"(2, 0.5)"`. Then, we removed all commas, decimal points, and spaces and ended up with the string `s`.
|
||
|
||
For example, `"(1, 3)"` becomes `s = "(13)"` and `"(2, 0.5)"` becomes `s = "(205)"`.
|
||
|
||
Return *a list of strings representing all possibilities for what our original coordinates could have been*.
|
||
|
||
Our original representation never had extraneous zeroes, so we never started with numbers like `"00"`, `"0.0"`, `"0.00"`, `"1.0"`, `"001"`, `"00.01"`, or any other number that can be represented with fewer digits. Also, a decimal point within a number never occurs without at least one digit occurring before it, so we never started with numbers like `".1"`.
|
||
|
||
The final answer list can be returned in any order. All coordinates in the final answer have exactly one space between them (occurring after the comma).
|
||
|
||
constraints: |
|
||
- `4 <= s.length <= 12`
|
||
- `s[0] == '('` and `s[s.length - 1] == ')'`
|
||
- The rest of `s` are digits
|
||
|
||
examples:
|
||
- input: 's = "(123)"'
|
||
output: '["(1, 2.3)","(1, 23)","(1.2, 3)","(12, 3)"]'
|
||
explanation: "We can split the digits '123' into x and y coordinates in multiple ways, and each coordinate can optionally have a decimal point."
|
||
- input: 's = "(0123)"'
|
||
output: '["(0, 1.23)","(0, 12.3)","(0, 123)","(0.1, 2.3)","(0.1, 23)","(0.12, 3)"]'
|
||
explanation: "0.0, 00, 0001, or 00.01 are not allowed due to extraneous zeros."
|
||
- input: 's = "(00011)"'
|
||
output: '["(0, 0.011)","(0.001, 1)"]'
|
||
explanation: "Leading zeros severely limit valid representations. '00' as an integer or '00.x' as a decimal are invalid."
|
||
|
||
explanation:
|
||
intuition: |
|
||
Imagine you're trying to decode a compressed message. Someone took a coordinate like `(1.5, 2.3)` and stripped away all the formatting — the comma, spaces, and decimal points — leaving just `(1523)`. Your job is to figure out all the ways this could be "unpacked" back into valid coordinates.
|
||
|
||
The key insight is to break this into **two independent subproblems**:
|
||
|
||
1. **Split the string** into two parts: one for the x-coordinate and one for the y-coordinate
|
||
2. **Generate all valid numbers** from each part by optionally inserting a decimal point
|
||
|
||
For each split position, you independently generate all valid representations for the left part (x) and the right part (y), then combine them. This is essentially a **Cartesian product** of possibilities.
|
||
|
||
The tricky part is knowing which number representations are *valid*. A number is invalid if it has:
|
||
- Leading zeros (like `"01"` or `"001"`) — unless it's just `"0"`
|
||
- Trailing zeros after a decimal point (like `"1.0"` or `"2.30"`)
|
||
|
||
Think of it like reconstructing a puzzle where you try every valid piece combination systematically.
|
||
|
||
approach: |
|
||
We solve this using **systematic enumeration** with careful validation:
|
||
|
||
**Step 1: Extract the digit string**
|
||
|
||
- Remove the parentheses from `s` to get the raw digits
|
||
- Example: `"(123)"` → `"123"`
|
||
|
||
|
||
|
||
**Step 2: Split into two coordinate parts**
|
||
|
||
- Try every possible split point: left part gets 1 to n-1 digits, right part gets the rest
|
||
- For `"123"`: splits are `("1", "23")`, `("12", "3")`
|
||
- Each split represents a potential (x, y) pairing
|
||
|
||
|
||
|
||
**Step 3: Generate valid numbers from a digit string**
|
||
|
||
For each part, generate all valid number representations:
|
||
|
||
- **Integer form**: Valid only if it doesn't have leading zeros (except for `"0"` itself)
|
||
- **Decimal form**: Try placing a decimal point at each position. Valid only if:
|
||
- The integer part doesn't have leading zeros (unless it's `"0"`)
|
||
- The fractional part doesn't have trailing zeros
|
||
|
||
|
||
|
||
**Step 4: Combine results**
|
||
|
||
- For each split, compute the Cartesian product of valid x-numbers and valid y-numbers
|
||
- Format each combination as `"(x, y)"`
|
||
|
||
|
||
|
||
**Step 5: Return all valid coordinates**
|
||
|
||
- Collect all formatted coordinate strings across all splits
|
||
|
||
common_pitfalls:
|
||
- title: Missing Edge Cases for Zero
|
||
description: |
|
||
The rules around zeros are subtle:
|
||
- `"0"` is valid as an integer
|
||
- `"00"` is NOT valid (leading zero)
|
||
- `"0.5"` is valid (zero before decimal is allowed)
|
||
- `"0.0"` is NOT valid (trailing zero after decimal)
|
||
- `"10"` is valid, but `"01"` is NOT (leading zero)
|
||
|
||
Many solutions fail by not handling these cases correctly. Always test with inputs containing zeros like `"(0123)"` and `"(00011)"`.
|
||
wrong_approach: "Treating all zero-containing strings the same"
|
||
correct_approach: "Separate checks for leading zeros in integers and trailing zeros in decimals"
|
||
|
||
- title: Forgetting Single-Digit Numbers
|
||
description: |
|
||
When generating decimal representations, don't forget that a single digit like `"5"` can only be an integer — you can't place a decimal point in a single character.
|
||
|
||
Some solutions try to generate `"5."` or `".5"` which are invalid formats.
|
||
wrong_approach: "Trying to insert decimal in single-digit strings"
|
||
correct_approach: "Only try decimal insertion when string length >= 2"
|
||
|
||
- title: Incorrect Cartesian Product
|
||
description: |
|
||
The final answer requires combining valid x-coordinates with valid y-coordinates. If either side produces zero valid numbers (due to the zero rules), that entire split should contribute nothing to the answer.
|
||
|
||
For example, splitting `"00011"` as `("000", "11")` yields no valid x-coordinates (since `"000"`, `"0.00"`, `"00.0"` are all invalid), so this split produces no results.
|
||
wrong_approach: "Including partial results when one coordinate is invalid"
|
||
correct_approach: "Only combine when both sides have valid representations"
|
||
|
||
key_takeaways:
|
||
- "**Decomposition pattern**: Break complex enumeration into independent subproblems (split position × valid numbers)"
|
||
- "**Validation rules matter**: Carefully encode the constraints — leading zeros for integers, trailing zeros for decimals"
|
||
- "**Cartesian product**: When combining independent choices, iterate through all pairs systematically"
|
||
- "**Edge case testing**: Problems with multiple validation rules need thorough testing with boundary inputs like zeros"
|
||
|
||
time_complexity: "O(n^4). For each of the O(n) split positions, we generate O(n) valid numbers for each side, and each number generation involves O(n) string operations. The total combinations can be O(n^2) per split."
|
||
space_complexity: "O(n^3). The output list can contain O(n^2) coordinate pairs (O(n) splits × O(n) combinations), each of length O(n)."
|
||
|
||
solutions:
|
||
- approach_name: Enumeration with Validation
|
||
is_optimal: true
|
||
code: |
|
||
def ambiguous_coordinates(s: str) -> list[str]:
|
||
# Extract digits by removing parentheses
|
||
digits = s[1:-1]
|
||
n = len(digits)
|
||
result = []
|
||
|
||
def valid_numbers(string: str) -> list[str]:
|
||
"""Generate all valid number representations from a digit string."""
|
||
if not string:
|
||
return []
|
||
|
||
valid = []
|
||
|
||
# Try as integer (no decimal point)
|
||
# Valid if no leading zeros, or it's just "0"
|
||
if string == "0" or not string.startswith("0"):
|
||
valid.append(string)
|
||
|
||
# Try with decimal point at each position
|
||
for i in range(1, len(string)):
|
||
integer_part = string[:i]
|
||
decimal_part = string[i:]
|
||
|
||
# Integer part: no leading zeros (unless it's "0")
|
||
if len(integer_part) > 1 and integer_part.startswith("0"):
|
||
continue
|
||
|
||
# Decimal part: no trailing zeros
|
||
if decimal_part.endswith("0"):
|
||
continue
|
||
|
||
valid.append(f"{integer_part}.{decimal_part}")
|
||
|
||
return valid
|
||
|
||
# Try each split position
|
||
for i in range(1, n):
|
||
left = digits[:i] # x-coordinate digits
|
||
right = digits[i:] # y-coordinate digits
|
||
|
||
# Get all valid representations for each side
|
||
left_valid = valid_numbers(left)
|
||
right_valid = valid_numbers(right)
|
||
|
||
# Cartesian product: combine each valid x with each valid y
|
||
for x in left_valid:
|
||
for y in right_valid:
|
||
result.append(f"({x}, {y})")
|
||
|
||
return result
|
||
explanation: |
|
||
**Time Complexity:** O(n^4) — For each of O(n) splits, we generate up to O(n) valid numbers per side, and combining them produces O(n^2) pairs. String operations add another O(n) factor.
|
||
|
||
**Space Complexity:** O(n^3) — The result list can hold O(n^3) characters total across all coordinate strings.
|
||
|
||
The solution systematically tries every split position and every decimal placement, filtering out invalid representations based on the zero rules. The nested loops for Cartesian product ensure we capture all valid combinations.
|
||
|
||
- approach_name: Generator-Based Approach
|
||
is_optimal: false
|
||
code: |
|
||
def ambiguous_coordinates(s: str) -> list[str]:
|
||
digits = s[1:-1]
|
||
|
||
def generate(string: str):
|
||
"""Yield all valid number representations."""
|
||
# Integer representation
|
||
if string == "0" or not string.startswith("0"):
|
||
yield string
|
||
|
||
# Decimal representations
|
||
for i in range(1, len(string)):
|
||
left, right = string[:i], string[i:]
|
||
# Check: no leading zeros in integer part (except "0")
|
||
# Check: no trailing zeros in decimal part
|
||
if (left == "0" or not left.startswith("0")) and not right.endswith("0"):
|
||
yield f"{left}.{right}"
|
||
|
||
result = []
|
||
n = len(digits)
|
||
|
||
# Try all split positions
|
||
for i in range(1, n):
|
||
# Generate Cartesian product of valid coordinates
|
||
for x in generate(digits[:i]):
|
||
for y in generate(digits[i:]):
|
||
result.append(f"({x}, {y})")
|
||
|
||
return result
|
||
explanation: |
|
||
**Time Complexity:** O(n^4) — Same as the list-based approach.
|
||
|
||
**Space Complexity:** O(n^3) — Output dominates; generators reduce intermediate storage.
|
||
|
||
This variation uses Python generators (`yield`) instead of building lists, which can be slightly more memory-efficient for the intermediate valid number lists. The logic is identical, but the lazy evaluation avoids storing all valid numbers before combining them.
|