Chinaunix首页 | 论坛 | 博客
  • 博客访问: 340918
  • 博文数量: 54
  • 博客积分: 446
  • 博客等级: 下士
  • 技术积分: 821
  • 用 户 组: 普通用户
  • 注册时间: 2011-04-30 17:37
文章分类

全部博文(54)

文章存档

2015年(35)

2014年(19)

我的朋友

分类: C/C++

2015-09-09 22:59:19

  要解决的问题是这样的,给定一个十进制正整数N,写下从1开始,到N的所有整数,然后数一下其中出现的所有“1”的个数。
 例如:
 N = 2, 写下1,2。这样只出现了1个“1”。
 N = 12, 写下1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12。这样,1的个数是5。
解法一、最简单的思路当然就是从1到N每一个数的每一位都进行判断,代码如下:
 

点击(此处)折叠或打开

  1. int NumberOf1(unsigned int n)
  2. {
  3.    int number = 0;
  4.    
  5.    while (n)
  6.    {
  7.       if (n % 10 == 1)
  8.            number++;
  9.       
  10.       n = n / 10;
  11.    }

  12.    return number;
  13. }

  14. int NumberOf1Between1AndN(unsigned int n)
  15. {
  16.     int number = 0;

  17.     for (unsigned int i = 1; i <= n; i++)
  18.         number += NumberOf1(i);
  19.     
  20.     return number;
  21. }
算法的复杂度是O(N*lgN),其中lgN指十进制数N的位数,当N非常大的时候(该算法中给出N的类型是unsigned int,但事实上可以扩展到unsigned long long型),这个算法的复杂度是非常高的。
解法二与解法三都是基于对数字N本身的分析提出,先给出解法二:

点击(此处)折叠或打开

  1. int NumberOf1Between1AndN(int n)
  2. {
  3.    if (n <= 0)
  4.        return 0;

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

  7.    return NumberOf1(strN);
  8. }

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

  13.    int first = *strN - '0';
  14.    unsigned int length = static_cast<unsigned int>(strlen(strN));
  15.   
  16.    if (length == 1 && first == 0)
  17.        return 0;
  18.    if (length == 1 && first > 0)
  19.        return 1;
  20.    
  21.    int numFirstDigit = 0;
  22.    if (first > 1)
  23.        numFirstDigit = PowerBase10(length - 1);
  24.    else if (first == 1)
  25.        numFirstDigit = atoi(strN + 1) + 1;
  26.    
  27.    int numOtherDigits = first * (length - 1) * PowerBase10(length - 2);
  28.    int numRecursive = NumberOf1(strN + 1);
  29.    return numFirstDigit + numOtherDigits + numRecursive;
  30. }

  31. int PowerBase10(unsigned int n)
  32. {
  33.    int result = 1;
  34.    for (unsigned int i = 0; i < n; i++)
  35.        result *= 10;
  36.    return result;
  37. } 
函数PowerBase10的功能是这样的,例如给定n = 4,则它返回10000,n = 3, 返回1000, 依次类推,理解一个算法最便捷的方式仍然是代入实际的值去计算,我们看n = 123的执行情况:
首先第一次递归调用NumberOf1("123"), first = 1,length = 3,
则numFirstDigit = 24(line 29)
其实numFirstDigit可以理解为计算百位上出现1的次数是多少,不难理解如果fist > 1,例如n = 200的情况,则执行line 27,结果是100,也就是100~199这100个数的百位上都是1,无论n = 300, 400, 500甚至更大,百位上出现1的次数固定就是100次,但first == 1时不一样,出现1的次数与后两位有关,以123为例,则是100~123共24次。
numOtherDigits = 1 * 2 * 10 = 20
表示十位上出现“1”20次
然后就要进入递归过程:numRecursive = NumberOf1(“23”)
进入第二次递归,计算NumberOf1(“23”),first = 2, length = 2
numFirstDigit = 10 
numOtherDigits = 2*1*1 = 2
numRecursive = NumberOf1("3")
进入第三次递归,这时只有1位,执行line 22,返回1;
这样把所有递归的结果相加:24 + 20 + 12 + 1 = 57
这是《剑指Offer》上给出的递归解法,当然结果是对的,但是总觉得表述得不是很清晰,它的大段文字的算法描述也不是很想看,总得来说,它是从高位到低位的转换,所以算法实现需要一些技巧,比如先把整型转换成字符型,再转换成整型,初看以为是为了表示大数,仔细看原来是为了方便获取除高位外低位数的值(line 29),但个人认为相对于额外的转换过程其实没有什么必要,除高位外低位数完全可以用别的方法获得。
下面直接上解法三,这是《编程之美给出的解法,个人认为较之前一个解法要清晰得多,它是从低位开始计算起的,所以不需要递归过程,代码也更易懂

点击(此处)折叠或打开

  1. LONGLONG Sum1s(ULONGLONG n)
  2. {
  3.    ULONGLONG iCount = 0;

  4.    ULONGLONG iFactor = 1;

  5.    ULONGLONG iLowerNum = 0;
  6.    ULONGLONG iCurrNum = 0;
  7.    ULONGLONG iHigherNum = 0;

  8.    while (n / iFactor != 0)
  9.    {
  10.       iLowerNum = n - (n / iFactor) * iFactor;
  11.       iCurrNum = (n / iFactor) % 10;
  12.       iHigherNum = n / (iFactor * 10);

  13.       switch (iCurrNum)
  14.       {
  15.       case 0:
  16.           iCount += iHigherNum * iFactor;
  17.           break;
  18.       case 1:
  19.           iCount += iHigherNum * iFactor + iLowerNum + 1;
  20.           break;
  21.       default:
  22.           iCount += (iHigherNum + 1) * iFactor;
  23.           break;
  24.       }

  25.       iFactor *= 10;
  26.    }

  27.    return iCount;
  28. }
这个算法结构非常清晰,还是简要说明一下,首先ULONLONG指的是unsigned long long型,还是以n = 123的情况说明,iFactor指的是每轮检测的位数,iFactor = 1表示检测个位,iFactor = 10表示检测十位,iFactor = 100表示检测百位,以此类推。
第一轮迭代,iLowerNum = 0, iCurrNum = 3, iHigherNum = 12, 则icount = 13,表示个位出现"1"的次数为13次
第二轮迭代,iLowerNum = 3, iCurrNum = 2, iHigherNum = 1, 这时iFactor = 10, 每一轮迭代结束,iFactor都乘以10,
                  iCount = 20,表示十位出现"1"的次数是20次
第三轮迭代,iLowerNum = 23, iCurrNum = 1, iHigherNum = 0, 这时iFactor = 100, icount = 24,表示百位出现"1"的次数为24次
为什么要这样计算,这里不再讨论,可以参考《编程之美》相应章节,个人觉得对这个问题的描述上,《编程之美》要高于《剑指Offer》一畴
解法二与解法三的复杂度均为O(lgN),但解法二也许有空间消耗,所以不要求递归的情况下,解法三是最佳选择。

参考资料:
        《编程之美》2.4
         《剑指Offer》 面试题32   
阅读(2011) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~