题目
原文:
Given an integer, print the next smallest and next largest number that have the same number of 1 bits in their binary representation.
译文:
给一个整数,打印两个数,并且他们的二进制表示中的1的个数和这个整数的一样,其中一个是比该数大的数中最小的,另一个是比该数小的数中最大的。
解答
用简单粗暴的方法就是,直接求出该整数的二进制的1的个数,然后该整数逐减一并判断其二进制的1的个数是否与该整数的相同,相同即可返回比该数小的数中的最大值,同理,该整数逐加一并判断其二进制的1的个数是否与该整数的相同,相同即可返回比该数大的数中的最小值,代码如下:
int next(int x){ int max_int = ~(1<<31); int num = count_one(x); if(num == 0 || x == -1) return -1; for(++x; count_one(x) != num && x < max_int; ++x); if(count_one(x) == num) return x; return -1; } int previous(int x){ int min_int = (1<<31); int num = count_one(x); if(num == 0 || x == -1) return -1; for(--x; count_one(x) != num && x > min_int; --x); if(count_one(x) == num) return x; return -1; }
那如何求整数的二进制中的1的个数呢?通常做法是模2取余累加或不断地移位判断最低位是否为1然后计数器累加的方法:
int count_one(int x){ int cnt = 0; for(int i=0; i<32; ++i){ if((x & 1)==1) ++cnt; x >>= 1; } return cnt; }不过还有一种更高效的方法:位操作的归并算法,即Hamming_weight,http://en.wikipedia.org/wiki/Hamming_weight
public static int hammingWeight(int n){ n=(n&(0x55555555))+((n>>1)&(0x55555555)); n=(n&(0x33333333))+((n>>2)&(0x33333333)); n=(n&(0x0f0f0f0f))+((n>>4)&(0x0f0f0f0f)); n=(n&(0x00ff00ff))+((n>>8)&(0x00ff00ff)); n=(n&(0x0000ffff))+((n>>16)&(0x0000ffff)); return n; }
更高效的方案(转自:http://hawstein.com/posts/5.3.html):假设给定的数的二进制表示为: 1101110,我们从低位看起,找到第一个1,从它开始找到第一个0,然后把这个0变为1, 比这个位低的位全置0,得到1110000,这个数比原数大,但比它少了两个1, 直接在低位补上这两个1,得到,1110011,这就是最终答案。 我们可以通过朴素版本来模拟这个答案是怎么得到的:
1101110->1101111->1110000->1110001->1110010->1110011
接下来,我们来考虑一些边界情况,这是最容易被忽略的地方(感谢细心的读者)。 假设一个32位的整数, 它的第31位为1,即:0100..00,那么按照上面的操作,我们会得到1000..00, 很不幸,这是错误的。因为int是有符号的,意味着我们得到了一个负数。 我们想要得到的是一个比0100..00大的数,结果得到一个负数,自然是不对的。 事实上比0100..00大的且1的个数和它一样的整数是不存在的,扩展可知, 对于所有的0111..,都没有比它们大且1的个数和它们一样的整数。对于这种情况, 直接返回-1。-1的所有二进制位全为1,不存在一个数说1的个数和它一样还比它大或小的, 因此适合作为找不到答案时的返回值。
另一个边界情况是什么呢?就是对于形如11100..00的整数,它是一个负数, 比它大且1的个数相同的整数有好多个,最小的当然是把1都放在最低位了:00..0111。
代码如下:
int next1(int x){ int xx = x, bit = 0; for(; (x&1) != 1 && bit < 32; x >>= 1, ++bit); for(; (x&1) != 0 && bit < 32; x >>= 1, ++bit); if(bit == 31) return -1; //011.., none satisify x |= 1; x <<= bit; // wtf, x<<32 != 0,so use next line to make x=0 if(bit == 32) x = 0; // for 11100..00 int num1 = count_one(xx) - count_one(x); int c = 1; for(; num1 > 0; x |= c, --num1, c <<= 1); return x; }
int previous1(int x){ int xx = x, bit = 0; for(; (x&1) != 0 && bit < 32; x >>= 1, ++bit); for(; (x&1) != 1 && bit < 32; x >>= 1, ++bit); if(bit == 31) return -1; //100..11, none satisify x -= 1; x <<= bit; if(bit == 32) x = 0; int num1 = count_one(xx) - count_one(x); x >>= bit; for(; num1 > 0; x = (x<<1) | 1, --num1, --bit); for(; bit > 0; x <<= 1, --bit); return x; }