LeetCode 3272 - Find the Count of Good Integers
The problem asks us to count n-digit integers that are "good" with respect to a given integer k. A "good" integer is one whose digits can be rearranged to form a k-palindromic integer.
Difficulty: 🔴 Hard
Topics: Hash Table, Math, Combinatorics, Enumeration
Solution
Problem Understanding
The problem asks us to count n-digit integers that are "good" with respect to a given integer k. A "good" integer is one whose digits can be rearranged to form a k-palindromic integer. A k-palindromic integer is defined as an integer that satisfies two properties: it is a palindrome (reads the same forwards and backwards), and it is divisible by k.
The input consists of two integers: n, the number of digits, and k, the divisor. The output is the count of all n-digit integers that can be rearranged into a k-palindrome. The constraints tell us that n can be up to 10, and k is a single-digit integer. Because n is small, we can explore combinatorial techniques, but a naive enumeration of all n-digit integers would be inefficient for large n. Leading zeros are not allowed, either in the original number or in any rearranged palindrome.
Important edge cases include n = 1, where palindromes are single digits, and k = 1, which makes any palindrome divisible by 1. We must also handle numbers that contain zeros carefully because leading zeros in the palindrome would make them invalid.
Approaches
Brute Force
The brute-force approach would enumerate all n-digit integers. For each integer, we would generate all permutations of its digits, check if any permutation forms a palindrome, and then check if the palindrome is divisible by k. This approach is correct because it directly tests the definition of a "good" integer. However, the complexity is factorial in n due to permutations, making it infeasible for n = 10.
Optimal Approach
The key observation is that we do not need to permute all integers. Instead, we can focus on counting valid palindromes that are divisible by k, and then compute how many integers can be rearranged to form those palindromes. We can generate half of the palindrome digits (because a palindrome is symmetric) and determine the full number. Using combinatorial counts of digit frequencies, we can determine how many original integers can rearrange into each palindrome.
The steps are:
- Generate all candidate palindromes of length
nwith digits 1-9 in the first position and 0-9 elsewhere. - Check divisibility by
k. - Count permutations of digits that could form this palindrome using factorials, accounting for repeated digits.
This reduces the search space dramatically and avoids generating all n-digit integers explicitly.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n! * 10^n) | O(n) | Enumerate all n-digit numbers and check all permutations |
| Optimal | O(9 * 10^(ceil(n/2))) | O(n) | Generate half-palindromes, check k-divisibility, count rearrangements |
Algorithm Walkthrough
- Compute the length of half the palindrome,
half_len = (n + 1) // 2. For odd n, the middle digit is handled separately. - Enumerate all digit sequences for the first half of the palindrome. The first digit cannot be zero.
- Construct the full palindrome from the half by mirroring digits around the center.
- Check if the full palindrome is divisible by
k. If not, discard it. - For valid palindromes, count the number of original integers whose digits can be rearranged to form this palindrome. This uses factorial division for repeated digits.
- Accumulate the counts to get the total number of good integers.
Why it works: Any n-digit integer that can be rearranged into a palindrome must have digits that match the palindrome's frequency distribution. By enumerating palindromes and counting possible rearrangements using combinatorics, we guarantee all "good" integers are counted exactly once.
Python Solution
from math import factorial
from collections import Counter
class Solution:
def countGoodIntegers(self, n: int, k: int) -> int:
def generate_half(length):
if length == 0:
return ['']
if length == 1:
return [str(d) for d in range(10)]
prev = generate_half(length - 1)
result = []
for num in prev:
for d in range(10):
result.append(num + str(d))
return result
def count_permutations(counter):
total = sum(counter.values())
res = factorial(total)
for v in counter.values():
res //= factorial(v)
return res
half_len = (n + 1) // 2
total_count = 0
for first_digit in range(1, 10):
stack = [(str(first_digit), 1)]
while stack:
seq, idx = stack.pop()
if idx == half_len:
# build full palindrome
if n % 2 == 0:
full = seq + seq[::-1]
else:
full = seq + seq[-2::-1]
if int(full) % k == 0:
counter = Counter(full)
total_count += count_permutations(counter)
continue
for d in range(10):
stack.append((seq + str(d), idx + 1))
return total_count
Explanation: We recursively build all valid halves of the palindrome, ensuring the first digit is not zero. For each half, we generate the full palindrome and check divisibility. For valid palindromes, we count permutations of digits using Counter and factorial math to account for repeated digits.
Go Solution
package main
import (
"fmt"
)
func factorial(n int) int64 {
if n == 0 || n == 1 {
return 1
}
res := int64(1)
for i := 2; i <= n; i++ {
res *= int64(i)
}
return res
}
func countPermutations(counter map[int]int) int64 {
total := 0
for _, v := range counter {
total += v
}
res := factorial(total)
for _, v := range counter {
res /= factorial(v)
}
return res
}
func buildFullPalindrome(seq []int, n int) []int {
res := make([]int, n)
copy(res[:len(seq)], seq)
if n%2 == 0 {
for i := 0; i < len(seq); i++ {
res[n-i-1] = seq[i]
}
} else {
for i := 0; i < len(seq)-1; i++ {
res[n-i-1] = seq[i]
}
}
return res
}
func countGoodIntegers(n int, k int) int64 {
total := int64(0)
var dfs func([]int, int)
halfLen := (n + 1) / 2
dfs = func(seq []int, idx int) {
if idx == halfLen {
full := buildFullPalindrome(seq, n)
val := 0
for _, d := range full {
val = val*10 + d
}
if val%k == 0 {
counter := make(map[int]int)
for _, d := range full {
counter[d]++
}
total += countPermutations(counter)
}
return
}
start := 0
if idx == 0 {
start = 1
}
for d := start; d <= 9; d++ {
dfs(append(seq, d), idx+1)
}
}
for d := 1; d <= 9; d++ {
dfs([]int{d}, 1)
}
return total
}
func main() {
fmt.Println(countGoodIntegers(3, 5)) // 27
}
Go differences: We use slices instead of strings, maps instead of Counter, and explicit int64 factorials to avoid overflow. DFS is used to build half palindromes recursively.
Worked Examples
Example 1: n = 3, k = 5
- Half length = 2
- First half sequences: 11, 12, ..., 99 (first digit != 0)
- Construct full palindromes: 111, 121, 131, ..., 999
- Check divisible by 5: 505, 515, 525, ...
- Count permutations of digits: sequences like 525 => 525, 552, 255 (3 permutations)
- Sum over all valid palindromes => 27
Example 2: n = 1, k = 4
- Single-digit palindromes: 1, 2, 3, ..., 9
- Divisible by 4: 4, 8
- Count = 2
Example 3: n = 5, k = 6
- Half length = 3
- Generate half sequences: 100 - 999
- Construct full palindrome: e.g., 12321
- Check divisible by 6
- Count permutations => 2468 total
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(9 * 10^(ceil(n/2))) | Enumerate first half of palindrome, which dominates the runtime |
| Space | O(n) | Stack or recursion depth proportional to half-length of palindrome |
The time complexity is acceptable because `