算法题 统计各位数字都不同的数字个数

LeetCode 357. 统计各位数字都不同的数字个数

问题描述

给定一个非负整数 n,计算所有满足 0 ≤ x < 10ⁿx 的各位数字都不相同 的整数 x 的个数。

示例

输入: n = 2
输出: 91
解释: 答案应为除去 11,22,33,44,55,66,77,88,99 外的所有数。

约束条件

  • 0 <= n <= 8

算法思路

核心思想:排列组合 + 数位DP

关键

  1. 位数分析:对于 n 位数,需要考虑 1 位数、2 位数、…、n 位数的情况
  2. 排列组合
    • 1 位数:0-9 共 10 个(都满足条件)
    • 2 位数:第一位不能为 0(9 种选择),第二位不能等于第一位(9 种选择)→ 9×9 = 81
    • 3 位数:第一位 9 种,第二位 9 种,第三位 8 种 → 9×9×8 = 648
    • k 位数(k > 1):9 × 9 × 8 × 7 × … × (11-k)

特殊情况

  • 当 n = 0 时,只有数字 0,返回 1
  • 当 n ≥ 10 时,由于只有 10 个不同的数字(0-9),所以最多只能有 10 位不重复数字的数
  • 题目约束 n ≤ 8,所以不需要考虑 n ≥ 10 的情况

步骤:

  1. 处理边界情况:n = 0 返回 1
  2. 初始化结果为 1(包含数字 0)
  3. 对于 1 到 n 的每一位数长度进行计算:
    • 1 位数:9 个(1-9,0 已经在初始化时计算)
    • k 位数(k ≥ 2):9 × 9 × 8 × … × (11-k)
  4. 累加所有位数的结果

代码实现

方法一:数学排列组合

class Solution {
    /**
     * 计算各位数字都不同的数字个数
     * 使用排列组合的数学方法
     * 
     * @param n 非负整数,表示范围是 [0, 10^n)
     * @return 满足条件的数字个数
     */
    public int countNumbersWithUniqueDigits(int n) {
        // 边界情况:n = 0 时,只有数字 0
        if (n == 0) {
            return 1;
        }
        
        // 结果初始化为1,包含数字0
        int result = 1;
        
        // 计算1位数到n位数的情况
        for (int digits = 1; digits <= n; digits++) {
            if (digits == 1) {
                // 1位数:1,2,3,4,5,6,7,8,9(共9个,0已经在result=1中包含了)
                result += 9;
            } else {
                // k位数的计算(k >= 2)
                // 第一位:不能为0,有9种选择(1-9)
                // 第二位:不能等于第一位,有9种选择(0-9除去第一位)
                // 第三位:不能等于前两位,有8种选择
                // ...
                // 第k位:有(11-k)种选择
                
                int count = 9;  // 第一位的选择数
                int available = 9;  // 剩余可选数字数量(0-9共10个,第一位用了1个,还剩9个)
                
                // 计算第2位到第digits位的选择数
                for (int i = 1; i < digits; i++) {
                    count *= available;
                    available--;  // 每用一个数字,可选数量减1
                }
                
                result += count;
            }
        }
        
        return result;
    }
}

方法二:优化排列组合

class Solution {
    /**
     * 优化:更简洁的排列组合实现
     * 
     * @param n 非负整数
     * @return 各位数字都不同的数字个数
     */
    public int countNumbersWithUniqueDigits(int n) {
        // 边界处理
        if (n == 0) return 1;
        if (n == 1) return 10;
        
        // 由于只有10个不同数字,当n>10时结果不会增加
        n = Math.min(n, 10);
        
        int result = 10;  // 包含0-9共10个1位数
        int uniqueDigits = 9;  // 当前位数下不重复数字的个数
        int available = 9;     // 可用数字数量
        
        // 从2位数开始计算到n位数
        for (int i = 2; i <= n; i++) {
            uniqueDigits *= available;  // 计算当前位数的不重复数字个数
            result += uniqueDigits;     // 累加到结果
            available--;                // 可用数字减少1
        }
        
        return result;
    }
}

算法分析

  • 时间复杂度:O(min(n, 10))

    • 由于只有10个不同的数字(0-9),当n > 10时,结果不会再增加
    • 最多只需要计算10次循环
  • 空间复杂度:O(1)

    • 只使用了常数个变量
  • 方法对比

    • 方法一:逻辑更清晰,分步骤处理每种位数
    • 方法二:代码更简洁,直接累加计算

算法过程

n = 2

  1. 初始化result = 1(包含数字 0)

  2. 1位数计算

    • digits = 1
    • 添加 9 个数字(1-9)
    • result = 1 + 9 = 10
  3. 2位数计算

    • digits = 2
    • 第一位:9种选择(1-9)
    • 第二位:9种选择(0-9除去第一位)
    • count = 9 × 9 = 81
    • result = 10 + 81 = 91
  4. 返回结果:91

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准示例
    System.out.println("Test 1 (n=2): " + solution.countNumbersWithUniqueDigits(2)); // 91
    
    // 测试用例2:n=0
    System.out.println("Test 2 (n=0): " + solution.countNumbersWithUniqueDigits(0)); // 1
    
    // 测试用例3:n=1
    System.out.println("Test 3 (n=1): " + solution.countNumbersWithUniqueDigits(1)); // 10
    
    // 测试用例4:n=3
    System.out.println("Test 4 (n=3): " + solution.countNumbersWithUniqueDigits(3)); // 739
    // 计算:1 + 9 + 81 + 648 = 739
    
    // 测试用例5:n=4
    System.out.println("Test 5 (n=4): " + solution.countNumbersWithUniqueDigits(4)); // 5275
    // 计算:1 + 9 + 81 + 648 + 4536 = 5275
    
    // 测试用例6:n=8(最大值)
    System.out.println("Test 6 (n=8): " + solution.countNumbersWithUniqueDigits(8)); // 2345851
    
    // 测试用例7:边界情况n=10
    System.out.println("Test 7 (n=10): " + solution.countNumbersWithUniqueDigits(10)); // 8877691
}

关键点

  1. 数字0

    • 数字0是有效的,且满足"各位数字不重复"的条件
    • 在计算1位数时,通常只计算1-9,而0单独处理
  2. 首位不能为0

    • 对于多位数,第一位不能是0,否则就变成了更少位数的数字
    • 第一位有9种选择(1-9),后续位有更多选择
  3. 数字重复

    • 由于只有10个不同的数字(0-9),所以最多只能构成10位不重复数字的数
    • 当n > 10时,结果与n = 10时相同
  4. 排列组合公式

    • k位数(k ≥ 1)的不重复数字个数:
      • k = 1: 10(包含0)
      • k ≥ 2: 9 × 9 × 8 × 7 × … × (11-k)

常见问题

  1. 为什么1位数只算9个而不是10个?

    • 数字0已经在初始化result = 1时被包含了
    • 1位数中的1-9共9个数字需要额外加上
  2. 为什么第一位有9种选择,第二位也有9种选择?

    • 第一位:1-9(9种,不能为0)
    • 第二位:0-9中除去第一位的数字(10-1=9种)
  3. 当n很大时?

    • 由于只有10个不同的数字,当n ≥ 10时,无法构造出更多位数的不重复数字
  4. 算法能扩展到其他进制吗?

    • 只需要将数字范围从0-9改为对应进制的数字范围
    • 例如八进制就是0-7,第一位有7种选择
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值