Facebook Pixel

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 remains 0
  • 1 remains 1
  • 6 becomes 9
  • 8 remains 8
  • 9 becomes 6
  • 2, 3, 4, 5, and 7 become invalid (cannot be rotated)

To determine if a number is confusing:

  1. Each digit in the number must be rotatable (only 0, 1, 6, 8, 9 are valid)
  2. 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 becomes 96, which is different from 69
  • 88 is NOT a confusing number because when rotated it remains 88
  • 25 is NOT a confusing number because 2 and 5 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:

  1. Build valid numbers digit by digit (only using rotatable digits: 0, 1, 6, 8, 9)
  2. Ensure we don't exceed the limit n
  3. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. We make a choice (pick a valid digit)
  2. Explore further (move to the next position)
  3. If we complete a valid number, check if it's confusing
  4. 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 building
  • limit: Whether we're still bounded by the digits of n
  • 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 is True, we can only use digits up to s[pos] (the digit at position pos in n)
  • If limit is False, we can use any digit from 0-9
  • Only consider rotatable digits (d[i] != -1)
  • Update limit for the next position: remains True 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 Evaluator

Example 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' and limit=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 becomes False
  • 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 number n
  • 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 as O(n^0.7)
  • The check() function takes O(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 in n
  • The string s stores the digits of n, which takes O(log(n)) space
  • The dictionary d is a fixed-size array of 10 elements, contributing O(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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More