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:

A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".

What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?


Recommended Readings

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

Load More