基于位掩码的n/2组合计数高效实现方案问询
高效生成恰好n/2个1的位掩码方法
嘿,这个问题我太熟悉了!你当前遍历所有2ⁿ种状态再判断1的位数的方法,在n稍微大一点的时候就会变得极其低效——比如n=20时总状态数超过百万,n=30更是直奔十亿级别,完全不适合实际场景。
其实我们根本不需要遍历所有状态,直接生成恰好包含k个1的位掩码(这里k=n/2)就可以,效率能提升几个数量级。最经典的算法就是Gosper's Hack,它专门用来按顺序生成所有恰好有k个1的二进制数,时间复杂度是O(C(n,k)),也就是组合数的量级,比2ⁿ小太多了。
为什么Gosper's Hack高效?
它通过位运算直接跳转到下一个符合条件的掩码,完全跳过所有1的位数不等于k的状态。每个生成步骤都是O(1)的位运算操作,不需要额外的计数判断——生成的每个掩码天然满足1的位数是k,你的possible()函数直接可以删掉!
算法实现示例(Python)
def query(n): k = n // 2 if k == 0 or k > n: return # 初始化第一个掩码:最低k位全为1 mask = (1 << k) - 1 max_mask = 1 << n while mask < max_mask: # 这里处理当前mask,比如你的业务逻辑 # 示例:打印掩码的二进制形式 print(f"当前掩码: {bin(mask)[2:].zfill(n)}") # Gosper's Hack 核心步骤 c = mask & -mask # 获取最右边的1的位置(孤立的1) r = mask + c # 把最右边的连续1串进位 mask = (((r ^ mask) >> 2) // c) | r # 调整后续1的位置,生成下一个掩码
核心步骤解释
- 初始化:
(1 << k) - 1生成最低k位全为1的二进制数,这是第一个符合条件的掩码。 - 获取最右1:
mask & -mask利用补码特性,得到mask最右边的那个1对应的数值(比如mask=0b10110,结果就是0b10)。 - 进位操作:
mask + c会把最右边的连续1串变成0,并把左边第一个0变成1(比如0b10110 + 0b10 = 0b11000)。 - 调整后续1:
(r ^ mask) >> 2得到进位后变化的部分,除以c后右移两位,再和r合并,就能把剩下的k-1个1放到最右边的低位,形成下一个符合条件的掩码。
其他可选方法
如果你的场景不需要按顺序生成掩码,也可以用递归回溯的方式生成:每一位决定是否置1,累计置1的数量达到k时就记录当前掩码。不过递归的效率略低于Gosper's Hack,适合代码可读性优先的场景。
举个简单的递归示例:
def recursive_generate(n, k, current_mask=0, pos=0, count=0): if count == k: print(f"当前掩码: {bin(current_mask)[2:].zfill(n)}") return if pos >= n: return # 选择当前位置1 recursive_generate(n, k, current_mask | (1 << pos), pos + 1, count + 1) # 选择当前位置0 recursive_generate(n, k, current_mask, pos + 1, count)
注意事项
- 确保n是偶数(因为你要选n/2个元素),否则k会是小数,不符合位掩码的逻辑。
- 如果n特别大(比如n>40),组合数C(n,n/2)会变得非常巨大(比如n=40时C(40,20)=137846528820),这时候可能需要考虑采样或者分治优化,但常规规模下Gosper's Hack完全够用。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

