九度OJ-题目1373:整数中1出现的次数(从1到n整数中1出现的次数)

题目链接地址:

九度OJ-题目1373:整数中1出现的次数(从1到n整数中1出现的次数)


题目描述:
亲们!!我们的外国友人YZ这几天总是睡不好,初中奥数里有一个题目一直困扰着他,特此他向JOBDU发来求助信,希望亲们能帮帮他。问题是:求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数。

输入:
输入有多组数据,每组测试数据为一行。
每一行有两个整数a,b(0<=a,b<=1,000,000,000)。

输出:
对应每个测试案例,输出a和b之间1出现的次数。

样例输入:
0  5
1  13
21  55
31  99

样例输出:
1
6
4
7


解题思路:

这道题最直观的想法应该是遍历a和b之间的所有整数,然后依次计算出每个数字中1出现的次数,再累加起来就能得出a和b之间1出现的次数了。但是这种算法肯定会超时。之前在《编程之美》上见过这道题,书上的解法是先分别统计整数n中的每个十进制位中1出现的次数,再累加起来就能得出区间[1,n]中1出现的次数。因此我的想法是:先统计出区间[1,a-1]中1出现的次数countA,然后统计出区间[1,b]中1出现的次数countB,那么countB – countA就是区间[a,b]中1出现的次数了。接下来问题就转换成求区间[1,n]中1出现的次数了。
求区间[1,n]中1出现的次数就是对数字n中的每个进制位数字与1进行比较:
1)如果进制位数字大于1,则当前进制位1出现的次数仅由高进制位数字确定;
2)如果进制位数字等于1,则当前进制位1出现的次数由高进制位数字和低进制位数字共同确定;
3)如果进制位数字小于1,则当前进制位1出现的次数仅由高进制位数字确定。
举个栗子,下面是求从1到1012中1出现的次数的步骤:
(1)先判断1012的个位数2,因为2 > 1,所以区间[1, 1012]中个位数出现1的次数仅由1012的高3位数(000 ~ 101)确定,

         个位数1出现的次数是(101 + 1) * 1=102次,分别是:(000 ~ 101)1
(2)再判断1012的十位数1,因为1 = 1,所以区间[1, 1012]中个位数出现1的次数由1012的高2位数(00~09)和最低位数(0~2)

         共同确定。先算出区间[1, 999]中十位数出现1的次数是10 * 10 = 100次,分别是:(00 ~ 09)1(0 ~ 9),再算出

         区间[1000,1012]中十位数出现1的次数有3次,分别是101(0 ~ 2),因此区间[1, 1012]中十位数出现1的次数为100 + 3 = 103;

(3)再判断1012的百位数0,因为0 < 1,所以区间[1, 1012]中百位数出现1的次数仅由1012的最高位数1确定,

         区间[1,1012]中百位数出现1的次数是1 * 100=100次,分别是01(00 ~ 99);
(4)最后判断1012的千位数1,因为1 = 1,所以区间[1, 1012]中千位数出现1的次数由1012的高位数字0和低3位数字(000~012)

         共同确定,因此区间[1, 1012]中千位数出现1的次数是0 * 1000 + 13 = 13次,分别是1(000 ~ 012);
综上可知,区间[1,1012]中1出现的次数是102 + 103 + 100 + 13 = 318次。
此外这道题还有个坑爹的地方需要注意:请看题目的描述“对应每个测试案例,输出a和b之间1出现的次数”,而不是“输出区间[a,b]中1出现的次数”,所以输入的第一个整数a可能大于第2个整数b ,如果输入的a > b,必须要先交换a与b。
AC代码如下:

#include<stdio.h>
 
/**
* 统计区间[1,n]中1出现的次数
* @param n  输入的整数n
* @return count  返回区间[1,n]中1出现的次数
*/
long long countOneFromOneToN(int n)
{
  long long count = 0;   // 统计1 ~ n中1出现的次数
  if(n < 1)
      return count;
  else
  {
      int tempN = n;                   // 用于保存处理过程中的n
      int lowBits = 0;                 // 整数n中比当前进制位更低的低位部分
      int highBits = n;                // 整数n中比当前进制位更高的高位部分
      int currentDecimalBit = 0;       // 整数n当前被处理数位所对应的数字,最先处理个位上的数字
      int currentDecimalWeight = 1;    // 整数n当前正在处理的进制位,最先处理个位
      while(tempN > 0)
      {
          highBits = n / (10 * currentDecimalWeight);   // 求高位部分
          if(currentDecimalWeight > 1)
          {
              lowBits = n % currentDecimalWeight;       // 求低位部分
          }
          currentDecimalBit = tempN % 10;               // 求正在被处理的当前位
 
          // 将当前位与1进行比较,然后分类讨论统计1出现的次数
          if(currentDecimalBit > 1)
          {
              count += (highBits + 1) * currentDecimalWeight;
          }
          else if(currentDecimalBit == 1)
          {
              count += highBits * currentDecimalWeight + lowBits + 1;
          }
          else
          {
              count += highBits * currentDecimalWeight;
          }
          tempN = tempN / 10;
          currentDecimalWeight = 10 * currentDecimalWeight; // 跳到n的下一个高位,例如从个位跳到十位
      }
      return count;
  }
}
 
/**
* 交换两个整数
* @param a  整数a的地址
* @param b  整数b的地址
* @return void
*/
void swap(int * a,int * b)
{
   int temp;
   temp = *a;
   *a = *b;
   *b = temp;
}
 
int main()
{
    long long result;                  // [a,b]区间中1出现的次数
    long long CountA;                  // [1,a-1]区间中1出现的次数
    long long CountB;                  // [1,b]区间中1出现的次数
    int a,b;
    while(EOF != scanf("%d%d",&a,&b))
    {
        if(a > b)                      // 如果输入的a比b大,则需要交换两个数
        {
            swap(&a,&b);
        }
        CountA  = countOneFromOneToN(a - 1);      // 统计[1,a-1]区间中1出现的次数
        CountB  = countOneFromOneToN(b);          // 统计[1,b]区间中1出现的次数
        result = CountB - CountA;
        printf("%lld\n",result);
    }
    return 0;
}
 
/**************************************************************
    Problem: 1373
    User: blueshell
    Language: C
    Result: Accepted
    Time:0 ms
    Memory:912 kb
****************************************************************/


你可能感兴趣的:(面试题,剑指offer)