Rotated Digits
MEDIUMDescription
An integer x is a good if after rotating each digit individually by 180 degrees, we get a valid number that is different from x. Each digit must be rotated - we cannot choose to leave it alone.
A number is valid if each digit remains a digit after rotation. For example:
0,1, and8rotate to themselves,2and5rotate to each other (in this case they are rotated in a different direction, in other words,2or5gets mirrored),6and9rotate to each other, and- the rest of the numbers do not rotate to any other number and become invalid.
Given an integer n, return the number of good integers in the range [1, n].
Example 1:
Input: n = 10 Output: 4 Explanation: There are four good numbers in the range [1, 10] : 2, 5, 6, 9. Note that 1 and 10 are not good numbers, since they remain unchanged after rotating.
Example 2:
Input: n = 1 Output: 0
Example 3:
Input: n = 2 Output: 1
Constraints:
1 <= n <= 104
Approaches
Checkout 2 different approaches to solve Rotated Digits. Click on different approaches to view the approach and algorithm in detail.
Digit Dynamic Programming
A highly efficient method using Digit Dynamic Programming (DP). Instead of checking each number individually, this approach constructs the count of "good" numbers by considering the digits of n from left to right. It counts the number of valid combinations of digits that can form a good number up to n.
Algorithm
- Convert the input
ninto a stringS. - Create a 3D memoization table
memo[S.length()][2][2]to store results of subproblems, avoiding re-computation. - Define a recursive function
solve(index, isTight, hasChangingDigit):index: The current digit position we are filling (from left to right).isTight: A boolean indicating if our choices are limited by the digits ofS.hasChangingDigit: A boolean indicating if a digit like 2, 5, 6, or 9 has been used.
- Base Case: If
indexequals the length ofS, we have formed a complete number. Return1ifhasChangingDigitis true, otherwise0. - Recursive Step:
a. Determine the upper bound for the current digit's loop (either
S[index]ifisTightis true, or9otherwise). b. Iterate from digitd = 0to theupperBound. c. Skip invalid digits3, 4, 7. d. For each valid digitd, make a recursive call:solve(index + 1, newTight, newHasChangingDigit). e. UpdatenewTightandnewHasChangingDigitbased on the current digitd. f. Sum the results of the recursive calls. - Store the result in the memoization table before returning.
- The initial call to start the process is
solve(0, true, false).
This approach rephrases the problem as "count the numbers up to n that contain at least one digit from {2, 5, 6, 9} and no digits from {3, 4, 7}". We can solve this using a recursive function with memoization.
Let's define a function solve(index, isTight, hasChangingDigit) that counts the number of ways to complete a valid number from the current state.
index: The current digit position (from left) we are placing.isTight: A boolean flag.trueif we are restricted by the digits ofn(i.e., we can't place a digit greater thann's digit at this position). It becomesfalseonce we place a digit smaller thann's corresponding digit.hasChangingDigit: A boolean flag that istrueif we have already placed a digit from{2, 5, 6, 9}.
The recursion explores all possible digits at the current index. For each valid choice, it calls itself for the next index with updated isTight and hasChangingDigit flags. The base case is when we have placed all digits; we return 1 if hasChangingDigit is true (indicating a good number) and 0 otherwise. A memoization table stores the results for each state (index, isTight, hasChangingDigit) to prevent re-computation.
import java.util.Arrays;
class Solution {
private int[][][] memo;
private String s;
public int rotatedDigits(int n) {
s = String.valueOf(n);
memo = new int[s.length()][2][2];
for (int[][] a2 : memo) {
for (int[] a1 : a2) {
Arrays.fill(a1, -1);
}
}
return solve(0, true, false);
}
// Returns the count of good numbers that can be formed from this state up to N
private int solve(int index, boolean isTight, boolean hasChangingDigit) {
// Base case: we have successfully constructed a number.
if (index == s.length()) {
return hasChangingDigit ? 1 : 0;
}
// Memoization check
if (memo[index][isTight ? 1 : 0][hasChangingDigit ? 1 : 0] != -1) {
return memo[index][isTight ? 1 : 0][hasChangingDigit ? 1 : 0];
}
int count = 0;
int upperBound = isTight ? (s.charAt(index) - '0') : 9;
for (int d = 0; d <= upperBound; d++) {
// Skip digits that make the number invalid upon rotation.
if (d == 3 || d == 4 || d == 7) {
continue;
}
// The new number is still tightly bound only if the original was tight
// and we are choosing the maximum possible digit.
boolean newTight = isTight && (d == upperBound);
// The new number has a changing digit if the original did, or if the current digit is a changing one.
boolean newHasChangingDigit = hasChangingDigit || (d == 2 || d == 5 || d == 6 || d == 9);
count += solve(index + 1, newTight, newHasChangingDigit);
}
return memo[index][isTight ? 1 : 0][hasChangingDigit ? 1 : 0] = count;
}
}
Complexity Analysis
Pros and Cons
- Extremely efficient, with a time complexity logarithmic in
n. - Scales to much larger constraints on
nwhere brute-force would be too slow.
- Significantly more complex to understand and implement correctly.
- The overhead of recursion and memoization might make it slightly slower for very small
ncompared to the simple loop, though its asymptotic complexity is superior.
Code Solutions
Checking out 3 solutions in different languages for Rotated Digits. Click on different languages to view the code.
class Solution {
private
int[] a = new int[6];
private
int[][] dp = new int[6][2];
public
int rotatedDigits(int n) {
int len = 0;
for (var e : dp) {
Arrays.fill(e, -1);
}
while (n > 0) {
a[++len] = n % 10;
n /= 10;
}
return dfs(len, 0, true);
}
private
int dfs(int pos, int ok, boolean limit) {
if (pos <= 0) {
return ok;
}
if (!limit && dp[pos][ok] != -1) {
return dp[pos][ok];
}
int up = limit ? a[pos] : 9;
int ans = 0;
for (int i = 0; i <= up; ++i) {
if (i == 0 || i == 1 || i == 8) {
ans += dfs(pos - 1, ok, limit && i == up);
}
if (i == 2 || i == 5 || i == 6 || i == 9) {
ans += dfs(pos - 1, 1, limit && i == up);
}
}
if (!limit) {
dp[pos][ok] = ans;
}
return ans;
}
}
Video Solution
Watch the video walkthrough for Rotated Digits
Similar Questions
5 related questions you might find useful
Patterns:
Companies:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.