《编程之美》之前有看过,不过看完之后不仅啥也没记住,反而是把自己绕得一团晕,重读《编程之美》也是想重新梳理一下算法中的逻辑,并试图找出那些所谓“美”的算法的共性,同时也希望能够结交一些有着共同爱好的童鞋。好了,废话到此,咱们开始吧。
1、题目:对于一个字节(8bit)的变量,求其二进制表示中 “1” 的个数,要求算法的执行效率尽可能高。
解法一、
思路:(输入)变量为125,其二进制表示为01111101,统计该二进制表示中“1”出现的个数,可(从低位到高位)依次统计每位上“1”出现情况。
计算:
1、计算(输入)变量二进制表示中最低位数字(num % 2),若为“1”则count加1;
2、计算(输入)变量二进制表示向右位移一位值(num / 2);
3、迭代步骤1、步骤2,直到(输入)变量值为0;
4、返回count值。
代码:
public int countNumberOne(int inputNumber) { int retCount = 0; while(inputNumber != 0) { if(1 == (inputNumber % 2)) { ++retCount; } inputNumber /= 2; } return retCount; }
注:该算法时间复杂度为O(N),N为输入变量的二进制表示长度。
解法二、
思路:解法二是对解法一的改进,对于(输入)变量只需统计“1”的出现次数即可。我们知道num & num – 1的二进制运算,是对num最低“1”位计算为“0”,特别地,若num & num – 1 = 0,则表示num为2的幂次方。因此,可直接依次(从低位到高位)统计最低“1”位个数。
计算:
1、计算(输入)变量二进制表示是否为0,若不为0,则count加1;
2、计算(输入)变量二进制表示最低“1”位值变为“0”(num &= num –1);
3、迭代步骤1、步骤2,直到(输入)变量值为0;
4、返回count值。
代码:
public int countNumberOne2(int inputNumber) { int retCount = 0; while(inputNumber != 0) { retCount++; inputNumber &= (inputNumber - 1); } return retCount; }
注:该算法时间复杂度为O(M),M为输入变量的二进制表示中“1”的个数。
解法三、
思路:因为题目要求输入变量长度为8bit,即一个字节,其取值范围为0~255。因此,可直接建立0~255中每个数字所对应“1”出现个数的关系映射表,键值(key–value)关系可简单通过数组来表示:数组下标(i)表示关键字(key),即所要查找的数字;数组值(arr[i])表示所要查找的结果,即“1”出现个数。
计算:
1、装载关系映射表(即0~255中每个数字所对应“1”个数的映射关系);
2、以输入数字为键值(key),查找关系映射表中该键值对应的value值,并返回。
代码:
static int[] countTable = null; static void loadCountTable() { countTable = new int[256]; for(int i = 0; i < 256; i++) { countTable[i] = countNumberOne(i); } } public int countNumberOne3(int inputNumber) { if(countTable == null) { loadCountTable(); } return countTable[inputNumber]; }
注:该算法时间复杂度为O(1),空间复杂度为O(2 ^ n),n为输入变量最大长度。
另:该算法为典型的空间换时间案例,得益于存储开销远低于计算(cpu)开销,空间换时间在计算机领域中十分常见,如:搜索引擎中的倒排索引、网站中的降级处理(如某电子商务公司在价格战中会将部分动态页面静态化,以提高响应时间)等。如果大家有什么这方面的案例,希望能共同探讨,谢谢!