Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

816. Ambiguous Coordinates - Leetcode Solution

Code Implementation

class Solution:
    def ambiguousCoordinates(self, s: str):
        def make_numbers(s):
            res = []
            n = len(s)
            for i in range(1, n+1):
                left = s[:i]
                right = s[i:]
                if (len(left) > 1 and left[0] == '0' and left[-1] != '.') or (right and right[-1] == '0'):
                    continue
                if right:
                    num = left + '.' + right
                else:
                    num = left
                if (num[0] == '0' and len(num) > 1 and num[1] != '.') or (num[-1] == '0' and '.' in num):
                    continue
                res.append(num)
            return res

        s = s[1:-1]
        n = len(s)
        ans = []
        for i in range(1, n):
            left = s[:i]
            right = s[i:]
            lefts = make_numbers(left)
            rights = make_numbers(right)
            for l in lefts:
                for r in rights:
                    ans.append(f"({l}, {r})")
        return ans
      
class Solution {
public:
    vector<string> ambiguousCoordinates(string s) {
        vector<string> res;
        string str = s.substr(1, s.size() - 2);
        int n = str.size();
        for (int i = 1; i < n; ++i) {
            vector<string> lefts = makeNumbers(str.substr(0, i));
            vector<string> rights = makeNumbers(str.substr(i));
            for (const string& l : lefts) {
                for (const string& r : rights) {
                    res.push_back("(" + l + ", " + r + ")");
                }
            }
        }
        return res;
    }
    
    vector<string> makeNumbers(const string& s) {
        vector<string> res;
        int n = s.size();
        for (int i = 1; i <= n; ++i) {
            string left = s.substr(0, i);
            string right = s.substr(i);
            if ((left.size() > 1 && left[0] == '0' && (left.back() != '.')) || (!right.empty() && right.back() == '0'))
                continue;
            string num = left;
            if (!right.empty())
                num += "." + right;
            if ((num[0] == '0' && num.size() > 1 && num[1] != '.') || (num.back() == '0' && num.find('.') != string::npos))
                continue;
            res.push_back(num);
        }
        return res;
    }
};
      
class Solution {
    public List<String> ambiguousCoordinates(String s) {
        List<String> ans = new ArrayList<>();
        String str = s.substring(1, s.length() - 1);
        int n = str.length();
        for (int i = 1; i < n; i++) {
            List<String> lefts = makeNumbers(str.substring(0, i));
            List<String> rights = makeNumbers(str.substring(i));
            for (String l : lefts) {
                for (String r : rights) {
                    ans.add("(" + l + ", " + r + ")");
                }
            }
        }
        return ans;
    }

    private List<String> makeNumbers(String s) {
        List<String> res = new ArrayList<>();
        int n = s.length();
        for (int i = 1; i <= n; i++) {
            String left = s.substring(0, i);
            String right = s.substring(i);
            if ((left.length() > 1 && left.charAt(0) == '0' && (left.charAt(left.length() - 1) != '.')) 
                || (!right.isEmpty() && right.charAt(right.length() - 1) == '0')) continue;
            String num = left;
            if (!right.isEmpty()) num += "." + right;
            if ((num.charAt(0) == '0' && num.length() > 1 && num.charAt(1) != '.') 
                || (num.charAt(num.length() - 1) == '0' && num.contains("."))) continue;
            res.add(num);
        }
        return res;
    }
}
      
var ambiguousCoordinates = function(s) {
    function makeNumbers(str) {
        let res = [];
        let n = str.length;
        for (let i = 1; i <= n; i++) {
            let left = str.slice(0, i);
            let right = str.slice(i);
            if ((left.length > 1 && left[0] === '0' && left[left.length-1] !== '.') || (right && right[right.length-1] === '0')) continue;
            let num = left;
            if (right) num += '.' + right;
            if ((num[0] === '0' && num.length > 1 && num[1] !== '.') || (num[num.length-1] === '0' && num.includes('.'))) continue;
            res.push(num);
        }
        return res;
    }
    s = s.slice(1, -1);
    let n = s.length;
    let ans = [];
    for (let i = 1; i < n; i++) {
        let lefts = makeNumbers(s.slice(0, i));
        let rights = makeNumbers(s.slice(i));
        for (let l of lefts) {
            for (let r of rights) {
                ans.push(`(${l}, ${r})`);
            }
        }
    }
    return ans;
};
      

Problem Description

You are given a string s that represents a set of coordinates written as "(123)". Your task is to return all possible valid coordinate representations by inserting a comma and possibly a decimal point into the digits, such that each coordinate is valid.

  • A coordinate is valid if it does not have extra leading zeros (except for "0" itself), and if it contains a decimal point, it must not have trailing zeros after the decimal.
  • You must return all possible representations by splitting the digits into two non-empty groups (for x and y), and then for each group, consider all valid ways to insert a decimal point.
  • For example, "(123)" can be represented as "(1, 23)", "(1.2, 3)", "(12, 3)", "(1, 2.3)", and "(1.23, 3)".
  • Do not reuse or rearrange digits; only split and insert decimal points.

Thought Process

At first glance, the problem seems to require generating all possible splits of the digits into two groups, and then for each group, generating all valid numbers by possibly inserting a decimal point. The main challenge is to ensure that we only include valid numbers that obey the rules about leading and trailing zeros.

A brute-force approach would be to try every possible split and every possible decimal insertion, then filter out invalid options. However, this could be inefficient and error-prone, especially with the digit and decimal rules. To optimize, we realize that:

  • We only need to split once between each pair of digits (since both groups must be non-empty).
  • For each group, we can precompute all valid representations using a helper function.
  • By separating the concerns (splitting and validating), we can write cleaner and more efficient code.

The key insight is to focus on generating valid numbers for each substring, and then combine them for all possible splits.

Solution Approach

Let's break down the algorithm step by step:

  1. Remove the parentheses: The input string always starts and ends with ( and ), so we strip them to get the digits.
  2. Try all possible splits: For a string of length n, split it into two non-empty parts at each position from 1 to n-1. The left part will be the x-coordinate, the right part the y-coordinate.
  3. Generate valid representations for each part: For each part, generate all valid numbers by optionally inserting a decimal point at each position (including not inserting one). A helper function handles the logic for valid decimal placement and zero rules.
    • If the number has more than one digit and starts with '0', it must be "0.xxx" (no leading zeros unless it's zero before the decimal).
    • If the number ends with '0' after the decimal, it's invalid ("1.20" is invalid, but "1.2" is valid).
  4. Combine all valid x and y representations: For each split, combine every valid x with every valid y, formatting as "(x, y)".
  5. Return the result: Collect all combinations in a list and return them.

This approach is efficient because it only generates valid numbers for each substring, and the number of possible splits and decimal insertions is manageable given the constraints.

Example Walkthrough

Let's use the example s = "(123)":

  1. Strip parentheses: s = "123"
  2. Try splits:
    • Split after 1st digit: left = "1", right = "23"
    • Split after 2nd digit: left = "12", right = "3"
  3. For each part, generate valid numbers:
    • "1": Only "1" is valid
    • "23": "23", "2.3" are valid
    • "12": "12", "1.2" are valid
    • "3": Only "3" is valid
  4. Combine:
    • Split 1: ("1", "23") and ("1", "2.3") → "(1, 23)", "(1, 2.3)"
    • Split 2: ("12", "3") and ("1.2", "3") → "(12, 3)", "(1.2, 3)"
  5. Final output: ["(1, 23)", "(1, 2.3)", "(12, 3)", "(1.2, 3)"]

Each step ensures that only valid coordinate representations are included.

Time and Space Complexity

Brute-force approach:

  • For each possible split, try every possible decimal point insertion in both parts, then validate each one.
  • With n digits, there are O(n) splits, and for each, O(n) possible decimals per part, so total O(n^3).
Optimized approach (as above):
  • We generate all splits (O(n)), and for each, generate all valid numbers for each part (O(n) per part), so still O(n^3) in the worst case, but with much less redundant computation and validation.
  • Space complexity is O(n^3) in the worst case for storing all combinations.

In practice, the number of valid combinations is much less than the theoretical maximum, and the constraints (string length ≤ 10) make this approach efficient.

Summary

The Ambiguous Coordinates problem requires generating all valid coordinate representations by splitting a string of digits into two parts and inserting decimal points, while respecting rules about leading and trailing zeros. By focusing on generating only valid numbers for each substring and combining them, the solution avoids unnecessary computation and elegantly handles all edge cases. The approach is systematic, efficient, and easy to understand, making it a great example of breaking down a combinatorial problem into manageable subproblems.