题目描述
求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
对应牛客网剑指offer31题,Leetcode:233. Number of Digit One。
解题思路
方法1:暴力遍历
遍历k[0, n],并将每个k转成字符数组,遍历计数有多少个'1'。时间复杂度为O(nlogn)。
public int NumberOf1Between1AndN_Solution(int n) {
int count = 0;
for (int i=0; i<n; i++) {
String str = String.valueOf(i);
for (char item : str.toCharArray()) {
if (item == '1') {
count += 1;
}
}
}
return count;
}
在牛客上可以AC,对应的
运行时间:48ms
占用内存:12760k
方法2:指定位数分析
假设n是一个5位数abcde,其中a,b,c,d,e分别是对应位上的数字。整个问题的结果可以划分为5个部分,分别右数第i[1, 5]位上数字为1的次数相加。
以i=3,百位数字c为例开始分析,可以发现百位上为1的次数不仅与百位本身有关,也与其前面的高位数字和其后面的低位数字有关。具体的:
- 如果c=0,百位上为1的次数为 ab * 100,100是源于ab100 ~ ab199之间有100个数,而高位的ab可以取1 ~ ab 共ab个数。
- 如果c=1,百位上为1的次数为 ab * 100 + de + 1,其中de源于ab100 ~ ab1de之间有de+1个数。
- 如果c>=1,百位上为1的次数为 (ab+1) * 100,100是源于ab100 ~ ab199之间有100个数,而高位的ab可以取0 ~ ab 共ab个数。
于是我们可以根据规律给出如下解法,时间复杂度为O(logn)
public int NumberOf1Between1AndN_Solution(int n) {
if (n == 0) {
return 0;
}
int count = 0;
int size = String.valueOf(n).length();
for (int i=1; i<=size; i++) {
int val = getIdxVal(n, i);
int high = getHighVal(n, i);
int low = getLowVal(n, i);
double base = Math.pow(10, i-1);
if (val == 0) {
count += high * base;
} else if (val == 1) {
count += high * base + low + 1;
} else {
count += (high + 1) * base;
}
}
return count;
}
/**
* 获取n右数位第i位的数字
*/
private int getIdxVal(int n, int idx) {
return (int)((n / Math.pow(10, idx-1)) % 10);
}
/**
* 获取n右数第i位右边的高位数字
*/
private int getHighVal(int n, int idx) {
return (int)(n / Math.pow(10, idx));
}
/**
* 获取n右数第i位左边的低位数字
*/
private int getLowVal(int n, int idx) {
return (int)(n % Math.pow(10, idx-1));
}
在牛客上AC对应的
运行时间:20ms
占用内存:9472k
引申思考:100~1300的整数中1出现的次数呢?
这个问题可以借助上面的结果进行计算,先算出0 ~ 100的次数f(100),在算出0 ~ 1300的次数f(1300),结果就是:
f(1300) - f(100) + k(100),k(n)表示n这个数中1出现的次数。代码如下:
/**
* 求输入n数字中1出现的次数
*/
private int numOfOne(int n) {
int count = 0;
char[] chars = String.valueOf(n).toCharArray();
for (char item : chars) {
if (item == '1') {
count += 1;
}
}
return count;
}
/**
* 求start到end中整数1出现的次数,包含start和end
*/
public int NumberOf1BetweenAnd_Solution(int start, int end) {
if (start > end) {
throw new IllegalArgumentException();
}
return numOfOne(start) + NumberOf1Between1AndN_Solution2(end) - NumberOf1Between1AndN_Solution2(start);
}
如果觉得有用,就给个赞呗^_^ 如果发现内容有问题,麻烦指正下~