902.最大为N的数字组合
我们有一组排序的数字 D,它是 {‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’} 的非空子集。(请注意,‘0’ 不包括在内。)
现在,我们用这些数字进行组合写数字,想用多少次就用多少次。例如 D = {‘1’,‘3’,‘5’},我们可以写出像 ‘13’, ‘551’, ‘1351315’ 这样的数字。
返回可以用 D 中的数字写出的小于或等于 N 的正整数的数目。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/numbers-at-most-n-given-digit-set
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
一道典型的数位动态规划问题,掌握了数位动态规划思想和模板可以秒杀。
这道题有两点需要注意:首先是需要引入前导0,因为0不在可枚举数字的范围内,但是为了满足数字位数要求,0又是必须的;其次是上界的安全,因为枚举的数字是1到9的子集,所以枚举的时候只能在子集中枚举,这里的代码采用先枚举再验证的写法;最后是不要钻牛角尖,这里limit的判断与模板一样,这里很难表达,建议读者自己思考。
class Solution {
private int[][][] dp = null;
private int[] upperBound = null;
private List<Integer> list = null;
public int atMostNGivenDigitSet(String[] digits, int n) {
list = new ArrayList<>();
for(String digit : digits) {
list.add(Integer.valueOf(digit));
}
String string = String.valueOf(n);
char[] chars = string.toCharArray();
int length = chars.length;
upperBound = new int[length];
for(int i = 0; i < length; ++i) {
upperBound[i] = chars[length - 1 - i] - '0';
}
dp = new int[length][2][2];
for(int i = 0; i < length; ++i) {
for(int j = 0; j < 2; ++j) {
Arrays.fill(dp[i][j], -1);
}
}
return this.dfs(length - 1, 1, 1);//最高位数字默认有前导0
}
private int dfs(int pos, int limit, int zero) {
//0不在可选数字范围,但是为了补足数字位数,需要前导0
if(pos == -1) return (zero == 1) ? 0 : 1;
if(dp[pos][limit][zero] != -1) return dp[pos][limit][zero];
int maxNum = (limit == 1) ? upperBound[pos] : 9;
int res = 0;
for(int i = 0; i <= maxNum; ++i) {
if(zero == 1 && i == 0) {
//当当前第pos位数字有前导0,且当前第pos位数字为0时,第pos-1位数字才有前导0
res += dfs(pos - 1, (limit == 1 && i == maxNum) ? 1 : 0, 1);
}
else if(list.contains(i)) {
//这里的条件判断保证了上界的安全,需要理解,难以表达
res += dfs(pos - 1, (limit == 1 && i == maxNum) ? 1 : 0, 0);
}
}
return dp[pos][limit][zero] = res;
}
}