LeetCode 3704 - Count No-Zero Pairs That Sum to N

The problem asks us to count all pairs of positive integers (a, b) such that their sum equals a given integer n and neither a nor b contains the digit 0 in their decimal representation. These integers are called no-zero integers.

LeetCode Problem 3704

Difficulty: 🔴 Hard
Topics: Math, Dynamic Programming

Solution

Problem Understanding

The problem asks us to count all pairs of positive integers (a, b) such that their sum equals a given integer n and neither a nor b contains the digit 0 in their decimal representation. These integers are called no-zero integers.

The input n is guaranteed to be at least 2 and can be as large as $10^{15}$, which indicates that a naive brute-force approach iterating through all integers up to n would be infeasible due to both time and space constraints. The output is a single integer representing the number of valid (a, b) pairs.

Important constraints and implications are that a and b must both be strictly positive, cannot contain 0, and their sum must exactly equal n. Edge cases include small values of n like 2 and large values near $10^{15}$, as well as numbers where the sum could produce a carry that introduces a zero in a or b if not carefully chosen.

Approaches

The brute-force approach would involve iterating over all integers a from 1 to n-1 and checking if b = n - a is also a no-zero integer. This would guarantee correctness because it exhaustively checks all candidate pairs. However, this is too slow when n is large, particularly since n can reach $10^{15}$. Checking each number for zeros individually in a naive way would still require $O(n \log n)$ operations.

The optimal approach uses digit dynamic programming (digit DP). The key insight is that a number contains a zero if any of its digits is zero, and the sum of two numbers without zeros at each digit position can be recursively counted by handling carries carefully. By processing each digit from least significant to most significant and counting valid no-zero digits combinations that sum to the corresponding digit of n (taking carry into account), we can compute the total number of valid pairs efficiently. This reduces the time complexity to a factor dependent on the number of digits in n, which is at most 16, rather than n itself.

Approach Time Complexity Space Complexity Notes
Brute Force O(n log n) O(1) Iterate over all a and check b = n - a for zero digits
Optimal (Digit DP) O(d^2) O(d^2) Recursive DP over digits with carry, d is number of digits in n

Algorithm Walkthrough

  1. Convert the integer n to a string to process its digits individually.
  2. Define a recursive function dfs(index, carry) where index indicates the current digit being processed from least significant to most significant and carry is the carry from the previous addition.
  3. For each position, iterate over all valid digits 1-9 for a and b (no zeros allowed). For each combination, check if (a_digit + b_digit + carry) % 10 matches the corresponding digit of n.
  4. Recursively call dfs for the next higher digit with the updated carry (a_digit + b_digit + carry) // 10.
  5. The base case is when all digits have been processed. If the carry is zero, count this combination as valid.
  6. Use memoization to store results of dfs(index, carry) to avoid recomputation.
  7. Return the total count from the initial call.

Why it works: At each digit position, we consider all valid digit combinations for a and b that satisfy the no-zero constraint and sum to the correct digit modulo 10 with the carry. The recursive DP ensures all digit positions are covered systematically and efficiently, and memoization guarantees we do not double-count or recompute subproblems.

Python Solution

class Solution:
    def countNoZeroPairs(self, n: int) -> int:
        s = str(n)
        length = len(s)
        memo = {}
        
        def dfs(index: int, carry: int) -> int:
            if index == length:
                return 1 if carry == 0 else 0
            if (index, carry) in memo:
                return memo[(index, carry)]
            
            total = 0
            current_digit = int(s[length - 1 - index])
            for a in range(1, 10):
                for b in range(1, 10):
                    if (a + b + carry) % 10 == current_digit:
                        total += dfs(index + 1, (a + b + carry) // 10)
            
            memo[(index, carry)] = total
            return total
        
        return dfs(0, 0)

Explanation: We first convert n to a string to access its digits. The dfs function recursively counts valid no-zero pairs from the least significant digit upwards. At each position, we try all digits 1-9 for both numbers, check if the sum with the carry matches the target digit, and recurse. Memoization stores intermediate results keyed by (index, carry) to prevent redundant calculations.

Go Solution

func countNoZeroPairs(n int64) int64 {
    s := fmt.Sprintf("%d", n)
    length := len(s)
    memo := make(map[[2]int64]int64)

    var dfs func(index int, carry int) int64
    dfs = func(index int, carry int) int64 {
        if index == length {
            if carry == 0 {
                return 1
            }
            return 0
        }
        key := [2]int64{int64(index), int64(carry)}
        if val, ok := memo[key]; ok {
            return val
        }
        total := int64(0)
        currentDigit := int(s[length-1-index] - '0')
        for a := 1; a <= 9; a++ {
            for b := 1; b <= 9; b++ {
                if (a+b+carry)%10 == currentDigit {
                    total += dfs(index+1, (a+b+carry)/10)
                }
            }
        }
        memo[key] = total
        return total
    }

    return dfs(0, 0)
}

Explanation: The Go version mirrors the Python approach. We use a map with array keys [index, carry] for memoization. Since Go does not have native tuples, the array serves as a key. The recursion handles all digit combinations, ensuring no zeros, correct sum modulo 10, and proper carry handling.

Worked Examples

Example n = 11

index carry a b sum + carry match? next carry
0 0 2 9 11 yes 1
0 0 3 8 11 yes 1
0 0 4 7 11 yes 1
0 0 5 6 11 yes 1
0 0 6 5 11 yes 1
0 0 7 4 11 yes 1
0 0 8 3 11 yes 1
0 0 9 2 11 yes 1

All 8 valid pairs are counted. Recursion ends when index reaches length of n and carry is 0.

Complexity Analysis

Measure Complexity Explanation
Time O(d^2 * 81) d is number of digits in n. For each digit, try 9x9 combinations, and memoization reduces redundant calls
Space O(d * c) d digits and carry at most d can occur, memoization stores intermediate results

Since d <= 16 for $n \le 10^{15}$, this is efficient.

Test Cases

sol = Solution()

assert sol.countNoZeroPairs(2) == 1  # only (1, 1)
assert sol.countNoZeroPairs(3) == 2  # (1,2), (2,1)
assert sol.countNoZeroPairs(11) == 8  # (2,9)...(9,2)
assert sol.countNoZeroPairs(101) == 0  # sum requires a 0 digit
assert sol.countNoZeroPairs(1001) == 0  # sum requires zeros in digits
assert sol.countNoZeroPairs(22) > 0  # larger sum with multiple valid pairs
Test Why
2 minimal sum, only one valid pair
3 small sum with multiple combinations
11 requires ignoring sums containing 0
101 sum leads to zero digit, should be 0
1001 larger sum with unavoidable zeros, 0 pairs
22 stress test for multiple valid pairs

Edge Cases

One edge case is the smallest input n = 2, which only has (1,1) as a valid pair. Another is sums that would require zeros, e.g., n = 101