以上部分不再赘述,可参考前篇:
数位DP 详解及其案例实战 [模板+技巧+案例]
编写一个方法,计算从 0 到 n (含 n) 中数字 2 出现的次数。
输入: 25
输出: 9
解释: (2, 12, 20, 21, 22, 23, 24, 25)(注意 22 应该算作两次)
class Solution {
char[] ch;
int[][] memo;
public int numberOf2sInRange(int n) {
ch = Integer.toString(n).toCharArray();
memo = new int[ch.length][ch.length];
for(int[] arr : memo){
Arrays.fill(arr , -1);
}
return dfs(0,0,true);
}
public int dfs(int pos, int ctTwo, Boolean isLimit){
if(pos == ch.length) return ctTwo;
if(!isLimit && memo[pos][ctTwo] != -1) return memo[pos][ctTwo];
int up = isLimit ? ch[pos] - '0' : 9;
int ans = 0;
for(int d = 0; d <= up; d++){
ans += dfs(pos+1,ctTwo + (d == 2 ? 1 : 0), isLimit && d == up);
}
if(!isLimit) memo[pos][ctTwo] = ans;
return ans;
}
}
本题小结:(1)此题和 leetcode233 数字 1 的个数 属于同一类题目,不再赘述
给定一个按 非递减顺序 排列的数字数组 digits 。你可以用任意次数 digits[i] 来写的数字。例如,如果 digits = ['1','3','5'],我们可以写数字,如 '13', '551', 和 '1351315'。
返回 可以生成的小于或等于给定整数 n 的正整数的个数 。
输入: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.
输入:digits = ["1","4","9"], n = 1000000000
输出:29523
解释:
我们可以写 3 个一位数字,9 个两位数字,27 个三位数字,
81 个四位数字,243 个五位数字,729 个六位数字,
2187 个七位数字,6561 个八位数字和 19683 个九位数字。
总共,可以使用D中的数字写出 29523 个整数。
class Solution {
char[] ch;
int[] memo;
String[] digits;
public int atMostNGivenDigitSet(String[] digits, int n) {
this.digits = digits;
ch = Integer.toString(n).toCharArray();
memo = new int[ch.length];
Arrays.fill(memo,-1);
return dfs(0,true,true);
}
public int dfs(int pos, boolean isLimit, boolean swap){
if(pos == ch.length) return swap ? 0 : 1;
if(!isLimit && !swap && memo[pos] != -1) return memo[pos];
int ans = 0;
if(swap){
ans += dfs(pos+1, false, true);
}
int up = isLimit ? ch[pos] : '9';
for(int i = 0; i < digits.length; i++){
if(digits[i].charAt(0) > up) break;
ans += dfs(pos+1, isLimit && digits[i].charAt(0) == up, false);
}
if(!isLimit && !swap) memo[pos] = ans;
return ans;
}
}
本体小结:(1)此题和前面的题目最大不同就是数字的选择是固定的,不是从1~9选择
(2)由于是符合实际的数字,那么swap标志位是需要的
(3)isLimit位和swap共同决定了缓存的存储问题
(4)此题的缓存只有一维,不需要记录前面有没有用的特殊的数字
给定一个正整数 n ,请你统计在 [0, n] 范围的非负整数中,有多少个整数的二进制表示中不存在 连续的 1 。
输入: n = 5
输出: 5
解释:
下面列出范围在 [0, 5] 的非负整数与其对应的二进制表示:
0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
5 : 101
其中,只有整数 3 违反规则(有两个连续的 1 ),其他 5 个满足规则。
class Solution {
char[] ch;
int[][] memo;
public int findIntegers(int n) {
ch = Integer.toBinaryString(n).toCharArray();
memo = new int[ch.length][2];
for(int[] i : memo){
Arrays.fill(i,-1);
}
return dfs(0,true,false);
}
public int dfs(int pos, boolean isLimit, boolean isOne){
if(pos == ch.length) return 1;
int intOne = isOne ? 1 : 0;
if(!isLimit && memo[pos][intOne] != -1) return memo[pos][intOne];
int ans = 0;
int up = isLimit ? ch[pos] - '0' : 1;
for(int d = 0; d <= up; d++){
if(isOne && d == 1) continue;
ans += dfs(pos+1, isLimit && d == up, d==1);
}
if(!isLimit) memo[pos][intOne] = ans;
return ans;
}
}
(1)数字取值范围不再是0~9,而是0~1,相应得上限也从9变成1
(2)由于二进制遍历只有两种可能,那么相应的遍历也可以根据题目简化,在本题中for循环可以写成{Ref【1】}:
var res = f(i + 1, false, isLimit && up == 0); // 填 0
if (!pre1 && up == 1) res += f(i + 1, true, isLimit); // 填 1
本体小结:(1)重点需要保存上一个取值取得是不是1,若上一次取1且当位置又取了1,那么本轮得结果不符合题目要求,直接跳过
(2)备忘录里是来到第pos个位置,取值是1还是0得两种可能性的备忘录
(3)由于前导0对本题无影响,所以swap并不需要(取了前导0不影响实际数值,因为是二进制)
【1】leetcode 灵茶山艾府 数位 DP 通用模板,附题单(Python/Java/C++/Go)