我有以下练习:数字n0到n7是二进制系统中表示的字节。任务是一点点掉到底部,或者如果它遇到另一个位,它会保持在它之上。这是一个直观的例子:
我意识到如果我对从n0到n7的所有数字应用Bitwise OR,它总是n7的正确结果:
n7 = n0 | n1 | n2 | n3 | n4 | n5 | n6 | n7;
Console.WriteLine(n7); // n7 = 236
不幸的是我想不出其余字节n6,n5,n4,n3,n2,n1,n0的正确方法。 你有什么想法吗?
答案 0 :(得分:11)
我想提出一个解决方案,这个解决方案没有经过N次循环,我相信我找到了一种新颖的分而治之的方法:
int n0_, n1_, n2_, n3_, n4_, n5_, n6_, n7_;
// Input data
int n0 = 0;
int n1 = 64;
int n2 = 8;
int n3 = 8;
int n4 = 0;
int n5 = 12;
int n6 = 224;
int n7 = 0;
//Subdivide into four groups of 2 (trivial to solve each pair)
n0_ = n0 & n1;
n1_ = n0 | n1;
n2_ = n2 & n3;
n3_ = n2 | n3;
n4_ = n4 & n5;
n5_ = n4 | n5;
n6_ = n6 & n7;
n7_ = n6 | n7;
//Merge into two groups of 4
n0 = (n0_ & n2_);
n1 = (n0_ & n3_) | (n1_ & n2_);
n2 = (n0_ | n2_) | (n1_ & n3_);
n3 = (n1_ | n3_);
n4 = (n4_ & n6_);
n5 = (n4_ & n7_) | (n5_ & n6_);
n6 = (n4_ | n6_) | (n5_ & n7_);
n7 = (n5_ | n7_);
//Merge into final answer
n0_ = (n0 & n4);
n1_ = (n0 & n5) | (n1 & n4);
n2_ = (n0 & n6) | (n1 & n5) | (n2 & n4);
n3_ = (n0 & n7) | (n1 & n6) | (n2 & n5) | (n3 & n4);
n4_ = (n0) | (n1 & n7) | (n2 & n6) | (n3 & n5) | (n4);
n5_ = (n1) | (n2 & n7) | (n3 & n6) | (n5);
n6_ = (n2) | (n3 & n7) | (n6);
n7_ = (n3 | n7);
这种方法只需要56个按位操作,这比其他解决方案要少得多。
了解在最终答案中设置位的情况非常重要。例如,如果在该列中设置了三个或更多位,则n5中的列为1。这些位可以按任何顺序排列,这使得计算它们相当困难。
我们的想法是将问题分解为子问题,解决子问题,然后将解决方案合并在一起。每次我们合并两个块时,我们都知道每个块中的位都被正确“丢弃”。这意味着我们不必在每个阶段检查位的每个可能的排列。
虽然直到现在我才意识到这一点,但这与Merge Sort非常相似,Merge Sort在合并时利用了排序的子数组。
答案 1 :(得分:3)
此解决方案仅使用按位运算符:
class Program
{
static void Main(string[] args)
{
int n0, n1, n2, n3, n4, n5, n6, n7;
int n0_, n1_, n2_, n3_, n4_, n5_, n6_, n7_;
// Input data
n0 = 0;
n1 = 64;
n2 = 8;
n3 = 8;
n4 = 0;
n5 = 12;
n6 = 224;
n7 = 0;
for (int i = 0; i < 7; i++)
{
n0_ = n0 & n1 & n2 & n3 & n4 & n5 & n6 & n7;
n1_ = (n1 & n2 & n3 & n4 & n5 & n6 & n7) | n0;
n2_ = (n2 & n3 & n4 & n5 & n6 & n7) | n1;
n3_ = (n3 & n4 & n5 & n6 & n7) | n2;
n4_ = (n4 & n5 & n6 & n7) | n3;
n5_ = (n5 & n6 & n7) | n4;
n6_ = (n6 & n7) | n5;
n7_ = n7 | n6;
n0 = n0_;
n1 = n1_;
n2 = n2_;
n3 = n3_;
n4 = n4_;
n5 = n5_;
n6 = n6_;
n7 = n7_;
}
Console.WriteLine("n0: {0}", n0);
Console.WriteLine("n1: {0}", n1);
Console.WriteLine("n2: {0}", n2);
Console.WriteLine("n3: {0}", n3);
Console.WriteLine("n4: {0}", n4);
Console.WriteLine("n5: {0}", n5);
Console.WriteLine("n6: {0}", n6);
Console.WriteLine("n7: {0}", n7);
}
}
虽然可以简化,因为我们并不需要重新计算所有数字: 在每次迭代中,最上一行肯定是好的。
我的意思是:
class Program
{
static void Main(string[] args)
{
int n0, n1, n2, n3, n4, n5, n6, n7;
int n0_, n1_, n2_, n3_, n4_, n5_, n6_, n7_;
n0 = 0;
n1 = 64;
n2 = 8;
n3 = 8;
n4 = 0;
n5 = 12;
n6 = 224;
n7 = 0;
n0_ = n0 & n1 & n2 & n3 & n4 & n5 & n6 & n7;
n1_ = (n1 & n2 & n3 & n4 & n5 & n6 & n7) | n0;
n2_ = (n2 & n3 & n4 & n5 & n6 & n7) | n1;
n3_ = (n3 & n4 & n5 & n6 & n7) | n2;
n4_ = (n4 & n5 & n6 & n7) | n3;
n5_ = (n5 & n6 & n7) | n4;
n6_ = (n6 & n7) | n5;
n7_ = n7 | n6;
n0 = n0_;
n1 = n1_;
n2 = n2_;
n3 = n3_;
n4 = n4_;
n5 = n5_;
n6 = n6_;
n7 = n7_;
Console.WriteLine("n0: {0}", n0);
n1_ = (n1 & n2 & n3 & n4 & n5 & n6 & n7) | n0;
n2_ = (n2 & n3 & n4 & n5 & n6 & n7) | n1;
n3_ = (n3 & n4 & n5 & n6 & n7) | n2;
n4_ = (n4 & n5 & n6 & n7) | n3;
n5_ = (n5 & n6 & n7) | n4;
n6_ = (n6 & n7) | n5;
n7_ = n7 | n6;
n1 = n1_;
n2 = n2_;
n3 = n3_;
n4 = n4_;
n5 = n5_;
n6 = n6_;
n7 = n7_;
Console.WriteLine("n1: {0}", n1);
n2_ = (n2 & n3 & n4 & n5 & n6 & n7) | n1;
n3_ = (n3 & n4 & n5 & n6 & n7) | n2;
n4_ = (n4 & n5 & n6 & n7) | n3;
n5_ = (n5 & n6 & n7) | n4;
n6_ = (n6 & n7) | n5;
n7_ = n7 | n6;
n2 = n2_;
n3 = n3_;
n4 = n4_;
n5 = n5_;
n6 = n6_;
n7 = n7_;
Console.WriteLine("n2: {0}", n2);
n3_ = (n3 & n4 & n5 & n6 & n7) | n2;
n4_ = (n4 & n5 & n6 & n7) | n3;
n5_ = (n5 & n6 & n7) | n4;
n6_ = (n6 & n7) | n5;
n7_ = n7 | n6;
n3 = n3_;
n4 = n4_;
n5 = n5_;
n6 = n6_;
n7 = n7_;
Console.WriteLine("n3: {0}", n3);
n4_ = (n4 & n5 & n6 & n7) | n3;
n5_ = (n5 & n6 & n7) | n4;
n6_ = (n6 & n7) | n5;
n7_ = n7 | n6;
n4 = n4_;
n5 = n5_;
n6 = n6_;
n7 = n7_;
Console.WriteLine("n4: {0}", n4);
n5_ = (n5 & n6 & n7) | n4;
n6_ = (n6 & n7) | n5;
n7_ = n7 | n6;
n5 = n5_;
n6 = n6_;
n7 = n7_;
Console.WriteLine("n5: {0}", n5);
n6_ = (n6 & n7) | n5;
n7_ = n7 | n6;
n6 = n6_;
n7 = n7_;
Console.WriteLine("n6: {0}", n6);
n7_ = n7 | n6;
n7 = n7_;
Console.WriteLine("n7: {0}", n7);
}
}
答案 2 :(得分:2)
计算每列中的1位数。接下来,清除列并从底部添加正确数量的“令牌”。
答案 3 :(得分:1)
基于CodesInChaos的建议:
static class ExtensionMethods {
public static string AsBits(this int b) {
return Convert.ToString(b, 2).PadLeft(8, '0');
}
}
class Program {
static void Main() {
var intArray = new[] {0, 64, 8, 8, 0, 12, 224, 0 };
var intArray2 = (int[])intArray.Clone();
DropDownBits(intArray2);
for (var i = 0; i < intArray.Length; i++)
Console.WriteLine("{0} => {1}", intArray[i].AsBits(),
intArray2[i].AsBits());
}
static void DropDownBits(int[] intArray) {
var changed = true;
while (changed) {
changed = false;
for (var i = intArray.Length - 1; i > 0; i--) {
var orgValue = intArray[i];
intArray[i] = (intArray[i] | intArray[i - 1]);
intArray[i - 1] = (orgValue & intArray[i - 1]);
if (intArray[i] != orgValue) changed = true;
}
}
}
}
工作原理
让我们保持简单,从这三个半字节开始:
0) 1010
1) 0101
2) 0110
我们从底行(i = 2)开始。通过按位或使用上面的行(i-1),我们确保第2行中的所有位为0,如果它是第1行中的1则将变为1.所以我们让第1行中的1位落到第2行。
1) 0101
2) 0110
第1行的右侧位置可能会因为第2行中存在“空间”(0
)而下降。因此第2行变为第2行或第1行:0110 | 0101
,即{{1 }}
现在我们必须删除从第1行掉落的位。因此,我们按行2和1的原始值执行,因此0111
变为0110 & 0101
。由于第2行的值已更改,0100
变为changed
。
到目前为止的结果如下。
true
这结束了1) 0100
2) 0111
= 2的内部循环。
然后i
变为1.现在我们将第0行的位下降到第1行。
i
第1行成为第1行或第0行的结果:0) 1010
1) 0100
为0100 | 1010
。第0行成为按位的结果,并且在这两个值上:1110
为0100 & 1010
。而且,目前的行已经改变了。
0000
如你所见,我们尚未完成。这就是0) 0000
1) 1110
2) 0111
循环的用途。我们在第2行重新开始。
第2行= while (changed)
,第1行= 0111 | 1110 = 1111
。该行已更改,因此0111 & 1110 = 0110
为changed
。
true
然后0) 0000
1) 0110
2) 1111
变为1.第1行= i
,第0行= 0110 | 0000 = 0110
。第1行没有更改,但0110 & 0000 = 0000
的值已经是changed
并保持不变。
这轮true
循环再次发生了变化,所以我们将再次执行内循环。
这一次,所有行都不会更改,导致while (changed)
剩余changed
,从而结束false
循环。