我们如何从3000个单词的语料库中找到第n个3个单词的组合

时间:2018-07-28 05:04:30

标签: combinations combinatorics

我有一个说3000个单词的单词语料库,例如[hello,who,this ..]。 我想从该语料库中找到第n个3个单词的组合。只要算法给出一致的输出,我对任何顺序都可以。 该算法的时间复杂度如何?

我看到了this的答案,但正在寻找简单的东西。

1 个答案:

答案 0 :(得分:1)

(请注意,我将在此答案中使用基于1的索引和排名。)

要从n个元素的列表中生成3个元素的所有组合,我们将1到n-2的所有元素作为第一个元素,然后对于每个元素,我们将第一个元素之后的所有元素都作为取n-1作为第二个元素,那么对于每个元素,我们将第二个元素之后的所有元素取为n作为第三个元素。这给了我们固定的顺序,以及等级与特定组合之间的直接关系。

如果我们将元素i作为第一个元素,则第二个和第三个元素存在(n-i选择2)种可能性,因此,将i作为第一个元素的(n-i选择2个)组合。如果我们然后将元素j作为第二个元素,则第三个元素有(n-j个选择1)= n-j个可能性,因此将i和j作为前两个元素的n-j个组合。

在二项式系数表中进行线性搜索

使用这些二项式系数的表,我们可以快速找到特定组合,并赋予其等级。让我们看一个包含10个元素的简化示例。这些是元素i为第一个元素的组合数:

i

1    C(9,2) = 36
2    C(8,2) = 28
3    C(7,2) = 21
4    C(6,2) = 15
5    C(5,2) = 10
6    C(4,2) =  6
7    C(3,2) =  3
8    C(2,2) =  1
             ---
             120 = C(10,3)

这些是元素j为第二个元素的组合数:

j

2    C(8,1) =  8
3    C(7,1) =  7
4    C(6,1) =  6
5    C(5,1) =  5
6    C(4,1) =  4
7    C(3,1) =  3
8    C(2,1) =  2
9    C(1,1) =  1

因此,如果我们要寻找例如排名96,我们将查看第一个元素i的每个选择的组合数量,直到找到排名96的组合属于哪个组合组:

i

1    36    96 > 36    96 - 36 = 60
2    28    60 > 28    60 - 28 = 32
3    21    32 > 21    32 - 21 = 11
4    15    11 <= 15

因此,我们知道第一个元素i为4,并且在i = 4的15个组合中,我们正在寻找第11个组合。现在我们来看第二个元素j的每个选择的组合数量,从4开始:

j

5     5    11 > 5    11 - 5 = 6
6     4     6 > 4     6 - 4 = 2
7     3     2 <= 3

因此,我们知道第二个元素j为7,第三个元素是j = 7(即k = 9)的第二个组合。因此,等级为96的组合包含元素4、7和9。


在二项式系数的总和表中进行二分搜索

与其创建一个二项式系数表然后执行线性搜索,不如创建一个二项式系数的总和表,然后对其进行二值搜索,这当然会更有效率。这将改善从O(N)到O(logN)的时间复杂度;在N = 3000的情况下,可以以log 2 (3000)= 12步进行两次查找。

因此,我们将存储:

i

1     36
2     64
3     85
4    100
5    110
6    116
7    119
8    120

和:

j

2     8
3    15
4    21
5    26
6    30
7    33
8    35
9    36

请注意,在第二张表中找到j时,必须从和中减去与i对应的和。让我们再次看一下等级96和组合[4,7,9]的示例;我们发现第一个大于或等于等级的值:

3     85    96 > 85
4    100    96 <= 100

所以我们知道i = 4;然后,我们减去i-1之前的和,得到:

96 - 85 = 11

现在,我们在表中查找j,但我们在j = 4之后开始,并从这些和中减去与4对应的和,即21。再一次,我们发现第一个值大于或等于我们要寻找的等级(现在是11):

6    30 - 21 =  9    11 > 9
7    33 - 21 = 12    11 <= 12

所以我们知道j = 7;我们减去与j-1对应的先前和,得到:

11 - 9 = 2

因此,我们知道第二个元素j为7,第三个元素是j = 7(即k = 9)的第二个组合。因此,等级为96的组合包含元素4、7和9。


对查询表进行硬编码

当然,每次我们想要执行查找时,都不必再次生成这些查找表。我们只需要生成它们一次,然后将它们硬编码到秩组合算法中;这应该只需要2998 * 64位+ 2998 * 32位= 35kB的空间,并且可以使算法变得异常快。


逆算法

逆算法,在给定元素[i,j,k]组合的情况下查找排名,则意味着:

  • 查找列表中元素的索引;如果列表已排序(例如,单词按字母顺序排序),则可以通过在O(logN)中进行二进制搜索来完成。

  • 在表中找到与i-1对应的i的总和。

  • 在表中添加与j-1对应的j的总和,减去与i对应的总和。

  • 添加到该k-j。

让我们再看一下同一示例,其中包含元素[4,7,9]的组合:

i=4 -> table_i[3] = 85
j=7 -> table_j[6] - table_j[4] = 30 - 21 = 9
k=9 -> k-j = 2
rank = 85 + 9 + 2 = 96

N = 3000的查询表

此代码段生成查找表,其中包含i = 1至2998的二项式系数的总和:

function C(n, k) { // binomial coefficient (Pascal's triangle)
    if (k < 0 || k > n) return 0;
    if (k > n - k) k = n - k;
    if (! C.t) C.t = [[1]];
    while (C.t.length <= n) {
        C.t.push([1]);
        var l = C.t.length - 1;
        for (var i = 1; i < l / 2; i++)
            C.t[l].push(C.t[l - 1][i - 1] + C.t[l - 1][i]);
        if (l % 2 == 0)
            C.t[l].push(2 * C.t[l - 1][(l - 2) / 2]);
    }
    return C.t[n][k];
}

for (var total = 0, x = 2999; x > 1; x--) {
    total += C(x, 2);
    document.write(total + ", ");
}

此代码段生成查找表,其中包含j = 2到2999的二项式系数的总和:

for (var total = 0, x = 2998; x > 0; x--) {
    total += x;
    document.write(total + ", ");
}


代码示例

这是一个快速的代码示例,不幸的是,由于没有关于SO的答案的大小限制,因此没有完整的硬编码查找表。运行上面的代码片段,然后将结果粘贴到iTable和jTable数组中(在前导零之后),以使用硬编码的查找表获得更快的版本。

function combinationToRank(i, j, k) {
    return iTable[i - 1] + jTable[j - 1] - jTable[i] + k - j;
}
function rankToCombination(rank) {
    var i = binarySearch(iTable, rank, 1);
    rank -= iTable[i - 1];
    rank += jTable[i];
    var j = binarySearch(jTable, rank, i + 1);
    rank -= jTable[j - 1];
    var k = j + rank;
    return [i, j, k];

    function binarySearch(array, value, first) {
        var last = array.length - 1;
        while (first < last - 1) {
            var middle = Math.floor((last + first) / 2);
            if (value > array[middle]) first = middle;
            else last = middle;
        }
        return (value <= array[first]) ? first : last;
    }
}
var iTable = [0];    // append look-up table values here
var jTable = [0, 0]; // and here

// remove this part when using hard-coded look-up tables
function C(n,k){if(k<0||k>n)return 0;if(k>n-k)k=n-k;if(!C.t)C.t=[[1]];while(C.t.length<=n){C.t.push([1]);var l=C.t.length-1;for(var i=1;i<l/2;i++)C.t[l].push(C.t[l-1][i-1]+C.t[l-1][i]);if(l%2==0)C.t[l].push(2*C.t[l-1][(l-2)/2])}return C.t[n][k]}
for (var iTotal = 0, jTotal = 0, x = 2999; x > 1; x--) {
    iTable.push(iTotal += C(x, 2));
    jTable.push(jTotal += x - 1);
}

document.write(combinationToRank(500, 1500, 2500) + "<br>");
document.write(rankToCombination(1893333750) + "<br>");