在问 · 算法题解

336. 回文对

困难字符串哈希表枚举拆分点力扣原题 ↗

题目描述

输入一个字符串数组 words,其中的字符串两两不同,下标从 0 开始。

如果两个不同下标 i 和 j 满足:把 words[i] 和 words[j] 按这个先后拼接后得到回文串,就称 [i, j] 是一个回文对。注意 [i, j] 和 [j, i] 是两个不同的对,需要分别判断。

返回所有回文对组成的列表,顺序不限。字符串可以是空串。

要求算法的时间复杂度与所有字符串长度之和同阶。

示例 1
输入:words = ["abcd","dcba","lls","s","sssll"]
输出:[[1,0],[0,1],[3,2],[2,4]]

解释:拼接结果分别为 "dcbaabcd"、"abcddcba"、"slls"、"llssssll",都是回文。

示例 2
输入:words = ["bat","tab","cat"]
输出:[[1,0],[0,1]]

解释:"tabbat" 和 "battab" 是回文,"cat" 无法与其他词组成回文。

示例 3
输入:words = ["a",""]
输出:[[0,1],[1,0]]

解释:空串与 "a" 无论哪个在前,拼出来都是 "a"。

数据范围

  • 1 <= words.length <= 5000
  • 0 <= words[i].length <= 300
  • words[i] 只含小写英文字母

解题思路:哈希表枚举拆分点

设 w = words[i],把 w 在位置 k 切成 pre + suf。① 若 pre 是回文,而 reverse(suf) 也在数组中(下标 j),则 reverse(suf) + pre + suf 是回文,得到 [j, i]。② 若 suf 是回文,而 reverse(pre) 在数组中,则 pre + suf + reverse(pre) 是回文,得到 [i, j]。用哈希表记下每个单词的下标,枚举所有切分点即可。

为什么正确

两个单词拼成回文时,较长的那个多出来的部分必须自身是回文,其余部分必须是另一个单词的反转,所以每个合法的对都会在较长单词的某个切分点被找到。

复杂度分析

时间 O(n · L²),n 为单词数,L 为最大长度;空间 O(n · L)。

易错点

空串与任何回文单词都能配成两个对;为避免同一对被两个情况重复统计,情况 ② 要排除 k = len(w)(此时它与情况 ① 的 k = 0 对称)。还要排除 j = i 的自己配自己。暴力枚举所有对是 O(n² · L),n = 5000 时会超时。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def palindromePairs(self, words: List[str]) -> List[List[int]]:
        d = {w: i for i, w in enumerate(words)}
        res = []
        for i, w in enumerate(words):
            for k in range(len(w) + 1):
                pre, suf = w[:k], w[k:]
                if pre == pre[::-1] and suf[::-1] in d and d[suf[::-1]] != i:
                    res.append([d[suf[::-1]], i])
                if k != len(w) and suf == suf[::-1] and pre[::-1] in d and d[pre[::-1]] != i:
                    res.append([i, d[pre[::-1]]])
        return res

载入编辑器,自己跑一遍 →

其他解法

两种方法都枚举拆分点并检查另一半是否为回文:哈希表版用子串查表,字典树版把“查反转前缀”变成沿树前进。

解法 2:字典树(存反转单词) 时间 O(n·L²),空间 O(n·L)

把每个单词反转后插入字典树。插入时,如果反转串从当前位置到末尾是回文,就把单词下标记入当前节点的 pal 列表;结束节点记录 idx 并也加入 pal。查询单词 w 时沿字典树前进:途中遇到 idx,且 w 剩余部分是回文,就得到一对;w 走完后,当前节点 pal 列表里的单词都能接在 w 后面。

查询时一边走一边匹配,不用为每个拆分点生成子串并求哈希。代码更长,节点数组占内存更多。

class Solution:
    def palindromePairs(self, words: List[str]) -> List[List[int]]:
        def is_pal(s, l, r):
            while l < r:
                if s[l] != s[r]:
                    return False
                l += 1
                r -= 1
            return True

        children = [[0] * 26]
        idx = [-1]
        pal = [[]]
        for j, w in enumerate(words):
            r = w[::-1]
            node = 0
            for p, ch in enumerate(r):
                if is_pal(r, p, len(r) - 1):
                    pal[node].append(j)
                c = ord(ch) - 97
                if not children[node][c]:
                    children[node][c] = len(children)
                    children.append([0] * 26)
                    idx.append(-1)
                    pal.append([])
                node = children[node][c]
            idx[node] = j
            pal[node].append(j)
        res = []
        for i, w in enumerate(words):
            node = 0
            ok = True
            for p, ch in enumerate(w):
                if idx[node] >= 0 and is_pal(w, p, len(w) - 1):
                    res.append([i, idx[node]])
                node = children[node][ord(ch) - 97]
                if not node:
                    ok = False
                    break
            if ok:
                for j in pal[node]:
                    if j != i:
                        res.append([i, j])
        return res

字符串 专题的其他题目

查看 字符串 全部题目 →