剑指 Offer 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 题相同:https://leetcode-cn.com/problems/number-of-digit-one/
解法一:暴力求解
最直观的做法,就是累加1 ~ n
中每个整数出现1
的次数,我们可以通过对10
求余数判断整数的个位数字是不是1
,如果这个数字大于10
,则除以10
之后再判断个位数字是不是1
。
Java代码
class Solution {
public int countDigitOne(int n) {
int res = 0;
for(int i = 1;i <= n;i++){
int num = i;
while(num != 0){
if(num % 10 == 1) res++;
num /= 10;
}
}
return res;
}
}
遗憾的是TLE
了,在上述思路中,我们对每个数字都要做除法和求余运算,以求出该数字中1
出现的次数。而我们知道,对于一个数字n
,它有$O(logn)$位,如100
有$log_{10}100 +1=3$ 位,即求一个数n
有多少位的时间复杂度是$O(logn)$的,又我们需要求n
个数字,即n
次,那么总时间复杂度就是$O(nlogn)$,当输入的n
非常大的时候,需要大量的计算,运算效率不高,导致超时了。
解法二:数位统计
我们假设 n = abcdef
,其中等号右边a
为1~9
中的某个数字,其余的可为0~9
中某个数字,我们需要做的就是统计每个位上,如个位、十位、百位…上能出现1
的次数的总和。
假设求c
位置为1
的个数:
此时c
左边和右边的值分别为 lVal = ab, rVal = def
, 而t = 1000
,表示c
的位置是千位
的位置
此时则有如下两种情况:
1. lval
取[0, ab-1]
;右边可以从0
取到999
,也就是此时c
位置可以出现1000
次1
,我们用t
表示,因为t
就是1000
, 此时res += lval * t
;
2. lval
取 ab
;
- 如果c=0
,没有任何元素,及c
位此时一次1
都无法出现
- 如果c=1
,右边可以取[0,def]
也就是def+1
个元素,res += def+1
- 如果c>1
,右边可以从0
取到999
,也就是t=1000
, 此时res += t
;
举例 :
假如n = 123456
1. 假设当前计算千位上出现1
的个数,在千位上有 _ _x_ _ _
,x
的左边有 12
,右边有 456
当左边是[0,11]
时,千位上可以为1
,且千位右边的三位可以是0~999
,即千位上的数字我们固定为1
,左边是[0-11]
的任意一个数字时,右边可以是0~999
,共1000
个选择,所以千位是1
的个数是 12 * 1000
2. 如果左边是12
了
- x = 0
;千位上没有1
- x = 1
,那么前三位固定了是121
,但后三位可以是0~456
,共457
个数,故千位出现1
的次数应加上457
- x > 1
;此时我们如果把千位x
固定为1
,后面三位可以是0~999
,共1000
个数,故千位出现1
的次数应加上1000
,n = 123456时就是走的该情况
。
Java代码
class Solution {
public int countDigitOne(int n) {
int res = 0;
List<Integer> numbers = new ArrayList<>();
while (n != 0){
numbers.add(n % 10);
n /= 10;
}
for (int i = numbers.size() - 1; i >= 0 ; i--) {//枚举每一个数位、个位、十位、百位...
int lVal = 0, rVal = 0; // i位置元素 左右两边的值
int t = 1;
//数位左边的数字
for (int j = numbers.size() - 1; j > i; j--) lVal = lVal * 10 + numbers.get(j);
//数位右边的数字
for(int j = i - 1; j >=0; j--) {
rVal = rVal * 10 + numbers.get(j);
t *= 10;
}
// 左边
res += lVal * t;
// 右边
if (numbers.get(i) == 1) {
res += rVal + 1;
} else if (numbers.get(i) > 1) {
res += t;
}
}
return res;
}
}
### 666
666