1088. Confusing Number II 🔒
Problem Description
A confusing number is a number that when rotated 180 degrees becomes a different number with each digit valid.
When we rotate individual digits by 180 degrees:
0
remains0
1
remains1
6
becomes9
8
remains8
9
becomes6
2
,3
,4
,5
, and7
become invalid (cannot be rotated)
To determine if a number is confusing:
- Each digit in the number must be rotatable (only
0
,1
,6
,8
,9
are valid) - After rotating the entire number 180 degrees, it must result in a different number than the original
For example:
69
is a confusing number because when rotated it becomes96
, which is different from69
88
is NOT a confusing number because when rotated it remains88
25
is NOT a confusing number because2
and5
cannot be rotated
When rotating a number, leading zeros in the result are ignored. For instance, 8000
rotated becomes 0008
, which is treated as just 8
.
Given an integer n
, you need to count how many confusing numbers exist in the range [1, n]
inclusive.
The solution uses digit dynamic programming (digit DP) to efficiently count valid confusing numbers up to n
. It builds numbers digit by digit while ensuring they remain within the limit and checks if the final number is different from its rotated version.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- No: The problem doesn't involve nodes and edges or graph traversal. We're counting numbers with specific digit properties.
Need to solve for kth smallest/largest?
- No: We're not finding the kth element; we're counting all valid confusing numbers up to
n
.
Involves Linked Lists?
- No: The problem deals with numbers and their digit rotations, not linked list operations.
Does the problem have small constraints?
- Not necessarily small in the traditional sense, but we need to explore all possible valid numbers up to
n
. However, the nature of the problem requires us to generate and check multiple number combinations systematically.
About subarrays or substrings?
- No: We're not dealing with contiguous sequences within arrays or strings.
Calculating max/min of something?
- No: We're counting valid numbers, not finding maximum or minimum values.
Asking for number of ways?
- Yes: We're counting how many confusing numbers exist in the range [1, n].
Multiple sequences?
- No: We're dealing with individual numbers and their rotations, not comparing multiple sequences.
Find or enumerate indices?
- In a sense, yes: We need to enumerate all valid confusing numbers by systematically building them digit by digit.
O(1) memory required?
- No: The solution uses recursion and dynamic programming techniques.
Conclusion: While the flowchart path through "counting" doesn't directly lead to backtracking, the nature of this problem requires Backtracking/Digit DP. We need to:
- Build valid numbers digit by digit (only using rotatable digits: 0, 1, 6, 8, 9)
- Ensure we don't exceed the limit
n
- Check if each complete number is "confusing" (different from its rotation)
The backtracking approach systematically explores all possible valid digit combinations while pruning invalid branches (numbers exceeding n
or containing non-rotatable digits), making it the optimal solution pattern for this problem.
Intuition
The key insight is that we need to count valid confusing numbers efficiently without checking every single number from 1 to n. Since only certain digits (0, 1, 6, 8, 9) can be rotated, we can build valid candidates digit by digit rather than checking all numbers.
Think of it like building a number from left to right. At each position, we only have 5 choices (the rotatable digits), not all 10 digits. This immediately reduces our search space significantly.
The challenge is ensuring we don't exceed n
while building these numbers. This is where the "digit DP with limit" technique comes in. When we're building a number digit by digit, we need to track whether we're still bounded by the corresponding digit in n
. For example, if n = 234
, and we've built 2
so far, the next digit can only be 0, 1, or 3 (not 6, 8, or 9) to stay within the limit.
The backtracking approach naturally fits here because:
- We make a choice (pick a valid digit)
- Explore further (move to the next position)
- If we complete a valid number, check if it's confusing
- Backtrack and try other digit choices
To check if a number is confusing, we rotate it by replacing each digit with its rotated counterpart (6→9, 9→6, etc.) and compare with the original. The mapping array d = [0, 1, -1, -1, -1, -1, 9, -1, 8, 6]
cleverly encodes both which digits are valid (non-negative values) and what they become when rotated.
The limit
flag in the DFS is crucial - it tells us whether we're still constrained by n
's digits or can freely use any rotatable digit. Once we use a digit smaller than the corresponding digit in n
, all subsequent positions become unconstrained.
This approach is much more efficient than brute force because we're only generating potentially valid numbers and pruning invalid branches early, rather than checking all numbers from 1 to n.
Learn more about Math and Backtracking patterns.
Solution Approach
The solution uses digit dynamic programming with backtracking to efficiently count confusing numbers. Let's break down the implementation:
1. Digit Mapping Setup
d = [0, 1, -1, -1, -1, -1, 9, -1, 8, 6]
This array serves dual purpose:
- Index represents the original digit (0-9)
- Value represents what it becomes when rotated 180°
-1
indicates the digit cannot be rotated (invalid)
2. Check Function
def check(x: int) -> bool:
y, t = 0, x
while t:
t, v = divmod(t, 10)
y = y * 10 + d[v]
return x != y
This function rotates a number and checks if it's different from the original:
- Extract each digit from right to left using
divmod(t, 10)
- Build the rotated number
y
by appending rotated digits - Return
True
if the rotated number differs from the original
3. DFS with Digit DP
def dfs(pos: int, limit: bool, x: int) -> int:
The recursive function builds valid numbers digit by digit:
pos
: Current position in the number we're buildinglimit
: Whether we're still bounded by the digits ofn
x
: The number built so far
4. Base Case
if pos >= len(s):
return int(check(x))
When we've built a complete number (reached all positions), check if it's confusing.
5. Digit Selection
up = int(s[pos]) if limit else 9
ans = 0
for i in range(up + 1):
if d[i] != -1:
ans += dfs(pos + 1, limit and i == up, x * 10 + i)
- If
limit
isTrue
, we can only use digits up tos[pos]
(the digit at positionpos
inn
) - If
limit
isFalse
, we can use any digit from 0-9 - Only consider rotatable digits (
d[i] != -1
) - Update
limit
for the next position: remainsTrue
only if we used the maximum allowed digit
6. Main Function
s = str(n)
return dfs(0, True, 0)
Convert n
to string for easy digit access and start the DFS from position 0 with limit enabled.
The algorithm efficiently explores only valid number combinations while maintaining the constraint that numbers shouldn't exceed n
. By building numbers using only rotatable digits and checking the confusing property at the end, we avoid unnecessary iterations through invalid numbers.
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 trace through the algorithm with n = 20
to see how it counts confusing numbers.
Setup:
s = "20"
(string representation of n)d = [0, 1, -1, -1, -1, -1, 9, -1, 8, 6]
(rotation mapping)
DFS Exploration:
Starting with dfs(pos=0, limit=True, x=0)
:
- At position 0, we can use digits 0-2 (since
s[0]='2'
andlimit=True
) - Valid rotatable digits in range [0,2]: only 0 and 1 (2 maps to -1, so invalid)
Branch 1: Choose digit 0
- Call
dfs(pos=1, limit=False, x=0)
- Since we chose 0 < 2,
limit
becomesFalse
- At position 1, we can now use any digit 0-9
- Valid rotatable digits: 0, 1, 6, 8, 9
- This generates numbers: 00→0, 01→1, 06→6, 08→8, 09→9
- Checking each:
- 0: rotates to 0 (same) → NOT confusing
- 1: rotates to 1 (same) → NOT confusing
- 6: rotates to 9 (different) → CONFUSING ✓
- 8: rotates to 8 (same) → NOT confusing
- 9: rotates to 6 (different) → CONFUSING ✓
Branch 2: Choose digit 1
- Call
dfs(pos=1, limit=False, x=1)
- At position 1, we can use any digit 0-9
- Valid rotatable digits: 0, 1, 6, 8, 9
- This generates numbers: 10, 11, 16, 18, 19
- Checking each:
- 10: rotates to 01→1 (different) → CONFUSING ✓
- 11: rotates to 11 (same) → NOT confusing
- 16: rotates to 91 (different) → CONFUSING ✓
- 18: rotates to 81 (different) → CONFUSING ✓
- 19: rotates to 61 (different) → CONFUSING ✓
Rotation Examples:
- Number 16: digit 1→1, digit 6→9, reversed = 91
- Number 19: digit 1→1, digit 9→6, reversed = 61
Final Count: 6 confusing numbers (6, 9, 10, 16, 18, 19)
The algorithm efficiently explores only 10 valid candidates (2 first digits × 5 second digits) instead of checking all 20 numbers, demonstrating how digit DP prunes the search space by only building numbers with rotatable digits.
Solution Implementation
1class Solution:
2 def confusingNumberII(self, n: int) -> int:
3 def is_confusing_number(num: int) -> bool:
4 """
5 Check if a number is confusing by rotating it 180 degrees.
6 A confusing number becomes different after rotation.
7 """
8 rotated = 0
9 temp = num
10
11 # Rotate each digit and build the rotated number
12 while temp:
13 temp, digit = divmod(temp, 10)
14 rotated = rotated * 10 + rotation_map[digit]
15
16 # A confusing number must be different from its rotation
17 return num != rotated
18
19 def count_confusing_numbers(position: int, is_limit: bool, current_num: int) -> int:
20 """
21 Use digit DP to count confusing numbers.
22
23 Args:
24 position: Current digit position being processed
25 is_limit: Whether we're still bounded by the original number's digits
26 current_num: The number being built so far
27
28 Returns:
29 Count of confusing numbers
30 """
31 # Base case: finished building a number
32 if position >= len(digits_str):
33 return int(is_confusing_number(current_num))
34
35 # Determine the maximum digit we can use at this position
36 max_digit = int(digits_str[position]) if is_limit else 9
37
38 result = 0
39 # Try each valid digit (0-9 that can be rotated)
40 for digit in range(max_digit + 1):
41 # Skip digits that cannot be rotated (2, 3, 4, 5, 7)
42 if rotation_map[digit] != -1:
43 result += count_confusing_numbers(
44 position + 1,
45 is_limit and (digit == max_digit),
46 current_num * 10 + digit
47 )
48
49 return result
50
51 # Mapping of digits to their 180-degree rotation
52 # -1 means the digit cannot be rotated validly
53 rotation_map = [0, 1, -1, -1, -1, -1, 9, -1, 8, 6]
54 # 0 1 2 3 4 5 6 7 8 9
55
56 # Convert number to string for digit-by-digit processing
57 digits_str = str(n)
58
59 # Start DFS from the first digit
60 return count_confusing_numbers(0, True, 0)
61
1class Solution {
2 // Mapping of digits to their rotated counterparts
3 // -1 means the digit cannot be rotated to form a valid digit
4 // 0->0, 1->1, 6->9, 8->8, 9->6
5 private final int[] rotationMap = {0, 1, -1, -1, -1, -1, 9, -1, 8, 6};
6 private String numberString;
7
8 /**
9 * Counts all confusing numbers in the range [1, n]
10 * A confusing number is a number that when rotated 180 degrees becomes a different number
11 * @param n the upper bound (inclusive)
12 * @return count of confusing numbers
13 */
14 public int confusingNumberII(int n) {
15 numberString = String.valueOf(n);
16 // Start DFS from position 0, with limit flag set, and current number as 0
17 return dfs(0, 1, 0);
18 }
19
20 /**
21 * Depth-first search to build valid numbers digit by digit
22 * @param position current position in the number being built
23 * @param limitFlag 1 if we're still bounded by the original number n, 0 otherwise
24 * @param currentNumber the number being built so far
25 * @return count of valid confusing numbers
26 */
27 private int dfs(int position, int limitFlag, int currentNumber) {
28 // Base case: we've built a complete number
29 if (position >= numberString.length()) {
30 return isConfusingNumber(currentNumber) ? 1 : 0;
31 }
32
33 // Determine the upper bound for the current digit
34 // If we're still limited by n, use the corresponding digit from n
35 // Otherwise, we can use any digit up to 9
36 int upperBound = limitFlag == 1 ? numberString.charAt(position) - '0' : 9;
37 int count = 0;
38
39 // Try all possible digits at the current position
40 for (int digit = 0; digit <= upperBound; ++digit) {
41 // Only use digits that have valid rotations
42 if (rotationMap[digit] != -1) {
43 // Recursively build the rest of the number
44 // Update limit flag: remain limited only if we're currently limited AND using the max digit
45 int newLimitFlag = (limitFlag == 1 && digit == upperBound) ? 1 : 0;
46 count += dfs(position + 1, newLimitFlag, currentNumber * 10 + digit);
47 }
48 }
49
50 return count;
51 }
52
53 /**
54 * Checks if a number is a confusing number
55 * A number is confusing if it's different from its 180-degree rotation
56 * @param number the number to check
57 * @return true if the number is confusing, false otherwise
58 */
59 private boolean isConfusingNumber(int number) {
60 int rotatedNumber = 0;
61 int temp = number;
62
63 // Build the rotated number by processing digits from right to left
64 while (temp > 0) {
65 int lastDigit = temp % 10;
66 rotatedNumber = rotatedNumber * 10 + rotationMap[lastDigit];
67 temp /= 10;
68 }
69
70 // A confusing number must be different from its rotation
71 return number != rotatedNumber;
72 }
73}
74
1class Solution {
2public:
3 int confusingNumberII(int n) {
4 // Convert n to string for digit-by-digit processing
5 string maxNumStr = to_string(n);
6
7 // Mapping of digits to their 180-degree rotated counterparts
8 // -1 means the digit cannot be rotated to form a valid digit
9 // 0->0, 1->1, 6->9, 8->8, 9->6
10 int rotatedDigit[10] = {0, 1, -1, -1, -1, -1, 9, -1, 8, 6};
11
12 // Lambda function to check if a number is confusing
13 // A number is confusing if it's different from its rotated version
14 auto isConfusingNumber = [&](int number) -> bool {
15 int rotatedNumber = 0;
16 int temp = number;
17
18 // Build the rotated number by processing digits from right to left
19 while (temp > 0) {
20 int digit = temp % 10;
21 rotatedNumber = rotatedNumber * 10 + rotatedDigit[digit];
22 temp /= 10;
23 }
24
25 // Return true if the original and rotated numbers are different
26 return number != rotatedNumber;
27 };
28
29 // Recursive function using digit DP to count confusing numbers
30 // pos: current position in the number being built (0-indexed)
31 // hasLimit: whether we're still bounded by the digits of n
32 // currentNum: the number being constructed so far
33 function<int(int, int, int)> countConfusingNumbers = [&](int pos, int hasLimit, int currentNum) -> int {
34 // Base case: we've built a complete number
35 if (pos >= maxNumStr.size()) {
36 return isConfusingNumber(currentNum);
37 }
38
39 // Determine the upper bound for the current digit
40 // If hasLimit is true, we can't exceed the corresponding digit in n
41 int upperBound = hasLimit ? (maxNumStr[pos] - '0') : 9;
42
43 int count = 0;
44
45 // Try each valid digit at the current position
46 for (int digit = 0; digit <= upperBound; ++digit) {
47 // Skip digits that can't be rotated (2, 3, 4, 5, 7)
48 if (rotatedDigit[digit] != -1) {
49 // Recursively count numbers with this digit at current position
50 // Update hasLimit: remains true only if we chose the max allowed digit
51 count += countConfusingNumbers(
52 pos + 1,
53 hasLimit && (digit == upperBound),
54 currentNum * 10 + digit
55 );
56 }
57 }
58
59 return count;
60 };
61
62 // Start the recursive counting from position 0, with limit enabled, starting from 0
63 return countConfusingNumbers(0, 1, 0);
64 }
65};
66
1/**
2 * Counts the number of confusing numbers in the range [1, n]
3 * A confusing number is a number that when rotated 180 degrees becomes a different number with each digit valid
4 */
5function confusingNumberII(n: number): number {
6 // Convert the upper bound to string for digit-by-digit processing
7 const nString = n.toString();
8
9 // Mapping array: index represents original digit, value represents rotated digit
10 // -1 means the digit cannot be rotated (2, 3, 4, 5, 7 are invalid)
11 const rotationMap: number[] = [0, 1, -1, -1, -1, -1, 9, -1, 8, 6];
12
13 /**
14 * Checks if a number is a confusing number
15 * A number is confusing if it's different from its 180-degree rotation
16 * @param originalNumber - The number to check
17 * @returns true if the number is confusing, false otherwise
18 */
19 const isConfusingNumber = (originalNumber: number): boolean => {
20 let rotatedNumber = 0;
21 let tempNumber = originalNumber;
22
23 // Build the rotated number by processing digits from right to left
24 while (tempNumber > 0) {
25 const lastDigit = tempNumber % 10;
26 rotatedNumber = rotatedNumber * 10 + rotationMap[lastDigit];
27 tempNumber = Math.floor(tempNumber / 10);
28 }
29
30 // A confusing number must be different from its rotation
31 return originalNumber !== rotatedNumber;
32 };
33
34 /**
35 * Depth-first search to count confusing numbers
36 * @param currentPosition - Current digit position being processed
37 * @param hasUpperBoundLimit - Whether we're still bounded by the original number n
38 * @param currentNumber - The number being built so far
39 * @returns Count of confusing numbers
40 */
41 const countConfusingNumbers = (
42 currentPosition: number,
43 hasUpperBoundLimit: boolean,
44 currentNumber: number
45 ): number => {
46 // Base case: finished building a complete number
47 if (currentPosition >= nString.length) {
48 return isConfusingNumber(currentNumber) ? 1 : 0;
49 }
50
51 // Determine the maximum digit we can use at this position
52 const maxDigit = hasUpperBoundLimit ? parseInt(nString[currentPosition]) : 9;
53
54 let totalCount = 0;
55
56 // Try each valid digit from 0 to maxDigit
57 for (let digit = 0; digit <= maxDigit; digit++) {
58 // Skip digits that cannot be rotated
59 if (rotationMap[digit] !== -1) {
60 // Recursively count numbers with this digit at current position
61 // Update the limit flag: we're still limited if we were limited before AND we chose the max digit
62 totalCount += countConfusingNumbers(
63 currentPosition + 1,
64 hasUpperBoundLimit && digit === maxDigit,
65 currentNumber * 10 + digit
66 );
67 }
68 }
69
70 return totalCount;
71 };
72
73 // Start DFS from position 0, with upper bound limit, and initial number 0
74 return countConfusingNumbers(0, true, 0);
75}
76
Time and Space Complexity
Time Complexity: O(log(n) * 5^log(n))
or simplified to O(n^0.7)
The time complexity is determined by the DFS traversal:
- The recursion depth is
O(log(n))
since we process each digit position of the numbern
- At each position, we can choose from at most 5 valid digits (0, 1, 6, 8, 9) that have valid rotations
- In the worst case, we explore
5^log(n)
different number combinations - Since
5^log(n) = n^(log(5)/log(10)) ≈ n^0.699
, the overall time complexity can be approximated asO(n^0.7)
- The
check()
function takesO(log(n))
time to verify if a number is confusing, but this doesn't change the overall complexity
Space Complexity: O(log(n))
The space complexity comes from:
- The recursion call stack depth is
O(log(n))
corresponding to the number of digits inn
- The string
s
stores the digits ofn
, which takesO(log(n))
space - The dictionary
d
is a fixed-size array of 10 elements, contributingO(1)
space - Local variables in each recursive call use
O(1)
space
Therefore, the total space complexity is O(log(n))
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Handling Leading Zeros in Rotation
Pitfall: A common mistake is not properly handling the rotation of numbers that produce leading zeros. For example, when rotating 6000
, you get 0009
, which should be treated as 9
, not 0009
. If you compare the string representations or don't handle the numeric conversion correctly, you might incorrectly classify such numbers.
Example of Incorrect Implementation:
def check(x: int) -> bool:
# WRONG: String comparison doesn't handle leading zeros
original = str(x)
rotated = ""
for digit in reversed(original):
rotated += str(d[int(digit)])
return original != rotated # This fails for cases like 6000
Solution: Always work with integer values when comparing the final numbers, as shown in the correct implementation. The integer conversion automatically handles leading zeros:
def check(x: int) -> bool:
y, t = 0, x
while t:
t, v = divmod(t, 10)
y = y * 10 + d[v] # Building as integer naturally handles leading zeros
return x != y # Integer comparison: 6000 != 9 ✓
2. Starting DFS with Leading Zeros
Pitfall: When building numbers digit by digit, you might accidentally count numbers with leading zeros (like 069
as a separate number from 69
), which would give incorrect counts since we're counting integers in the range [1, n]
.
Example of Incorrect Start:
# WRONG: This could potentially count numbers with leading zeros
for first_digit in range(10): # Starting from 0
if d[first_digit] != -1:
count += dfs(1, limit and first_digit == int(s[0]), first_digit)
Solution: There are two approaches to handle this:
Approach 1: Skip leading zeros explicitly in the DFS:
def dfs(pos: int, limit: bool, x: int, started: bool) -> int:
if pos >= len(s):
return int(started and check(x)) # Only count if we've started the number
up = int(s[pos]) if limit else 9
ans = 0
for i in range(up + 1):
if d[i] != -1:
if i == 0 and not started:
# Leading zero - don't mark as started
ans += dfs(pos + 1, limit and i == up, x, False)
else:
# Valid digit that starts or continues the number
ans += dfs(pos + 1, limit and i == up, x * 10 + i, True)
return ans
Approach 2: Handle smaller numbers separately:
# For each length from 1 to len(str(n))
total = 0
for length in range(1, len(str(n)) + 1):
if length < len(str(n)):
# Count all confusing numbers of this length
total += count_fixed_length(length)
else:
# Count confusing numbers of max length up to n
total += count_up_to_n(n)
3. Forgetting to Check Digit Validity Before Rotation
Pitfall: Attempting to rotate invalid digits (2, 3, 4, 5, 7) or not properly filtering them during the DFS traversal.
Example of Incorrect Check:
def check(x: int) -> bool:
y, t = 0, x
while t:
t, v = divmod(t, 10)
# WRONG: Not checking if digit can be rotated
y = y * 10 + d[v] # This could use -1 for invalid digits!
return x != y
Solution: The DFS should only build numbers using valid rotatable digits, so the check function should never encounter invalid digits. This is ensured by the condition if d[i] != -1
in the DFS loop. However, for robustness, you could add validation:
def check(x: int) -> bool:
y, t = 0, x
while t:
t, v = divmod(t, 10)
if d[v] == -1: # Safety check
return False
y = y * 10 + d[v]
return x != y
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
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!