Java详解剑指offer面试题43–1~n整数中1出现的次数
输入一个整数n,求1~n这n个整数的十进制表示中1出现的次数,例如输入12, 1~12中出现1的有1、10、11、12共5次
计算每个数字出现1的次数
比较直接的思路就是写一个方法可以统计任意整数1的个数,然后用一个循环得到对1~n每一个数调用该方法统计总的1的出现次数。
package Chap5;
public class NumOf1 {
/**
* 方法1,计算每个数字中1的个数,复杂度O(nlgn)
*/
public int NumberOf1From1To(int n) {
// 正负数不影响1的个数,统一变成非负数
if (n < 0) n = Math.abs(n);
int count = 0;
// 循环求n个数字,共O(nlgn)的时间
for (int i = 1; i <= n; i++) {
count += numOf1(i);
}
return count;
}
/**
* O(lgn)的复杂度求一个数中含有1的数量
*/
private int numOf1(int n) {
int count = 0;
while (n != 0) {
if (n % 10 == 1) count++;
n = n / 10;
}
return count;
}
}
numOf1
方法中,n % 10可以得到个位,n = n / 10表示丢弃最低位。该方法的复杂度为O(lgn),对于1~n中n个数字都要调用一遍该方法,总的时间复杂度是O(nlgn)。
更暴力的解法–StringBuilder拼接
使用StringBuilder将1~n的所有数无缝拼接起来,然后一个个数。
package Chap5;
public class NumOf1 {
/**
* 方法2:使用StringBuilder将所有数字拼接,无脑数数
*/
public int numOf1Between1AndN(int n) {
// 正负数不影响1的个数,统一变成非负数
if (n < 0) n = Math.abs(n);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append(i);
}
int count = 0;
for (int i = 0; i < sb.length(); i++) {
if (sb.charAt(i) == '1') {
count++;
}
}
return count;
}
}
神一样的方法
参考LeetCode
1可以出现在任意一位,比如3245,1出现在个、十、百、千位都可以。只要固定某一位为1,计算出该位是1的所有情况,将固定每一位得到的情况数相加就是最终结果。
要固定某一位为1,可以使用m = 1, 10, 100, 1000…,对n作除、余操作,将输入整数分为高位和低位两部分。举个例子,对于输入n = 3101592,m = 100,如果令a = n / m, b = n % m,将得到a = 31015,b= 92两部分,现在固定百位为1(始终固定a的最低位),即xxxx1xx
这样的形式,这样形式的数有多少个呢?
0000 1 00
0000 1 01
.....
0000 1 99
0001 1 00
......
0001 1 99
0002 1 00
......
0002 1 99
......
3101 1 00
......
3101 1 99
为了看得直观,上面刻意将数字从百位处分隔开,对于百位之前的高位数,总共有0000~3101
共3102种情况,而每一种情况对应着低位有00~99
共100种情况,因此百位为1的情况数是3102*100,也就是(a / 10 + 1) * m
种情况。好,现在得到百位为1的情况数了,个位与千位等其他位计算方法和上面类似,只需取不同的m就能将输入的整数分成两部分并固定某一位为1.
接下来m = 1000时,3101592被分成a = 3101和b = 592两部分,现在固定千位为1,但是此时千位本来就是1了,来看和上面有什么不同
000 1 000
......
000 1 999
309 1 000
......
309 1 999
310 1 000
......
310 1 592
可以看到千位前的高位从000~309
和上面一样,每一种情况都有000~999
种可能,但是到310时,后面最多只能到592,共000~592
是593种情况。此时千位为1的情况总数为:310 * 1000 + 593,即当前要被固定的位在输入中本来就是1的话有(a / 10) * m + b + 1
种情况。
再看m = 10000,固定万位的情况。a = 310, b = 1592.
00 1 0000
......
00 1 9999
30 1 0000
......
30 1 9999
没有了,共31 * 10000种,即当前要被固定的位在输入中是0的话有(a / 10) * m
种情况。
一开始固定百位其实就是当前要被固定的位在输入中是2~9这种情况。
分析得差不多了,现在考虑这三种情况计算就好了。
/**
* 方法三
*/
public int numberOf1(int n) {
int ones = 0;
for (long m = 1; m <= n; m *= 10) {
long a = n / m;
long b = n % m;
if (a % 10 == 0) ones += a / 10 * m;
else if (a % 10 == 1) ones += (a / 10 * m) + (b + 1);
else ones += (a / 10 + 1) * m;
}
return ones;
}
本文参考文献:
[1]github.com/haiyusun/data-structures