Daily DSA: Count Parity-Alternating Integers in Range (Hard)
Problem Description
An integer is called parity-alternating if for every pair of adjacent digits in its standard decimal representation (without leading zeros), one digit is even and the other is odd. Single-digit numbers are trivially parity-alternating.
For example:
2385,703, and4are parity-alternating integers.248(adjacent evens),135(adjacent odds), and1223are not parity-alternating integers.
Given two positive integers represented as strings low and high, return the total number of parity-alternating integers $x$ such that $\text{low} \le x \le \text{high}$.
Since the answer can be very large, return it modulo $10^9 + 7$.
Examples
Example 1:
Input: low = "1", high = "15"
Output: 12
Explanation:
- The numbers 1 through 9 are all single digits and thus alternating (9 numbers).
- In the range 10-15: 10, 12, and 14 are alternating (3 numbers).
- 11, 13, and 15 have adjacent odd digits, so they are not alternating.
Total count = 9 + 3 = 12.
Example 2:
Input: low = "20", high = "35"
Output: 8
Explanation:
The alternating numbers in this range are: 21, 23, 25, 27, 29, 30, 32, and 34 (8 numbers).
Example 3:
Input: low = "88", high = "105"
Output: 6
Explanation:
The valid numbers are 89, 90, 92, 94, 96, 98.
Constraints
- $1 \le \text{low.length} \le \text{high.length} \le 1000$
lowandhighconsist only of decimal digits'0'-'9'.lowandhighdo not have leading zeros.- The integer represented by
lowis less than or equal to the integer represented byhigh.
Approach
To find the count of valid numbers in the range $[\text{low}, \text{high}]$, we can use the prefix technique: \(\text{Count}([\text{low}, \text{high}]) = \text{Count}([1, \text{high}]) - \text{Count}([1, \text{low}]) + \text{isAlternating}(\text{low})\)
To compute $\text{Count}([1, S])$ for any string $S$, we apply Digit DP (Digit Dynamic Programming):
- DP State Representation:
dp(idx, last_parity, is_less, is_started)idx: Current digit position in string $S$ (from $0$ to $N-1$).last_parity: Parity of the previous placed digit (0for even,1for odd,2for unassigned / leading zeros).is_less: Boolean flag indicating whether the current prefix is already strictly smaller than the prefix of $S$.is_started: Boolean flag indicating whether we have placed at least one non-zero digit (to handle variable length numbers).
- Transitions:
- Determine the upper bound for the current digit:
limit = is_less ? 9 : (S[idx] - '0'). - Iterate through every possible digit $d \in [0, \text{limit}]$:
- If
!is_started && d == 0: We continue placing leading zeros. The state transitions to(idx + 1, 2, is_less || (d < limit), false). - Otherwise (placing an actual digit): The parity of $d$ is $p = d \pmod 2$.
- If
is_started && p == last_parity, this digit choice is invalid (same parity adjacent). - If valid, transition to
(idx + 1, p, is_less || (d < limit), true).
- If
- Determine the upper bound for the current digit:
- Base Case:
- When
idx == N, return1ifis_startedistrue, otherwise0.
- When
- Complexity:
- Time Complexity: $O(N \times 3 \times 2 \times 2 \times 10) = O(N)$ where $N$ is the number of digits in $S$. For $N \le 1000$, this executes in a few milliseconds.
- Space Complexity: $O(N)$ for the memoization table and recursion stack.
C++ Solution
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
class Solution {
private:
static const int MOD = 1e9 + 7;
int memo[1005][3][2][2];
int solveDP(int idx, int last_parity, bool is_less, bool is_started, const std::string& s) {
if (idx == (int)s.length()) {
return is_started ? 1 : 0;
}
if (memo[idx][last_parity][is_less][is_started] != -1) {
return memo[idx][last_parity][is_less][is_started];
}
int limit = is_less ? 9 : (s[idx] - '0');
long long total = 0;
for (int digit = 0; digit <= limit; ++digit) {
bool next_less = is_less || (digit < limit);
if (!is_started && digit == 0) {
// Still in leading zero state
total = (total + solveDP(idx + 1, 2, next_less, false, s)) % MOD;
} else {
int cur_parity = digit % 2;
if (!is_started || cur_parity != last_parity) {
total = (total + solveDP(idx + 1, cur_parity, next_less, true, s)) % MOD;
}
}
}
return memo[idx][last_parity][is_less][is_started] = total;
}
int countUpTo(const std::string& s) {
std::memset(memo, -1, sizeof(memo));
return solveDP(0, 2, false, false, s);
}
bool isValid(const std::string& s) {
for (size_t i = 1; i < s.length(); ++i) {
if ((s[i] - '0') % 2 == (s[i - 1] - '0') % 2) {
return false;
}
}
return true;
}
public:
int countAlternatingIntegers(std::string low, std::string high) {
int count_high = countUpTo(high);
int count_low = countUpTo(low);
int is_low_valid = isValid(low) ? 1 : 0;
int result = (count_high - count_low + is_low_valid) % MOD;
if (result < 0) {
result += MOD;
}
return result;
}
};