使用10个设置位查找nth int

时间:2017-10-11 17:08:40

标签: optimization combinatorics

用10个设定位找到第n个int n是0 <= n <= 30 045 014的范围内的int 第0个int = 1023,第1个= 1535,依此类推

snob()相同的位数,
返回大于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;
}

示例

https://ideone.com/ikGNo7

有没有办法更快找到它?

我发现了一种模式,但不确定它是否有用。

使用factorial可以找到所有10个设置位都是连续的“索引”

  

1023&lt;&lt; x =(x + 10)! /(x!* 10!) - 第1个整数

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);  
}

2 个答案:

答案 0 :(得分:1)

让我们考虑设置k = 10位的数字。

诀窍是确定给定n的最重要的等级。

有一个长度为k的数:C(k,k)= 1。有k + 1 = C(k + 1,k)个长度为k + 1的... ...有长度为m的C(m,k)个数。

对于k = 10,限制n为1 + 10 + 55 + 220 + 715 + 2002 + 5005 + 11440 + ...

对于给定的n,您可以轻松找到相应的m。然后问题被简化为找到设置了k-1位的n-C(m,k)数。等等递归。

使用预先计算的表格,这可以非常快。 30045015需要30次查找,所以我猜最坏的情况是29 x 30/2 = 435次查找。

(这是基于线性查找,有利于小值。通过二分法搜索,你可以将其减少到小于29 x lg(30)= 145更差的查找。)

<强>更新

我以前的估计是悲观的。实际上,当我们寻找k位时,m只有10次测定。在线性情况下,更糟糕的245次查找,在二分法情况下,小于50。

(我不会在估算中排除一个错误,但显然这种方法效率很高,不需要势利。)

答案 1 :(得分:1)

我已经构建了一个满足您需求的实现。

/** 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]是算法的核心,使其尽可能快。该表的设计使得位置k的{​​{1}}位的位模式的位置为“\ sum_ {i = 0} ^ {k - 1} {LOOKUP_TABLE_COMBINATION_POS [i] [p_i]}

直觉是我们尝试逐个移回位,直到我们到达所有位都处于最低位置的模式。将p_0 < p_1 < ... < p_{k - 1}位从i移至k + 1会向后移动k个位置,前提是所有较低位位于最右侧位置(无移动位)因为我们跳过了C(k-1, i-1)个插槽中i-1位的所有可能组合。

因此我们可以将位模式“解码”到一个位置,跟踪遇到的位。然后我们前进k-1个位置(如果我们枚举n位的所有可能位置,则翻转)并再次编码该位置。

要对模式进行编码,我们会反转该过程。为此,只要位置小于我们的目标,我们就会将位从其起始位置向前移动。我们可以代替通过k进行线性搜索,对我们的目标索引LOOKUP_TABLE_COMBINATION_POS使用二进制搜索,但它几乎不需要,int的大小并不大。然而,我们重复使用我们的变体,一个较小的位也必须位于不太重要的位置,以便我们的算法有效m其中O(n)

我仍然使用以下断言来显示生成的算法:

n = Integer.SIZE