每天一道剑指offer-整数中1出现的次数

整数中1出现的次数(从1到n整数中1出现的次数)

题目描述

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

解析
遍历一遍不就完了吗

当然,你可从1遍历到n,然后将当前被遍历的到的数中1出现的次数累加到结果中可以很容易地写出如下代码:

public int NumberOf1Between1AndN_Solution(int n) {	
    if(n < 1){	
        return 0;	
    }	
    int res = 0;	
    for(int i = 1 ; i <= n ; i++){	
        res += count(i);	
    }	
    return res;	
}	
public int count(int n){	
    int count = 0;	
    while(n != 0){	
        //取个位	
        count = (n % 10 == 1) ? ++count : count;	
        //去掉个位	
        n /= 10;	
    }	
    return count;	
}

但n多大就会循环多少次,这并不是面试官所期待的,这时我们就需要找规律看是否有捷径可走

不用数我也知道

51234这个数为例,我们可以先将 51234划分成 1~1234(去掉最高位)和 1235~51234两部分来求解。下面先分析 1235~51234这个区间的结果:

  1. 所有的数中,1在最高位(万位)出现的次数

    对于 1235~51234,最高位为1时(即万位为1时)的数有 10000~19999这10000个数,也就是说1在最高位(万位)出现的次数为10000,因此我们可以得出结论:如果最高位大于1,那么在最高位上1出现的次数为最高位对应的单位(本例中为一万次);但如果最高位为1,比如 1235~11234,那么次数就为去掉最高位之后的数了, 11234去掉最高位后是 1234,即1在最高位上出现的次数为 1234

  2. 所有的数中,1在非最高位上出现的次数

    我们可以进一步将 1235~51234按照最高位的单位划分成4个区间(能划分成几个区间由最高位上的数决定,这里最高位为5,所以能划分5个大小为一万子区间):

  • 1235~11234

  • 11235~21234

  • 21235~31234

  • 31235~41234

  • 41235~51234

    而每个数不考虑万位(因为1在万位出现的总次数在步骤1中已统计好了),其余四位(个、十、百、千)取一位放1(比如千位),剩下的3位从 0~9中任意选( 10*10*10),那么仅统计1在千位上出现的次数之和就是: 5(子区间数)*10*10*10,还有百位、十位、个位,结果为: 4*10*10*10*5

    因此非高位上1出现的总次数的计算通式为: (n-1)*10^(n-2)*十进制最高位上的数(其中 n为十进制的总位数)

    于是 1235~51234之间所有的数的所有的位上1出现的次数的综合我们就计算出来了

剩下 1~1234,你会发现这与 1~51234的问题是一样的,因此可以做递归处理,即子过程也会将 1~1234也分成 1~234235~1234两部分,并计算 235~1234而将 1~234又进行递归处理。

而递归的终止条件如下:

  1. 如果 1~n中的 n: 1<=n<=9,那么就可以直接返回1了,因为只有数1出现了一次1

  2. 如果 n==0,比如将 10000划分成的两部分是 0~0(10000去掉最高位后的结果)和 1~10000,那么就返回0

public int NumberOf1Between1AndN_Solution(int n) {	
    if(n &lt; 1){	
        return 0;	
    }	
    return process(n);	
}	
public int process(int n){	
    if(n == 0){	
        return 0;	
    }	
    if(n &lt; 10 &amp;&amp; n &gt; 0){	
        return 1;	
    }	
    int res = 0;	
    //得到十进制位数	
    int bitCount = bitCount(n);	
    //十进制最高位上的数	
    int highestBit = numOfBit(n, bitCount);	
    //1、统计最高位为1时,共有多少个数	
    if(highestBit &gt; 1){	
        res += powerOf10(bitCount - 1);	
    }else{	
        //highestBit == 1	
        res += n - powerOf10(bitCount - 1) + 1;	
    }	
    //2、统计其它位为1的情况	
    res += powerOf10(bitCount - 2) * (bitCount - 1) * highestBit;	
    //3、剩下的部分交给递归	
    res += process(n % powerOf10(bitCount - 1));	
    return res;	
}	
//返回10的n次方	
public int powerOf10(int n){	
    if(n == 0){	
        return 1;	
    }	
    boolean minus = false;	
    if(n &lt; 0){	
        n = -n;	
        minus = true;	
    }	
    int res = 1;	
    for(int i = 1 ; i &lt;= n ; i++){	
        res *= 10;	
    }	
    return minus ? 1 / res : res;	
}	
public int bitCount(int n){	
    int count = 1;	
    while((n /= 10) != 0){	
        count++;	
    }	
    return count;	
}	
public int numOfBit(int n, int bit){	
    while(bit-- &gt; 1){	
        n /= 10;	
    }	
    return n % 10;	
}

笔者曾纠结,对于一个四位数,每个位上出现1时都统计了一遍会不会有重复,比如 11111这个数在最高位为1时的 10000~19999统计了一遍,在统计非最高位的其他位上为1时又统计了4次,总共被统计了5次,而这个数1出现的次数也确实是5次,因此没有重复。

出自:http://www.zhenganwen.top

已获授权

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值