3272. Find the Count of Good Integers
Problem Description
You are given two positive integers n
and k
.
An integer x
is called k-palindromic if it satisfies two conditions:
x
is a palindrome (reads the same forwards and backwards)x
is divisible byk
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 form0101
or101
(different number of digits)
The problem asks you to return the total count of n-digit good integers.
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:
- Generate all n-digit palindromes
- Check if each palindrome is divisible by
k
- 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 resultvis
: 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:
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 EvaluatorExample 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" tovis
- 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" tovis
- 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" tovis
- 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
tobase * 10
, wherebase = 10^((n-1)//2)
. This gives us10^m
iterations wherem = ⌊(n-1)/2⌋
. - For each iteration:
- String concatenation and reversal operations:
O(n)
(since the resulting string has lengthn
) - Modulo operation on an
n
-digit number:O(n)
- Sorting the string
s
to createt
:O(n log n)
- Checking membership in
vis
set:O(n)
(comparing strings of lengthn
) - Creating Counter from string
t
:O(n)
- Computing factorial division for permutations:
O(n)
(at mostn
distinct digits)
- String concatenation and reversal operations:
- 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 storesn+1
factorial values:O(n)
vis
set can store at most10^m
unique sorted strings, each of lengthn
:O(10^m × n)
- Other variables like
s
,t
, andcnt
useO(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])
Which of the following array represent a max heap?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!