编程之美--高效率算出1的数目之扩展问题

问题:给定一个十进制数N,写下从1开始,到N的所有二进制数,然后数一下其中出现的所有“1”的个数。

问题分析:

十进制 二进制 最右边1的个数总和 倒数第二1的个数总和  倒数第三1的个数总和 倒数第四1的个数总和 倒数第五1的个数总和 十进制 二进制 最右边1的个数总和 倒数第二1的个数总和 倒数第三1的个数总和 倒数第四1的个数总和 倒数第五1的个数总和
0 00000 0 0 0 0 0 13 01101 7 6 6 6 0
1 00001 1 0 0 0 0 14 01110 7 7 7 7 0
2 00010 1 1 0 0 0 15 01111 8 8 8 8 0
3 00011 2 2 0 0 0 16 10000 8 8 8 8 1
4 00100 2 2 1 0 0 17 10001 9 8 8 8 2
5 00101 3 2 2 0 0 18 10010 9 9 8 8 3
6 00110 3 3 3 0 0 19 10011 10 10 8 8 4
7 00111 4 4 4 0 0 20 10100 10 10 9 8 5
8 01000 4 4 4 1 0 21 10101 11 10 10 8 6
9 01001 5 4 4 2 0 22 10110 11 11 11 8 7
10 01010 5 5 4 3 0 23 10111 12 12 12 8 8
11 01011 6 6 4 4 0 24 11000 12 12 12 9 9
12 01100 6 6 5 5 0 25 11001 13 12 12 10 10

 

首先我们看2,4816四个数,2的二进制数总共有21,4的有5个,8的有13个,1633个。而且他们都有一定的规律。比如1624次方,二进制数是10000,在最右边1的个数和倒数第二、第三、第四都是8(即48),最高位是1823次方,二进制数是01000,在最右边1的个数和倒数第二、第三都是434),倒数第四个是1;以此类推42,那么我们可以看出符合这样的公式:

0~2^n的所有二进制数1的总和sum=2^(n-1)*n+1    其中2^n表示2n次方

 

现在我们观察0~24的数的二进制的最右边位的1的个数总和,0,1,1,2,2,3,3,4,4,5,5,6,6...,可以得出这样的公式:

0~n的所有二进制数最右边位的1的个数总和sum:当n%2=0时,sum=n/2; 否则sum=n/2+1

 

观察倒数第二位1的总和。0~10,其他的都是一个常数带上三个相同的常数;比如1,2,2,2,3,4,4,45,6,6,6....  那么设他们的规律是1+3为一组。

我们设sequenceLength=1,repeatLength=3;

这样子我们可以计算出18的二进制数的倒数第二位的1的总和为:看上面表格知道从0开始,那么18就是第19位,看倒数第二1的个数这一列前面是两个0,推出(18+1-2)/(1+3)取整为4,就是说有4组,每组的值其实是两个,那么sum=4*2=8,而(18+1-2)%(1+3)=1;所以sum=4*2+1=9

 

观察倒数第三位1的总和。0~30,其他的都是三个常数带上五个相同的常数;比如1,2,3,4,4,4,4,45,6,7,8,8,8,8,89,10,11,12,12,12,12,12....  那么设他们的规律是3+5为一组。

这样子我们可以计算出18的二进制数的倒数第三位的1的总和为:看上面表格知道从0开始,那么18就是第19位,看倒数第三1的个数这一列前面是四个0,推出(18+1-4)/(3+5)取整为1,就是说有1组,每组的值其实是4个,那么sum=1*4=4,而(18+1-4)%(3+5)=7;就是说18这个数为一组还多七个,由3+5为一组可知,多出来的七个的前三个的值是连续的,后面四个是一样的。那么这多出来的七个数其实就是3+1个值,推出sum=1*4+(3+1)=8

 

观察倒数第四位1的总和。0~70,其他的都是三个常数带上五个相同的常数;比如1,2,3,4,5,6,7,8,8,8,8,8,8,8,8,8....  那么设他们的规律是7+9为一组

这样子我们可以计算出18的二进制数的倒数第四位的1的总和为:看上面表格知道从0开始,那么18就是第19位,看倒数第四1的个数这一列前面是八个0,推出(18+1-8)/(7+9)取整为0,就是说有0组,每组的值其实是8个,那么sum=0*8=0,而(18+1-8)%(7+9)=11;就是说18这个数为零组还多十一个,由7+9为一组可知,多出来的十一个的前七个的值是连续的,后面九个是一样的。那么这多出来的十一个数其实就是7+1个值,推出sum=0*8+(7+1)=8

倒数第二位、倒数第三位。。。倒数第N位的组的规律。

1+33+57+9  可知(1+2^1)+(3+2^1)=3+5;  (3+2^2)+(5+2^2)=7+9......

和面组是前面组的2的幂次方。倒数第二位是2的一次方,倒数第三位是2的两次方...以此类推。

代码分析:

View Code
 1     public class NumberOfOne
 2     {
 3         public long Sum { get; set; }
 4         public void BinaryCount(int n)
 5         {
 6             CountLowest(n);
 7             CountCommont(n);
 8         }
 9 
10         public void CountLowest(int n)
11         {
12             //计算最低位(最右边位)1的总数
13             Sum = (n / 2) * 2 < n ? (n / 2) + 1 : Sum = n / 2;
14         }
15 
16         public void CountCommont(int n)
17         {
18             #region 开始计算除开最低位的1的总数
19             //比如说数列:1,2,3,4,4,4,1,2,3,4,4,4,1,2,3,4,4,4...,那么sequenceLength就是1,2,3的长度3,
20             //repeatLength就是4,4,4的长度(重复次数)3
21             long sequenceLength = 1;
22             long repeatLength = 3;
23             long exponent = 1;
24 
25             for (int i = 0; i < 32; i++)
26             {
27                 // exponent*2表示上面表格的每一列开头有多少个0,成2为幂底的指数增长
28                 long temp = (n + 1) - exponent * 2;
29                 if (temp <= 0)
30                 {
31                     break;
32                 }
33                 if (temp <= sequenceLength)
34                 {
35                     Sum += temp;
36                 }
37                 else
38                 {
39                     //组数
40                     long valueOfDivide = temp / (sequenceLength + repeatLength);
41                     long valueOfMockup = temp % (sequenceLength + repeatLength);
42 
43                     if (valueOfMockup <= sequenceLength)
44                     {
45                         //每组长度是sequenceLength+repeatLength,不过他们只有sequenceLength+1个不同的联系的值,
46                         //比如1,2,3,4,4这组有五个数,不过只有四个不同的连续的值:1,2,3,4。
47                         Sum += valueOfDivide * (sequenceLength + 1) + valueOfMockup;
48                     }
49                     else
50                     {
51                         Sum += valueOfDivide * (sequenceLength + 1) + sequenceLength + 1;
52                     }
53                 }
54                 exponent *= 2;
55                 sequenceLength += exponent;
56                 repeatLength += exponent;
57             }
58             #endregion
59         }
60 }

调用:

View Code
 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             NumberOfOne numberOfOne = new NumberOfOne();
 6             while (true)
 7             {
 8                 int number = int.Parse(Console.ReadLine());
 9                 numberOfOne.BinaryCount(number);
10                 Console.WriteLine("0~{0}的所有二进制数的1的总和为:{1}", number, numberOfOne.Sum);
11 
12                 if (number == 444)
13                 {
14                     break;
15                 }
16             }
17             Console.ReadLine();
18         }
19     }

 

 

posted @ 2012-12-26 11:19  四龙须  阅读(1124)  评论(6编辑  收藏  举报