题目链接:https://leetcode.cn/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/
1. 题目介绍(43. 1~n 整数中 1 出现的次数)
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
【测试用例】:
示例1:
输入:n = 12
输出:5
示例2:
输入:n = 13
输出:6
【条件约束】:
限制:
- 1 <= n < 2^31
【跟踪】:
注意:本题与主站 233. 数字 1 的个数 题目相同。
2. 题解
2.1 暴力枚举 – O(nlogn)
时间复杂度O(nlogn),空间复杂度O(1)
计算机解决问题其实没有任何奇技淫巧,它唯一的解决办法就是穷举,穷举所有可能性。算法设计无非就是先思考“如何穷举”,然后再追求“如何聪明地穷举”。
【解题思路】:
累加1~n
中每个整数1
出现的次数。我们可以通过每次对10
求余判断整数的个位数字是不是1
。如果这个数字大于10
,则除以10
之后再判断个位数字是不是1
。
……
【实现策略】:
- 从
1
开始循环遍历到 输入的参数n
,将其放入对数求余方法中;- 对数求余方法中定义变量
num
,记录每个数字中1
的数量并返回。
class Solution {
// Solution1:枚举
public int countDigitOne(int n) {
int res = 0;
// 枚举每一个数
for (int i = 1; i <= n; i++) {
res += NumberOf1(i);
}
// 循环结束,返回结果
return res;
}
// 对数求余,判断该数有多少个1
public int NumberOf1(int num) {
int count = 0;
while (num != 0){
if (num % 10 == 1) count++;
num = num/10;
}
return count;
}
}
2.2 数学分析1(原书题解)-- O(logn)
时间复杂度O(logn),空间复杂度O(1)
【解题思路】:
如果希望不用计算每个数字的1
的个数,那就只能去寻找1
在数字中出现的规律了。以数字21345
为例,我们可以1 ~ 21345
中的所有数字分为两段:
- 一段是
1 ~ 1345
;- 一段是
1346 ~ 21345
;……
其中1346 ~ 21345
又可以分为两段:
- 一段是
1346 ~ 11345
;- 一段是
11346 ~ 21345
;……
根据以上的分段,我们就可以把求所有数字中 1 的个数分为 2 个区域:
- 仅首位是 1 的个数;
- 除去首位之后的数字的 1 的个数;
那么我们就可以使用递归的方法来解决这个问题,思路就是:
每次去掉最高位进行递归
,递归的次数和位数相同。以21345
为例:
- 递归方法中要求 仅首位是 1 的个数(即,
10000 ~ 19999
),其个数为 Math.pow(10,len-1);- 递归方法中要求 除去首位之后的数字的 1 的个数 (即,
1346 ~x1345
),其个数为首位数字 * (位数-1) * Math.pow(10,len-2)
;- 去掉首位后 (即,
1 ~ 1345
),递归。……
【注意点】:
Math.pow(a,b)
的计算结果返回是double类型,double类型转换为int类型就需要用到类型转换: int c=(int)Math.pow(a,b) ;否则就会出现以下错误:
class Solution {
// Solution1:数学分析
public int countDigitOne(int n) {
// 无效数据判断
if (n <= 0) return 0;
// 将 n 转化为字符串(方便编程)
String str = String.valueOf(n);
return numberOf1(str);
}
// 分三部分判断 1~n 中 1 的个数
public int numberOf1(String str) {
// 获取字符数组长度(即,输入参数 n 的位数)
int len = str.length();
// 获取 n 中首位数字
int first = str.charAt(0) - '0';
// 如果长度为1,则根据情况返回 0 or 1
if (len == 1 && first == 0) return 0;
if (len == 1 && first > 0) return 1;
// 如果长度大于1,就对其进行数字划分,以21345为例
// 第一区域:10000 ~ 19999 首位 1 的个数 (10000)
int firstOneNum = 0;
if (first > 1) firstOneNum = (int) Math.pow(10,len-1);
// 如果首位为 1,那么首位 1 的个数就为后面的数字再加1
else if (first == 1) firstOneNum = Integer.parseInt(str.substring(1))+1;
// 第二区域:1346 ~ 21345 除首位之外所有 1 的个数
int otherOneNum = first * (len-1) * (int) Math.pow(10,len-2);
// 第三区域:1 ~ 1345 (递归求解)
int recursiveNum = 0;
recursiveNum = numberOf1(str.substring(1));
// 最后返回三个区域的加和
return firstOneNum + otherOneNum + recursiveNum;
}
// 返回 10 的 n 次幂
public int powerBase10(int n) {
int num = 1;
for (int i = 0; i < n; i++) {
num *= 10;
}
return num;
}
}
2.3 数学分析2 – O(logn)
时间复杂度O(logn),空间复杂度O(1)
【解题思路】:
某位中1
出现次数的计算方法:
根据当前位cur
值的不同,分为以下三种情况:
- cur = 0
- cur = 1
- curr > 1
其中 high 为高位,cur 为当前位,low 为 低位, digit 为 位因子
class Solution {
public int countDigitOne(int n) {
int digit = 1, res = 0;
int high = n / 10, cur = n % 10, low = 0;
while(high != 0 || cur != 0) {
if(cur == 0) res += high * digit;
else if(cur == 1) res += high * digit + low + 1;
else res += (high + 1) * digit;
low += cur * digit;
cur = high % 10;
high /= 10;
digit *= 10;
}
return res;
}
}
3. 参考资料
[1] 面试题43. 1~n 整数中 1 出现的次数(清晰图解)
[2] Java中数字、字符、字符串 转换方法总结
[3] Java_String 截取字符串方法substring()
[4] 灵魂拷问:Java如何获取数组和字符串的长度?length还是length()?