Numbers With Repeated Digits
HardPrompt
Given an integer n, return the number of positive integers in the range [1, n] that have at least one repeated digit.
Example 1:
Input: n = 20
Output: 1
Explanation: The only positive number (<= 20) with at least 1 repeated digit is 11.Example 2:
Input: n = 100
Output: 10
Explanation: The positive numbers (<= 100) with atleast 1 repeated digit are 11, 22, 33, 44, 55, 66, 77, 88, 99, and 100.Example 3:
Input: n = 1000
Output: 262
Constraints:
1 <= n <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves a straightforward iteration through every integer from 1 to n. For each integer, it performs a check to see if it contains any repeated digits. A counter is maintained and incremented for each number that is found to have at least one repeated digit.
Algorithm
- Initialize a counter
countto 0. - Loop through each integer
ifrom 1 ton. - For each
i, call a helper functionhasRepeatedDigits(i). - If the helper function returns
true, incrementcount. - Return
countafter the loop finishes.
Helper function hasRepeatedDigits(num):
- Create a boolean array
seenof size 10, initialized tofalse. - While
numis greater than 0:- Extract the last digit:
digit = num % 10. - If
seen[digit]is alreadytrue, it means the digit is repeated, so returntrue. - Mark the digit as seen:
seen[digit] = true. - Remove the last digit:
num = num / 10.
- Extract the last digit:
- If the loop completes without finding repeated digits, return
false.
Walkthrough
The algorithm begins by initializing a counter, count, to zero. It then enters a loop that iterates through each number i from 1 up to the given integer n. Inside this loop, a helper function, hasRepeatedDigits(i), is invoked to determine if the current number i has any duplicate digits.
The hasRepeatedDigits function works by examining the digits of the number one by one. It uses a boolean array of size 10 as a frequency map to keep track of the digits it has already encountered. It extracts digits from the number using the modulo (%) and division (/) operators. If it encounters a digit that is already marked as seen in the frequency map, it immediately returns true, indicating a repeated digit. If it processes all digits without finding any repeats, it returns false.
Back in the main loop, if the helper function returns true, the count is incremented. After checking all numbers up to n, the final count is returned, which represents the total number of integers in the range [1, n] with at least one repeated digit.
class Solution { public int numDupDigitsAtMostN(int n) { int count = 0; for (int i = 1; i <= n; i++) { if (hasRepeatedDigits(i)) { count++; } } return count; } private boolean hasRepeatedDigits(int num) { boolean[] seen = new boolean[10]; while (num > 0) { int digit = num % 10; if (seen[digit]) { return true; } seen[digit] = true; num /= 10; } return false; }}Complexity
Time
O(n * log₁₀(n)). The main loop runs `n` times. For each number `i`, the `hasRepeatedDigits` function takes a number of steps proportional to the number of digits in `i`, which is `O(log₁₀(i))`. Given `n` can be up to 10⁹, this approach is too slow.
Space
O(1). The space required for the `seen` array is constant as it always has a size of 10, regardless of the input `n`.
Trade-offs
Pros
Simple to understand and implement.
Correct for small values of
n.
Cons
Highly inefficient for large values of
n.Will result in a 'Time Limit Exceeded' error on most platforms for the given constraints.
A much more efficient method is to solve the complementary problem: counting the number of positive integers up to n that have all unique digits. If we find this count, let's call it countUnique, the final answer is simply n - countUnique. This technique is a form of Digit Dynamic Programming, where we build the count digit by digit based on the digits of n.
Algorithm
- The problem is transformed to find the count of numbers with unique digits up to
n, let's call itcountUnique. The final answer isn - countUnique. - Convert
nto its string representationSof lengthL. - Step 1: Count unique-digit numbers with fewer digits than
n.- Iterate for length
dfrom 1 toL-1. - For each
d, the count ofd-digit numbers with unique digits is9 * P(9, d-1). Sum these values.
- Iterate for length
- Step 2: Count unique-digit numbers with the same number of digits as
n.- Iterate through the digits of
Sfrom left to right (indexi). Maintain a set ofuseddigits from the prefix ofn. - For the current digit
S[i], iterate through all possible smaller digitsd < S[i]that have not been used. - For each such valid
d, addP(10 - (i+1), L - (i+1))to the count. This represents forming a number with a smaller prefix. - After checking smaller digits, if
S[i]itself has already been used, break the loop. - Otherwise, add
S[i]to theusedset and continue to the next position.
- Iterate through the digits of
- Step 3: Account for
nitself.- If the loop in Step 2 completes without breaking, it means
nitself has unique digits, so add 1 tocountUnique.
- If the loop in Step 2 completes without breaking, it means
- Return
n - countUnique.
Walkthrough
This approach cleverly avoids iterating through all numbers. Instead, it counts the numbers with all unique digits up to n and subtracts this from n.
Let n be represented as a string S of length L. The counting of unique-digit numbers is done in two main parts:
-
Count unique-digit numbers with fewer digits than
n: We calculate the number of unique-digit numbers for each length from 1 up toL-1.- For length 1, there are 9 (1-9).
- For a length
d > 1, the first digit has 9 choices (1-9), the second has 9 choices (0-9, excluding the first), the third has 8, and so on. The total for lengthdis9 * P(9, d-1), whereP(m, k)is the number of k-permutations of m. We sum these counts.
-
Count unique-digit numbers with length
Land less than or equal ton: We iterate through the digits ofnfrom left to right (most significant to least significant). We maintain a set of digits already used in the prefix of the number we are building.- At each position
i(with digitd_ifromn), we count the number of ways to form a validL-digit number by choosing a digit smaller thand_ifor this position. For each valid smaller digitj(not 0 for the first position and not already used), we can fill the remainingL-1-ipositions inP(10 - (i+1), L-1-i)ways. We add this to our count. - After considering all smaller digits, we 'fix' the digit
d_iat the current position. Ifd_ihas already been used in the prefix, we must stop, as any number starting with this prefix will have repeated digits. Otherwise, we addd_ito our set of used digits and proceed to the next position.
- At each position
Finally, if the process completes for all digits of n without stopping (which means n itself has unique digits), we add 1 to our count of unique-digit numbers.
class Solution { public int numDupDigitsAtMostN(int n) { // Count numbers with unique digits, then subtract from n. return n - countUniqueDigits(n); } // Counts numbers with all unique digits from 1 to n. private int countUniqueDigits(int n) { String s = String.valueOf(n); int L = s.length(); int res = 0; // 1. Count unique digit numbers with length < L for (int i = 1; i < L; i++) { res += 9 * permutation(9, i - 1); } // 2. Count unique digit numbers with length == L boolean[] used = new boolean[10]; for (int i = 0; i < L; i++) { int digit = s.charAt(i) - '0'; // Count numbers with the same prefix but a smaller digit at the current position for (int d = (i == 0) ? 1 : 0; d < digit; d++) { if (!used[d]) { res += permutation(10 - (i + 1), L - (i + 1)); } } // If the current digit has been used, all subsequent numbers with this prefix will have duplicates. if (used[digit]) { return res; // Return the count so far } used[digit] = true; } // 3. If n itself has unique digits, add 1 return res + 1; } // Helper to calculate permutation P(m, n) private int permutation(int m, int n) { if (n < 0 || n > m) return 0; if (n == 0) return 1; int res = 1; for (int i = 0; i < n; i++) { res *= (m - i); } return res; }}Complexity
Time
O((log₁₀(n))²). The number of digits in `n` is `L = O(log₁₀(n))`. The main loops run `L` times. Inside the loops, calculating permutations also takes `O(L)` time, leading to a near-quadratic complexity with respect to the number of digits.
Space
O(log₁₀(n)). Space is required to store the digits of `n` as a string, which has a length of `O(log₁₀(n))`. The `used` array is of constant size.
Trade-offs
Pros
Extremely efficient and fast, even for large
n.Scales logarithmically with the input
n.
Cons
More complex to understand and implement compared to the brute-force approach.
Requires knowledge of combinatorics (permutations).
Solutions
Solution
class Solution {private int[] nums = new int[11];private Integer[][] dp = new Integer[11][1 << 11];public int numDupDigitsAtMostN(int n) { return n - f(n); }private int f(int n) { int i = -1; for (; n > 0; n /= 10) { nums[++i] = n % 10; } return dfs(i, 0, true, true); }private int dfs(int pos, int mask, boolean lead, boolean limit) { if (pos < 0) { return lead ? 0 : 1; } if (!lead && !limit && dp[pos][mask] != null) { return dp[pos][mask]; } int ans = 0; int up = limit ? nums[pos] : 9; for (int i = 0; i <= up; ++i) { if ((mask >> i & 1) == 1) { continue; } if (i == 0 && lead) { ans += dfs(pos - 1, mask, lead, limit && i == up); } else { ans += dfs(pos - 1, mask | 1 << i, false, limit && i == up); } } if (!lead && !limit) { dp[pos][mask] = ans; } return ans; }}class Solution {public: int numDupDigitsAtMostN(int n) { return n - f(n); } private: int nums[11]; int dp[11][1 << 11]; int f(int n) { memset(dp, -1, sizeof(dp)); int i = -1; for (; n; n /= 10) { nums[++i] = n % 10; } return dfs(i, 0, true, true); } int dfs(int pos, int mask, bool lead, bool limit) { if (pos < 0) { return lead ? 0 : 1; } if (!lead && !limit && dp[pos][mask] != -1) { return dp[pos][mask]; } int up = limit ? nums[pos] : 9; int ans = 0; for (int i = 0; i <= up; ++i) { if (mask >> i & 1) { continue; } if (i == 0 && lead) { ans += dfs(pos - 1, mask, lead, limit && i == up); } else { ans += dfs(pos - 1, mask | 1 << i, false, limit && i == up); } } if (!lead && !limit) { dp[pos][mask] = ans; } return ans; }};class Solution: def numDupDigitsAtMostN(self, n: int) -> int: return n - self . f(n) def f(self, n: int) -> int: @ cache def dfs(pos: int, mask: int, lead: bool, limit: bool) -> int: if pos < 0: return int(lead) ^ 1 up = nums[pos] if limit else 9 ans = 0 for i in range(up + 1): if mask >> i & 1: continue if i == 0 and lead: ans += dfs(pos - 1, mask, lead, limit and i == up) else: ans += dfs(pos - 1, mask | 1 << i, False, limit and i == up) return ans nums = [] while n: nums . append(n % 10) n //= 10 return dfs(len(nums) - 1, 0, True, True)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.