带有匹配起始和结束字母的单词的最长列表


11

我的朋友给我一个问题,他说这很简单,但是我想不出一个好的算法来实现。

系统会为您提供100个随机英语单词的输入。您必须找到最长的字符串,其中一个单词的最后一个字母与下一个单词的第一个字母匹配。每个单词只能使用一次。

例如,如果给您单词“ cat”,“ dog”,“ that”,则您可以输入的最长字符串将是“ cat-> that”。如果给您单词“ mouse”,“ moose”,“ unicorn”,则您可以输入的最长字符串将仅为一个单词(因为这些单词均未链接)。如果给您单词“ bird”,“ dish”,“ harb”,则最长的字符串可能是“ harb-> bird-> dish”(或“ dish-> harb-> bird”或“ bird- >菜-> harb”)。

我想到了将其建模为有向循环图的想法。每个节点只是一个单词,每个单词/节点的顶点都以该单词结尾的字母开头。

+-------+         \ +------+
|  cat  |-----------| that |
+-------+         / +------+
    |                  |
   \|/                 |
+-------+ /            |
|  the  |--------------+
+-------+ \

这个问题似乎是最长路径搜索,即NP-Hard。

有更好的方法吗?甚至可以使用某种近似算法?还是采用某种方法来利用英语来减少搜索空间?


4
使用100个单词,您(至少)获得100个!= 9.332622e + 157组合。祝您好运,我认为您的朋友正在拉您的腿,说这很容易。
马丁·威克曼

1
但是,可能的组合数量要少得多,因为平均每个单词仅链接到大约6或7个其他单词。
安倍工具

2
您正确地认为这是最长的路径搜索。我认为你的朋友错了。但是,穷举搜索并不难编写代码,并且可能不会运行那么长时间。
凯文·克莱恩

4
只是为了好玩,我在Ruby(gist.github.com/anonymous/6225361)中编写了一个穷举性的穷举搜索(如@kevincline所指出的)。只需100个单词,就只花了约96秒(gist.github.com/anonymous/6225364)。这是一个效率极低,未优化,解释语言,快速而肮脏的脚本。因此,只有100个字甚至是缓慢版本的蛮力在相当长的时间内运行。我的代码实际上并没有创建一个非循环图然后对其进行搜索,它只是从每个单词递归地构建所有可能的路径,并跟踪最长的路径。
李·李

3
问题指出有100个字。我认为这意味着您可以应用动态编程解决方案,您所引用的文章中对此进行了提及。
朱利安·古托

Answers:


5

我认为这与您提到的最长路径(LP)问题有关,但这有所不同。主要区别在于LP问题的连接程度高于您提出的问题。通过将连接限制为最后一个字母和第一个字母,可以删除大量可能的组合。

这是我建议解决此问题的方法:

  1. 对于列表中的每个单词,计算可能的连接数和连接数。
  2. 丢弃所有具有0 ins和0 outs的单词。
  3. 标识出一组最少的ins和outs数量的“起始词”,并且outs必须大于0。
  4. 每个入门单词都会收到其自己的ins / outs连接计数工作副本。这形成了链的头。
  5. 对于每个链,根据以下内容识别“下一个单词”列表:
    • 入门者的最后一个字母或上一个单词
    • 最小的ins和outs连接数(同样,outs必须大于0)
  6. 对于每个next word,重复步骤5,直到链终止。

请记住:

  • 您需要跟踪链的长度,并具有某种全局机制来识别最长的链。

  • 您还需要从连接计数的工作副本中删除每个单词,以避免递归循环。

  • 在某个时候,您的链将终止,您必须选择连接计数为0的单词。

  • 从工作清单中删除单词后,您可能不得不重新计算ins / outs。乍一看,我认为这不是必需的,因为总体规模相对较小。如果扩展到1000个单词,则具有静态计数可能会减慢算法收敛的速度。

我有点将此视为包装问题。对我来说,进出的连接确定了要包装的形状。连接数越低,形状越奇特。形状越奇特,我越想打包它,因为我发现越晚能够打包奇特形状的几率就越小。

举个例子:

{dog, gopher, alpha, cube, elegant, this, that, bart}

dog     0, 1
gopher  1, 0
alpha   0, 0
cube    0, 1
elegant 1, 2
this    3, 0
that    2, 1
bart    0, 2

//alpha is dropped with 0 in and 0 out.
//two candidates found: dog, cube

//chain 1
dog => gopher
//chain 2
cube => elegant => that => this

//Note 1: the following chain won't occur due to selection rules
//that takes priority over this because of output count
cube => elegant => this

//Note 2: this chain won't occur either due to selection rules
bart => that => this

2
是否可以保证此算法始终会找到最长的路径?我不禁想到一个反例,但这似乎可能属于“局部最大值”类型的解决方案。
李·李

@BenLee-我是一名软件工程师;我从不保证我的代码。:-)严重的是,我不知道您问题的答案。坦率地说,我的理论和数学证明能力很弱,所以除了经验评估之外,我没有其他方法可以验证我的算法。我不确定这个问题是否真的是NP问题,但我也无法验证该说法。如果不是NP-hard,那么应该有一种方法可以验证算法。

2
像这样的单词列表呢:“狗,地鼠,小圆面包,修女,中午,小便”。该算法实际上是“小圆面包,修女,中午,小结”的任意组合时,会错误地将最长的列表选择为“狗->地鼠”。
安倍工具

1
@AbeTool-很好的例子。我将添加另一个迭代(或两个),以允许“最低输入> = 1”和“最低输出> = 1”组合。

2
我认为这并不能在所有情况下解决问题。我认为这属于“局部最大值”类型的解决方案。
安倍工具

3

如果制作26X26矩阵,以顶点的有向图表示每个字母,单词表示边缘。例如,单词-APPLE将顶点A和E与从A指向E的边连接起来。现在,问题减少到找到图中最大的欧拉径(路径包括最大数量的边,一旦可能重复顶点就访问每个边)。O(E)算法之一是从一对顶点随机开始。找到它们之间的路径。比保持放松的道路,直到有可能。

更新 @ GlenH7我最近在www.hackerearth / jda上解决了一个类似的问题,关于最佳解决方案存在相对分数,并且我在以下方法中获得了最高分数-

给定单词列表。找到可以由他们形成的最长链。如果每个单词都以字母*到最后一个单词的末尾结尾,则链是有效的。

申请=

1)将字母图作为顶点,将单词图作为边缘。代替使用多个边缘,使用一个权重等于边缘数的边缘。

2)找到具有最大边的图的强连通分量。暂时丢弃其他边缘。

3)对于每个顶点,使其入度等于其出度。

4)现在它们在图中存在欧拉回路。找到它。

5)现在剩下的图(原始图找到了在选定的强连通分量中具有第一个顶点的最长轨迹。我认为这是NP难的。

6)将上述路径包含在Elerian回路中,将欧拉回路转换为路径。

为什么-我接受这个问题很可能是NP难题(猜测,从数学上来说不是)。但是,当存在一长串(1000多个)均匀分布的单词时(即不打算用作上述方法的wc),上述方法最有效。让我们假设将给定列表转换为上述图形后,很幸运地证明它是欧拉图(有关条件,请参阅http://en.wikipedia.org/wiki/Eulerian_path),那么毫无疑问我们可以说答案上面的问题是P,实际上是图中的欧拉路径(有关执行此操作的非常简单的方法,请参见http://www.graph-magics.com/articles/euler.php,并查看此内容以验证您的图是否具有单个http://www.geeksforgeeks.org/strongly-connected-components/如果不是临时清洁其他小型scc,因为单个scc存在欧拉路径。因此,对于不太幸运的情况(几乎是所有情况),我尝试将它们转换为幸运的情况(即,满足欧拉路径的条件)。这该怎么做?我尝试对不相关的边缘进行深度搜索(路径中的一组边缘从以度大于度的顶角开始,以度大于度的顶点结束)。深度搜索的增加意味着首先我搜索路径中两个这样的一组边,而不是路径中的两个边缘,依此类推。乍看起来,第一个深度搜索将花费O(nodes ^ i),因此总的时间复杂度为O(nodes + nodes ^ 2 + nodes ^ 3 + ....),直到幸运的情况为止。但是,摊销分析将证明它是O(edges)。一旦减少,就可以找到欧拉回路。

到这里为止都是多项式时间。这将提供几乎最佳的解决方案。但是要进一步增加您的解决方案(完美的解决方案是NP困难的),请尝试在剩余图中使用一些贪婪的方法,以长的轨迹凝视所选scc中的一个顶点。现在,将其添加到上面发现的欧拉径中以进一步增加它。


@ GlenH7我最近在www.hackerearth / jda上解决了一个类似的问题,关于最佳解决方案有相对的分数,我在以下方法中获得了最高的分数
vishfrnds 2014年

0

理念:

首先,创建两个映射(散列),例如S和E,从字母到单词。第一个字母S将起始字母映射到单词,第二个字母E与结尾字母相同。

例如,如果字典由以下内容组成:

鸟,碟,狗,harb

我们有:

S:

a -> [ ]
b -> [ bird ]
c -> [ ]
d -> [ dish, dog ]
...
h -> [ harb ]
...

和,

E:

a -> [ ]
b -> [ harb ]
c -> [ ]
d -> [ bird ]
...
g -> [ dog ]
h -> [ dish ]
...

接下来,使用S和E进行快速查找,创建一个与字典大小相同的森林(一组树),每个词的根都在根中,并且不允许一个词在树中出现多次-缓存构造树木时的深度:

bird (depth: 2)
   dish
      harb
   dog

dish (depth: 3)
   harb
      bird
         dog

dog (depth: 0)

harb (depth: 2)
   bird
      dish
      dog

最后,遍历森林并找到深度最大的树。

解决方案将在这些树​​的后代轴上。

例如,

dish / harb / bird / dog

以上。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.