在计算以一个字母为单位的单词对

时间:2012-11-07 01:42:36

标签: algorithm sorting

  

让我们考虑n个单词,每个单词长度为k。这些单词由字母表上的字母组成(其基数为n),具有定义的顺序。任务是导出一个O(nk)算法来计算相差一个位置的单词对的数量(无论哪一个都是精确的,只要它只是一个位置)。

     

例如,在以下一组单词中(n = 5,k = 4):

     

abcd,abdd,adcb,adcd,aecd

     

共有5对:(abcd,abdd),(abcd,adcd),(abcd,aecd),(adcb,adcd),(adcd,aecd)。

到目前为止,我已经设法找到一种算法来解决一个稍微容易解决的问题:计算相差一个GIVEN位置(i-th)的单词对的数量。为了做到这一点,我将第i个位置的字母与每个单词中的最后一个字母交换,执行基数排序(忽略每个单词中的最后位置 - 以前是第i个位置),线性检测第1个字母的第1个字母到k-1个位置是相同的,最终计算每个重复项中最后一个(最初的第i个)位置的每个字母的出现次数,并计算所需的对(最后一部分很简单)。

然而,上面的算法似乎不适用于主要问题(在O(nk)约束下) - 至少没有一些修改。知道如何解决这个问题吗?

5 个答案:

答案 0 :(得分:1)

假设n和k不是太大,以至于它将适合内存:

删除第一个字母的集合,删除第二个字母的集合,删除第三个字母的集合,等等。从技术上讲,这必须是从字符串到计数的映射。

运行列表,只需将当前元素添加到每个地图中(显然首先删除适用的字母)(如果已存在,则将计数添加到totalPairs并将其递增1)。

然后totalPairs是所需的值。

修改

<强>复杂度:

这应该是O(n.k.logn)

您可以使用使用散列的地图(例如Java中的HashMap),而不是理论复杂度为O(nk)的有序地图(尽管我通常发现哈希映射速度较慢)比排序的基于树的地图)。

<强>改进:

对此的一个小改动是将前2个字母的地图移除到2个地图,其中一个删除了第一个字母,另一个删除了第二个字母,并且对于第3个和第4个字母具有相同的字母,依此类推。

然后将这些字母移入地图中,删除4个字母,将这些字母移除到8个字母的地图中,依此类推,最多可移除一半字母。

这种复杂性是:

  • 您对包含最多k个元素的2个有序集合(每半个)进行2次查找。

  • 对于其中每一项,您再次对2个排序集进行2次查找(每季度一次)。

  • 所以查找次数是2 + 4 + 8 + ... + k / 2 + k,我相信是O(k)

  • 我可能在这里错了,但是,最坏的情况是,任何给定地图中的元素数量都是n,但这会导致所有其他地图只有1个元素,所以仍然{{1但是,每个O(logn)(不是每个n)。

所以我认为那是n.k

编辑2:

我的地图示例(没有改进):

O(n.(logn + k))表示(x-1)映射到x

假设我们有1

第一张地图为abcd, abdd, adcb, adcd, aecd

第二张地图为(bcd-1), (bdd-1), (dcb-1), (dcd-1), (ecd-1)(第4和第5张值已存在,因此增量)。

第三张地图:(acd-3), (add-1), (acb-1)(已存在第二张)。

第四张地图:(abd-2), (adb-1), (add-1), (aed-1)(已存在第4张)。

(abc-1), (abd-1), (adc-2), (aec-1)

对于第二张地图 - totalPairs = 0,对于第4张,我们添加1,对于第5张,我们添加2。

acd

对于第三张地图 - totalPairs = 3,对于第二张地图,我们添加1。

abd

对于第四张地图 - totalPairs = 4,对于第四张地图,我们加1。

adc

改进地图的部分示例:

与上述相同的输入。

删除了第1个和第2个字母地图的前2个字母的地图:

totalPairs = 5

以上是由映射到2个地图的元素(cd-{ {(bcd-1)}, {(acd-1)} }), (dd-{ {(bdd-1)}, {(add-1)} }), (cb-{ {(dcb-1)}, {(acb-1)} }), (cd-{ {(dcd-1)}, {(acd-1)} }), (cd-{ {(ecd-1)}, {(acd-1)} }) 组成的地图,其中一个包含一个元素cd,另一个包含(bcd-1)

但是对于第4和第5 (acd-1)已经存在,所以,不是生成上面的内容,而是将其添加到该地图中,如下所示:

cd

答案 1 :(得分:0)

您可以将每个单词放入一个数组中。逐个从该数组中删除元素。然后比较生成的数组。最后,添加弹出元素以返回原始数组。 两个阵列中的弹出元素不能相同。 计算发生这种情况的案例数,最后除以2得到确切的解决方案

答案 2 :(得分:0)

考虑如何枚举语言 - 您可能会使用递归算法。递归算法映射到树结构上。如果你构造这样一棵树,每个分歧代表一个字母的差异,每个叶子代表一个单词。

答案 3 :(得分:0)

我在这里提交问题已经两个月了。在此期间,我与同行讨论过这个问题,并希望分享结果。

主要想法类似于Dukeling提出的想法。对于每个单词A和该单词中的每个第i个位置,我们将考虑一个元组:(前缀,后缀,第i个位置的字母),即(A [1..i-1],A [i + 1。 .n],A [i])。如果i为1或n,则适用的子字符串被视为空(这些是简单的边界情况)。

掌握了这些元组后,我们应该能够应用我在第一篇文章中提供的推理来计算不同单词对的数量。我们所要做的就是用前缀和后缀值对每个元组进行排序(对于每个i分别) - 然后,除了第i个位置之外,字母相等的单词将彼此相邻。

这里虽然是我缺乏的技术部分。为了使排序过程(RadixSort似乎是要走的路)满足O(nk)约束,我们可能想要为我们的前缀和后缀分配标签(我们每个i只需要n个标签)。我不太清楚如何去贴标签的东西。 (当然,我们可能会做一些散列,但我相信前一种解决方案是可行的。)

虽然这不是一个完全完整的解决方案,但我相信它对解决这个问题的可行方法有所了解,这就是我在这里发布的原因。如果有人想出如何做标签部分,我将在这篇文章中实现它。

答案 4 :(得分:0)

以下Python解决方案如何?

import string

def one_apart(words, word):
    res = set()
    for i, _ in enumerate(word):
        for c in string.ascii_lowercase:
            w = word[:i] + c + word[i+1:]
            if w != word and w in words:
                res.add(w)
    return res


pairs = set()
for w in words:
  for other in one_apart(words, w):
    pairs.add(frozenset((w, other)))

for pair in pairs:
  print(pair)

输出:

frozenset({'abcd', 'adcd'})
frozenset({'aecd', 'adcd'})
frozenset({'adcb', 'adcd'})
frozenset({'abcd', 'aecd'})
frozenset({'abcd', 'abdd'})