1015. Numbers With Repeated Digits 刷题笔记

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 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 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

你可能感兴趣的:(1015. Numbers With Repeated Digits 刷题笔记)