思路
首先我们先理解一下题意,这道题就是让我们在digits数组上任意选择若干个数字(同一个数字可以选无数次),将这些选出来的数字有顺序地拼接在一起形成一个整数,问这样拼接的整数小于或等于n的有多少个。举个例子,例如digits = ["1","3","5","7"], n = 300;我可以选出两个"1",一个“3”,“5”和“7”我可以不选,或者说选零个,那么这样我拼接起来的数字就可以是“113”,“311”,“131”,满足<= 300的就有两个;我也可以只选一个“7”或者一个“5”,只选一个拼接起来的数字就是个位,也符合该例子的要求(7 < 300且5 < 300),当然满足该例子的整数远不止这些。那么如何求出所有整数的个数呢?
我们可以认识到如果一个拼接的数字的位数大于n,那么它肯定不符合要求,也就是说,拼接的整数要满足要求的第一个条件就是位数要小于等于n的位数。依据这一条件,我们可以利用像01背包那样选与不选的思想。假设n为300,那么会有三个空箱子(空箱子的个数取决于n的位数),里面装或者不装digits上的数字,装或不装是分别两种不同的情况,把这两种不同的情况求出的结果相加就是我们最终所要求得的结果。
解题方法
第一步
我们构造出选与不选的递归函数,接下来就是确定递归函数里面的参数
(1)我们要确定递归函数遍历到哪个箱子,需要len参数,len表示接下来还有len个箱子需要遍历。
(2)我们再进一步分析问题,我们从高位的箱子遍历到低位的箱子进行选择,在递归遍历的过程中,如果前一个箱子已经装了数字,那么我目前遍历到的箱子是不是就必须得装数字,不能不装(这还能不装的?题目已经说明给的数据digits数组里面没有0这一个数字,所以不能架空,必须装),那如何判断我前面的箱子装没装数字呢,那我就引入一个递归函数的参数fix来表示。
(3)装不装数字确定了,那装哪一个数字呢?例如n是4876,目前我遍历到第二个箱子,假如第一个箱子我装的数字小于4,那么后面的箱子(包括目前遍历到的箱子)是不是随便装digits数组上哪一个数字都行,这时候就可以停止遍历,直接返回答案。那如果我第一个箱子等于4呢,那说明我接下来的箱子不可以随便选,得遍历digits数组看哪一些数字可以选。那如果我第一个箱子大于4呢,不好意思,我们不允许这种情况发生,大于4后面怎么选都无法满足情况,直接在递归函数pass掉。如何确定当前及后面的箱子是否可以随便选呢,我们引入free参数。
至此,递归函数的参数就确定下来了。
第二步
写出递归函数并表明各参数的意义
int f(vector<int>& digits, int num, int len, int free, int fix)
//digits数组这个不用解释,由于题目给的是string类型,预处理是会把它处理成int类型的,方便后续比较
//num就是n
//len表示接下来需要遍历的箱子的个数
//free表示当前及后面的箱子是否可以自由选择数字。(1)free == 1,可以自由选择 (2)free == 0,不可以自由选择
//fix表示前一个箱子是否有装数字。(1)fix == 0,前一个箱子并没有装数字(2)fix == 1,前一个箱子装了数字
//整个f函数的意思是在剩余len个箱子的情况下,可以拼成<=符合num后len位的数字有几个并返回
第三步
确定递归终止条件(这个尤为重要) 我们知道,每遍历完一个箱子,剩余需要遍历箱子的个数都会减少,也就是说len会递减,那么当遍历完最后一个箱子的时候,len 就会等于 0,这个时候意味着什么?,如果这时候fix == 0,那么从头到尾就没有装数字,返回0。如果fix == 1,那么一个符合要求的整数就此生成,返回1。 所以,终止条件这样写
if (len == 0) {
return fix == 1 ? 1 : 0;
}
//可以简写成
if (len == 0){
return fix;
}
//两种写法都行,第一种更能体现出逻辑,第二种更简便
第四步
写出剩下递归函数的整体 (1)在当前的箱子中不选数字,只有在fix == 0的情况下这种情况才可以满足。不选数字意味着接下来的位数会比n小,那么就可以自由选数字,所以free为1,不选数字fix就为0。 (2)在当前的箱子中选数字。fix就一定是1了。 (1)当free为1时,递归函数不需要再调用了,digits数组的个数为size,剩下的箱子(包括当前遍历的箱子)为len,返回答案size的len次方即可。 (2) 当free为0时,不可以自由选数字,取出当前箱子与num比较的位的数字cur,遍历digits数组,进行digits[i] 与 cur的大小比较。
int ans = 0;
// 第一个选择:不选,但是不选只有前面不选才能不选
if (fix == 0) {
ans += f(digits, num, len - 1, 1, 0);
}
// 第二个选择:选
if (free == 1) {
ans += pow(digits.size(), len);
} else {
int temp = pow(10, len - 1);
int cur = num / temp % 10;
for (int i : digits) {
if (i < cur) {
ans += f(digits, num, len - 1, 1, 1);
} else if (i == cur) {
ans += f(digits, num, len - 1, 0, 1);
} else {
break;//哈哈,这里就体现出来上一个箱子如果比与num相应的位大的话,直接pass掉
}
}
}
预处理
vector<int> digit(digits.size(), 0);
for (int i = 0; i < digits.size(); i++) {
digit[i] = stoi(digits[i]);
}
int len = 0;
int tmp = n;
//计算n的位数,用len表示
while (tmp) {
len++;
tmp /= 10;
}
如何在主函数里面调用递归函数呢?我们可以看成最高位前面的位都是0,也就是说是最高位前一位是空箱子,且遍历最高位的时候不可以随便选,于是乎,free = 0,fix = 0。
最后整合的代码
可以拿它取AC哈哈哈哈
class Solution {
public:
int f(vector<int>& digits, int num, int len, int free, int fix) {
if (len == 0) {
return fix == 1 ? 1 : 0;
}
int ans = 0;
// 第一个选择:不选,但是不选只有前面不选才能不选
if (fix == 0) {
ans += f(digits, num, len - 1, 1, 0);
}
// 第二个选择:选
if (free == 1) {
ans += pow(digits.size(), len);
} else {
int temp = pow(10, len - 1);
int cur = num / temp % 10;
for (int i : digits) {
if (i < cur) {
ans += f(digits, num, len - 1, 1, 1);
} else if (i == cur) {
ans += f(digits, num, len - 1, 0, 1);
} else {
break;
}
}
}
return ans;
}
int atMostNGivenDigitSet(vector<string>& digits, int n) {
vector<int> digit(digits.size(), 0);
for (int i = 0; i < digits.size(); i++) {
digit[i] = stoi(digits[i]);
}
int len = 0;
int tmp = n;
while (tmp) {
len++;
tmp /= 10;
}
int ans = f(digit, n, len, 0, 0);
return ans;
}
};