leetcode902.最大为N的数字组合(数位dp)

 思路

首先我们先理解一下题意,这道题就是让我们在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;
    }
};

也可以去力扣支持一下我,力扣题解链接如下

. - 力扣(LeetCode)

  • 29
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值