数位DP,一般给定一定数值范围内然后求满足某个条件的总数,就可以用此方法了。
以这题来说,就是将数位拆分为各个位数存入一个数组。因为找存在重复数字的比较麻烦(2,3,4个重复数字啥啥的),就转换为找不重复的。
在[0,N]之间,低于N最高位数的数字(最高位取0),所有数字范围是可以任选的。那么就可以按位用排列来做Anm
而取到最高位就需要根据下一位来判断了,又可以分为两种情况,上面那种情况和现在这种情况,不过唯一不同的是,不允许使用重复的数字(因为前面数字已经固定了所以选择范围就不一样了)。
而且需要注意的是第二种情况下,最高位不能为0,要不然就重复计算了一遍An-110-1
class Solution {
public int numDupDigitsAtMostN(int N) {
int[] f = Arrays.stream(new StringBuilder(String.valueOf(N)).reverse().toString().split("")).mapToInt(Integer::parseInt).toArray();
int count = 0;
for(int i=0;i<f.length-1;i++){
count += 9*A(i,10-1);
}
boolean[] num = new boolean[10];
for(int j=f.length-1;j>=0;j--){
int n = (int) IntStream.range(0,f[j]).filter(i->num[i]).count();
count += (f[j]-n)*A(j,10-f.length+j);
if(num[f[j]]){
break;
}
num[f[j]] = true;
if(j==0){
count++;
}
}
return N-count+A(f.length-1,10-1);
}
private int A(int i,int j){
if(i==0){
return 1;
}
return j*A(i-1,j-1);
}
}
因为需要仅包含数字1,如果存在,K必然是个奇数而且不能为5,那可以尝试用1,11,111…,11111111…去除它,不过这会很快就超出int的最大值。除了2,5之外,所有的状态都是不会重复的而1~K-1的状态又是有限的,所以必然会存在结果。
上图证明来源
class Solution {
public int smallestRepunitDivByK(int K) {
if(K%2==0||K%5==0){
return -1;
}
int x = 1,len = 1;
while(x%K!=0){
x %= K;
x = x*10+1;
len++;
}
return len;
}
}
要求<=N(位数为n),所以我们就可以求用这些数字来组成N,这题简直是简易版的第一题。
分两种情况:在最高位为0的情况下,所有的数字,都可以使用,任意的位数都可以,而且还能重复使用,那每一位作为最高位的可能的总和就是 Σi n-1数组长度i 。
第二种情况,使用最高位,那么又分为两种情况;小于N最高位的情况,本次和为 (最高位值-1)*数组长度n-1 。如果没有恰好与N最高位相等的数,就没有然后了,如果有,则向下找下一位数,重复第二种情况的流程。
class Solution {
public int atMostNGivenDigitSet(String[] D, int N) {
int[] f = Arrays.stream(new StringBuilder(String.valueOf(N)).reverse().toString().split("")).mapToInt(Integer::parseInt).toArray();
Set<Integer> d = Arrays.stream(D).mapToInt(Integer::parseInt).boxed().collect(Collectors.toSet());
int len_f = f.length,len_d = d.size();
int[] f2 = new int[len_f];
f2[0] =1;
int ans = 0;
for(int i=0;i<len_f-1;i++){
f2[i+1] = f2[i]*len_d;
ans += f2[i+1];
}
for(int i=len_f-1;i>=0;i--){
int n = find(d,f[i]);
ans += n*f2[i];
if(!d.contains(f[i])){
break;
}
if(i==0){
ans++;
}
}
return ans;
}
private int find(Set<Integer> d,int t){
return (int) d.stream().filter(i->i<t).count();
}
}
数位DP嘛,首先拆分位数,范围就是[1,n]。
到最高位前
从第一位开始,如果为1,一个,不为1,0种。
第二位,为1,9个+12个,不为1,9一个。
第三位,为1,99个+9个2+13个,不为1,9(9+1*2+1)。
…
如果最高位:
最高位恰为1,第二高位大于1:Σk【1,n-2】 9k-1 *(i-k+2)+个数 *2+dp[n-2].
若第二高位等于0则判断第三位(…直到某位大于1或者全部遍历完为止。
最高位大于1,最高位为1:Σk【1,n-1】 9k-1 *(i-k+1),最高位不为1,dp[n-1]
dp[i]=位数为i时的总个数
低位
分成两种情况:
1.某位为1:dp[i] = 9i-1 +9i-2 *2+9i-3*3… = Σk【1,i】 9k-1 (i-k+1)
2.某位不为1:dp[i] = 9dp[i-1];
最高位:
若最高位恰为1,则判断下一位是否大于1,
啊终于有点动态规划的感觉了。
算了坑太多了,换种思路。
看了下数位dp的模板。有点妙啊。
用的深搜,自顶向下的搜索,刚才在理思路时,俺就在想,这玩意好多思路可以用深搜来整呀。
dp[i][j]的含义就是,当位数最高位为i时,有j个1的情况下一共有几个1。
首先我们知道,如果当前位数不是最高位时,数字的选择是不受限制的,也就是说0~9可以自由选择。而最高位只能为 最高位的值 ~ 0.
然后用深搜来选择它的各个分支(用什么数来填充p位),就选择0~允许到的值,如果填充p位的为1,则当前数字中有的1的数量+1.
如果是最高位的最大值时,这时,就会受到限制,然后将限制转移到下一个分支,然后再到当前位数的最大值时,再将限制转移到下一分支。
黑色箭头啥也不表示,就是单纯表示可构成某数(其他没画箭头的连接的就当我画了吧哈哈),红色箭头就是dp[0][1]=1,蓝色箭头是dp[1][1] =1 ,蓝红那条就是dp[1][2] = 2
class Solution {
private int[] f;
private int[][] dp;
private int len;
public int countDigitOne(int n) {
if(n<=0){
return 0;
}
f = Arrays.stream(String.valueOf(n).split("")).mapToInt(Integer::parseInt).toArray();
dp = new int[f.length+1][f.length+1];
for(int[] d:dp)Arrays.fill(d,-1);
len = f.length;
return dfs(0,true,0);
}
private int dfs(int p,boolean limit,int count){
if(p==len)
return count;
if(!limit&&dp[p][count]!=-1)
return dp[p][count];
int sum =0;
int max = limit?f[p]:9;
for(int i=0;i<=max;i++){
sum+=dfs(p+1,limit&&(i==max),count+(i==1?1:0));
}
if(!limit)
dp[p][count] = sum;
return sum;
}
}