从1到n整数中1出现的次数(比如121这一个整数里就含有两个1)

1、通过举例来描述问题

【用例1】5

        答案:1。因为只有1中含有一个。

【用例2】10

        答案:2。因为只有1和10中各含有一个。

【用例3】55

        答案:16。因为10~19中的十位各有一个;01、11、21、31、41、51的个位各有一个。    

【用例4】99

        答案:20。因为10~19中的十位各有一个;01、11、21、31、41、51..91的个位各有一个。    

【用例5】10000

        答案:401。因为只有10000的万位含有1;千位含有1共10^3个数;同理,百位和个位。   

【用例6】21345

        答案:18821。稍复杂,算法思路是不断降低位数,逐步的计算从高位到低位中1的数目。

2、算法思路

算法思路很简单,每次去掉最高位做递归。从高位到低位计算是1的个数,再累加。
咱们还是以【用例6】来具体介绍,每次划分为两个区间,如下所示。

2.1、尝试性的思路

21345
区间一: 1~20000
区间二: 20001~21345
求解区间一,最高位(万位)是1的数是10000~19999共10^4个;次高位(千位)是1的数是01000~01xxx和11000~11xxx共2*10^3个;第三高位(百位)是1的数是0x100~0x1xx和1x100~1x1xx共2*10^3个;同理,十位是1的数是2*10^3个,个位是1的数是2*10^3个。
求解区间二,因为最高位确定是2了,等价于求0001~1345,即用例1345。

1345
区间一: 1~1000
区间二: 1001~1345
求解区间一,最高位(千位)是1的数是1000共1个;次高位(百位)是1的数是0100~01xx共10^2个;同理,十位是1的数共10^2个,个位是1的数共10^2个。
求解区间二,因为最高位(千位)是1的数是1001~1345共345个;因为最高位确定是1了,等价与求001~345,即用例345。

345
区间一: 1~300
区间二: 301~345
求解区间一,最高位(百位)是1的数共100个;次高位(十位)是1的数是010~01x和110~11x和210~21x共3*10个;同理,个位是1的数共3*10个。
求解区间二,因为最高位确定是3了,等价于求01~45,即用例45。

45
区间一: 1~40
区间二: 41~45
求解区间一,最高位(十位)是1的数共10个;次高位(个位)是1的数是01、11、21、31共4个。
求解区间二,因为最高位确定是4了,等价于求1~5,即用例5,所以1的个数共1个。

综上可知,sum=(10^4+2*10^3*4)+(1+10^2*3)+345+(100+3*10*2)+(10+4)+(1)
=18000+301+345+160+14+1
=18821


2.2、整理后的思路

在上边步骤中,如果最高位是1,那么在求解区间2时要计算最高位是1的个数(这在区间1中也要算,其实分两次不太合适)。所以,为了简化算法,统一的把它拿出来单独计算,作为区间零,而在区间二和三中都不再重复计算重复最高位是1的个数。
21345
区间零:10000~19999
区间一: 1~20000,排除区间零(最高位是1的情况)中的
区间二: 20001~21345
求解区间零,最高位(万位)是1的数是10000~19999共10^4个
求解区间一,次高位(千位)是1的数是01000~01xxx和11000~11xxx共2*10^3个;第三高位(百位)是1的数是0x100~0x1xx和1x100~1x1xx共2*10^3个;同理,十位是1的数是2*10^3个,个位是1的数是2*10^3个。
求解区间二,因为最高位确定是2了,等价于求0001~1345,即用例1345。

1345
区间零:1000~1345
区间一: 1~1000
区间二: 1001~1345
求解区间零,最高位(千位)是1的数是1000~1345共346个
求解区间一,次高位(百位)是1的数是0100~01xx共10^2个;同理,十位是1的数共10^2个,个位是1的数共10^2个。
求解区间二,因为最高位确定是1了,等价与求001~345,即用例345。

345
区间零:100~199
区间一: 1~300
区间二: 301~345
求解区间零,最高位(百位)是1的数共100个
求解区间一,次高位(十位)是1的数是010~01x和110~11x和210~21x共3*10个;同理,个位是1的数共3*10个。
求解区间二,因为最高位确定是3了,等价于求01~45,即用例45。

45
区间零:10~19
区间一: 1~40
区间二: 41~45
求解区间零,最高位(十位)是1的数共10个
求解区间一,次高位(个位)是1的数是01、11、21、31共4个。
求解区间二,因为最高位确定是4了,等价于求1~5,即用例5,所以1的个数共1个。

综上可知,sum=(10^4+2*10^3*4)+(1+10^2*3)+345+(100+3*10*2)+(10+4)+(1)
=18000+301+345+160+14+1
=18821

3、算法实现

3.1、原始的方法

这里写图片描述

// ====================原始的方法(累加1到n中每个整数1出现的次数)====================
int NumberOf1(unsigned int n);

int NumberOf1Between1AndN_Solution1(unsigned int n)
{
    int number = 0;

    for(unsigned int i = 1; i <= n; ++ i)
        number += NumberOf1(i);

    return number;
}

int NumberOf1(unsigned int n)
{
    int number = 0;
    while(n)
    {
        if(n % 10 == 1)
            number ++;

        n = n / 10;
    }

    return number;
}

这里写图片描述

3.2、在2中介绍的方法

// ====================在2中介绍的方法(每次去掉最高位做递归)====================
int NumberOf1(const char* strN);
int PowerBase10(unsigned int n);

int NumberOf1Between1AndN_Solution2(int n)
{
    if(n <= 0)
        return 0;

    char strN[50];
    sprintf(strN, "%d", n);

    return NumberOf1(strN);
}

int NumberOf1(const char* strN)
{
    if(!strN || *strN < '0' || *strN > '9' || *strN == '\0')
        return 0;

    int first = *strN - '0';
    unsigned int length = static_cast<unsigned int>(strlen(strN));

    if(length == 1 && first == 0)
        return 0;

    if(length == 1 && first > 0)
        return 1;

    // 假设strN是"21345"
    // numFirstDigit是数字10000-19999的第一个位中1的数目
    int numFirstDigit = 0;
    if(first > 1)
        numFirstDigit = PowerBase10(length - 1);
    else if(first == 1)
        numFirstDigit = atoi(strN + 1) + 1;

    // numOtherDigits是01346-21345除了第一位之外的数位中1的数目
    int numOtherDigits = first * (length - 1) * PowerBase10(length - 2);
    // numRecursive是1-1345中1的数目
    int numRecursive = NumberOf1(strN + 1);

    return numFirstDigit + numOtherDigits + numRecursive;
}

int PowerBase10(unsigned int n)
{
    int result = 1;
    for(unsigned int i = 0; i < n; ++ i)
        result *= 10;

    return result;
}

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值