每日算法——数位DP

在这里插入图片描述
数位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] = 9
dp[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;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值