查找具有 10 个设置位的第 n 个整数
Find nth int with 10 set bits
找到第 n 个具有 10 个设置位的整数
n 是 0<= n <= 30 045 014
范围内的整数
第 0 个 int = 1023,第 1 个 = 1535 等等
snob() 位数相同,
returns 大于 n 且设置位数与 n 相同的最小整数
int snob(int n) {
int a=n&-n, b=a+n;
return b|(n^b)/a>>2;
}
调用 snob n 次即可
int nth(int n){
int o =1023;
for(int i=0;i<n;i++)o=snob(o);
return o;
}
示例
有什么方法可以更快地找到它吗?
我找到了一种模式,但不确定它是否有用。
使用阶乘,您可以找到 "indexes",其中所有 10 个设置位都是连续的
1023 << x = the (x+10)! / (x! * 10!) - 1 th integer
1023<<1 is the 10th
1023<<2 is the 65th
1023<<3 the 285th
...
顺便说一句,我不是学生,这不是作业。
编辑:
找到了 snob() 的替代方法
https://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation
int lnbp(int v){
int t = (v | (v - 1)) + 1;
return t | ((((t & -t) / (v & -v)) >> 1) - 1);
}
让我们考虑设置了 k=10 位的数字。
诀窍是针对给定的 n 确定最重要的排名。
有一个长度为k的数:C(k, k)=1。有 k+1 = C(k+1, k) 个长度为 k + 1 的数字。 ... 有 C(m, k) 个长度为 m 的数字。
对于k=10,极限n为1 + 10 + 55 + 220 + 715 + 2002 + 5005 + 11440 + ...
对于给定的n,你很容易找到对应的m。然后问题简化为找到第 n - C(m, k) 个具有 k - 1 位集的数字。以此类推。
使用预先计算的表格,这会非常快。 30045015 需要 30 次查找,所以我猜最坏的情况是 29 x 30 / 2 = 435 次查找。
(这是基于线性查找,以支持较小的值。通过二分法搜索,您可以将其减少到小于 29 x lg(30) = 145 次查找,更糟糕的是。)
更新:
我之前的估计是悲观的。事实上,由于我们正在寻找 k 位,因此只有 10 个确定的 m。在线性情况下,更糟糕的是 245 次查找,在二分法情况下,少于 50.
(我不排除估计中的差一错误,但显然这种方法非常有效并且不需要势利。)
我已经构建了一个可以满足您需求的实现。
/** A lookup table to see how many combinations preceeded this one */
private static int[][] LOOKUP_TABLE_COMBINATION_POS;
/** The number of possible combinations with i bits */
private static int[] NBR_COMBINATIONS;
static {
LOOKUP_TABLE_COMBINATION_POS = new int[Integer.SIZE][Integer.SIZE];
for (int bit = 0; bit < Integer.SIZE; bit++) {
// Ignore less significant bits, compute how many combinations have to be
// visited to set this bit, i.e.
// (bit = 4, pos = 5), before came 0b1XXX and 0b1XXXX, that's C(3, 3) + C(4, 3)
int nbrBefore = 0;
// The nth-bit can be only encountered after pos n
for (int pos = bit; pos < Integer.SIZE; pos++) {
LOOKUP_TABLE_COMBINATION_POS[bit][pos] = nbrBefore;
nbrBefore += nChooseK(pos, bit);
}
}
NBR_COMBINATIONS = new int[Integer.SIZE + 1];
for (int bits = 0; bits < NBR_COMBINATIONS.length; bits++) {
NBR_COMBINATIONS[bits] = nChooseK(Integer.SIZE, bits);
assert NBR_COMBINATIONS[bits] > 0; // Important for modulo check. Otherwise we must use unsigned arithmetic
}
}
private static int nChooseK(int n, int k) {
assert k >= 0 && k <= n;
if (k > n / 2) {
k = n - k;
}
long nCk = 1; // (N choose 0)
for (int i = 0; i < k; i++) {
// (N choose K+1) = (N choose K) * (n-k) / (k+1);
nCk *= (n - i);
nCk /= (i + 1);
}
return (int) nCk;
}
public static int nextCombination(int w, int n) {
// TODO: maybe for small n just advance naively
// Get the position of the current pattern w
int nbrBits = 0;
int position = 0;
while (w != 0) {
final int currentBit = Integer.lowestOneBit(w); // w & -w;
final int bitPos = Integer.numberOfTrailingZeros(currentBit);
position += LOOKUP_TABLE_COMBINATION_POS[nbrBits][bitPos];
// toggle off bit
w ^= currentBit;
nbrBits++;
}
position += n;
// Wrapping, optional
position %= NBR_COMBINATIONS[nbrBits];
// And reverse lookup
int v = 0;
int m = Integer.SIZE - 1;
while (nbrBits-- > 0) {
final int[] bitPositions = LOOKUP_TABLE_COMBINATION_POS[nbrBits];
// Search for largest bitPos such that position >= bitPositions[bitPos]
while (Integer.compareUnsigned(position, bitPositions[m]) < 0)
m--;
position -= bitPositions[m];
v ^= (0b1 << m--);
}
return v;
}
现在进行一些解释。 LOOKUP_TABLE_COMBINATION_POS[bit][pos]
是算法的核心,使它尽可能快。 table 的设计使得在位置 p_0 < p_1 < ... < p_{k - 1}
处具有 k
位的位模式的位置为 `\sum_{i = 0}^{k - 1}{ LOOKUP_TABLE_COMBINATION_POS[i][p_i] }.
直觉是我们尝试将位一位一位地移回,直到我们达到所有位都位于最低可能位置的模式。将第 i
位从位置 k + 1
移动到 k
向后移动 C(k-1, i-1)
个位置,前提是所有低位都在最右边的位置(没有移动位进入或通过彼此)因为我们跳过了所有可能的组合 k-1
槽中的 i-1
位。
因此我们可以 "decode" 一个位置的位模式,跟踪遇到的位。然后我们前进 n
个位置(如果我们为 k
位枚举所有可能的位置则滚动)并再次编码该位置。
为了对模式进行编码,我们将这个过程颠倒过来。为此,我们将位从它们的起始位置向前移动,只要该位置小于我们的目标。我们可以不通过 LOOKUP_TABLE_COMBINATION_POS
进行线性搜索,而是对我们的目标索引 m
使用二进制搜索,但这几乎不需要,int 的大小并不大。然而,我们重用我们的变体,即较小的位也必须出现在不太重要的位置,因此我们的算法实际上是 O(n)
where n = Integer.SIZE
.
我仍然使用以下断言来展示生成的算法:
nextCombination(0b1111111111, 1) == 0b10111111111;
nextCombination(0b1111111111, 10) == 0b11111111110;
nextCombination(0x00FF , 4) == 0x01EF;
nextCombination(0x7FFFFFFF , 4) == 0xF7FFFFFF;
nextCombination(0x03FF , 10) == 0x07FE;
// Correct wrapping
nextCombination(0b1 , 32) == 0b1;
nextCombination(0x7FFFFFFF , 32) == 0x7FFFFFFF;
nextCombination(0xFFFFFFEF , 5) == 0x7FFFFFFF;
找到第 n 个具有 10 个设置位的整数
n 是 0<= n <= 30 045 014
范围内的整数
第 0 个 int = 1023,第 1 个 = 1535 等等
snob() 位数相同,
returns 大于 n 且设置位数与 n 相同的最小整数
int snob(int n) {
int a=n&-n, b=a+n;
return b|(n^b)/a>>2;
}
调用 snob n 次即可
int nth(int n){
int o =1023;
for(int i=0;i<n;i++)o=snob(o);
return o;
}
示例
有什么方法可以更快地找到它吗?
我找到了一种模式,但不确定它是否有用。
使用阶乘,您可以找到 "indexes",其中所有 10 个设置位都是连续的
1023 << x = the (x+10)! / (x! * 10!) - 1 th integer
1023<<1 is the 10th
1023<<2 is the 65th
1023<<3 the 285th
...
顺便说一句,我不是学生,这不是作业。
编辑:
找到了 snob() 的替代方法
https://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation
int lnbp(int v){
int t = (v | (v - 1)) + 1;
return t | ((((t & -t) / (v & -v)) >> 1) - 1);
}
让我们考虑设置了 k=10 位的数字。
诀窍是针对给定的 n 确定最重要的排名。
有一个长度为k的数:C(k, k)=1。有 k+1 = C(k+1, k) 个长度为 k + 1 的数字。 ... 有 C(m, k) 个长度为 m 的数字。
对于k=10,极限n为1 + 10 + 55 + 220 + 715 + 2002 + 5005 + 11440 + ...
对于给定的n,你很容易找到对应的m。然后问题简化为找到第 n - C(m, k) 个具有 k - 1 位集的数字。以此类推。
使用预先计算的表格,这会非常快。 30045015 需要 30 次查找,所以我猜最坏的情况是 29 x 30 / 2 = 435 次查找。
(这是基于线性查找,以支持较小的值。通过二分法搜索,您可以将其减少到小于 29 x lg(30) = 145 次查找,更糟糕的是。)
更新:
我之前的估计是悲观的。事实上,由于我们正在寻找 k 位,因此只有 10 个确定的 m。在线性情况下,更糟糕的是 245 次查找,在二分法情况下,少于 50.
(我不排除估计中的差一错误,但显然这种方法非常有效并且不需要势利。)
我已经构建了一个可以满足您需求的实现。
/** A lookup table to see how many combinations preceeded this one */
private static int[][] LOOKUP_TABLE_COMBINATION_POS;
/** The number of possible combinations with i bits */
private static int[] NBR_COMBINATIONS;
static {
LOOKUP_TABLE_COMBINATION_POS = new int[Integer.SIZE][Integer.SIZE];
for (int bit = 0; bit < Integer.SIZE; bit++) {
// Ignore less significant bits, compute how many combinations have to be
// visited to set this bit, i.e.
// (bit = 4, pos = 5), before came 0b1XXX and 0b1XXXX, that's C(3, 3) + C(4, 3)
int nbrBefore = 0;
// The nth-bit can be only encountered after pos n
for (int pos = bit; pos < Integer.SIZE; pos++) {
LOOKUP_TABLE_COMBINATION_POS[bit][pos] = nbrBefore;
nbrBefore += nChooseK(pos, bit);
}
}
NBR_COMBINATIONS = new int[Integer.SIZE + 1];
for (int bits = 0; bits < NBR_COMBINATIONS.length; bits++) {
NBR_COMBINATIONS[bits] = nChooseK(Integer.SIZE, bits);
assert NBR_COMBINATIONS[bits] > 0; // Important for modulo check. Otherwise we must use unsigned arithmetic
}
}
private static int nChooseK(int n, int k) {
assert k >= 0 && k <= n;
if (k > n / 2) {
k = n - k;
}
long nCk = 1; // (N choose 0)
for (int i = 0; i < k; i++) {
// (N choose K+1) = (N choose K) * (n-k) / (k+1);
nCk *= (n - i);
nCk /= (i + 1);
}
return (int) nCk;
}
public static int nextCombination(int w, int n) {
// TODO: maybe for small n just advance naively
// Get the position of the current pattern w
int nbrBits = 0;
int position = 0;
while (w != 0) {
final int currentBit = Integer.lowestOneBit(w); // w & -w;
final int bitPos = Integer.numberOfTrailingZeros(currentBit);
position += LOOKUP_TABLE_COMBINATION_POS[nbrBits][bitPos];
// toggle off bit
w ^= currentBit;
nbrBits++;
}
position += n;
// Wrapping, optional
position %= NBR_COMBINATIONS[nbrBits];
// And reverse lookup
int v = 0;
int m = Integer.SIZE - 1;
while (nbrBits-- > 0) {
final int[] bitPositions = LOOKUP_TABLE_COMBINATION_POS[nbrBits];
// Search for largest bitPos such that position >= bitPositions[bitPos]
while (Integer.compareUnsigned(position, bitPositions[m]) < 0)
m--;
position -= bitPositions[m];
v ^= (0b1 << m--);
}
return v;
}
现在进行一些解释。 LOOKUP_TABLE_COMBINATION_POS[bit][pos]
是算法的核心,使它尽可能快。 table 的设计使得在位置 p_0 < p_1 < ... < p_{k - 1}
处具有 k
位的位模式的位置为 `\sum_{i = 0}^{k - 1}{ LOOKUP_TABLE_COMBINATION_POS[i][p_i] }.
直觉是我们尝试将位一位一位地移回,直到我们达到所有位都位于最低可能位置的模式。将第 i
位从位置 k + 1
移动到 k
向后移动 C(k-1, i-1)
个位置,前提是所有低位都在最右边的位置(没有移动位进入或通过彼此)因为我们跳过了所有可能的组合 k-1
槽中的 i-1
位。
因此我们可以 "decode" 一个位置的位模式,跟踪遇到的位。然后我们前进 n
个位置(如果我们为 k
位枚举所有可能的位置则滚动)并再次编码该位置。
为了对模式进行编码,我们将这个过程颠倒过来。为此,我们将位从它们的起始位置向前移动,只要该位置小于我们的目标。我们可以不通过 LOOKUP_TABLE_COMBINATION_POS
进行线性搜索,而是对我们的目标索引 m
使用二进制搜索,但这几乎不需要,int 的大小并不大。然而,我们重用我们的变体,即较小的位也必须出现在不太重要的位置,因此我们的算法实际上是 O(n)
where n = Integer.SIZE
.
我仍然使用以下断言来展示生成的算法:
nextCombination(0b1111111111, 1) == 0b10111111111;
nextCombination(0b1111111111, 10) == 0b11111111110;
nextCombination(0x00FF , 4) == 0x01EF;
nextCombination(0x7FFFFFFF , 4) == 0xF7FFFFFF;
nextCombination(0x03FF , 10) == 0x07FE;
// Correct wrapping
nextCombination(0b1 , 32) == 0b1;
nextCombination(0x7FFFFFFF , 32) == 0x7FFFFFFF;
nextCombination(0xFFFFFFEF , 5) == 0x7FFFFFFF;