Number of Beautiful Integers in the Range
HardPrompt
You are given positive integers low, high, and k.
A number is beautiful if it meets both of the following conditions:
- The count of even digits in the number is equal to the count of odd digits.
- The number is divisible by
k.
Return the number of beautiful integers in the range [low, high].
Example 1:
Input: low = 10, high = 20, k = 3
Output: 2
Explanation: There are 2 beautiful integers in the given range: [12,18].
- 12 is beautiful because it contains 1 odd digit and 1 even digit, and is divisible by k = 3.
- 18 is beautiful because it contains 1 odd digit and 1 even digit, and is divisible by k = 3.
Additionally we can see that:
- 16 is not beautiful because it is not divisible by k = 3.
- 15 is not beautiful because it does not contain equal counts even and odd digits.
It can be shown that there are only 2 beautiful integers in the given range.Example 2:
Input: low = 1, high = 10, k = 1
Output: 1
Explanation: There is 1 beautiful integer in the given range: [10].
- 10 is beautiful because it contains 1 odd digit and 1 even digit, and is divisible by k = 1.
It can be shown that there is only 1 beautiful integer in the given range.Example 3:
Input: low = 5, high = 5, k = 2
Output: 0
Explanation: There are 0 beautiful integers in the given range.
- 5 is not beautiful because it is not divisible by k = 2 and it does not contain equal even and odd digits.
Constraints:
0 < low <= high <= 1090 < k <= 20
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves iterating through each number in the given range [low, high] and checking if it satisfies the conditions of a beautiful integer. While simple to conceptualize, its performance is inadequate for the given constraints.
Algorithm
- Initialize a counter
beautifulCountto 0. - Loop through each integer
ifromlowtohigh. - For each
i, check if it's beautiful:- First, check if
i % k == 0. If not, continue to the next integer. - If divisible, count its even and odd digits. A simple way is to convert the number to a string and iterate over its characters.
- Initialize
evenCount = 0andoddCount = 0. - For each digit, if it's even, increment
evenCount; otherwise, incrementoddCount. - After checking all digits, if
evenCount == oddCountandevenCount > 0, the number is beautiful.
- First, check if
- If
iis beautiful, incrementbeautifulCount. - After the loop finishes, return
beautifulCount.
Walkthrough
The core idea is to create a loop that runs from low to high. In each iteration, we take the current number and test it against the two properties of a beautiful integer:
- Equal Even/Odd Digit Counts: We count the occurrences of even digits (0, 2, 4, 6, 8) and odd digits (1, 3, 5, 7, 9). The counts must be equal and non-zero.
- Divisibility by k: The number must be perfectly divisible by
k(i.e.,number % k == 0). If a number satisfies both conditions, we increment a counter. After checking all numbers in the range, the final value of the counter is the answer.
class Solution { private boolean isBeautiful(int n, int k) { if (n % k != 0) { return false; } int evenCount = 0; int oddCount = 0; String s = Integer.toString(n); for (char c : s.toCharArray()) { int digit = c - '0'; if (digit % 2 == 0) { evenCount++; } else { oddCount++; } } return evenCount > 0 && evenCount == oddCount; } public int numberOfBeautifulIntegers(int low, int high, int k) { int count = 0; for (int i = low; i <= high; i++) { if (isBeautiful(i, k)) { count++; } } return count; }}Complexity
Time
O((high - low) * log(high)). The loop runs `high - low + 1` times. Inside the loop, counting digits of a number `n` takes `O(log10(n))` time. For the given constraints where `high - low` can be up to `10^9`, this approach is too slow.
Space
O(log(high)) if we convert the number to a string for processing its digits. This is considered very low.
Trade-offs
Pros
Simple to understand and implement.
Works correctly for small ranges.
Cons
Extremely inefficient for large ranges.
Guaranteed to cause a Time Limit Exceeded (TLE) error on competitive programming platforms for the given constraints.
This is an optimized approach that uses dynamic programming on digits to count the numbers efficiently. Instead of checking each number, we build the numbers digit by digit and count how many valid "beautiful" numbers can be formed. The problem is solved by calculating count(high) - count(low - 1), where count(n) finds the number of beautiful integers up to n.
Algorithm
- The final answer is
count(high) - count(low - 1). - The
count(n)function convertsnto a stringsand calls a recursive helpersolve. - The
solve(index, rem, diff, isTight, isLeadingZero)function works as follows:- Base Case: If
index == s.length(), return 1 ifrem == 0,diffindicates equal counts, and the number is not zero (!isLeadingZero). Otherwise, return 0. - Memoization: Check if the state
(index, rem, diff)has been computed before (only whenisTightandisLeadingZeroare false) and return the stored value. - Recursion:
- Determine the upper bound for the current digit (either
s[index]or 9, based onisTight). - Iterate through all valid digits from 0 to the upper bound.
- For each digit, update the state (
rem,diff,isTight,isLeadingZero) and make a recursive call tosolveforindex + 1. - Sum the results from all recursive calls.
- Determine the upper bound for the current digit (either
- Memoize: Store the calculated sum in the memoization table before returning.
- Base Case: If
Walkthrough
We design a recursive function with memoization to count beautiful numbers up to a given number N. The function, let's call it solve, constructs numbers from left to right (most significant digit to least significant). The state of our recursion must track all information needed to validate the beautiful number conditions.
The state can be defined as solve(index, rem, diff, isTight, isLeadingZero):
index: The current digit position we are filling.rem: The remainder of the number formed so far modulok.diff: The difference between the count of even and odd digits (evenCount - oddCount). We use an offset to keep this index non-negative.isTight: A boolean flag. Iftrue, it means we are restricted to digits up toN's digit at the currentindex. If we pick a smaller digit, this flag becomesfalsefor subsequent recursive calls, allowing any digit from 0-9.isLeadingZero: A boolean flag to handle cases where we are placing leading zeros. These do not contribute to the digit counts or the number's value.
The base case for the recursion is when index reaches the end of the number's length. We return 1 if the formed number is beautiful (rem == 0, diff corresponds to evenCount == oddCount, and it's not the number zero), and 0 otherwise. Memoization is used on the state (index, rem, diff) to store and reuse results for subproblems, drastically reducing computation.
class Solution { private String s; private int k; private int len; private Integer[][][] memo; public int numberOfBeautifulIntegers(int low, int high, int k) { return count(high) - count(low - 1); } private int count(int n) { this.s = String.valueOf(n); this.k = k; this.len = s.length(); // memo[index][rem][diff] // diff = evenCount - oddCount. Range: -len to len. Offset by len. this.memo = new Integer[len][k][2 * len + 1]; return solve(0, 0, len, true, true); } private int solve(int index, int rem, int diff, boolean isTight, boolean isLeadingZero) { if (index == len) { // diff == len means evenCount == oddCount // !isLeadingZero means the number is not 0. return !isLeadingZero && diff == len && rem == 0 ? 1 : 0; } if (!isTight && !isLeadingZero && memo[index][rem][diff] != null) { return memo[index][rem][diff]; } int ans = 0; int upperBound = isTight ? (s.charAt(index) - '0') : 9; for (int digit = 0; digit <= upperBound; digit++) { boolean newTight = isTight && (digit == upperBound); if (isLeadingZero && digit == 0) { ans += solve(index + 1, 0, len, newTight, true); } else { int newDiff = diff + (digit % 2 == 0 ? 1 : -1); int newRem = (rem * 10 + digit) % k; ans += solve(index + 1, newRem, newDiff, newTight, false); } } if (!isTight && !isLeadingZero) { memo[index][rem][diff] = ans; } return ans; }}Complexity
Time
O(D^2 * k * 10), where `D` is the number of digits in `high` (max 10) and `k` is the given divisor (max 20). The number of DP states is `D * k * (2*D)`. Each state computation involves a loop of up to 10 digits. This is highly efficient.
Space
O(D^2 * k) for the memoization table, where D is the number of digits in `high`. This is well within memory limits.
Trade-offs
Pros
Very efficient and passes within the time limits for large constraints.
A standard and powerful technique for a wide range of digit-based counting problems.
Cons
Significantly more complex to understand and implement than the brute-force approach.
Requires careful handling of states, transitions, and edge cases like leading zeros and the 'tight' constraint.
Solutions
Solution
class Solution {private String s;private int k;private Integer[][][] f = new Integer[11][21][21];public int numberOfBeautifulIntegers(int low, int high, int k) { this.k = k; s = String.valueOf(high); int a = dfs(0, 0, 10, true, true); s = String.valueOf(low - 1); f = new Integer[11][21][21]; int b = dfs(0, 0, 10, true, true); return a - b; }private int dfs(int pos, int mod, int diff, boolean lead, boolean limit) { if (pos >= s.length()) { return mod == 0 && diff == 10 ? 1 : 0; } if (!lead && !limit && f[pos][mod][diff] != null) { return f[pos][mod][diff]; } int ans = 0; int up = limit ? s.charAt(pos) - '0' : 9; for (int i = 0; i <= up; ++i) { if (i == 0 && lead) { ans += dfs(pos + 1, mod, diff, true, limit && i == up); } else { int nxt = diff + (i % 2 == 1 ? 1 : -1); ans += dfs(pos + 1, (mod * 10 + i) % k, nxt, false, limit && i == up); } } if (!lead && !limit) { f[pos][mod][diff] = ans; } return ans; }}class Solution {public: int numberOfBeautifulIntegers(int low, int high, int k) { int f[11][21][21]; memset(f, -1, sizeof(f)); string s = to_string(high); function<int(int, int, int, bool, bool)> dfs = [&](int pos, int mod, int diff, bool lead, bool limit) { if (pos >= s.size()) { return mod == 0 && diff == 10 ? 1 : 0; } if (!lead && !limit && f[pos][mod][diff] != -1) { return f[pos][mod][diff]; } int ans = 0; int up = limit ? s[pos] - '0' : 9; for (int i = 0; i <= up; ++i) { if (i == 0 && lead) { ans += dfs(pos + 1, mod, diff, true, limit && i == up); } else { int nxt = diff + (i % 2 == 1 ? 1 : -1); ans += dfs(pos + 1, (mod * 10 + i) % k, nxt, false, limit && i == up); } } if (!lead && !limit) { f[pos][mod][diff] = ans; } return ans; }; int a = dfs(0, 0, 10, true, true); memset(f, -1, sizeof(f)); s = to_string(low - 1); int b = dfs(0, 0, 10, true, true); return a - b; }};class Solution: def numberOfBeautifulIntegers(self, low: int, high: int, k: int) -> int: @ cache def dfs(pos: int, mod: int, diff: int, lead: int, limit: int) -> int: if pos >= len(s): return mod == 0 and diff == 10 up = int(s[pos]) if limit else 9 ans = 0 for i in range(up + 1): if i == 0 and lead: ans += dfs(pos + 1, mod, diff, 1, limit and i == up) else: nxt = diff + (1 if i % 2 == 1 else - 1) ans += dfs(pos + 1, (mod * 10 + i) % k, nxt, 0, limit and i == up) return ans s = str(high) a = dfs(0, 0, 10, 1, 1) dfs . cache_clear() s = str(low - 1) b = dfs(0, 0, 10, 1, 1) return a - bVideo 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.