编程之美--高效率算出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,、4、8、16四个数,2的二进制数总共有2个1,4的有5个,8的有13个,16有33个。而且他们都有一定的规律。比如16是2的4次方,二进制数是10000,在最右边1的个数和倒数第二、第三、第四都是8(即4个8),最高位是1;8是2的3次方,二进制数是01000,在最右边1的个数和倒数第二、第三都是4(3个4),倒数第四个是1;以此类推4和2,那么我们可以看出符合这样的公式:
0~2^n的所有二进制数1的总和sum=2^(n-1)*n+1 其中2^n表示2的n次方
现在我们观察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~1是0,其他的都是一个常数带上三个相同的常数;比如1,2,2,2,;3,4,4,4;5,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~3是0,其他的都是三个常数带上五个相同的常数;比如1,2,3,4,4,4,4,4;5,6,7,8,8,8,8,8;9,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~7是0,其他的都是三个常数带上五个相同的常数;比如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+3;3+5;7+9 可知(1+2^1)+(3+2^1)=3+5; (3+2^2)+(5+2^2)=7+9......
和面组是前面组的2的幂次方。倒数第二位是2的一次方,倒数第三位是2的两次方...以此类推。
代码分析:
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 }
调用:
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 }
转载于:https://www.cnblogs.com/silongxu/archive/2012/12/26/2833539.html