816. Ambiguous Coordinates
Problem Description
You are given a string s
that represents a coordinate pair with all formatting removed. Originally, the coordinate was in the format "(x, y)"
where x
and y
could be integers or decimal numbers. However, all commas, spaces, and decimal points have been removed, leaving only the parentheses and digits.
For example:
"(1, 3)"
becomes"(13)"
"(2, 0.5)"
becomes"(205)"
Your task is to find all possible valid original coordinates that could have produced the given string s
.
The original coordinates must follow these rules:
- No leading zeros are allowed (except for the number
"0"
itself)- Invalid:
"00"
,"001"
,"01"
- Valid:
"0"
,"10"
,"101"
- Invalid:
- No trailing zeros after a decimal point
- Invalid:
"1.0"
,"1.00"
,"0.50"
- Valid:
"1"
,"0.5"
,"1.5"
- Invalid:
- No unnecessary decimal representations
- Invalid:
"1.0"
(should be"1"
) - Valid:
"1.5"
,"0.5"
- Invalid:
- A decimal point must have at least one digit before it
- Invalid:
".1"
,".5"
- Valid:
"0.1"
,"0.5"
- Invalid:
You need to return a list of all possible valid coordinate pairs in the format "(x, y)"
with exactly one space after the comma. The order of the results doesn't matter.
For instance, if s = "(123)"
, possible outputs could include:
"(1, 23)"
- split as 1 and 23"(12, 3)"
- split as 12 and 3"(1.2, 3)"
- split as 1.2 and 3"(1, 2.3)"
- split as 1 and 2.3
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- No: The problem doesn't involve nodes and edges or graph traversal. We're working with a string that needs to be parsed into coordinate pairs.
Need to solve for kth smallest/largest?
- No: We're not looking for the kth smallest or largest element. We need to find all valid coordinate interpretations.
Involves Linked Lists?
- No: The problem works with strings and coordinate parsing, not linked list data structures.
Does the problem have small constraints?
- Yes: The string represents a single coordinate pair with parentheses, so the length is relatively small (typically under 20 characters). We need to explore all possible ways to:
- Split the digits into two numbers (x and y)
- Place decimal points within each number (if valid)
Brute force / Backtracking?
- Yes: We need to systematically explore all possible combinations:
- Try every possible split position to separate x and y coordinates
- For each split, try all valid decimal point placements in x
- For each split, try all valid decimal point placements in y
- Validate each combination against the rules (no leading zeros, no trailing zeros after decimal, etc.)
Conclusion: The flowchart correctly leads us to a backtracking/brute force approach. The solution generates all possible valid coordinate pairs by:
- Iterating through all possible split positions between x and y
- For each split, generating all valid decimal representations of x
- For each split, generating all valid decimal representations of y
- Combining valid x and y values to form coordinate pairs
This exhaustive exploration of possibilities is characteristic of the backtracking pattern, where we systematically try all options and collect the valid ones.
Intuition
When we look at a string like "(123)"
, we need to reconstruct the original coordinate pair. The key insight is that we have two independent problems to solve:
-
Where to split the digits - The digits inside the parentheses must be divided into two numbers for x and y coordinates. For
"123"
, we could split it as"1"
and"23"
, or"12"
and"3"
. -
Where to place decimal points - For each number after splitting, we might need to insert a decimal point. For example,
"23"
could become"2.3"
or stay as"23"
.
The challenge is that not all combinations are valid. We need to respect the original number formatting rules:
- A number like
"03"
can't exist as is (leading zero), but"0.3"
is valid - A number like
"30"
can't become"3.0"
(trailing zero after decimal) - A single
"0"
is valid, but"00"
is not
This naturally leads to a systematic exploration approach:
- Try every possible position to split the string into two parts (x and y)
- For each part, generate all valid number representations (with or without decimals)
- Combine valid x and y representations to form coordinate pairs
The helper function f(i, j)
generates all valid decimal representations for a substring s[i:j]
. It tries placing a decimal point at every possible position (or no decimal at all). For each attempt, it checks:
- The left part (before decimal) doesn't have leading zeros (unless it's just
"0"
) - The right part (after decimal) doesn't have trailing zeros
By generating all valid representations for both x and y coordinates independently and then combining them, we ensure we find all possible original coordinates that could have produced the given string.
Learn more about Backtracking patterns.
Solution Approach
The solution implements a systematic enumeration approach using a helper function to generate all valid decimal representations for a given substring.
Main Algorithm Structure:
-
Helper Function
f(i, j)
: This function generates all valid number representations for the substrings[i:j]
.- It iterates through all possible positions
k
to place a decimal point (from 1 toj-i
) - For each position
k
, it splits the substring into:- Left part:
l = s[i:i+k]
(digits before the decimal) - Right part:
r = s[i+k:j]
(digits after the decimal, if any)
- Left part:
- It iterates through all possible positions
-
Validation Logic: For each split, the function checks if the representation is valid:
ok = (l == '0' or not l.startswith('0')) and not r.endswith('0')
- The left part is valid if it's exactly
"0"
OR doesn't start with'0'
- The right part (after decimal) must not end with
'0'
(no trailing zeros) - If
k == j-i
, there's no decimal point (the entire substring is used as an integer)
- The left part is valid if it's exactly
-
String Construction: If valid, the function constructs the number:
res.append(l + ('.' if k < j - i else '') + r)
- Adds a decimal point only if
k < j-i
(meaning we're not using the entire substring as the left part)
- Adds a decimal point only if
-
Main Loop: The solution iterates through all possible split positions:
for i in range(2, n - 1)
i
represents where to split between x and y coordinates- Starts at 2 (after opening parenthesis) and ends at
n-1
(before closing parenthesis) - For each split position:
- Generate all valid x values:
f(1, i)
- Generate all valid y values:
f(i, n-1)
- Create coordinate pairs using list comprehension
- Generate all valid x values:
-
Output Formation: Each valid combination is formatted as
f'({x}, {y})'
with proper spacing.
Example Walkthrough:
For s = "(123)"
:
- Split at position 2: x from
s[1:2]
="1"
, y froms[2:4]
="23"
- x can be:
["1"]
- y can be:
["23", "2.3"]
- Results:
["(1, 23)", "(1, 2.3)"]
- x can be:
- Split at position 3: x from
s[1:3]
="12"
, y froms[3:4]
="3"
- x can be:
["12", "1.2"]
- y can be:
["3"]
- Results:
["(12, 3)", "(1.2, 3)"]
- x can be:
The algorithm efficiently explores all possibilities while filtering out invalid representations through the validation logic.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with s = "(205)"
:
Step 1: Extract digits
- Remove parentheses: digits =
"205"
- We need to split these 3 digits into x and y coordinates
Step 2: Try split position i=2
- x uses
s[1:2]
="2"
, y usess[2:5]
="05"
- Generate valid representations for x =
"2"
:- As integer:
"2"
✓ (no leading zeros) - No decimal possible (single digit)
- Result:
["2"]
- As integer:
- Generate valid representations for y =
"05"
:- As integer:
"05"
✗ (has leading zero) - With decimal at position 1:
"0.5"
✓ (left="0" is valid, right="5" has no trailing zero) - Result:
["0.5"]
- As integer:
- Combine:
"(2, 0.5)"
Step 3: Try split position i=3
- x uses
s[1:3]
="20"
, y usess[3:5]
="5"
- Generate valid representations for x =
"20"
:- As integer:
"20"
✓ (no leading zeros) - With decimal at position 1:
"2.0"
✗ (trailing zero after decimal) - Result:
["20"]
- As integer:
- Generate valid representations for y =
"5"
:- As integer:
"5"
✓ (no leading zeros) - No decimal possible (single digit)
- Result:
["5"]
- As integer:
- Combine:
"(20, 5)"
Step 4: Try split position i=4
- x uses
s[1:4]
="205"
, y usess[4:5]
=""
- Invalid: y would be empty
Final Result: ["(2, 0.5)", "(20, 5)"]
The helper function f(i, j)
systematically checks each possible decimal placement, validating against the rules (no leading zeros except "0", no trailing zeros after decimal). The main loop tries every valid split position to ensure all possible original coordinates are found.
Solution Implementation
1class Solution:
2 def ambiguousCoordinates(self, s: str) -> List[str]:
3 def generate_valid_numbers(start: int, end: int) -> List[str]:
4 """
5 Generate all valid numbers (with or without decimal point) from substring s[start:end].
6 A valid number:
7 - Cannot have leading zeros (except "0" itself)
8 - Cannot have trailing zeros after decimal point
9 """
10 valid_numbers = []
11
12 # Try all possible positions for decimal point
13 for decimal_pos in range(1, end - start + 1):
14 # Split into integer part and decimal part
15 integer_part = s[start:start + decimal_pos]
16 decimal_part = s[start + decimal_pos:end]
17
18 # Check validity conditions:
19 # 1. Integer part should not have leading zeros (unless it's just "0")
20 # 2. Decimal part should not have trailing zeros
21 has_valid_integer = (integer_part == '0' or not integer_part.startswith('0'))
22 has_valid_decimal = not decimal_part.endswith('0')
23
24 if has_valid_integer and has_valid_decimal:
25 # Add decimal point only if there's a decimal part
26 if decimal_pos < end - start:
27 number = integer_part + '.' + decimal_part
28 else:
29 number = integer_part
30 valid_numbers.append(number)
31
32 return valid_numbers
33
34 # Remove parentheses from input string
35 string_length = len(s)
36 result = []
37
38 # Try all possible positions to split into two coordinates
39 for split_pos in range(2, string_length - 1):
40 # Generate all valid numbers for first coordinate
41 first_coordinate_options = generate_valid_numbers(1, split_pos)
42 # Generate all valid numbers for second coordinate
43 second_coordinate_options = generate_valid_numbers(split_pos, string_length - 1)
44
45 # Create all combinations of valid coordinates
46 for x in first_coordinate_options:
47 for y in second_coordinate_options:
48 result.append(f'({x}, {y})')
49
50 return result
51
1class Solution {
2 /**
3 * Given a string of digits, return all possible valid coordinate representations
4 * by adding parentheses, comma, and optional decimal points.
5 * @param s Input string containing only digits (e.g., "123")
6 * @return List of all valid coordinate strings (e.g., ["(1, 23)", "(1, 2.3)", "(1.2, 3)"])
7 */
8 public List<String> ambiguousCoordinates(String s) {
9 int totalLength = s.length();
10 List<String> result = new ArrayList<>();
11
12 // Try all possible positions to split the string into two coordinates
13 // Start from 2 to skip opening parenthesis, end before closing parenthesis
14 for (int splitPosition = 2; splitPosition < totalLength - 1; ++splitPosition) {
15 // Generate all valid decimal representations for the first coordinate
16 List<String> firstCoordinates = f(s, 1, splitPosition);
17
18 // Generate all valid decimal representations for the second coordinate
19 List<String> secondCoordinates = f(s, splitPosition, totalLength - 1);
20
21 // Combine each valid first coordinate with each valid second coordinate
22 for (String firstCoord : firstCoordinates) {
23 for (String secondCoord : secondCoordinates) {
24 result.add(String.format("(%s, %s)", firstCoord, secondCoord));
25 }
26 }
27 }
28
29 return result;
30 }
31
32 /**
33 * Generate all valid decimal number representations for a substring.
34 * @param s The original string
35 * @param startIndex Starting index (inclusive) of the substring
36 * @param endIndex Ending index (exclusive) of the substring
37 * @return List of all valid decimal representations
38 */
39 private List<String> f(String s, int startIndex, int endIndex) {
40 List<String> validNumbers = new ArrayList<>();
41
42 // Try placing decimal point at each possible position (including no decimal)
43 for (int decimalPosition = 1; decimalPosition <= endIndex - startIndex; ++decimalPosition) {
44 // Extract the integer part (before decimal point)
45 String integerPart = s.substring(startIndex, startIndex + decimalPosition);
46
47 // Extract the decimal part (after decimal point)
48 String decimalPart = s.substring(startIndex + decimalPosition, endIndex);
49
50 // Validate the number:
51 // 1. Integer part can only start with 0 if it's exactly "0"
52 // 2. Decimal part cannot end with 0 (no trailing zeros after decimal)
53 boolean isValidNumber = ("0".equals(integerPart) || !integerPart.startsWith("0"))
54 && !decimalPart.endsWith("0");
55
56 if (isValidNumber) {
57 // Add decimal point only if there's a decimal part
58 String formattedNumber = integerPart +
59 (decimalPosition < endIndex - startIndex ? "." : "") +
60 decimalPart;
61 validNumbers.add(formattedNumber);
62 }
63 }
64
65 return validNumbers;
66 }
67}
68
1class Solution {
2public:
3 vector<string> ambiguousCoordinates(string s) {
4 int length = s.size();
5 vector<string> result;
6
7 // Helper function to generate all valid decimal representations for a substring
8 auto generateValidNumbers = [&](int startIdx, int endIdx) {
9 vector<string> validNumbers;
10
11 // Try placing decimal point at each possible position (including no decimal)
12 for (int decimalPos = 1; decimalPos <= endIdx - startIdx; ++decimalPos) {
13 string integerPart = s.substr(startIdx, decimalPos);
14 string decimalPart = s.substr(startIdx + decimalPos, endIdx - startIdx - decimalPos);
15
16 // Validation rules:
17 // 1. Integer part cannot have leading zeros (except "0" itself)
18 // 2. Decimal part cannot have trailing zeros
19 bool isValid = (integerPart == "0" || integerPart[0] != '0') &&
20 (decimalPart.empty() || decimalPart.back() != '0');
21
22 if (isValid) {
23 // Add decimal point only if there's a decimal part
24 string number = integerPart;
25 if (decimalPos < endIdx - startIdx) {
26 number += "." + decimalPart;
27 }
28 validNumbers.push_back(number);
29 }
30 }
31 return validNumbers;
32 };
33
34 // Try all possible ways to split the string into two coordinates
35 // Skip first '(' and last ')' characters
36 for (int splitPos = 2; splitPos < length - 1; ++splitPos) {
37 // Generate all valid representations for the first coordinate (X)
38 vector<string> xCoordinates = generateValidNumbers(1, splitPos);
39
40 // Generate all valid representations for the second coordinate (Y)
41 vector<string> yCoordinates = generateValidNumbers(splitPos, length - 1);
42
43 // Combine each valid X with each valid Y to form coordinate pairs
44 for (const auto& x : xCoordinates) {
45 for (const auto& y : yCoordinates) {
46 result.emplace_back("(" + x + ", " + y + ")");
47 }
48 }
49 }
50
51 return result;
52 }
53};
54
1/**
2 * Generates all possible valid coordinate representations from a string of digits
3 * @param s - Input string in format "(digits)" where digits will be split into coordinates
4 * @returns Array of all valid coordinate representations in format "(x, y)"
5 */
6function ambiguousCoordinates(s: string): string[] {
7 // Remove the parentheses from input string
8 const digitsOnly: string = s.slice(1, s.length - 1);
9 const totalLength: number = digitsOnly.length;
10
11 /**
12 * Generates all valid decimal number representations from a string of digits
13 * @param digits - String of digits to process
14 * @returns Array of valid number representations (with or without decimal point)
15 */
16 const generateValidNumbers = (digits: string): string[] => {
17 const validNumbers: string[] = [];
18
19 // Try adding decimal point at each position (except at the very end)
20 for (let decimalPosition = 1; decimalPosition < digits.length; decimalPosition++) {
21 const numberWithDecimal: string = `${digits.slice(0, decimalPosition)}.${digits.slice(decimalPosition)}`;
22
23 // Validate the decimal number (no leading zeros, no trailing zeros after decimal)
24 if (`${Number(numberWithDecimal)}` === numberWithDecimal) {
25 validNumbers.push(numberWithDecimal);
26 }
27 }
28
29 // Try using the string as a whole number (no decimal point)
30 if (`${Number(digits)}` === digits) {
31 validNumbers.push(digits);
32 }
33
34 return validNumbers;
35 };
36
37 const result: string[] = [];
38
39 // Split the digits at each possible position
40 for (let splitPosition = 1; splitPosition < totalLength; splitPosition++) {
41 const leftPart: string = digitsOnly.slice(0, splitPosition);
42 const rightPart: string = digitsOnly.slice(splitPosition);
43
44 // Generate all valid numbers for left coordinate
45 const validLeftNumbers: string[] = generateValidNumbers(leftPart);
46
47 // Generate all valid numbers for right coordinate
48 const validRightNumbers: string[] = generateValidNumbers(rightPart);
49
50 // Combine all valid left and right coordinates
51 for (const leftCoordinate of validLeftNumbers) {
52 for (const rightCoordinate of validRightNumbers) {
53 result.push(`(${leftCoordinate}, ${rightCoordinate})`);
54 }
55 }
56 }
57
58 return result;
59}
60
Time and Space Complexity
Time Complexity: O(n^4)
The analysis breaks down as follows:
- The outer loops iterate through all possible split positions
i
from2
ton-1
, giving usO(n)
iterations - For each split position
i
, we callf(1, i)
andf(i, n-1)
- Inside function
f(i, j)
, we iteratek
from1
toj - i + 1
, which isO(n)
in the worst case - For each
k
, we perform string slicing operationss[i:i+k]
ands[i+k:j]
, each takingO(n)
time - The string operations (checking
startswith
,endswith
, and concatenation) takeO(n)
time - The number of valid coordinates generated by
f(i, j)
can be at mostO(n)
- The nested list comprehension iterates through all combinations of
x
fromf(1, i)
andy
fromf(i, n-1)
, which in the worst case isO(n) × O(n) = O(n^2)
combinations - For each combination, we create a formatted string which takes
O(n)
time
Overall: O(n)
split positions × O(n^2)
combinations × O(n)
string operations = O(n^4)
Space Complexity: O(n^3)
The space analysis:
- Function
f(i, j)
creates a listres
that can contain at mostO(n)
strings, each of lengthO(n)
, usingO(n^2)
space - The main function creates lists from
f(1, i)
andf(i, n-1)
for each split position - The final result list can contain at most
O(n)
split positions ×O(n^2)
combinations =O(n^3)
strings - Each string in the result has length
O(n)
- The total space used is
O(n^3)
for storing all possible valid coordinate strings
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Handling Single-Digit Zero Cases
A frequent mistake is mishandling the validation logic for zeros, particularly when dealing with single-digit zeros as valid coordinates.
Pitfall Example:
# Incorrect validation that rejects valid "0" has_valid_integer = not integer_part.startswith('0') # This rejects "0" itself!
Why it's wrong: The string "0"
is a valid number, but this check would incorrectly reject it since "0".startswith('0')
is True
.
Correct Approach:
# Proper validation that accepts "0" as valid has_valid_integer = (integer_part == '0' or not integer_part.startswith('0'))
2. Forgetting Edge Cases with Decimal Points
Another common mistake is not properly handling when NOT to add a decimal point.
Pitfall Example:
# Always adding a decimal point when there's a split number = integer_part + '.' + decimal_part
Why it's wrong: When decimal_pos
equals end - start
, there's no decimal part, so adding a decimal point would create invalid numbers like "123."
.
Correct Approach:
if decimal_pos < end - start: number = integer_part + '.' + decimal_part else: number = integer_part # No decimal point when using entire substring
3. Off-by-One Errors in Range Iterations
Developers often make indexing mistakes when iterating through split positions.
Pitfall Example:
# Incorrect range that misses valid splits or includes invalid ones
for split_pos in range(1, string_length): # Includes parentheses!
Why it's wrong: This would try to include the opening parenthesis (
in the first coordinate or the closing parenthesis )
in the second coordinate.
Correct Approach:
# Proper range that excludes parentheses
for split_pos in range(2, string_length - 1):
# split_pos = 2 means first coordinate is s[1:2]
# split_pos = n-2 means second coordinate is s[n-2:n-1]
4. Misunderstanding Trailing Zero Rules
A subtle but critical mistake is incorrectly implementing the trailing zero validation for decimal parts.
Pitfall Example:
# Checking the entire number for trailing zeros has_valid_number = not number.endswith('0') # Wrong!
Why it's wrong: This would incorrectly reject valid integers like "10"
or "20"
. The trailing zero rule only applies to the decimal part AFTER a decimal point.
Correct Approach:
# Only check decimal part for trailing zeros has_valid_decimal = not decimal_part.endswith('0') # This correctly allows "10" (no decimal) but rejects "1.0" (decimal_part = "0")
5. Not Handling Empty Decimal Parts Correctly
When the decimal part is empty (using the entire substring as integer), the validation needs special handling.
Pitfall Example:
# This fails when decimal_part is empty string has_valid_decimal = not decimal_part.endswith('0') # "".endswith('0') is False, seems ok... number = integer_part + '.' + decimal_part # But this creates "123." which is invalid!
Correct Approach:
# When decimal_part is empty, don't check it and don't add decimal point if decimal_pos < end - start: has_valid_decimal = not decimal_part.endswith('0') number = integer_part + '.' + decimal_part else: has_valid_decimal = True # No decimal part to validate number = integer_part
These pitfalls highlight the importance of careful consideration of edge cases, particularly around zero handling and decimal point placement in string manipulation problems.
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
Recommended Readings
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!