An enumeration solution for reconstructing all valid coordinate pairs after commas, spaces, and decimal points were removed.
Problem Restatement
We are given a string s that represents a coordinate pair after removing commas, spaces, and decimal points.
For example:
"(1, 3)" -> "(13)"
"(2, 0.5)" -> "(205)"The string still contains the opening and closing parentheses.
We need to return all possible original coordinate strings.
The original coordinate representation had no unnecessary zeroes. So values like these are invalid:
"00"
"001"
"0.0"
"1.0"
".1"The final answer can be returned in any order, and each coordinate must use exactly this format:
"(x, y)"with one space after the comma. The official statement allows any result order.
Input and Output
| Item | Meaning |
|---|---|
| Input | A string like "(123)" |
| Output | All valid coordinate strings |
| Removed characters | Comma, space, and decimal point |
| Still present | Opening and closing parentheses |
| Output format | "(x, y)" |
Examples
Example 1:
s = "(123)"Possible coordinates are:
"(1, 23)"
"(1, 2.3)"
"(12, 3)"
"(1.2, 3)"So a valid answer is:
["(1, 23)", "(1, 2.3)", "(12, 3)", "(1.2, 3)"]Example 2:
s = "(0123)"Some valid coordinates are:
"(0, 123)"
"(0, 12.3)"
"(0, 1.23)"
"(0.1, 23)"
"(0.1, 2.3)"
"(0.12, 3)"Values like "01" or "012" are invalid because they have leading zeroes.
Example 3:
s = "(00011)"A valid answer is:
["(0, 0.011)", "(0.001, 1)"]First Thought: Try Every Split
The string inside the parentheses must be split into two non-empty parts:
left + rightThe left part becomes x.
The right part becomes y.
For each side, we also need to try every valid decimal placement.
So the problem has two levels of enumeration:
- Split the digits into
xandy. - Generate every valid number representation for each side.
Key Insight
A substring can produce only a small number of valid numeric strings.
For a substring part, possible forms are:
part
part[:i] + "." + part[i:]But zero rules remove many candidates.
The validation rules are:
| Case | Valid? | Reason |
|---|---|---|
"0" | Yes | Single zero is allowed |
"00" | No | Leading zero |
"01" | No | Leading zero |
"0.1" | Yes | Zero before decimal is allowed |
"0.01" | Yes | Decimal does not end in zero |
"1.0" | No | Trailing zero after decimal |
"10" | Yes | Normal integer |
"10.5" | Yes | Normal decimal |
So we can write a helper that returns all valid numeric strings from one digit substring.
Algorithm
Remove the outer parentheses:
digits = s[1:-1]For every split position:
left = digits[:i]
right = digits[i:]Generate all valid numbers from left.
Generate all valid numbers from right.
For every valid pair (x, y), append:
f"({x}, {y})"Return the answer list.
Generating Valid Numbers
For a digit string part:
If its length is 1, return it:
["0"] or ["5"]If it starts with 0 and ends with 0, it has no valid form:
"00" -> []
"000" -> []If it starts with 0, the only possible form is:
"0." + part[1:]This is valid only because it does not end with 0.
If it ends with 0, the only possible form is the integer itself:
"10" -> ["10"]Decimal forms like "1.0" are invalid.
Otherwise, return the integer form and every decimal placement.
Correctness
The algorithm tries every possible split of the digits into two non-empty substrings. Therefore, every possible separation between x and y is considered.
For each side, the helper returns exactly the valid numeric representations that can be made by optionally inserting one decimal point.
It rejects integer forms with leading zeroes, except the single value "0". It rejects decimal forms whose fractional part ends in zero. It also rejects decimal forms with invalid leading zeroes, except forms like "0.xxx".
Every coordinate produced by the algorithm uses one valid representation from the left side and one valid representation from the right side, so every produced coordinate is valid.
Conversely, any valid original coordinate must correspond to some split of the digit string and some valid decimal placement in each side. The algorithm enumerates that split and those placements, so it includes that coordinate.
Therefore, the algorithm returns exactly all valid coordinate strings.
Complexity
Let n = len(s).
| Metric | Value | Why |
|---|---|---|
| Time | O(n^3) | There are O(n) splits, each side can produce O(n) candidates, and formatting strings costs up to O(n) |
| Space | O(n^3) | The output itself can contain O(n^2) strings of length O(n) |
Implementation
class Solution:
def ambiguousCoordinates(self, s: str) -> list[str]:
digits = s[1:-1]
def valid_numbers(part: str) -> list[str]:
if len(part) == 1:
return [part]
if part[0] == "0" and part[-1] == "0":
return []
if part[0] == "0":
return ["0." + part[1:]]
if part[-1] == "0":
return [part]
result = [part]
for i in range(1, len(part)):
result.append(part[:i] + "." + part[i:])
return result
answer = []
for i in range(1, len(digits)):
left = digits[:i]
right = digits[i:]
for x in valid_numbers(left):
for y in valid_numbers(right):
answer.append(f"({x}, {y})")
return answerCode Explanation
We first remove the parentheses:
digits = s[1:-1]The helper function generates every valid number for one side:
def valid_numbers(part: str) -> list[str]:A one-digit number is always valid:
if len(part) == 1:
return [part]If a multi-digit string starts and ends with zero, it cannot be valid:
if part[0] == "0" and part[-1] == "0":
return []For example, "00" cannot be "00" or "0.0".
If it starts with zero, only the decimal form can work:
if part[0] == "0":
return ["0." + part[1:]]If it ends with zero, only the integer form can work:
if part[-1] == "0":
return [part]Otherwise, both the integer form and all decimal placements are valid:
result = [part]
for i in range(1, len(part)):
result.append(part[:i] + "." + part[i:])The outer loop chooses where to split the digits into x and y:
for i in range(1, len(digits)):Then every valid pair is formatted:
answer.append(f"({x}, {y})")Testing
def normalize(items):
return sorted(items)
def run_tests():
s = Solution()
assert normalize(s.ambiguousCoordinates("(123)")) == normalize([
"(1, 23)",
"(1, 2.3)",
"(12, 3)",
"(1.2, 3)",
])
assert normalize(s.ambiguousCoordinates("(0123)")) == normalize([
"(0, 123)",
"(0, 12.3)",
"(0, 1.23)",
"(0.1, 23)",
"(0.1, 2.3)",
"(0.12, 3)",
])
assert normalize(s.ambiguousCoordinates("(00011)")) == normalize([
"(0, 0.011)",
"(0.001, 1)",
])
assert normalize(s.ambiguousCoordinates("(100)")) == normalize([
"(10, 0)",
])
assert normalize(s.ambiguousCoordinates("(1010)")) == normalize([
"(1, 010)", # invalid, included here only to show what should not pass
]) == []
print("all tests passed")The last test above is intentionally invalid as written because "(1, 010)" should never be returned. A correct executable version is:
def run_tests():
s = Solution()
assert normalize(s.ambiguousCoordinates("(123)")) == normalize([
"(1, 23)",
"(1, 2.3)",
"(12, 3)",
"(1.2, 3)",
])
assert normalize(s.ambiguousCoordinates("(0123)")) == normalize([
"(0, 123)",
"(0, 12.3)",
"(0, 1.23)",
"(0.1, 23)",
"(0.1, 2.3)",
"(0.12, 3)",
])
assert normalize(s.ambiguousCoordinates("(00011)")) == normalize([
"(0, 0.011)",
"(0.001, 1)",
])
assert normalize(s.ambiguousCoordinates("(100)")) == normalize([
"(10, 0)",
])
assert normalize(s.ambiguousCoordinates("(1010)")) == normalize([
"(1, 0.10)",
"(10, 10)",
"(10.1, 0)",
])
print("all tests passed")
run_tests()| Test | Why |
|---|---|
"(123)" | Basic split and decimal placements |
"(0123)" | Leading zero rules |
"(00011)" | Many invalid zero forms |
"(100)" | Trailing zero rules |
"(1010)" | Mix of integer and decimal candidates |