从单词列表构造回文
Posted
技术标签:
【中文标题】从单词列表构造回文【英文标题】:Constructing palindrome from a list of words 【发布时间】:2015-05-17 21:39:13 【问题描述】:最近在翻一些面试题,发现了一个很有意思的:
给你一个单词列表。找出两个单词是否可以连接在一起形成回文。例如,考虑一个列表 bat, tab, cat 然后 bat 和 tab 可以连接起来形成一个回文。 期待一个 O(nk) 解决方案,其中 n = 作品数,k 是长度
可以有多对,找到一个就返回true。
此外,在 cmets 中,其中一种方法是:
1) 将第一个单词添加到 trie (A B) 2) 取第二个单词 (D E E D B A) 并反转 (A B D E E D) 3) 看看你可以在 trie 中匹配多少个反转单词中的字母(前 2 个) 4) 取出字符串的其余部分 (D E E D),看看它是否是回文,如果是,则返回 true 5) 将第二个单词添加到 trie (D E E D B A) 6) 使用下一个单词返回第 2 步 7) 用完的话返回false
但在我看来,这不是 O(nk) 的解决方案。
任何人都可以提出解决方案吗?或者解释一下为什么上面描述的算法是O(nk)??
【问题讨论】:
k
是什么长度?所有单词的长度都一样吗?
k 是最长单词的长度。
你真的要代码还是算法?去实现完整的 c++ 是相当多的。
不,只是算法,步骤
那么为什么要用c++
标记它呢?
【参考方案1】:
算法是正确的,或者至少它非常接近。有一些小的技术问题。在第 4 步中,如果一个解决方案的提议比当前的更好,则应该保存它,并在第 7 步中,返回它,或者说不可能产生回文。
主要思想是将单词处理成核心和前缀。如果核心是回文,那么我们需要将前缀与其他单词匹配。 Trie 充当处理字符串的“数据库”,因此对于每个新单词,都可以检查所有可能的扩展名。如果将单词分开保存,则需要分别比较每个单词的前缀。
(编辑:我认为仍然存在一个小漏洞,以防一个 trie 中有两个单词以相同的开头,而传入的单词会与较短的一个回文,但不是较长的,但我不会详细说明。处理它会使算法复杂化,但不会影响复杂性。)
它也是O(n*k)
。添加和检查前缀与特里树的步骤数与字符数成正比。所以在这种情况下,它受k
的约束。就像树操作是O(h)
,其中h
是树的高度。所以总结:
k
步数。
采取k
步骤。
最多也需要k
个步骤。
也少于k
步骤,但我们可以通过k
绑定它。
也采取k
步骤。
步骤 2 到 5 已完成n-1
次。
当然,每个步骤都有不同的主导操作,因此很难指定确切的常数,但它们都受k
的约束,因此复杂度为O(c*(n-1)*k)
,本质上为O(n*k)
。
【讨论】:
1.您在编辑中发现的问题对我来说似乎不仅仅是一个小漏洞:要在不改变时间复杂度的情况下正确处理它,您需要一种在 O(k ) 总时间 - 即,您需要能够测试以字符 i 开头的当前单词的后缀是否是每次测试的 O(1) 摊销时间中的回文。如何做到这一点当然不是很明显。 2.另外,第 3 步仅测试我们是否见过一个词,其中的某些前缀 可以与我们当前的词一起使用。我认为第 3 步需要更改为“查看反向单词中有多少个字母可以在 trie 中匹配,当我们只允许在某个输入单词的最后一个字符的节点处结束匹配时。” 显然我不被允许写评论来解释我投反对票的原因——这似乎是一种耻辱,因为当我的答案被否决时,我总是很想知道为什么。 @j_random_hacker 好吧,您确实写了一个广泛的评论。我没有足够的时间来回答它。我想知道我是否应该解释为什么我认为可以在不包含复杂性的情况下解决漏洞,因为我认为没有人会觉得它有趣。我今天会尝试将其作为编辑。【参考方案2】:早在 2004 年,Dr. Dobbs 的一篇文章中对此进行了非常有趣的讨论。完整的解释有点长,但总体思路是:
假设您从 Lion 开始,其中支点位于实际单词的左侧。我可以计算字符串的中心,即位置二。枢轴为零,所以右边的弦太重了,但目前,狮子有资格作为部分回文。枢轴点处的“点”与枢轴点处的点匹配,因此至少有一个正确的字符,尽管是相同的字符。您现在希望添加以 noil 结尾的单词,尝试将字符串转换为 noil.Lion。我用来表示任何字符串。如果您成功了,那么您需要找到以 开头的单词,以便将它们附加到字符串中。
请注意,他将部分回文定义为:
如果从枢轴点向外工作,在发生不匹配之前遇到字符串的左端或右端,则字符串是部分回文。
【讨论】:
以上是关于从单词列表构造回文的主要内容,如果未能解决你的问题,请参考以下文章