【剑指offer】面试题43:1~n整数中1出现的次数【C++版本】

20180608

题目:

输入一个整数n,求1~n这n个整数的十进制表示中1出现的次数。例如:输入12,1~12这些整数中包含1的数字有1、10、11和12,1一共出现了5次

注意:

1.求解的是1出现的次数,一个数字里面1可能出现多次,比如111。而不是包含1的数字出现的次数
2.不能这样分段,比如把21345分成1~20000,20001~21345两段,这样分段更加不好分析,因为在分析后一段的时候,还需要考虑高位的影响。

解题思路:

1.这个题目不能使用暴力求解,时间复杂度太高了。
2.使用递归的方式来求解,并且为了方便递归和最后递归结束的判断,把数字转换成C风格字符串来处理。
3.使用输入21345作为例子来进行分析:
I.把21345分为两段:1~1345、1346~21345,后面一段其实是20000个整数。这种分段方法的好处在于,递归求解之后,可以完全剥离高位的影响,后续只需要继续分析低位就可以了。
II.首先分析1346~21345这一段出现1的次数,分两种情况:
1).最高位万位出现1的次数,在1346~21345中1出现在10000~19999这10000个数字的万位中,一共出现了10000万次。但是注意这里还有一种情况,例如对于输入12345,1只出现在10000~12345的万位,即只出现了2346次(12345-10000+1)
2).1出现在除了最高位之外的其他4位中的情况。由于最高位是2,我们可以再把1346~21345分成两段1346~11345和11346~21345.每一段剩下的四位数字中,在其中一位是1的情况下,其余三位可以在0~9这10个数字中任意选择,因此根据排列组合,总共出现的次数是2(1346~11345和11346~21345两段)×4(除最高位之外还有四位)×10^3(除选定的一位为1其余四位可以任选)
III.这样就统计完1346~21345中1出现的次数。至于1~1345,我们使用递归的方法来进行统计,即把这一段又分成两段,1~345和346~1345,其中分两种情况统计346~1345中1出现的次数,1~345继续递归。

可以AC的代码【C++版本】

#include <vector>                                                                                                                                                                                                                           
#include <stdlib.h>
#include <string>
#include <stdio.h>
#include <string.h>
#include <iostream>

using namespace std;

int highest(unsigned int length)
{
//    if(length <= 0)return 0;

    int resu = 1;
    for(int i = 0;i != length;++i){
        resu *= 10;
    }

    return resu;
}

int Numberof1(char *str)
{
    //如果C字符串的第一个字符不符合标准,或者已经到了结尾,或者字符串为空,都返回0
    if((str == NULL)||(*str < '0')||(*str > '9')||(*str == '\0'))
        return 0;

    //提取最高位的数字
    int first = *str - '0';
    //str的长度,不包括最后的空字符'\0'
    unsigned int length = strlen(str);

    //如果只剩下一个个位数,这个数是0则拥有1的个数为0,否则为1
    if((length == 1)&&(first == 0))
        return 0;
    if(length == 1)
        return 1;

    //最高位中可能的1的数目
    int cntFirstPosi = 0;
    if(first == 1){
        cntFirstPosi = atoi(str+1)+1;
    }
    else if(first > 1){
        cntFirstPosi = highest(length-1);
    }

    //除去最高位其他位总共出现1的次数
    int cntOtherPosi = first*highest(length-2)*(length-1);

    //对剩余的数字进行递归处理
    int cntRecursive = Numberof1(str+1);

    return cntFirstPosi + cntOtherPosi + cntRecursive;
}

//为了方便递归等等操作,把数字转换为C风格的字符串
int NumberOf1Between1AndN_Solution(int n)
{
    if(n <= 0)return 0;

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

int main()
{
    cout << NumberOf1Between1AndN_Solution(21345) << endl;

    return 0;
} 

猜你喜欢

转载自blog.csdn.net/m0_37950361/article/details/80627986