Facebook Pixel

3272. Find the Count of Good Integers

HardHash TableMathCombinatoricsEnumeration
Leetcode Link

Problem Description

You are given two positive integers n and k.

An integer x is called k-palindromic if it satisfies two conditions:

  1. x is a palindrome (reads the same forwards and backwards)
  2. x is divisible by k

An integer is called good if its digits can be rearranged to form a k-palindromic integer. For example, when k = 2, the number 2020 is good because it can be rearranged to form 2002, which is both a palindrome and divisible by 2. However, 1010 is not good because no rearrangement of its digits can form a k-palindromic integer.

Your task is to count how many good integers have exactly n digits.

Important constraints:

  • No integer can have leading zeros, neither in its original form nor after rearrangement
  • For example, 1010 cannot be rearranged to form 0101 or 101 (different number of digits)

The problem asks you to return the total count of n-digit good integers.

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

Intuition

The key insight is that instead of checking all n-digit numbers to see if they can be rearranged into k-palindromic numbers, we can work backwards: generate all possible n-digit palindromes and check if they're divisible by k.

Why does this work? Because if a number can be rearranged to form a palindrome, then that palindrome must have the same digit frequency as the original number. So we can:

  1. Generate all n-digit palindromes
  2. Check if each palindrome is divisible by k
  3. For valid palindromes, count how many different n-digit numbers can be rearranged to form them

To generate palindromes efficiently, we only need to enumerate the first half of the digits. For a palindrome of length n, we take the first ⌈n/2⌉ digits and mirror them. For example, if n = 5 and we have 123 as the first half, we create 12321 by appending the reverse of 12 (excluding the middle digit since n is odd).

The range for the first half is [10^((n-1)//2), 10^((n-1)//2 + 1)). This ensures we generate all possible n-digit palindromes.

Once we find a valid k-palindromic number, we need to count how many different n-digit numbers share the same digits. This is a permutation problem with repetition. However, we must avoid counting the same set of digits multiple times. For instance, 2002, 2020, and 0220 all have the same digits {0,0,2,2}, so we should only count this digit set once.

To avoid duplicates, we use the sorted string representation of the palindrome as a unique identifier. If we've already processed a palindrome with the same sorted digits, we skip it.

For counting permutations, we use the formula: (n - count_of_zeros) * (n-1)! / (product of factorials of digit frequencies). The (n - count_of_zeros) term accounts for the fact that we can't place a zero in the first position (no leading zeros allowed).

Learn more about Math and Combinatorics patterns.

Solution Approach

The implementation follows these steps:

1. Precompute Factorials

fac = [factorial(i) for i in range(n + 1)]

We precompute factorials from 0! to n! for efficient permutation counting later.

2. Initialize Variables

  • ans: Counter for the final result
  • vis: Set to track processed digit combinations (avoids duplicates)
  • base = 10^((n-1)//2): Starting point for enumerating the first half of palindromes

3. Generate Palindromes

for i in range(base, base * 10):
    s = str(i)
    s += s[::-1][n % 2:]

For each number i in the range [base, base * 10):

  • Convert it to string s
  • Append its reverse to form a palindrome
  • If n is odd (n % 2 = 1), skip the first character of the reverse to avoid duplicating the middle digit
  • If n is even (n % 2 = 0), append the full reverse

Example: If n = 5 and i = 123, we get s = "123" + "21" = "12321"

4. Check Divisibility by k

if int(s) % k:
    continue

Skip palindromes that aren't divisible by k.

5. Handle Duplicate Digit Sets

t = "".join(sorted(s))
if t in vis:
    continue
vis.add(t)

Sort the digits of the palindrome to create a unique identifier. If we've already processed this digit combination, skip it. Otherwise, add it to vis.

6. Count Valid Permutations

cnt = Counter(t)
res = (n - cnt["0"]) * fac[n - 1]
for x in cnt.values():
    res //= fac[x]
ans += res

Using combinatorics formula: (nx0)(n1)!x0!x1!x9!\frac{(n - x_0) \cdot (n - 1)!}{x_0! \cdot x_1! \cdots x_9!}

  • Counter(t) counts the frequency of each digit
  • (n - cnt["0"]) represents valid choices for the first position (non-zero digits)
  • fac[n - 1] represents (n-1)! permutations for remaining positions
  • Divide by fac[x] for each digit frequency to eliminate duplicate permutations
  • Add the result to the total answer

The algorithm efficiently counts all n-digit good integers by generating palindromes, checking divisibility, and using combinatorics to count valid permutations while avoiding duplicates.

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 n = 4 and k = 2.

Step 1: Setup

  • Precompute factorials: fac = [1, 1, 2, 6, 24] (for 0! through 4!)
  • Initialize ans = 0, vis = set()
  • Calculate base = 10^((4-1)//2) = 10^1 = 10

Step 2: Generate 4-digit palindromes We iterate i from 10 to 99 (first half of 4-digit palindromes):

For i = 10:

  • s = "10" + "01" = "1001" (palindrome)
  • Check: 1001 % 2 = 1 (odd, not divisible by 2)
  • Skip this palindrome

For i = 11:

  • s = "11" + "11" = "1111" (palindrome)
  • Check: 1111 % 2 = 1 (odd, not divisible by 2)
  • Skip this palindrome

For i = 12:

  • s = "12" + "21" = "1221" (palindrome)
  • Check: 1221 % 2 = 1 (odd, not divisible by 2)
  • Skip this palindrome

...continuing...

For i = 20:

  • s = "20" + "02" = "2002" (palindrome)
  • Check: 2002 % 2 = 0 ✓ (divisible by 2)
  • Sort digits: t = "0022"
  • Not in vis, so add "0022" to vis
  • Count permutations:
    • cnt = Counter("0022") = {'0': 2, '2': 2}
    • Valid first positions: 4 - 2 = 2 (can't use zeros)
    • Permutations: res = 2 * 3! / (2! * 2!) = 2 * 6 / 4 = 3
    • The 3 valid numbers are: 2002, 2020, 2200
  • Add to answer: ans = 0 + 3 = 3

For i = 22:

  • s = "22" + "22" = "2222" (palindrome)
  • Check: 2222 % 2 = 0 ✓ (divisible by 2)
  • Sort digits: t = "2222"
  • Not in vis, so add "2222" to vis
  • Count permutations:
    • cnt = Counter("2222") = {'2': 4}
    • Valid first positions: 4 - 0 = 4 (no zeros)
    • Permutations: res = 4 * 3! / 4! = 4 * 6 / 24 = 1
    • The only valid number is: 2222
  • Add to answer: ans = 3 + 1 = 4

...continuing through all values...

For i = 40:

  • s = "40" + "04" = "4004" (palindrome)
  • Check: 4004 % 2 = 0 ✓ (divisible by 2)
  • Sort digits: t = "0044"
  • Not in vis, so add "0044" to vis
  • Count permutations:
    • cnt = Counter("0044") = {'0': 2, '4': 2}
    • Valid first positions: 4 - 2 = 2
    • Permutations: res = 2 * 3! / (2! * 2!) = 2 * 6 / 4 = 3
    • The 3 valid numbers are: 4004, 4040, 4400
  • Add to answer: ans = 4 + 3 = 7

The process continues for all even palindromes from 10 to 99, accumulating the count of good integers. Each valid k-palindromic number contributes the count of its unique permutations (excluding those with leading zeros) to the final answer.

Solution Implementation

1from math import factorial
2from collections import Counter
3
4class Solution:
5    def countGoodIntegers(self, n: int, k: int) -> int:
6        # Precompute factorials from 0 to n for permutation calculations
7        factorials = [factorial(i) for i in range(n + 1)]
8      
9        # Total count of good integers
10        total_count = 0
11      
12        # Set to track already processed digit combinations
13        visited_combinations = set()
14      
15        # Calculate the starting point for generating n-digit palindromes
16        # For n digits, we need to generate the first half
17        half_start = 10 ** ((n - 1) // 2)
18        half_end = half_start * 10
19      
20        # Generate all possible n-digit palindromes
21        for half_number in range(half_start, half_end):
22            # Convert half to string
23            half_str = str(half_number)
24          
25            # Build palindrome based on whether n is odd or even
26            # If n is odd, skip the middle character when reversing
27            # If n is even, append the full reverse
28            palindrome_str = half_str + half_str[::-1][n % 2:]
29          
30            # Check if palindrome is divisible by k
31            palindrome_num = int(palindrome_str)
32            if palindrome_num % k != 0:
33                continue
34          
35            # Create a canonical form (sorted digits) to identify unique digit combinations
36            sorted_digits = "".join(sorted(palindrome_str))
37          
38            # Skip if we've already processed this combination of digits
39            if sorted_digits in visited_combinations:
40                continue
41          
42            visited_combinations.add(sorted_digits)
43          
44            # Count frequency of each digit
45            digit_counts = Counter(sorted_digits)
46          
47            # Calculate number of valid permutations (numbers that don't start with 0)
48            # First position can be any non-zero digit: (n - count_of_zeros) choices
49            # Remaining positions: (n-1)! ways to arrange
50            valid_permutations = (n - digit_counts["0"]) * factorials[n - 1]
51          
52            # Divide by factorial of each digit count to account for duplicate digits
53            for count in digit_counts.values():
54                valid_permutations //= factorials[count]
55          
56            total_count += valid_permutations
57      
58        return total_count
59
1class Solution {
2    public long countGoodIntegers(int n, int k) {
3        // Precompute factorials up to n for permutation calculations
4        long[] factorial = new long[n + 1];
5        factorial[0] = 1;
6        for (int i = 1; i <= n; i++) {
7            factorial[i] = factorial[i - 1] * i;
8        }
9
10        long totalCount = 0;
11        // Track visited digit combinations to avoid counting duplicates
12        Set<String> visitedCombinations = new HashSet<>();
13      
14        // Calculate the starting point for generating palindromes
15        // For n digits, we need to generate all possible first halves
16        int halfPalindromeStart = (int) Math.pow(10, (n - 1) / 2);
17        int halfPalindromeEnd = halfPalindromeStart * 10;
18
19        // Generate all palindromes of length n
20        for (int halfValue = halfPalindromeStart; halfValue < halfPalindromeEnd; halfValue++) {
21            // Create palindrome by mirroring the half value
22            String halfString = String.valueOf(halfValue);
23            StringBuilder reversedHalf = new StringBuilder(halfString).reverse();
24          
25            // For odd length palindromes, don't duplicate the middle digit
26            String palindrome = halfString + reversedHalf.substring(n % 2);
27          
28            // Check if palindrome is divisible by k
29            if (Long.parseLong(palindrome) % k != 0) {
30                continue;
31            }
32
33            // Sort digits to create a canonical representation
34            char[] digits = palindrome.toCharArray();
35            Arrays.sort(digits);
36            String sortedDigits = new String(digits);
37          
38            // Skip if we've already processed this digit combination
39            if (visitedCombinations.contains(sortedDigits)) {
40                continue;
41            }
42            visitedCombinations.add(sortedDigits);
43          
44            // Count frequency of each digit (0-9)
45            int[] digitFrequency = new int[10];
46            for (char digit : digits) {
47                digitFrequency[digit - '0']++;
48            }
49
50            // Calculate number of valid permutations
51            // Valid numbers cannot start with 0, so we have (n - count[0]) choices for first position
52            long validPermutations = (n - digitFrequency[0]) * factorial[n - 1];
53          
54            // Divide by factorial of each digit's frequency to account for repeated digits
55            for (int frequency : digitFrequency) {
56                validPermutations /= factorial[frequency];
57            }
58          
59            totalCount += validPermutations;
60        }
61
62        return totalCount;
63    }
64}
65
1class Solution {
2public:
3    long long countGoodIntegers(int n, int k) {
4        // Precompute factorials up to n for permutation calculations
5        vector<long long> factorial(n + 1, 1);
6        for (int i = 1; i <= n; ++i) {
7            factorial[i] = factorial[i - 1] * i;
8        }
9
10        long long totalCount = 0;
11        unordered_set<string> visitedPatterns;  // Track unique digit patterns already processed
12      
13        // Calculate the starting point for generating palindromes
14        // For n digits, we need to iterate through all possible first halves
15        int halfStart = pow(10, (n - 1) / 2);
16        int halfEnd = halfStart * 10;
17
18        // Generate all possible palindromes of length n
19        for (int halfValue = halfStart; halfValue < halfEnd; ++halfValue) {
20            // Create palindrome from the half value
21            string firstHalf = to_string(halfValue);
22            string secondHalf = firstHalf;
23            reverse(secondHalf.begin(), secondHalf.end());
24          
25            // For odd length palindromes, don't duplicate the middle digit
26            string palindrome = firstHalf + secondHalf.substr(n % 2);
27          
28            // Check if palindrome is divisible by k
29            if (stoll(palindrome) % k != 0) {
30                continue;
31            }
32          
33            // Create a canonical form (sorted digits) to identify unique digit patterns
34            string sortedDigits = palindrome;
35            sort(sortedDigits.begin(), sortedDigits.end());
36          
37            // Skip if we've already processed this digit pattern
38            if (visitedPatterns.count(sortedDigits)) {
39                continue;
40            }
41            visitedPatterns.insert(sortedDigits);
42          
43            // Count digit frequencies for permutation calculation
44            vector<int> digitFrequency(10, 0);
45            for (char digit : sortedDigits) {
46                digitFrequency[digit - '0']++;
47            }
48          
49            // Calculate number of valid permutations
50            // Valid numbers can't start with 0, so we have (n - count of zeros) choices for first digit
51            // Then arrange remaining (n-1) digits
52            long long validPermutations = (n - digitFrequency[0]) * factorial[n - 1];
53          
54            // Divide by factorial of each digit frequency to account for duplicate digits
55            for (int frequency : digitFrequency) {
56                validPermutations /= factorial[frequency];
57            }
58          
59            totalCount += validPermutations;
60        }
61      
62        return totalCount;
63    }
64};
65
1/**
2 * Counts the number of good integers with n digits that are divisible by k
3 * A good integer is a palindrome-like number formed by specific rules
4 * @param n - The number of digits in the target integers
5 * @param k - The divisor to check divisibility against
6 * @returns The count of good integers
7 */
8function countGoodIntegers(n: number, k: number): number {
9    // Precompute factorials up to n for permutation calculations
10    const factorials = factorial(n);
11    let totalCount = 0;
12    // Set to track visited sorted digit combinations to avoid duplicates
13    const visitedCombinations = new Set<string>();
14  
15    // Calculate the starting point for half-length numbers
16    // For n digits, we need numbers with (n+1)/2 digits to build palindromes
17    const halfLengthBase = Math.pow(10, Math.floor((n - 1) / 2));
18
19    // Iterate through all possible half-length numbers
20    for (let halfNumber = halfLengthBase; halfNumber < halfLengthBase * 10; halfNumber++) {
21        // Convert number to string for manipulation
22        let numberString = `${halfNumber}`;
23        const reversedString = reverseString(numberString);
24      
25        // Build the full palindrome based on whether n is odd or even
26        if (n % 2 === 1) {
27            // For odd n, don't duplicate the middle digit
28            numberString += reversedString.substring(1);
29        } else {
30            // For even n, append the full reversed string
31            numberString += reversedString;
32        }
33
34        // Check if the constructed number is divisible by k
35        if (+numberString % k !== 0) {
36            continue;
37        }
38
39        // Sort digits to create a canonical representation
40        const sortedDigits = Array.from(numberString).sort();
41        const canonicalForm = sortedDigits.join('');
42
43        // Skip if we've already processed this digit combination
44        if (visitedCombinations.has(canonicalForm)) {
45            continue;
46        }
47
48        visitedCombinations.add(canonicalForm);
49
50        // Count frequency of each digit (0-9)
51        const digitFrequency = Array(10).fill(0);
52        for (const digit of canonicalForm) {
53            digitFrequency[+digit]++;
54        }
55
56        // Calculate permutations: total arrangements minus those starting with 0
57        // First position can't be 0, so we have (n - count[0]) choices
58        let permutationCount = (n - digitFrequency[0]) * factorials[n - 1];
59      
60        // Divide by factorial of each digit's frequency to account for duplicates
61        for (const frequency of digitFrequency) {
62            permutationCount /= factorials[frequency];
63        }
64      
65        totalCount += permutationCount;
66    }
67
68    return totalCount;
69}
70
71/**
72 * Generates an array of factorials from 0! to n!
73 * @param n - The maximum factorial to compute
74 * @returns Array where index i contains i!
75 */
76function factorial(n: number): number[] {
77    const factorialArray = Array(n + 1).fill(1);
78    for (let i = 1; i <= n; i++) {
79        factorialArray[i] = factorialArray[i - 1] * i;
80    }
81    return factorialArray;
82}
83
84/**
85 * Reverses a string
86 * @param s - The string to reverse
87 * @returns The reversed string
88 */
89function reverseString(s: string): string {
90    return s.split('').reverse().join('');
91}
92

Time and Space Complexity

Time Complexity: O(10^m × n × log n) where m = ⌊(n-1)/2⌋

  • The main loop iterates from base to base * 10, where base = 10^((n-1)//2). This gives us 10^m iterations where m = ⌊(n-1)/2⌋.
  • For each iteration:
    • String concatenation and reversal operations: O(n) (since the resulting string has length n)
    • Modulo operation on an n-digit number: O(n)
    • Sorting the string s to create t: O(n log n)
    • Checking membership in vis set: O(n) (comparing strings of length n)
    • Creating Counter from string t: O(n)
    • Computing factorial division for permutations: O(n) (at most n distinct digits)
  • The dominant operation per iteration is sorting: O(n log n)
  • Total: O(10^m × n × log n)

Space Complexity: O(10^m × n) where m = ⌊(n-1)/2⌋

  • fac array stores n+1 factorial values: O(n)
  • vis set can store at most 10^m unique sorted strings, each of length n: O(10^m × n)
  • Other variables like s, t, and cnt use O(n) space but are reused in each iteration
  • The dominant space usage is the vis set: O(10^m × n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Palindrome Generation for Odd-Length Numbers

Pitfall: When generating palindromes of odd length, a common mistake is duplicating the middle digit incorrectly. For example, if n = 5 and we start with 123, we might incorrectly generate 12332321 (8 digits) instead of 12321 (5 digits).

Why it happens: The slicing logic s[::-1][n % 2:] can be confusing. When n is odd, we need to skip the first character of the reversed string to avoid duplicating the middle digit.

Solution: Ensure the palindrome generation logic correctly handles both odd and even lengths:

# For odd n: append reverse without the first character
# For even n: append the full reverse
palindrome_str = half_str + half_str[::-1][n % 2:]

2. Division by Zero in Permutation Calculation

Pitfall: If the factorial array isn't properly initialized or if digit counts are incorrect, you might attempt to divide by factorial[0] when it's undefined or zero.

Why it happens: The code divides by factorials[count] for each digit count. If factorials[0] isn't properly set to 1, this will cause an error.

Solution: Always initialize factorial(0) = 1:

factorials = [factorial(i) for i in range(n + 1)]  # factorial(0) = 1 by definition

3. Integer Overflow in Permutation Counting

Pitfall: For large values of n, the intermediate calculation (n - digit_counts["0"]) * factorials[n - 1] can overflow, even though the final result after division might be within bounds.

Why it happens: Python handles big integers well, but in other languages or if optimizing, you might face overflow issues.

Solution: Perform divisions as early as possible to keep numbers manageable:

# Instead of computing the full numerator first
valid_permutations = factorials[n - 1]
for count in digit_counts.values():
    valid_permutations //= factorials[count]
valid_permutations *= (n - digit_counts["0"])

4. Missing Edge Case: All Zeros in Digit Count

Pitfall: When a palindrome consists entirely of zeros (which shouldn't happen for valid n-digit numbers), the formula (n - digit_counts["0"]) would be 0, but this case should never occur since we're generating valid palindromes.

Why it happens: The palindrome generation starts from 10^((n-1)//2), which ensures at least one non-zero digit, but it's worth being aware of this constraint.

Solution: The current implementation naturally avoids this by starting palindrome generation from a non-zero base:

half_start = 10 ** ((n - 1) // 2)  # Always starts with a non-zero digit

5. Incorrect Handling of Single-Digit Numbers (n=1)

Pitfall: When n = 1, the calculation 10 ** ((n - 1) // 2) equals 10 ** 0 = 1, which means the range would be [1, 10). This works correctly, but the permutation counting logic might seem unnecessary for single digits.

Solution: The current implementation handles this correctly, but you could add a special case for clarity:

if n == 1:
    # Count single-digit numbers divisible by k
    return len([i for i in range(1, 10) if i % k == 0])
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

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

Load More