一、前言
sheepice的刷力扣篇章,虽然现在比较忙,但是如果有空,且写到一些比较有启发的力扣题的时候希望能够发一点点题解,这些题解的灵感来源于我自己看了很多神犇的题解,真的会很有收获。
sheepice的CSDN地址:大家感兴趣也可以去里面,说不定能学到一点东西哦!
二、数位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×3×3×3 3×3×3×3,也就是每个位置上都有3种选择
考虑一种情况,如果考虑 n = 4567 n=4567 n=4567的时候给到的选择为2,4,8,9呢?此时我们发现如果选择4的时候,会有两种选择2,4但是出于考虑,我们并不能直接说第一个位置2,4都能选,因为我们无法保证选完4之后,是否后面的选择能够保证百位上的数比5小,因此在计算第一位的时候,我们必须只能选2,然后后面的三个位置任意选,也就是 1 × 3 × 3 × 3 1×3×3×3 1×3×3×3,那么很多人在这里就不理解,那4如果作为第一位呢?其实他放到了后面的判断中去了,因为当把4前面的2选完以后,如果后面的操作还能进行,就已经固定了千位为4,然后再进行后面结果的积累。
所以最终的结果一定是不断的去更新每一位之后的选择可能性,因为我们已经确定好了位数之前的可能的结果,这也就是正确解答这个题目的一个顺序!当然如果大家不能理解的话,可以跟着后面的代码进行走一遍,然后多举一些例子辅助性的理解!
③情况二的数学描述
跟据情况二的文字描述,下面把它用数学语言再进行描述一遍。假设一个数字为n,这个数字一共的位数为len,题目给我们选择的大小位changes(也就是digits数组的长度)。如果我们此时正在确定该位数的第k位时候的结果(也就是说前k位的结果已经被统计确认好了)。我们首先要找到digits数组中的一个位置l(数组已经转化为int类型了),此位置满足 d i g i t s [ l ] < = n [ k ] digits[l] <= n[k] digits[l]<=n[k](其中n[k]代表第k位代表的数字),会分为三种情况:
-
① d i g i t s [ l ] < n [ k ] digits[l] < n[k] digits[l]<n[k]:说明l位置包括l在内的数都可以填充这个位置,也就是这个位置有 l + 1 l+1 l+1种可能性(别忘了下标是从0开始的),当确定这个位置之后,剩余的位置可以任意的进行选择也就是有 p o w ( l e n − k , c h a n g e s ) pow(len-k,changes) pow(len−k,changes)。所以累加的结果应该为: ( l + 1 ) × p o w ( l e n − k , c h a n g e s ) (l+1)×pow(len-k,changes) (l+1)×pow(len−k,changes),此后的结果,因为已经被全部确认了,所以循环要直接break!!
-
② d i g i t s [ l ] = n [ k ] digits[l] = n[k] digits[l]=n[k]:说明l位置不包括l在内的数都可以填充这个位置(因为l位置后面还是一个未知的结果,应该等到下一次循环再进行累加),也就是这个位置有 l + 1 l+1 l+1种可能性(别忘了下标是从0开始的),当确定这个位置之后,剩余的位置可以任意的进行选择也就是有 p o w ( l e n − k , c h a n g e s ) pow(len-k,changes) pow(len−k,changes)。所以累加的结果应该为: ( l + 1 ) ∗ p o w ( l e n − k , c h a n g e s ) (l+1)*pow(len-k,changes) (l+1)∗pow(len−k,changes)
-
③找不到满足 d i g i t s [ l ] < = n [ k ] digits[l] <= n[k] 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;
}
};