我正在研究这个问题,并想出一个解决方案(可能需要添加一两个条件),但不确定这是否是正确的方法,并且发现使用两个循环而不是很麻烦确定这是否是有效的方式。如果有人有一些很好的技巧,或者任何更好的方法将是受欢迎的将是伟大的:)。 (语言不是障碍)
我的算法:
void nextSmaller(int number) {
int firstZeroBitHelper = 1, nextOneBitHelper;
while (firstZeroBitHelper < number) {
// when we find first lsb zero bit we'll stop
bool bit = number & firstZeroBitHelper;
if (bit == false)
break;
firstZeroBitHelper = firstZeroBitHelper << 1;
}
if (firstZeroBitHelper >= number) {
cout << "No minimum number exists" << endl;
return;
}
nextOneBitHelper = firstZeroBitHelper;
nextOneBitHelper = nextOneBitHelper << 1;
while (nextOneBitHelper < number) {
// when we get '1' after the previous zero we stop
bool bit = number & nextOneBitHelper;
if (bit == true)
break;
nextOneBitHelper = nextOneBitHelper << 1;
}
// change the first zero to 1
number = number | firstZeroBitHelper;
// change the next set bit to zero
number = number & ~nextOneBitHelper;
cout << number << endl;
}
答案 0 :(得分:3)
继续发表评论..
好吧,我找到了它,而且很快。参见“计算机编程艺术”第7.1.3章(第4A卷),回答问题21:“与Gosper的黑客相反”。
看起来像这样:
t = y + 1;
u = t ^ y;
v = t & y;
x = v - (v & -v) / (u + 1);
其中y
是输入,x
是结果。与Gosper的黑客相同的优化适用于该部门。
答案 1 :(得分:3)
向上走:
向下走:
使向下案例清晰的一个例子:
我将首先解释向上的情况,因为对我来说感觉不那么棘手。我们希望找到最不重要的位置,我们可以向左移动1位的位置(换句话说,最右边的0向右移动1)。应该清楚的是,这是我们可能设置的最右边的位,因为我们需要为我们设置的每个位清除一点,我们需要在某处清除 right 我们设置的位,否则数字会变小而不是更大。
现在我们已经设置了这个位,我们想要清除一位(恢复设置位的总数),并重新洗牌剩下的位,使数字尽可能小(这使它成为 next 具有相同设置位数的最大数字)。我们可以清除我们刚设置的那个位右边的位,并且我们可以尽可能地向右移动任何剩余的1位而不用担心低于我们的原始数,因为所有不太重要的位仍然加起来少于我们设定的单个位。
找到下一个较低的数字而不是下一个较高的数字基本相同,只是我们正在寻找最右边的位置,我们可以移动一个位置正确,然后执行该操作我们希望尽可能远离 left 移动所有不太重要的位。
看起来其他人手头上已经掌握了这个版本的点点滴滴版本,但我想知道我是否可以对算法的逻辑/数学方面做出很好的解释。
答案 2 :(得分:2)
您描述的算法不太正确;除了一个细节,它做的一切正确。任何二进制数都具有以下形式,位于算法的中间:
xxxxx...10000...1111...
---n---// f //
此处xxx...
是任意位,连续的0和1的数量由firstZeroBitHelper
和nextOneBitHelper
(f
和n
确定。 / p>
现在你必须减少这个数字,留下相同数量的设定位,这必然会将最重要的1
变为0
:
xxxxx...0????...????...
-----n+f------
请注意,位???
的任何值都会使新数字小于原始数字,并且您确实希望选择这些位,以使得结果数字具有最大值:
xxxxx...011111...0000...
---f+1--//n-1//
所以你不得不翻转2位,而是f+2
位(从1
到0
的一位,以及从f+1
到0
的其他位1
)。
一种方法如下。
首先关闭所有相关位:
number &= ~nextOneBitHelper;
number &= ~(nextOneBitHelper - 1);
现在从MSB开始打开所需的位:
nextOneBitHelper >>= 1;
while (firstZeroBitHelper != 0)
{
number |= nextOneBitHelper;
nextOneBitHelper >>= 1;
firstZeroBitHelper >>= 1;
}
可以在没有循环的情况下实现上述的比特琐事;为此,您需要计算n
和f
。做完后:
unsigned mask = (1 << (f + 1)) - 1; // has f+1 bits set to 1
mask <<= n - 1; // now has these bits at correct positions
number |= mask; // now the number has these bits set
答案 3 :(得分:2)
anatolyg很好地覆盖了你的算法,但是有一个更有效的解决方案。
你可以巧妙地使用Gosper's hack,如果你翻转一下这些位,那么Gosper会按降序生成值。
像这样的伪代码会起作用:
let k := number
let n := num bits in k (log base 2)
k = k ^ ((1 << n) - 1)
k = gosper(k)
k = k ^ ((1 << n) - 1)
return k
如果你认为xor是线性时间算法,这给你一个很好的O(1)(或O(log n))。 :)
在某些情况下你必须考虑,比如某些k=2^x-1
的{{1}},但这很容易被抓住。
答案 4 :(得分:1)
#include <iostream>
bool AlmostdivBy2(int num) {
return (-~num & (num)) == 0;
}
void toggleright(int &num) {
int t;
for (t = -1; num & 1; t++)
num >>= 1;
++num = (num << t) | ~-(1 << t);
}
void toggleleft(int &num) {
while (~num & 1)
num >>= 1; //Simply keep chopping off zeros
//~num & 1 checks if the number is even
//Even numbers have a zero at bit at the rightmost spot
}
int main() {
int value;
std::cin >> value;
if (!AlmostdivBy2(value)) {
(~value & 1) ? toggleleft(value) : toggleright(value);
}
std::cout << value << "\n";
return 0;
}
我想我可能已经考虑过这个,但这是我的解释:
如果数字接近于2的幂,即1,3,7,15,31 ......之类的值,则没有小于可能具有相同数量的值的值在他们的二进制表示。因此,我们不担心这些数字。
如果数字是偶数,这是另一个简单的解决方法,我们只是从末尾切断零,直到数字为奇数
奇数是最具挑战性的,这就是为什么它是递归的。首先,您必须从数字右侧开始找到第一个零位。当找到它时,你将一个数字加上一个将最后一位变为1的数字。当递归展开时,你继续将位移到左边并加一个。完成后,您将拥有下一个最小的。
希望我没有混淆你
修改强>
更多地处理它,这里是toggleright
void toggleright(int &num) {
int t = 1;
while ( (num >>= 1) & 1 && t++ );
num = (-~num << ~-t) | ~-(1 << t);
}