我们只需要考虑在第一行和第一列有冲孔的图案。如果第一行为空,则模式可以上移。如果第一列为空,则模式可以向左移动。无论哪种情况,我们都可以得出我们确实考虑的类似模式。
对于这些模式,我们需要检查旋转后的版本是否相同。为此,我们应用最多三个 90 度旋转,可能会向左移动以删除前导空列(第一行永远不为空)并查找具有最低数值的模式。
然后我们可以将该值添加到哈希集中,该哈希集将仅保留唯一值。
不包括空模式,因为它的所有行都是空的。
为了实现这一点,我们将模式编码为连续位:
012
345
678
我们需要的操作大多非常简单:
Test for an empty row: (n & 7) == 0 // bits 0,1,2 not set
Test for an empty column: (n & 73) == 0 // bits 0,3,6 not set
Shift pattern up: n -> (n >> 3)
Shift pattern left: n -> (n >> 1)
最棘手的部分是旋转,这实际上只是重新排列所有位:
n -> ((n & 1) << 2) + ((n & 2) << 4) + ((n & 4) << 6)
+ ((n & 8) >> 2) + (n & 16) + ((n & 32) << 2)
+ ((n & 64) >> 6) + ((n & 128) >> 4) + ((n & 256) >> 2);
In C#:
public static int Count3x3() {
HashSet<int> patterns = new HashSet<int>();
for (int i = 0; i < 512; i++) {
if ((i & 7) == 0 || (i & 73) == 0)
continue;
int nLowest = i;
int n = i;
do {
nLowest = Math.Min(nLowest, n);
n = ((n & 1) << 2) + ((n & 2) << 4) + ((n & 4) << 6)
+ ((n & 8) >> 2) + (n & 16) + ((n & 32) << 2)
+ ((n & 64) >> 6) + ((n & 128) >> 4) + ((n & 256) >> 2);
while ((n & 73) == 0)
n >>= 1;
} while (n != i);
patterns.Add(nLowest);
}
return patterns.Count;
}
这个函数返回116。在我的机器上花费的时间是0.023ms。
EDIT:通过使用 4 个观察结果,您可以获得额外 7 倍的改进:
- 我们可以使用简单的访问数组来代替哈希集。如果以前见过某种模式,我们就不会计算它。这也消除了跟踪内循环中“最低”模式的需要。如果访问了一个模式,那么它的最低旋转模式也会被访问。
- 如果我们没有 180 度旋转对称性,那么第三次旋转将不会产生原始图案。第四次旋转总是会发生,所以没有必要。
- 旋转表达式可以稍微简化。
因此,如果我们应用这些观察结果并展开内部 do 循环,我们会得到以下结果:
static int Rotate(int n) {
n = ((n & (1+32)) << 2) + ((n & 2) << 4) + ((n & 4) << 6)
+ ((n & (8+256)) >> 2) + (n & 16)
+ ((n & 64) >> 6) + ((n & 128) >> 4);
while ((n & 73) == 0)
n >>= 1;
return n;
}
public static int Count3x3_3() {
bool[] visited = new bool[512];
int count = 0, r;
for (int i = 0; i < 512; i++) {
if (visited[i])
continue;
if ((i & 7) == 0 || (i & 73) == 0)
continue;
count++;
if ((r = Rotate(i)) == i) continue;
visited[r] = true;
if ((r = Rotate(r)) == i) continue;
visited[r] = true;
visited[Rotate(r)] = true;
}
return count;
}
在同一台机器上运行时间约为 3μs。