leetcode1015 至少有1位重复的数字

题目链接: https://leetcode-cn.com/problems/numbers-with-repeated-digits/

题目内容:给定正整数 N,返回小于等于 N 且具有至少 1 位重复数字的正整数。

思路:

看到题目,求范围内出现某类数字的个数,思路应该是数位dp的方向。

关于数位dp是什么,这篇博客十分清楚地介绍了:https://blog.csdn.net/wust_zzwh/article/details/52100392

题目要求得出范围内至少有1位重复的个数,采用逆向的想法就是 先求出 范围内每位的数字相互不重复的个数 ss,用总数N - ss(总数减掉每位数字不重复的情况) 就可以得到我们的结果;

①状态dp[i][j]: i表示当枚举到数字的 第 i 位,j表示在 第i位之前出现过的所有数字(比如前面出现过7、8、 9三个数字) ,

                     dp[i][j] 表示枚举到第 i 位,前面状态为 j 时,没有出现重复数字的数字个数。

          要用1个数字 j 表示前面出现过7,8,9这三个数字,这里用到了状态压缩,只是简单应用,比如在第i位之前 前面出现过 7 8 9 ,

          那么 j 就是  (1<<7) | (1<<8) | (1<<9) = 128 + 256 + 512 = 896 , 这样我们用一个数字就可以表示前面 出现过的数字。

②状态转移: 如果当前第 i位 的数字是x[i],if((x[i]  & j)  != 0) {就说明了前面出现过了这个数字x[i],不进行递归},else {否则 递归 查找下一位 }。

③代码:

class Solution {
        int pow[] = new int []{ //pow[i] 表示 (1<<i) 的结果
            1,2,4,8,16,32,64,128,256,512    
        };
        int x[] = new int[20+1];
        int dp[][] ; // dp[i][j] 表示枚举到第i位,前面的状态是j,没有出现过重复数字的数目
        int dfs(int pos,int pre,boolean limit,boolean flag) 
        //枚举到pos位,前面的状态是pre,limit表示是不是上限,flag表示是否前缀0
        {
            if(pos == -1) {
                if(flag){//全是0,直接返回0
                    return 0;
                }
                return 1;//前面不全是0,走到了最后,说明每一位数字都不同,返回1
            }
            if(!limit && dp[pos][pre]!=-1) return dp[pos][pre];
            
            int up = limit?x[pos]:9;
            int rs = 0;
            
            for(int i=0;i<=up;i++)
            {
                if(!flag && (pre & pow[i])!=0){ //不是前缀零,那么出现重复就不往后面走
                    continue;
                }
                int tmp = 0;

     //因为前缀0所以判断,1如果不是前缀0,叠加状态,2如果前缀是0,当前位也是0,直接传递 0 状态,最后返回dp[0][0].
                if(!(flag && i==0)){
                    tmp = pre | pow[i];
                }
                //递归下一位数字
                rs = rs + dfs(pos-1,tmp,limit&&i==up,flag && i==0);
            }
            if(!limit){
                dp[pos][pre] = rs;
            }
            return rs;
        }
        int solve(int n)
        {
            int pos = 0;
            while(n>0)
            {
                x[pos++] = n%10; 
                n = n/10;
            }
            return dfs(pos-1,0,true,true);
        }
        public int numDupDigitsAtMostN(int N) {
            int sum = 0;
            for(int i=0;i<pow.length;i++)
            {
                sum = sum + pow[i];
            }
            dp = new int [10+1][sum+1]; 
            //初始化数组
            for(int i=0;i<dp.length;i++)
                for(int j=0;j<dp[i].length;j++)
                    dp[i][j] = -1;
            //最原始的,只有1位数字的情况, 1~9一共是9种。
            dp[0][0] = 9;
            return N-solve(N);//总数 - 没有出现重复数字的个数 = 出现重复位的个数。
        }
    }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值