902. 最大为 N 的数字组合 和 2376. 统计特殊整数(数位dp,记忆化搜索模板)

给定一个按 非递减顺序 排列的数字数组 digits 。你可以用任意次数 digits[i] 来写的数字。例如,如果 digits = ['1','3','5'],我们可以写数字,如 '13''551', 和 '1351315'

返回 可以生成的小于或等于给定整数 n 的正整数的个数 。

示例 1:

输入:digits = ["1","3","5","7"], n = 100
输出:20
解释:
可写出的 20 个数字是:
1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77.

示例 2:

输入:digits = ["1","4","9"], n = 1000000000
输出:29523
解释:
我们可以写 3 个一位数字,9 个两位数字,27 个三位数字,
81 个四位数字,243 个五位数字,729 个六位数字,
2187 个七位数字,6561 个八位数字和 19683 个九位数字。
总共,可以使用D中的数字写出 29523 个整数。

示例 3:

输入:digits = ["7"], n = 8
输出:1

提示:

  • 1 <= digits.length <= 9
  • digits[i].length == 1
  • digits[i] 是从 '1' 到 '9' 的数
  • digits 中的所有值都 不同 
  • digits 按 非递减顺序 排列
  • 1 <= n <= 109

解析:

我们要用这些数组中的数字组合成一个小于n的数字;

数位dp模板函数;

f(int i,int is_limit,bool is_num) 

分别表示,第几个为,这个为是否有限制,这个为是否有前导0

class Solution {
public:

    vector digits;
    int calc(int n){
        string s = to_string(n);
        int len = s.size();
        int memo[len];
        memset(memo,-1,sizeof(memo));
        function f = [&](int i,bool is_limit,bool is_num) -> int{
            if(i == len){
                return is_num; //如果有数字合格
            }
            if(!is_limit && is_num && memo[i] != -1){ //这个数为没有限制且后面的数可以随机组合的
                return memo[i];
            }
            int res = 0;//记录结果
            if(!is_num){ //前导为0
                res = f(i+1,false,false);
            }
            char up = is_limit ? s[i]: '9'; // 查看是否有没有限制
            for(int d = 0; d < digits.size();d ++){
                if(digits[d][0] > up){//有没有超出限制
                    break;
                }
                else{
                    res += f(i+1,is_limit&&digits[d][0] == up, true); // 注意还要判断是否个个为都是限制
                    //is_limit&&digits[d][0]== up 还有一个 这个为不为0 就是不是前导为0
                }
            }
            if(!is_limit && is_num) memo[i] = res;//没有限制可以记录 记忆化搜索
            return res;
        };
        return f(0,true,false);
    }
    int atMostNGivenDigitSet(vector& digit, int n) {
        digits = digit;
        return calc(n);
    }
};

2376. 统计特殊整数

如果一个正整数每一个数位都是 互不相同 的,我们称它是 特殊整数 。

给你一个  整数 n ,请你返回区间 [1, n] 之间特殊整数的数目。

示例 1:

输入:n = 20
输出:19
解释:1 到 20 之间所有整数除了 11 以外都是特殊整数。所以总共有 19 个特殊整数。

示例 2:

输入:n = 5
输出:5
解释:1 到 5 所有整数都是特殊整数。

示例 3:

输入:n = 135
输出:110
解释:从 1 到 135 总共有 110 个整数是特殊整数。
不特殊的部分数字为:22 ,114 和 131 

提示:

  • 1 <= n <= 2 * 109

解析:

每个数位互不相同的

可以用二进制快捷的判断是否有用过这个数字

一般的记忆化回溯数组只需要记录几个数字后面合法的数字个数,可是这里有个限制,所以要用二位的数组记录dp[i][mask] ,i表示第i为,mask表示那些数字已被使用过了,其他数字的组合个数。

class Solution {
public:
    int countSpecialNumbers(int n) {
        auto s = to_string(n);
        int m = s.length(), dp[m][1 << 10];
        memset(dp, -1, sizeof(dp));
        function f = [&](int i, int mask, bool is_limit, bool is_num) -> int {
            if (i == m) return is_num;
            if (!is_limit && is_num && dp[i][mask] >= 0) return dp[i][mask];
            int res = 0;
            if (!is_num) res = f(i + 1, mask, false, false); // 可以跳过当前数位
            for (int d = 1 - is_num, up = is_limit ? s[i] - '0' : 9; d <= up; ++d) // 枚举要填入的数字 d
                if ((mask >> d & 1) == 0) // d 不在 mask 中
                    res += f(i + 1, mask | (1 << d), is_limit && d == up, true);
            if (!is_limit && is_num) dp[i][mask] = res;
            return res;
        };
        return f(0, 0, true, false);
    }
};

时间复杂度:O(mD2^D),D为10

你可能感兴趣的:(动态规划,动态规划,算法)