Facebook Pixel

816. Ambiguous Coordinates

MediumStringBacktrackingEnumeration
Leetcode Link

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"
  • No trailing zeros after a decimal point
    • Invalid: "1.0", "1.00", "0.50"
    • Valid: "1", "0.5", "1.5"
  • No unnecessary decimal representations
    • Invalid: "1.0" (should be "1")
    • Valid: "1.5", "0.5"
  • A decimal point must have at least one digit before it
    • Invalid: ".1", ".5"
    • Valid: "0.1", "0.5"

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:
    1. Split the digits into two numbers (x and y)
    2. 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:

  1. Iterating through all possible split positions between x and y
  2. For each split, generating all valid decimal representations of x
  3. For each split, generating all valid decimal representations of y
  4. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. 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".

  2. 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:

  1. Try every possible position to split the string into two parts (x and y)
  2. For each part, generate all valid number representations (with or without decimals)
  3. 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:

  1. Helper Function f(i, j): This function generates all valid number representations for the substring s[i:j].

    • It iterates through all possible positions k to place a decimal point (from 1 to j-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)
  2. 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)
  3. 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)
  4. 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
  5. 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 from s[2:4] = "23"
    • x can be: ["1"]
    • y can be: ["23", "2.3"]
    • Results: ["(1, 23)", "(1, 2.3)"]
  • Split at position 3: x from s[1:3] = "12", y from s[3:4] = "3"
    • x can be: ["12", "1.2"]
    • y can be: ["3"]
    • Results: ["(12, 3)", "(1.2, 3)"]

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 Evaluator

Example 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 uses s[2:5] = "05"
  • Generate valid representations for x = "2":
    • As integer: "2" ✓ (no leading zeros)
    • No decimal possible (single digit)
    • Result: ["2"]
  • 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"]
  • Combine: "(2, 0.5)"

Step 3: Try split position i=3

  • x uses s[1:3] = "20", y uses s[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"]
  • Generate valid representations for y = "5":
    • As integer: "5" ✓ (no leading zeros)
    • No decimal possible (single digit)
    • Result: ["5"]
  • Combine: "(20, 5)"

Step 4: Try split position i=4

  • x uses s[1:4] = "205", y uses s[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 from 2 to n-1, giving us O(n) iterations
  • For each split position i, we call f(1, i) and f(i, n-1)
  • Inside function f(i, j), we iterate k from 1 to j - i + 1, which is O(n) in the worst case
  • For each k, we perform string slicing operations s[i:i+k] and s[i+k:j], each taking O(n) time
  • The string operations (checking startswith, endswith, and concatenation) take O(n) time
  • The number of valid coordinates generated by f(i, j) can be at most O(n)
  • The nested list comprehension iterates through all combinations of x from f(1, i) and y from f(i, n-1), which in the worst case is O(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 list res that can contain at most O(n) strings, each of length O(n), using O(n^2) space
  • The main function creates lists from f(1, i) and f(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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More