LeetCode 357. 统计各位数字都不同的数字个数
问题描述
给定一个非负整数 n,计算所有满足 0 ≤ x < 10ⁿ 且 x 的各位数字都不相同 的整数 x 的个数。
示例:
输入: n = 2
输出: 91
解释: 答案应为除去 11,22,33,44,55,66,77,88,99 外的所有数。
约束条件:
0 <= n <= 8
算法思路
核心思想:排列组合 + 数位DP
关键:
- 位数分析:对于 n 位数,需要考虑 1 位数、2 位数、…、n 位数的情况
- 排列组合:
- 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 的情况
步骤:
- 处理边界情况:n = 0 返回 1
- 初始化结果为 1(包含数字 0)
- 对于 1 到 n 的每一位数长度进行计算:
- 1 位数:9 个(1-9,0 已经在初始化时计算)
- k 位数(k ≥ 2):9 × 9 × 8 × … × (11-k)
- 累加所有位数的结果
代码实现
方法一:数学排列组合
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 :
-
初始化:
result = 1(包含数字 0) -
1位数计算:
digits = 1- 添加 9 个数字(1-9)
result = 1 + 9 = 10
-
2位数计算:
digits = 2- 第一位:9种选择(1-9)
- 第二位:9种选择(0-9除去第一位)
count = 9 × 9 = 81result = 10 + 81 = 91
-
返回结果: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
}
关键点
-
数字0:
- 数字0是有效的,且满足"各位数字不重复"的条件
- 在计算1位数时,通常只计算1-9,而0单独处理
-
首位不能为0:
- 对于多位数,第一位不能是0,否则就变成了更少位数的数字
- 第一位有9种选择(1-9),后续位有更多选择
-
数字重复:
- 由于只有10个不同的数字(0-9),所以最多只能构成10位不重复数字的数
- 当n > 10时,结果与n = 10时相同
-
排列组合公式:
- k位数(k ≥ 1)的不重复数字个数:
- k = 1: 10(包含0)
- k ≥ 2: 9 × 9 × 8 × 7 × … × (11-k)
- k位数(k ≥ 1)的不重复数字个数:
常见问题
-
为什么1位数只算9个而不是10个?
- 数字0已经在初始化
result = 1时被包含了 - 1位数中的1-9共9个数字需要额外加上
- 数字0已经在初始化
-
为什么第一位有9种选择,第二位也有9种选择?
- 第一位:1-9(9种,不能为0)
- 第二位:0-9中除去第一位的数字(10-1=9种)
-
当n很大时?
- 由于只有10个不同的数字,当n ≥ 10时,无法构造出更多位数的不重复数字
-
算法能扩展到其他进制吗?
- 只需要将数字范围从0-9改为对应进制的数字范围
- 例如八进制就是0-7,第一位有7种选择
1437

被折叠的 条评论
为什么被折叠?



