给定一组唯一的单词, 找出所有不同 的索引对(i, j),使得列表中的两个单词, words[i] + words[j] ,可拼接成回文串。
示例 1:
输入: [“abcd”,“dcba”,“lls”,“s”,“sssll”]
输出: [[0,1],[1,0],[3,2],[2,4]]
解释: 可拼接成的回文串为 [“dcbaabcd”,“abcddcba”,“slls”,“llssssll”]
示例 2:
输入: [“bat”,“tab”,“cat”]
输出: [[0,1],[1,0]]
解释: 可拼接成的回文串为 [“battab”,“tabbat”]
暴力枚举
本题可以想到暴力做法,我们枚举每一对字符串的组合,暴力判断它们是否能够构成回文串即可。时间复杂度 O(n2×m),其中 n 是字符串的数量,m 是字符串的平均长度。时间复杂度并不理想,考虑进行优化。
Code
def palindromePairs(self, words: List[str]) -> List[List[int]]:
ans = []
for i in range(len(words)):
for j in range(len(words)):
if i != j:
temp = words[i] + words[j]
if temp == temp[::-1]:
ans.append([i, j])
return ans
字典树/哈希表+前后缀
假设存在两个字符串 s1 和 s2,s1+s2 是一个回文串,记这两个字符串的长度分别为 len1 和 len2,我们分三种情况进行讨论:
- len1=len2,这种情况下 s1 是 s2 的翻转。
- len1>len2,这种情况下我们可以将 s1 拆成左右两部分:t1 和 t2,其中 t1 是 s2 的翻转,t2是一个回文串。
- len1<len2,这种情况下我们可以将 s2 拆成左右两部分:t1 和 t2,其中 t2 是 s1 的翻转,t1是一个回文串。
这样,对于每一个字符串,我们令其为 s1 和 s2 中较长的那一个,然后找到可能和它构成回文串的字符串即可。
具体地说,我们枚举每一个字符串 kkk,令其为 s1s_1s1 和 s2s_2s2 中较长的那一个,那么 kkk 可以被拆分为两部分,t1t_1t1 和 t2t_2t2。
- 当 t1t_1t1 是回文串时,符合情况 333,我们只需要查询给定的字符串序列中是否包含 t2t_2t2 的翻转。
- 当 t2t_2t2 是回文串时,符合情况 222,我们只需要查询给定的字符串序列中是否包含 t1t_1t1 的翻转。
也就是说,我们要枚举字符串 kkk 的每一个前缀和后缀,判断其是否为回文串。如果是回文串,我们就查询其剩余部分的翻转是否在给定的字符串序列中出现即可。
注意到空串也是回文串,所以我们可以将 kkk 拆解为 k+∅k+\varnothingk+∅ 或 ∅+k\varnothing+k∅+k,这样我们就能将情况 111 也解释为特殊的情况 222 或情况 333。
而要实现这些操作,我们只需要设计一个能够在一系列字符串中查询「某个字符串的子串的翻转」是否存在的数据结构,有两种实现方法:
-
我们可以使用字典树存储所有的字符串。在进行查询时,我们将待查询串的子串逆序地在字典树上进行遍历,即可判断其是否存在。
-
我们可以使用哈希表存储所有字符串的翻转串。在进行查询时,我们判断带查询串的子串是否在哈希表中出现,就等价于判断了其翻转是否存在。
字典树 Code
class Node:
def __init__(self):
self.ch = [0] * 26
self.flag = -1
class Solution:
def palindromePairs(self, words: List[str]) -> List[List[int]]:
tree = [Node()]
def insert(s: str, index: int):
length = len(s)
add = 0
for i in range(length):
x = ord(s[i]) - ord("a")
if tree[add].ch[x] == 0:
tree.append(Node())
tree[add].ch[x] = len(tree) - 1
add = tree[add].ch[x]
tree[add].flag = index
def findWord(s: str, left: int, right: int) -> int:
add = 0
for i in range(right, left - 1, -1):
x = ord(s[i]) - ord("a")
if tree[add].ch[x] == 0:
return -1
add = tree[add].ch[x]
return tree[add].flag
def isPalindrome(s: str, left: int, right: int) -> bool:
length = right - left + 1
return length < 0 or all(s[left + i] == s[right - i] for i in range(length // 2))
n = len(words)
for i, word in enumerate(words):
insert(word, i)
ret = list()
for i, word in enumerate(words):
m = len(word)
for j in range(m + 1):
if isPalindrome(word, j, m - 1):
leftId = findWord(word, 0, j - 1)
if leftId != -1 and leftId != i:
ret.append([i, leftId])
if j and isPalindrome(word, 0, j - 1):
rightId = findWord(word, j, m - 1)
if rightId != -1 and rightId != i:
ret.append([rightId, i])
return ret
哈希表 Code
def palindromePairs(self, words: List[str]) -> List[List[int]]:
def findWord(s: str, left: int, right: int) -> int:
return indices.get(s[left: right + 1], -1)
def isPalindrome(s: str, left: int, right: int) -> bool:
sub = s[left: right + 1]
return sub == sub[::-1]
n, ret = len(words), list()
indices = {word[::-1]: i for i, word in enumerate(words)}
for i, word in enumerate(words):
m = len(word)
for j in range(m + 1):
if isPalindrome(word, j, m - 1):
leftId = findWord(word, 0, j - 1)
if leftId != -1 and leftId != i:
ret.append([i, leftId])
if j and isPalindrome(word, 0, j - 1):
rightId = findWord(word, j, m - 1)
if rightId != -1 and rightId != i:
ret.append([rightId, i])
return ret