最短的普通超弦


26

给定一个字符串列表s_0, s_1, ..., s_n找到最短的字符串S,其中包含每s_0, s_1, ..., s_n一个字符串

例子

  • S('LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE')='SEDOLOREMAGNAD'
  • S('ABCDE', 'BCD', 'C')='ABCDE'

编写解决此问题的最短程序(或函数)。您可以根据需要将字符串表示为数组或字符/整数列表。标准库可以。对于输入/输出,可以使用更方便的方法:STDIN / STDOUT,用户提示,函数的参数/返回值等。

性能并不重要-假设,对于总长度<100个字符的输入,平均现代硬件上的结果必须在<10秒内计算出来。


3
+1好问题。我建议您提供一些其他预期结果的示例,以便人们可以轻松判断提交的内容是否能够处理各种情况。
DavidC

输入/输出应如何处理?应该打印结果还是从函数返回结果?
flornquake

因此,没有“对于每个字符串,如果包含所有...,则返回它”不是有效的解决方案吗?
约翰·德沃夏克

我怀疑是否会有答案。这个问题可能很好地适用于Stack Overflow(不包括代码高尔夫部分)。
约翰·德沃夏克

Answers:


8

Python 2中,170 153/157/159

归功于Baptiste的一些想法

from itertools import*
print min((reduce(lambda s,w:(w+s[max(i*(s[:i]==w[-i:])for i in range(99)):],s)[w in s],p)
for p in permutations(input())),key=len)

不需要第二个换行符。

输入:'LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE'
输出:SEDOLOREMAGNAD

即使输入字符串很长,如果最多有7个输入字符串,它也将在不到2秒的时间内运行(在给定的示例中就是这种情况,在我的机器上,运行时间为1.7 1.5秒)。但是,对于8个或更多输入字符串,由于时间复杂度为,所以需要10秒钟以上O(n!)

正如Baptiste指出的那样,如果应支持任意输入长度(则代码的总长度为157个字符),则range(99)需要替换为range(len(w))。如果应该支持空的输入字符串,则必须将其更改为range(len(w)+1)。我认为range(99)任何小于200的总输入长度都可以正常工作。

更多测试:

>>> "AD", "DO", "DOLOR", "DOLORE", "LOREM", "MAGNA", "SED", "ORE",  "R"
SEDOLOREMAGNAD

>>> 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvw
... xyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstu
... vwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ZOOM', 'aZ', 'Za', 'ZA'
aZABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZOOM

5

数学337 418 372

在尝试使用Mathematica的实现失败后LongestCommonSubsequencePositions,我转向了模式匹配。

v=Length;
p[t_]:=Subsets[t,{2}];
f[w_]:=Module[{c,x,s=Flatten,r={{a___,Longest[y__]},{y__,b___}}:>{{a,y},{y,b},{y},{a,y,b}}},
c=p@w;
x=SortBy[Cases[s[{#/.r,(Reverse@#)/.r}&/@c,1],{_,_,_,_}],v[#[[3]]]&][[-1]];
Append[Complement[w,{x[[1]],x[[2]]}],x[[4]]]]

g[r_]:=With[{h=Complement[r,Cases[Join[p@r,p@Reverse@r],y_/;!StringFreeQ@@y:>y[[2]]]]},
FixedPoint[f,Characters/@h,v@h-1]<>""]

模式匹配规则

r={{a___,Longest[y__]},{y__,b___}}:> {{a,y},{y,b},{y},{a,y,b}}},

需要的有序对的话(表示为字符的列表),并返回:(1)的话,{a,y}{y,b}随后(2)的共同子串,y即通一个单词的末尾与其他单词的开始,和,最后,{a,y,b}将替换输入单词的组合单词。参见贝利萨留斯的相关示例:https : //mathematica.stackexchange.com/questions/6144/looking-for-longest-common-substring-solution

三个连续的下划线字符表示该元素是一个零个或多个字符的序列。

Reverse稍后使用以确保两个订单都经过测试。那些共享可链接字母的对将保持不变并被忽略。

编辑

以下内容从列表中删除了另一个单词中“埋藏”(即完全包含)的单词(以响应@flornquake的评论)。

h=Complement[r,Cases[Join[p@r,p@Reverse@r],x_/;!StringFreeQ@@x:> x[[2]]]]

范例

 {{"D", "O", "L", "O", "R", "E"}, {"L", "O", "R", "E", "M"}} /. r

退货

{{“ D”,“ O”,“ L”,“ O”,“ R”,“ E”},{“ L”,“ O”,“ R”,“ E”,“ M”},{ “ L”,“ O”,“ R”,“ E”}},{“ D”,“ O”,“ L”,“ O”,“ R”,“ E”,“ M”}}


用法

g[{"LOREM", "ORE", "R"}]

AbsoluteTiming[g[{"AD", "DO", "DOLOR", "DOLORE", "LOREM", "MAGNA", "SED", "ORE",  "R"}]]

“ LOREM”

{0.006256,“ SEDOLOREMAGNAD”}


这对输入有用"LOREM", "ORE", "R"吗?
flornquake

(即,产生的输出正确"LOREM"吗?)
flornquake

@flornquake。不错的收获。我在当前版本中解决了它。我希望我没有错过任何其他情况。谢谢。
DavidC

最好不过了!
DavidC

3

GolfScript,66个字符

{.,1>{.`{[1$]-s:h;.,),\`{:g<`{\+.g?0<{;}*}+h%~}+/}+%.&}*}:s~{,}$0=

很短,但是由于指数时间复杂度(和GolfScript)确实很慢,它打破了10秒的时间限制。

例子:

['LOREM' 'DOLOR' 'SED' 'DO' 'MAGNA' 'AD' 'DOLORE']
{.,1>{.`{[1$]-s:h;.,),\`{:g<`{\+.g?0<{;}*}+h%~}+/}+%.&}*}:s~{,}$0=
# => SEDOLOREMAGNAD

['AB' 'BC' 'CA' 'BCD' 'CDE']
{.,1>{.`{[1$]-s:h;.,),\`{:g<`{\+.g?0<{;}*}+h%~}+/}+%.&}*}:s~{,}$0=
# => CABCDE

2

python 2,203 187 200

from itertools import permutations as p
def n(c,s=''):
 for x in c:s+=x[next((i+1 for i,l in [(j,x[:j+1])for j in range(len(x))][::-1]if s.endswith(l)),0):]
 return s
print min(map(n,p(input())),key=len)

输入:['LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE']
输出:SEDOLOREMAGNAD

编辑

通过使用reduce一些肮脏的导入技巧,我可以进一步减少这种情况(仅减少到一行!):

print min((reduce(lambda a,x:a+x[next((i+1 for i,l in [(j,x[:j+1])for j in range(len(x))][::-1]if a.endswith(l)),0):],P,'')for P in __import__('itertools').permutations(input())),key=len)

编辑2

正如flornquake所指出的,当一个单词包含在另一个单词中时,这会给出错误的结果。此修复程序还增加了13个字符:

print min((reduce(lambda a,x:a+(x[next((i+1 for i,l in [(j,x[:j+1])for j in range(len(x))][::-1]if a.endswith(l)),0):],'')[x in a],P,'')for P in __import__('itertools').permutations(input())),key=len)

这是清理后的版本:

from itertools import permutations

def solve(*strings):
    """
    Given a list of strings, return the shortest string that contains them all.
    """
    return min((simplify(p) for p in permutations(strings)), key=len)

def prefixes(s):
    """
    Return a list of all the prefixes of the given string (including itself),
    in ascending order (from shortest to longest).
    """
    return [s[:i+1] for i in range(len(s))]
    return [(i,s[:i+1]) for i in range(len(s))][::-1]

def simplify(strings):
    """
    Given a list of strings, concatenate them wile removing overlaps between
    successive elements.
    """
    ret = ''
    for s in strings:
        if s in ret:
            break
        for i, prefix in reversed(list(enumerate(prefixes(s)))):
            if ret.endswith(prefix):
                ret += s[i+1:]
                break
        else:
            ret += s
    return ret

print solve('LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE')

可以使用range(99)而不是range(len(x))(为了思考这一点而获得flornquake的信用),从而以理论上的正确性为代价来删除一些字符。


如果您愿意牺牲正确性,那么不妨使用贪婪方法或2方法的多项式逼近因子。
彼得·泰勒

不错的解决方案!不过,您需要检查新字符串中是否已经包含新单词:'LOREM', 'ORE', 'R'错误地产生了output LOREMORER
flornquake

@flornquake好收获。我设法修复了它,但它增加了13个字符。
Baptiste M.

1

Python,144个字符

S=lambda A,s:min(S(A-set([a]),s+a[i:])for a in A for i in range(len(a)+1)if i==0 or s[-i:]==a[:i])if A else(len(s),s)
T=lambda L:S(set(L),'')[1]

S接受一组A仍需要放置的单词和一个s包含到目前为止放置的单词的字符串。我们a从中选择一个剩余的单词,A并将其从重叠0len(a)字符的末尾s

在给定的示例上仅花费0.15秒。


非常好!但是像其他解决方案一样,这不适用于像这样的输入['LOREM', 'ORE', 'R']。我已经采取了自由修复措施,并进一步解决了您的问题:( S=lambda A,s='':A and min((S(A-{a},(s+a[max(i*(s[-i:]==a[:i])for i in range(len(a))):],s)[a in s])for a in A),key=len)or s不需要第二行)。用法:S({'LOREM', 'DOLOR', 'SED', 'DO', 'MAGNA', 'AD', 'DOLORE'})返回'SEDOLOREMAGNAD'
flornquake

0

哈斯克尔(121)

import Data.List
a p []=[(length p,p)]
a p s=[r|w<-s,t<-tails w,isInfixOf w$p++t,r<-a(p++t)(s\\[w])]
s=snd.minimum.a ""

如果该函数不需要绑定到名称,则减负2

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.