1012. 至少有 1 位重复的数字

描述:给定正整数 N,返回小于等于 N 且具有至少 1 位重复数字的正整数的个数。

输入:20
输出:1,解释:只有11

 

解题思路(逆向求解,先组合不重复的个数,再用总数减):

有重复数总和 = 输入数n - 没有重复的数总和
没有重复的数总和 = 1 到(n - 1)位没有重复数总和 + 第n位没有重复数字总和

输入:234
[1,9] = 9
[10,99] = 9 * 9
[100,234] 是重头戏,拆解如下:

百位:有0、1、2共3中选择,但首位为0前面已经计算过了,可排除0,还有1、2

选择1时,是十位和个位可从[0,9]中随机选,只要保证不重复即可,于是有:1 * 9 * 8
选择2时,拆解十位,十位有0、1、2、3共4中选择,但百位占用了2,排除2,还有0、1、3
a) 选择0、2,个位可从[0,9]还剩下的8位(排除百位和十位占用2位)中随机选择,于是有:1 * 2 * 8
b) 选择3,个位有0、1、2、3、4位,但前面占用了2位,排除2、3,于是有:1 * 1 * 3
综上所述:有重复数总和 = 234 - (9 + 9 * 9) - (1 * 9 * 8 + 1 * 2 * 8 + 3) = 53

代码实现如下:

class Solution {
    public int numDupDigitsAtMostN(int n) {
        if (n <= 10) {
            return 0;
        }

        List<Integer> nList = splitN(n);

        // 1 到(n-1)位不重复的数字,如:输入1124,计算[1-999]中不重复的数字,0-9组合即可9*9*8 + 9*9 + 9
        int highPositionZeroNotSameCount = calculateHighPositionZeroNotSame(nList.size() - 1);

        // n位不重复的数字,如:输入1124,计算[1000-1124]中不重复的数字
        int fullLengthNotSameCount = calculateFullLengthNotSame(nList);

        return n - highPositionZeroNotSameCount - fullLengthNotSameCount;
    }

    private List<Integer> splitN(int n) {
        List<Integer> nList = new ArrayList<>();
        while (n > 0) {
            nList.add(n % 10);
            n = n / 10;
        }

        List<Integer> nListAsc = new ArrayList<>();
        for (int i = (nList.size() - 1); i >= 0; i--) {
            nListAsc.add(nList.get(i));
        }
        return nListAsc;
    }

    private int calculateHighPositionZeroNotSame(int size) {
        int notSameCount = 9;
        int canChooseNumber = 9;
        // 组合后的数量
        int cCount = 9;
        for (int i = 0; i < size - 1; i++) {
            cCount = cCount * canChooseNumber--;
            notSameCount = notSameCount + cCount;
        }

        return notSameCount;
    }

    private int calculateFullLengthNotSame(List<Integer> nList) {
        int canChooseNumber = 9;
        int notSameCount = 0;
        Set<Integer> nSet = new HashSet<>();
        for (int i = 0; i < nList.size(); i++) {
            int canChooseNumberCount = calculateCanChooseNumber(i, nList, nSet);
            nSet.add(nList.get(i));

            // 若是为0,不需要再组合
            if (canChooseNumberCount == 0) {
                canChooseNumber--;
                continue;
            }

            // 非当前数位的后面可以随机组合的情况
            int chooseNumNotSameCount = permutation(nList.size() - i - 1, canChooseNumber--);
            notSameCount = notSameCount + canChooseNumberCount * chooseNumNotSameCount;
        }

        return notSameCount;
    }

    private int calculateCanChooseNumber(int targetIndex, List<Integer> nList, Set<Integer> nSet) {
        int targetN = nList.get(targetIndex);
        int canChooseNumber = targetN;
        // 若是首位,排除自己和0
        if (targetIndex == 0) {
            return targetN - 1;
        }

        // 不包含当前位前面有重复的
        if (nSet.size() != targetIndex) {
            return 0;
        }

        boolean lastNumber = targetIndex == (nList.size() - 1);
        if (lastNumber) {
            // 若是最后一位还包括自身
            canChooseNumber = canChooseNumber + 1;
        }

        for (int i = 0; i < targetIndex; i++) {
            int currentNum = nList.get(i);
            // 若是最后一位,且和前面的相等,也需要减掉一个坑位,如11
            boolean needSubtract = lastNumber && targetN == currentNum;
            // 减掉前面已存在坑位
            if ((targetN > currentNum) || needSubtract) {
                canChooseNumber--;
            }
        }

        return canChooseNumber;
    }

    private int permutation(int size, int canChooseNumber) {
        if (size == 0) {
            return 1;
        }

        int notSameCount = canChooseNumber;
        for (int i = 0; i < size - 1; i++) {
            notSameCount = notSameCount * (--canChooseNumber);
        }

        return notSameCount;
    }
}

 

https://leetcode-cn.com/problems/numbers-with-repeated-digits/solution/xian-zu-he-bu-zhong-fu-de-ge-shu-zai-yon-eqor/

 

这是10月份面试菜鸟时的一道算法题,当场手撕的话还是有点难度的==,好运哦亲们~

posted on 2021-10-14 13:58  呵呵静  阅读(143)  评论(0编辑  收藏  举报