这是一个关于LintCode 3868 "混淆数字(二)"的问题。这个题目是计算混淆数字的变体,让我们先理解题目要求,然后提供一个解决方案。

问题理解

混淆数字:当一个数字旋转180度后变成另一个不同的有效数字,则称这个数字为混淆数字。

  • 有效数字旋转映射:0→0, 1→1, 6→9, 8→8, 9→9
  • 其他数字(2,3,4,5,7)旋转后无效
  • 数字旋转后不能以0开头

题目要求:给定一个正整数n,计算从1到n(包含)之间有多少个混淆数字?

解题思路

这是一个典型的DFS/回溯问题,我们需要:

  1. 生成所有可能的混淆数字(只使用0,1,6,8,9)
  2. 检查旋转后是否与原数字不同
  3. 排除旋转后以0开头的数字
  4. 计数不超过n的数字

代码实现

public class Solution {
    /**
     * @param n: a integer
     * @return: return an integer
     */
    public int confusingNumber(int n) {
        // 有效数字映射
        int[] mapping = {0, 1, -1, -1, -1, -1, 9, -1, 8, 6};
        
        // 用于DFS的数字集合(注意0不能作为首位)
        int[] digits = {0, 1, 6, 8, 9};
        
        // 计数器
        int[] count = new int[1];
        
        // 从1开始DFS(因为0不是正数,且题目要求从1到n)
        dfs(0, n, digits, mapping, count, false);
        
        return count[0];
    }
    
    private void dfs(long current, int n, int[] digits, int[] mapping, 
                     int[] count, boolean started) {
        // 如果当前数字大于n,返回
        if (current > n) {
            return;
        }
        
        // 如果已经开始构建数字(排除初始的0),检查是否是混淆数字
        if (started && current > 0) {
            if (isConfusing(current, mapping)) {
                count[0]++;
            }
        }
        
        // 继续构建更大的数字
        for (int digit : digits) {
            long next = current * 10 + digit;
            
            // 如果当前还没有开始构建,且digit是0,跳过(避免前导0)
            if (!started && digit == 0) {
                continue;
            }
            
            dfs(next, n, digits, mapping, count, true);
        }
    }
    
    private boolean isConfusing(long num, int[] mapping) {
        // 旋转数字
        long rotated = 0;
        long original = num;
        
        while (num > 0) {
            int digit = (int)(num % 10);
            rotated = rotated * 10 + mapping[digit];
            num /= 10;
        }
        
        // 检查是否与原数字不同
        return rotated != original;
    }
}

优化版本(更高效的DFS)

public class Solution {
    private int count = 0;
    private int[] mapping = {0, 1, -1, -1, -1, -1, 9, -1, 8, 6};
    private int[] digits = {0, 1, 6, 8, 9};
    
    public int confusingNumber(int n) {
        // 从各个位数开始DFS
        for (int digit : digits) {
            if (digit != 0) {  // 首位不能是0
                dfs(digit, n);
            }
        }
        return count;
    }
    
    private void dfs(long num, int n) {
        // 剪枝:如果当前数字已经大于n,返回
        if (num > n) {
            return;
        }
        
        // 检查是否是混淆数字
        if (isConfusing(num)) {
            count++;
        }
        
        // 继续添加数字
        for (int digit : digits) {
            long next = num * 10 + digit;
            if (next <= n) {
                dfs(next, n);
            }
        }
    }
    
    private boolean isConfusing(long num) {
        long rotated = 0;
        long original = num;
        
        while (num > 0) {
            int digit = (int)(num % 10);
            rotated = rotated * 10 + mapping[digit];
            num /= 10;
        }
        
        return rotated != original;
    }
}

算法分析

  • 时间复杂度:O(5^k),其中k是n的位数。在最坏情况下,我们需要生成所有由{0,1,6,8,9}组成的数字。
  • 空间复杂度:O(k),递归调用栈的深度。

示例验证

假设n=100:

  • 有效的混淆数字有:6,9,10,18,19,60,68,69,80,81,89,90,91,98
  • 共计14个

关键点说明

  1. 首位不能是0:数字如"01"旋转后变成"10",但原数字"01"在数学上就是1,这不属于混淆数字的范畴。

  2. 旋转映射:使用-1表示无效数字,在判断混淆数字时只考虑有效数字组成的数字。

  3. DFS构建顺序:从1位数字开始,逐渐构建更长的数字,这样可以保证不会漏掉任何可能的混淆数字。

这个解法可以正确处理n的各种取值,并通过DFS的剪枝来优化性能。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐