如何有效地生成0,1,2...n
内的所有数字。
(大n
)。
对于固定的x
和不同的k
(0 <= k < n)
,k & x = k
。
很容易发现,1
中值k
的所有位在1
中也是x
。
但我无法计算所有这些
我使用DP
查找x
中设置位的所有子集和,以得出所有可能的解决方案。
但是这种方法证明在请求不同x
的多个此类情况下效率低下。
我是否必须考虑需要改变的每一点以获得所有可能性?还有其他有效的方法吗?另外,我当然不想查看所有n
。
答案 0 :(得分:2)
有一种巧妙的方法可以做到这一点
for(int i = x ;; i = x & (i - 1)){
print i;
if(i == 0)
break;
}
请注意条件i = x & (i - 1)
确保i
始终减少且仅包含x
中的位
请参阅here
中的Java代码如果是x > n
,那么i
应该以{{1}}
答案 1 :(得分:1)
首先请注意, x 中的0位表示 k 中必须为0的位,而 x 中的1位在 k 中可以是0或1。因此,算法应迭代 k 中的所有可能的位组合,其中 x 具有1位且结果数( k )不大比 n 。
这些组合最好通过使用Grey code序列之类的东西来产生,因为可以在一个恒定的时间内从一个位模式步进到下一个位模式。
示例:
x = 0b011010 (26)
n = 0b010000 (16)
为 k 生成的值是(按格雷码顺序排列):
0b000000 ( = 0)
0b000010 ( = 2)
0b001010 ( = 10)
0b001000 ( = 8)
0b011000 ( = 24) too large: exclude
0b011010 ( = 26) too large: exclude
0b010010 ( = 18) too large: exclude
0b010000 ( = 16)
由于使用格雷码方案,只有一位从一个组合变为另一个组合。这意味着数字不是按顺序生成的,有些可能太大(&gt; n )。这种不利因素仍然值得,因为按顺序生成它们会在每一步中产生更多的位变化。
以下是在JavaScript中实现此想法的代码段:
function get_nums(n, x) {
// Solution array. Add zero as it is always a solution (assuming non-negative n)
let result = [0],
k = 0,
arr = []; // Helper to follow Grey code sequence
for (let i = 1; i <= n && i <= x; i <<= 1) { // Shift bit to the left
if (x & i) { // This bit is set to 1 in x
arr.push(i);
k += i; // Set this bit in k
if (k <= n) result.push(k); // Add k to solution array
// Produce other matches following Grey code sequence
for (let j = arr.length-2; j >= 0; j--) {
arr.push(-arr[j]);
k -= arr[j]; // Toggle a bit in k
if (k <= n) result.push(k);
}
}
}
return result;
}
console.log(get_nums(16, 26));
请注意,输出未排序(因为使用了格雷码序列)。如果您需要它们,请应用一些基数排序(或散列)。
在JavaScript中,实现这样的基数排序非常容易,因为值是唯一的。但在其他语言中,您可以实现更明确,简化的radix sort。这是它的JavaScript函数:
function radix_sort_uniques(arr) {
let result = {};
// Add a property to the object for each value in the array
for (let i of arr) result[i] = true;
// Get those properties and convert them back to numeric data type (via map)
// JavaScript will produce them in ascending order:
return Object.keys(result).map(Number);
}
console.log(radix_sort_uniques([0, 2, 10, 8, 16]));
外部循环在 n 中每位位置迭代一次,即 log(n)次,而内循环每次迭代次数大约加倍。因此,在最坏的情况下(当 x 为0且内部循环始终执行时),我们按 2 log(n) 次,给出 O(n)时间复杂度。
当 x 修复时,复杂性也应该用 x 表示。假设 x 具有 b 1位,那么时间复杂度为 O(b + 2 b )。
答案 2 :(得分:1)
想象一下,我们在二进制表示中有x
,如下所示:
x = 00001010110
在这种情况下,所有k
都应k & x = k
形式
x = 00001010110
k = 0000?0?0??0
其中?
为0
或1
。因此,我们必须获取1
中的所有索引x
(上例中的[1, 2, 4, 6]
),并生成16
的所有组合(示例中为0
)和1
位于相应的索引处:
C#实施:
private static IEnumerable<int> MySolution(int x) {
int[] indexes = Enumerable
.Range(0, 32)
.Where(i => (x >> i) % 2 != 0)
.ToArray();
for (int value = 0; value < 1 << indexes.Length; ++value)
yield return indexes
.Select((v, i) => ((value >> i) % 2) * (1 << v))
.Sum();
}
测试:
Console.WriteLine(String.Join(", ", MySolution(5)));
结果(请注意,解决方案已整理):
0, 1, 4, 5
如果您想限制生成的解决方案,您可以修改循环:
private static IEnumerable<int> MySolution(int x, int n = -1) {
int[] indexes = Enumerable
.Range(0, 32)
.Where(i => (x >> i) % 2 != 0)
.ToArray();
for (int value = 0; value < 1 << indexes.Length; ++value) {
int result = indexes
.Select((v, i) => ((value >> i) % 2) * (1 << v))
.Sum();
if (n < 0 || result <= n)
yield return;
else
break;
}
}