编程之美 - 1 的数目

来源:互联网 发布:java main spring 编辑:程序博客网 时间:2024/06/05 14:57
问题:
给定一个十进制整数N,写下从1开始到N的所有数字,然后数一下其中1的个数。

例如N = 16,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1112, 13, 14, 15, 16

其中 1 的个数为 9 个。

分析:
第一种方式比较暴力, 一个数字一个数字的查。复杂度为  N*(log2N)

第二种方式找规律,如例子中N=16的情况来看:
      -      第一个是个位上的 1: 1, 11    两个
      -      第二个是十位上的 1: 10, 11, 12,13,14,15,16    七个
1: 9 个

再如N = 23
      -      第一个是个位上的 1: 1, 11,21    三个
      -      第二个是十位上的 1: 10  ~  19    十个
1: 13 个

所以如果 十位上递增了一个,那么个位上的1便会多加一个; 十位数决定个位数
如果十位上的数是一个1,或大于1,那么个位数有多少个,便决定十位上1的个数。

再如 N = 123
      -      第一个是个位上的 1: 1, 11,21...121    0, 10 ,20, .... 120     13个
      -      第二个是十位上的 1: 10  ~  19,  110  ~  119    20个
      -      第三个是百位上的 1: 100  ~  123,      24个
1:13 + 20 + 24= 57

按照位数分析,每位上的1和它的高位和低位都有关系。  假设当前位 : c      高位: h    低位的数值: l   step:10的进步

if (c == 1)     // 1 的个数和高位和低位都有关系
    cnt += h*(step/10) + l + 1
else if (c == 0)   // 1 的个数和高位有关系
    cnt += h*(step/10)
else   //  当前位大于 1 的情况,和高位有关系
    cnt += (h+1)*(step/10) +1

实例程序
#include <iostream>using namespace std;int calc(int N){    int nCnt = 0;    int nH=0, nC=0, nL=0;    int nStep = 10;    cout << "N=" << N << endl << endl;    while(nStep/10 <= N)    {        nC = (N % nStep)/(nStep/10);        nH = N / nStep;        nL = (N % nStep) - nC * (nStep/10);        cout << "C=" << nC << "   H=" << nH << "   L=" << nL << "   Step=" << nStep/10 << endl;        switch(nC)        {        case 0:            nCnt += nH*(nStep/10);            break;        case 1:            nCnt += nH*(nStep/10) + nL+1;            break;        default:            nCnt += (nH+1)*(nStep/10);            break;        }        nStep *= 10;    }    cout << "count=" << nCnt << endl;    cout << "=====================================\n" <<endl;    return nCnt;}int main(){    int nRet = 0, i = 0;    int test[] = {19,0,1,10,23,123,223,1023};    int len = sizeof(test)/sizeof(test[0]);        for (i = 0; i < len; i++)    {        nRet = calc(test[i]);    }    cin >> nRet;    return 0;}



扩展问题

二进制数,有下面的规律
f(1)   = 1       (1有一个 1)
f(10) = 10     (01, 10 有2个 1)
f(11) = 100   (01  10 11 有4个 1)

函数f()的作用就是求二进制数中有多少个 1存在。


扩展问题分析

可以先从全 1 的情况开始分析:
f(1)       =  1      (有1位  k = 1)
f(11)     =  4      (有2位  k = 2)
f(111)   =  12    (有3位  k = 3)

所以可以得到全 1 时的公式   k * 2^(k-1)    k是一共有多少位数
那么一个二进制数 1 的个数可以拆成两个部分,去掉最高位 1 以后,剩下(k-1)位的全1的个数 + 加上最高位的1后的剩余个数

例如:
10010 就可以分成两个部分  1111 的1的和 + 从10000 到 10010 的1的和
从10000 到 10010 还可以继续拆分: 
-    10010  最高位的1的个数是由后面的低位决定的 比如10010-->  10000, 10001,10010 红色的部分有三个
-    0010   低位还需要继续按照上面的规则迭代,直到结束  0001  00010  两个 同 f(10)


扩展问题代码:

#include <iostream>using namespace std;/*    first calculate the count of 1 in a full 1 number    eg: 1 => cnt = 1;   11 => (01 10 11) cnt = 4;   111 => (001 010 011 100 101 110 111) cnt = 12    so we got cnt = k * 2^(k-1)*/int calc(char* binary, int len){    int nCnt = 1, num = 0;    int k = len, total = 0;    char* p = binary+1;    if (len <= 0)        return 0;    while (k > 1)    {        if (binary[k-1]=='1')            num += nCnt;        nCnt *= 2;        k--;    }    if (num == (nCnt-1))      // full of 1        return total = len*nCnt;    else        total = (len-1)*(nCnt/2);    total += num+1;    k = 1;    while ((*p != '1') && (k<len))    {        *p++; k++;    }    total += calc(p, len-k);    return total;}int main(){    char* test[]= {"100", "1", "10", "11", "111",                   "10000", "1111", "10110", "110011", "110011001"};    int test_num = 10;    int len = 0, i = 0, total=0;    for (i = 0; i < test_num; i++)    {        len = strlen(test[i]);        total = calc(test[i], len);        cout << test[i] << ":  result=" << total << endl;        cout << "\n\n =========================\n" << endl;    }    cin >> len;    return 0;}









1 0
原创粉丝点击