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.
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
- Convert the integer
nto a string to process its digits individually. - Define a recursive function
dfs(index, carry)whereindexindicates the current digit being processed from least significant to most significant andcarryis the carry from the previous addition. - For each position, iterate over all valid digits
1-9foraandb(no zeros allowed). For each combination, check if(a_digit + b_digit + carry) % 10matches the corresponding digit ofn. - Recursively call
dfsfor the next higher digit with the updated carry(a_digit + b_digit + carry) // 10. - The base case is when all digits have been processed. If the carry is zero, count this combination as valid.
- Use memoization to store results of
dfs(index, carry)to avoid recomputation. - 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