问题描述
找到3个等长n的字符串的最长公共子序列。
我知道仅使用2个字符串时使用的动态编程算法,该字符串的末尾是矩阵nxn,所以n ^ 2的时间和空间(如果我错了,请纠正我)。
对索引进行一些调整后,很容易对3个字符串进行相同的算法。但是时间和空间的复杂度变为n ^ 3。
我的问题是,有没有一种动态编程算法更有效?字符串长度相等的事实改变了问题中的任何内容吗?
非常感谢
解决方法
将每个字符串转换为一个Trie,以生成所有子序列。
与前两个相交,尝试为前两个共同的子序列提出一个特里。将其与第三个相交,以得出所有三个共同子序列的特里。在最后一个特里搜索最长的公共子序列。
结果为O(length_of_sequence * size_of_alphabet)
。 (是的,这比朴素的动态编程lcs算法效率更高。)
这是一个证明了这一点的Python解决方案。请注意谨慎使用缓存,以免避免创建冗余数据结构。
from collections import namedtuple
LinkedList = namedtuple('LinkedList','value length tail')
class TrieNode:
last_id = 0
def __init__(self,char,trie=None):
TrieNode.last_id += 1
self.id = TrieNode.last_id
self.char = char
if trie is None:
trie = {}
self.trie = trie.copy()
def trie_from_string(string):
trie = {}
for i in reversed(range(len(string))):
char = string[i]
trie[char] = TrieNode(char,trie)
return TrieNode('start',trie)
def intersect_tries (trie1,trie2):
intersected = {} # Memoize to avoid repeated work
def _intersect (t1,t2):
key = (t1.id,t2.id)
if key not in intersected:
trie = {}
for next_char,next_node in t1.trie.items():
if next_char in t2.trie:
trie[next_char] = _intersect(next_node,t2.trie[next_char])
node = TrieNode(t1.char,trie)
intersected[key] = node
return intersected[key]
return _intersect(trie1,trie2)
def find_longest_in_trie (trie):
seen = {} # Memoize to avoid repeated work.
def _longest(t):
if t.id not in seen:
best = LinkedList(length=0,value=None,tail=None)
for char,inner in t.trie.items():
candidate = _longest(inner)
if best.length < candidate.length:
best = candidate
seen[t.id] = LinkedList(
length = best.length + 1,value = t.char,tail = best
)
return seen[t.id]
ll = _longest(trie)
answer = []
while ll is not None:
answer.append(ll.value)
ll = ll.tail
# drop 'start' and None
return answer[1:-1]
foo = trie_from_string('hello,world')
bar = trie_from_string('this world is weird')
baz = trie_from_string('have a wonderful day')
print(find_longest_in_trie(intersect_tries(intersect_tries(foo,bar),baz)))