力扣刷题5


一、前言

sheepice的刷力扣篇章,虽然现在比较忙,但是如果有空,且写到一些比较有启发的力扣题的时候希望能够发一点点题解,这些题解的灵感来源于我自己看了很多神犇的题解,真的会很有收获。

sheepice的CSDN地址:大家感兴趣也可以去里面,说不定能学到一点东西哦!

二、数位dp初刷

组合题dp

三、思路

这题其实如果用我们高中学的组合的思想,可以做。因为他本身就是一个组合的题目,也就是说,最简单的思路就是,分析不同位数情况下构成的小于n的情况。所以很容易得到,一共只有两种情况

  • 情况一:构成的数字比要求数字的位数小
  • 情况二:构成的数字比要求数字的位数大

①情况一

对于情况一来说,我们很容易进行分析,就是假设当前需要构成的位数为i,我们可以选择的数就是digits数组的长度,因此这种情况的结果累加的代码比较简单,直接给出:

//位数不相同的情况
//m代表的是数字n的位数一共有多少位
//m - 1就可以满足i所代表的位数一定小于n所代表的位数
        for(int i = 1; i <= m - 1; i++) {
            ans += pow(lent,i);
        }

②情况二的文字描述

对于情况二来说,由我们高中的知识就可以知道,我们可以依次的去比较每一位数在原digits的位置(由于数组是一个递增的情况),所以当我们找到某个位置的时候,我们就能确定在位置之前的数,都是小于本位置,也就是可以进行一个选择。但是在这个地方,我们要知道不能完全依赖于这样的思想去写题目。

就是假设我们现在选择到了4567的其中某一位且一定要从高位选起(也就是先选2所对应的位置),这是为什么呢。比如我们给到的选择为1,2,3我们会发现在选择第一个高位的时候,无论我们选择什么,都能满足千位的数一定比4小,那根据乘法原理,很显然所有的答案其实就是$3×3×3×3$,也就是每个位置上都有3种选择

考虑一种情况,如果考虑$n=4567$的时候给到的选择为2,4,8,9呢?此时我们发现如果选择4的时候,会有两种选择2,4但是出于考虑,我们并不能直接说第一个位置2,4都能选,因为我们无法保证选完4之后,是否后面的选择能够保证百位上的数比5小,因此在计算第一位的时候,我们必须只能选2,然后后面的三个位置任意选,也就是$1×3×3×3$,那么很多人在这里就不理解,那4如果作为第一位呢?其实他放到了后面的判断中去了,因为当把4前面的2选完以后,如果后面的操作还能进行,就已经固定了千位为4,然后再进行后面结果的积累。

所以最终的结果一定是不断的去更新每一位之后的选择可能性,因为我们已经确定好了位数之前的可能的结果,这也就是正确解答这个题目的一个顺序!当然如果大家不能理解的话,可以跟着后面的代码进行走一遍,然后多举一些例子辅助性的理解!

③情况二的数学描述

跟据情况二的文字描述,下面把它用数学语言再进行描述一遍。假设一个数字为n,这个数字一共的位数为len,题目给我们选择的大小位changes(也就是digits数组的长度)。如果我们此时正在确定该位数的第k位时候的结果(也就是说前k位的结果已经被统计确认好了)。我们首先要找到digits数组中的一个位置l(数组已经转化为int类型了),此位置满足$digits[l] <= n[k]$(其中n[k]代表第k位代表的数字),会分为三种情况:

  • ①$digits[l] < n[k]$:说明l位置包括l在内的数都可以填充这个位置,也就是这个位置有$l+1$种可能性(别忘了下标是从0开始的),当确定这个位置之后,剩余的位置可以任意的进行选择也就是有$pow(len-k,changes)$。所以累加的结果应该为:$(l+1)×pow(len-k,changes)$,此后的结果,因为已经被全部确认了,所以循环要直接break!!

  • ②$digits[l] = n[k]$:说明l位置不包括l在内的数都可以填充这个位置(因为l位置后面还是一个未知的结果,应该等到下一次循环再进行累加),也就是这个位置有$l+1$种可能性(别忘了下标是从0开始的),当确定这个位置之后,剩余的位置可以任意的进行选择也就是有$pow(len-k,changes)$。所以累加的结果应该为:$(l+1)*pow(len-k,changes)$

  • ③找不到满足$digits[l] <= n[k]$条件的数:直接break!

四、AC代码

综上所述,最后把代码全部贴出来,仅供参考。

class Solution {
public:
    int atMostNGivenDigitSet(vector<string>& digits, int n) {
        int lent = digits.size();
        vector<int> nums(lent, 0);
        for(int i = 0; i < lent; i++) {
            nums[i] = digits[i][0] - '0';
        }
        long long ans = 0;
        //求出n的位数以及每一位数字上的数
        vector<int> nmb;
        while(n != 0) {
            nmb.push_back(n % 10);
            n /= 10;
        }
        int m = nmb.size();   //一共的位数
        //位数不相同的情况
        for(int i = 1; i <= m - 1; i++) {
            ans += pow(lent,i);
        }
        //位数相同的情况
        for(int i = m - 1, p = 1; i >= 0; i--, p++) {
            //因为数组的递增,可以用二分去寻找位置
            //先找到小于等于此位数的第一个位置
            //当然其实因为数组的常量比较小,直接循环找也没有问题的
            int l = -1, r = lent;
            while(l + 1 != r) {
                int mid = (l + r) >> 1;
                if(nums[mid] > nmb[i]) r = mid;
                else l = mid;
            }
            if(l == -1) break;
            else if(nums[l] == nmb[i]) {
                ans += l * pow(lent, m - p);
                //如果就连最后一位都相等的时候,一定要记得加上与本身完全相同的那一个结果
                //因为循环后不会再继续探索后面的结果了
                //这里可以举一些例子帮助理解
                if(i == 0) ans++;
            }
            else {
                ans += (l + 1) * pow(lent, m - p);
                //已经找到了所有结果了记得break
                break;
            }
        }
        return ans;
    }
};

文章作者: sheepice
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 sheepice !
  目录