Given a positive integer N, return the number of positive integers less than or equal to N that have at least 1 repeated digit.
Example 1:
Input: 20
Output: 1
Explanation: The only positive number (<= 20) with at least 1 repeated digit is 11.
Example 2:
Input: 100
Output: 10
Explanation: The positive numbers (<= 100) with atleast 1 repeated digit are 11, 22, 33, 44, 55, 66, 77, 88, 99, and 100.
Example 3:
Input: 1000
Output: 262
Note:
1 <= N <= 10^9
Note:
这题的题意是给一个整数,找到小于或者等于这个数且有至少一个重复数的数字的个数。这题刚开始做的时候完全没有思路,想着枚举出全部数字,发现是个死胡同,于是就看答案啦~
步骤很简单,反过来考虑就可以了。
如果想找出小于等于某个数且至少有一个重复数字的个数,先计算出没有重复数字的个数,再用当前数去剪就可以了。
计算没有重复数字个数的步骤,我们以 7654 为例。
1.先计算百位以及百位以下的数。
我们有
XXX ---- 9 * 9 * 8
XX--------9 * 9
X----------9
总共有 9 * 9 * 8 + 9 * 9 + 9 = 738
2.计算包含前缀的没有重复的数字
XXXX --- 6 * 9 * 8 * 7 (第1位小于7, 不包括0,有6个数)
7XXX --- 6 * 8 * 7 (第2位小于6, 包括0, 有6个数)
76XX --- 5 * 7 (第3位小于5, 包括0, 有5个数)
765X --- 5 (第4位小于等于4, 包括0, 有5个数)
结果为7654 - 4138 = 3516
这题的corner cases 挺有意思的, 我们先上代码。
public int numDupDigitsAtMostN(int N) {
List<Integer> digits = new ArrayList<>();
//这个N + 1是解法的精髓,想想如果N=20 (末尾为0的情况)
for (int tmp = N + 1; tmp > 0; tmp /= 10) {
digits.add(0, tmp % 10);
}
int len = digits.size();
int noRepeatNum = 0;
for (int i = 0; i < len - 1; i++) {
noRepeatNum += calNoRepeatWithinDigitNum(i);
}
// 用set来记录经过的prefix
// 如果出现N=220, 当我们发现十位上的数已经出现过了
// break掉loop,也就是说不用计算22X
Set<Integer> set = new HashSet<>();
for (int i = 0; i < len; i++) {
int start = 1;
if (i != 0) {
start = 0;
}
for (int j = start; j < digits.get(i); j++) {
if (!set.contains(j)) {
noRepeatNum += calNoRepeatWithinDigitNum(9 - i, len - i - 1);
}
}
if (set.contains(digits.get(i))) break;
set.add(digits.get(i));
}
return N - noRepeatNum;
}
private int calNoRepeatWithinDigitNum(int num) {
int res = 9;
for (int i = 0; i < num; i++) {
res *= (9 - i);
}
return res;
}
private int calNoRepeatWithinDigitNum(int largestNum, int num) {
if (num == 0) return 1;
int res = largestNum;
for (int i = 1; i < num; i++) {
res *= (largestNum - i);
}
return res;
}
corner case1. N = 20 (N 以0 结尾)
把N+1当做input, 理解了代码就会发现,我们不用把最后一位单独处理了,不然的的话最后一位就要翻译0和非0两种情况讨论,非常麻烦
corner case2. N = 220 (N 里面有重复的数字)
如果有重复的数字,break掉整个loop
网友评论