面试题43:1~n整数中1出现的次数
题目:输入一个整数 n,求 1~n这n个整数的十进制表示中1出现的次数。例如,输入12,1~12这些整数中包含1的数字有1、10、11和12,1一共出现了5次。
比较直接一点的思路,在不考虑时间效率的情况下,可以循环1~n,对每个数都先判断个位数是不是1,如果这个数大于10则除10后再判断这个数个位数是不是1,这种算法思路比较简单直接,而且算法时间复杂度也不符合要求O(nlogn),就不展开详细解释。
function NumberOf1(n){
let number = 0;
while(n){
if(n%10 == 1)
number++;
n = n/10;
}
return number;
}
function Count(n){
let sum = 0;
for(let i = 1; i <= n; i++)
sum += NumberOf1(i);
return sum;
}
《剑指Offer》给出了提高时间效率的解法,但是代码十分繁琐,时间复杂度为O(logn),与数字的位数有关,而且不易于理解。这时候看到了一个大佬的解法,代码来自国外leetcode上的一个解答,核心代码只有短短的三行就解决了这个问题,时间复杂度也是O(logn),不得不说大佬牛x。我花了一定的时间进行了查找资以及分析,把代码稍微扩展成我能理解的方式,在这里记录下我的思路。
国外大佬的解答:
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
int count=0;
long long i=1;
for(i=1;i<=n;i*=10)
{
//i表示当前分析的是哪一个数位
int a = n/i,b = n%i;
count=count+(a+8)/10*i+(a%10==1)*(b+1);
}
return count;
}
};
主要思路:设定整数点(如1、10、100等等)作为位置点i(对应n的个位、十位、百位等等),分别对每个数位上有多少包含1的点进行分析。每个整数点都仅仅针对当前位(个位、十位、百位、千位等)计算包含1的个数,不考虑其他位上1的情况。
当 i = 1,就从个位开始,就计算从1~n,个位上出现了几次1。
当 i = 10,就从十位开始,就计算1~n,十位上出现了几次1。
当 i = 100,从百位开始,就计算1~n,百位上出现了几次1。
以此类推。
然后把他们全部加起来,就得到了1~n上所有位数出现的1的次数总和。
这里假定一种情况来讨论,其他相似类推就行了,本质上计算方法一样的。这里假设 i = 100 ,即当 i 表示百位的时候,求 1~n 百位上出现1的总次数。
根据设定的整数位置,对n进行分割,分为两部分,高位n/i,低位n%i。
分三种情况讨论即:
(1)百位大于等于2
(2)百位等于1
(3)百位等于0
(1) 当百位的情况大于等于2 (n = 31456, i = 100)
当 n = 31456,a = 314(a为高位,百位和百位之前的数),b = 56(b为低位,百位之后的数),当前百位的数字为 4 >= 2,百位出现1的次数为 a/10+1,分别为 001,011,021,031,041,051,061,071,081,091,101...301,311共32次(000~314)。只考虑百位为 1 的情况,百位的 1 出现了 32 次,算上低位每次1-100的完整循环,1-n上百位共出现了 (a/10+1)*100 次个1。(31+1)*100 = 3200次
(2) 当百位的情况等于 1 (n = 31156, i = 100)
当 n = 31156,百位的情况等于 1 的时候,有 a = 311, b = 56,此时百位对应的就是1,则百位出现的次数为a/10,分别为 001,011,021,...301 共 31 次(000-309),当最高两位为31(即a=311),本次没有完整的1-100的循环,因为低位最多只到56为止,所以共b+1次 = 56+1 = 57次(算上 b = 0 的时候),1-n 百位1共出现了(a/10*100)+(b+1) = (31*100)+57 = 3157 次
(3)当百位的情况等于 0 的时候
当 n = 31056,a = 310,b = 56的时候,百位对应的数为0,此时百位为1的次数有a/10=31(最高两位0~30)。所以出现1的次数为 (a/10*100) = 31*100 = 3100 次
综合以上三种情况,依次从个位、十位、百位...开始遍历到最高位,把每一位出现1的次数进行累计求和即是最终的结果。
function getNumberOf1 (n) {
let digit = 1 // 当前遍历的位,从各位开始
let res = 0 // 统计1出现的总数
let high = Math.floor(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 = Math.floor(high/10)
digit *= 10
}
return res
}