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 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

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.

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])
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does quick sort divide the problem into subproblems?


Recommended Readings

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

Load More