力扣233(java)-数字1的个数(困难)


题目:

给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。

 

示例 1:

输入:n = 13
输出:6
示例 2:

输入:n = 0
输出:0
 

提示:

0 <= n <= 109

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/number-of-digit-one
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路:

这个题是真的很打脑壳啊~参考【 liweiwei1419】和 【2021293707L2】的题解,谢谢大佬!

主要运用数位dp的思路,采用【自底向上】的递推求解,实际是【动态规划】关于数位的入门问题。

题目的意思是:在1~n 的所有整数中出现1的个数,不是整数的个数,而是整数中包含的1的个数!

例如:15

包含1的整数有:1, 10, 11, 12, 13, 14, 15, 一共有 1 + 1 + 2  + 1 + 1 + 1 + 1 = 8 ,所以1~15的所有整数中一共包含8个1。

数位:是把一个数按照个位、十位、百位拆开来看。因此本题就可以拆分为:个位出现1的个数、十位出现1的个数、百位出现1的个数…

定义状态1:A[i] 表示0~10i+1-1的所有i+1位数里1的总数。

例如:

  • 0~9 所有的 1 位数里的1的总数;
  • 0~99 所有的 2 位数里的1的总数;
  • 0~999所有的 3 位数里的1的总数;
  • ……
  • 0~999999所有的 6 位数里的1的总数;

  • A[0] =1,0~9所有的1位数里含有的1的个数只有1个;
  • A[1]考虑的范围为0~99所有两位数中含有的1的个数,分类讨论:
    • 个位数固定为1( _1 ),十位数可能为0~9(01,11,21,31,41,51,61,71,81,91),一共10种可能,十位为1这个1先不统计,这种情况下的结果是 10 * A[0];
    • 十位数固定为1,个位数为0~9(10,11,12,13,14,15,16,17,18,19),一共10种可能,这时候把十位为1这个1统计到了,这种情况下的结果是10种。因此A[1]= 10 * A[0] +101
  • A[2]考虑的范围为0~999所有的三位数中含有的1的个数,分类讨论:
    • 最高位是0,剩下的是0~99,即为A[1], 最高位是1,剩下的是0~99即为A[1],…..,最高位是9,剩下是A[1],这里也暂时不统计最高位的这个1,一共是 10*A[1];
    • 最高位固定为1,十位,个位为0~99,一共100个。因此A[2]= 10 * A[1] +102

根据上述规律,总结归纳出:A[i]= 10 * A[i -1 ] +10i

定义状态2:dp[i]表示:从右向左数截取到第 i + 1位到最后一个数组成的数字里面包含1的总个数。

例如:2875

  • dp[0]表示截取到1位数4中(1~4)所有数里1的总数;
  • dp[1]表示截取到2位数75中(1~75)所有数里1的总数;
  • dp[2]表示截取到3位数875中(1~875)所有数里1的总数;
  • dp[3]表示截取到4位数2875中(1~2875)所有数里1的总数;

因此从右向左遍历 给定整数n的每一位 i ,假设当前遍历到的数是m(0 <= m <= 9),根据遍历到的数值进行分类讨论:

dp[0]代表的是个位数包含的1的个数,如果m =0,表示个位数为0即dp[0] == 0,当m==0~9时,这10个数只有一个1即dp[0] == 1

  • 当m == 0时,此时的状态值取决于m右边一位的状态值,dp[ i ] = dp[ i-1 ];
  • 当m == 1时,例如166:
    • 首先:求出0~66中元素1的个数,即 dp[ i-1 ]
    • 其次:求出100~166中元素1的个数,最高位是1,只需要看他右边的数,即共有67个(100、101、……、166)
    • 最后:求出0~99中元素1的个数即为A[i-1](注意:这里我还想过会和首先:0~66重合,其实不会,因为166分为三批次:0~99小于100的元素1的个数,100~166首位为1的三位数,0~66这里是算100以上的数中1的个数)。
  • 当m > 1时,例如465:
    • 首先:最高位不是1,看它后一位的状态值即dp[i-1],0~65中元素1的个数;
    • 其次:最高位是1,数出最高位是1的元素的个数即 10i,100~199中元素1的个数;
    • 最后:算其他模块中包含1的个数即m* A[i-1],465中0~99,100~199(与其次不是同一种),200~299,300~399各模块中包含的1的个数,即4 * A[1]。

代码:

 1 class Solution {
 2     public int countDigitOne(int n) {
 3         //将整数转换成字符串
 4         String s = String.valueOf(n);
 5         char[] ca = s.toCharArray();
 6         int m = s.length();
 7         //如果只有个位数则只包含一个1
 8         if(m == 1){
 9             return n == 0 ? 0 : 1;
10         }
11         //求状态1:A[i]: 0~10^(i+1) -1包含1的个数
12         //0~9,0~99,0~999
13         //如果是5位数,只需要求0~9999中出现1的个数
14         int[] A = new int[m-1];
15         // 0~9
16         A[0] = 1;
17         for(int i = 1; i < m-1; i++){
18             A[i] = 10 * A[i-1] + (int) Math.pow(10, i);
19         }
20         //求状态2:dp[i]
21         int[] dp = new int[m];
22         //如果最后一位为0
23         if(ca[m - 1] == '0'){
24             dp[0] = 0;
25         }else{
26         //最后一位不为0,即为0~9
27           dp[0] = 1;
28         }
29         for(int i = 1; i < m; i++){
30             //从右向左截取每一数位
31             char currChar = ca[m - i - 1];
32             if (currChar == '0'){
33                 //当前位为0,状态值取决于它右边的1的个数
34                 dp[i] = dp[i - 1];
35             } else if (currChar == '1'){
36                 //最高位为1,分为三个步骤相加
37                 //例如166,其次:求出100~166中元素1的个数
38                 int res = Integer.parseInt(s.substring(m - i, m)) + 1;
39                 //166中0~66,和小于100d 0~99中1的个数
40                 dp[i] = res + dp[i-1] + A[i-1];
41             }else{
42                 //最高位大于1的情况,例如465
43                 //最高位不是1个数,dp[i-1]
44                 //最高位是1,即为10^i
45                 //算其他模块:m * A[i-1]
46                dp[i] = (currChar - '0') * A[i-1] + dp[i-1] + (int) Math.pow(10,i);
47             }
48         }
49         return dp[m - 1];
50     }
51 }

力扣233(java)-数字1的个数(困难)

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/280470.html

(0)
上一篇 2022年8月14日
下一篇 2022年8月14日

相关推荐

发表回复

登录后才能评论