leetcode No336. Palindrome Pairs

这篇博客介绍了LeetCode中的336题,即寻找列表中形成回文串的单词对。文章详细分析了暴力求解的高时间复杂度,并提出了优化算法,利用前缀树(Trie)结构进行反向构建,以解决不同长度字符串组合成回文串的两种情况。博主还展示了构建和查找过程,并提供了完整的Java代码实现,时间复杂度为O(n×k×k)。

Question

Given a list of unique words, find all pairs of distinct indices (i, j) in the given list, so that the concatenation of the two words, i.e. words[i] + words[j] is a palindrome.

Example 1:

Input: ["abcd","dcba","lls","s","sssll"]
Output: [[0,1],[1,0],[3,2],[2,4]] 
Explanation: The palindromes are ["dcbaabcd","abcddcba","slls","llssssll"]

Example 2:

Input: ["bat","tab","cat"]
Output: [[0,1],[1,0]] 
Explanation: The palindromes are ["battab","tabbat"]

Algorithm

暴力法我就不说了,暴力法时间复杂度为O(n×n×k),k为单词的平均长度。

下面来具体分析,先来看字符串s1(长度为k1),字符串s2(长度为k2)组合在一起判断是否是一个回文串有哪几种情况?

  1. k1 = k2
  2. k1 < k2
    s2剩下的部分必须要满足回文
  3. k1 > k2
    s1剩下的部分必须要满足回文
    在这里插入图片描述

思考两个问题?

  • 假设s1的首字符是a,怎么快速找到以a结尾的字符串?
    • 构建Trie,只不过要反着构建
  • 第2,3种情况,如何快速的知道剩下的部分是否是回文?
    • 第2种情况,已知s1,查找s2,s1先遍历完,因此在遍历到s2的节点b时,为了能快速判断剩下的部分是回文,只要记录从该节点向后所有剩余能构成回文的字符串的下标即可。
    • 第3种情况,已知s1,查找s2,s2先遍历完,只需要判断s1剩下的部分是回文即可。

因此,需要构建的前缀树的结构如下:

class TrieNode {
    public TrieNode[] children;
    public int index;  //题目要求返回下标,因为这里记录下标
    public List<Integer> suffixs;  //记录节点向后所有的剩余能构成回文的字符串下标
    
    public TrieNode(){
        this.children = new TrieNode[26];
        this.index = -1;
        this.suffixs = new ArrayList<>();
    }
}

一个简单示例:

  • 构建图示
    在这里插入图片描述
  • 查找图示(以字符串"a"为例)
    在这里插入图片描述
    完整代码如下:
  • 构建
    • 字符串取反;
    • 遍历word,创建节点;
    • word.substring(j+1)为回文,则添加到该节点的suffixs列表中;这里要注意word本身为回文则添加到root节点的suffixs列表中;
  • 查找
    • 遍历word;
    • 如果word.substring(j)为回文,则要看当前节点是否为一个单词,如果是,添加到结果中;(对应第三种情况,k1 > k2)
    • word遍历结束且有以word结尾的单词,则要看当前节点的suffixs列表;(对应第二种情况,k1 < k2)

Code

class Solution {
    private TrieNode root;
    public boolean isPalindrome(String s){
        int i=0, j=s.length()-1;
        while (i < j){
            if (s.charAt(i) != s.charAt(j)){
                return false;
            }
            i++;
            j--;
        }

        return true;
    }
    public List<List<Integer>> palindromePairs(String[] words) {
        this.root = new TrieNode();
        int n = words.length;

        //build TrieNode Tree
        for (int i=0; i<n; i++){
            String word = new StringBuilder(words[i]).reverse().toString();
            TrieNode cur = root;

            if (isPalindrome(word.substring(0)))
                cur.suffixs.add(i);
            for (int j=0; j<word.length(); j++){
                int index = word.charAt(j) - 'a';
                if (cur.children[index] == null)
                    cur.children[index] = new TrieNode();
                cur = cur.children[index];
                if (isPalindrome(word.substring(j+1)))
                    cur.suffixs.add(i);
            }
            cur.index = i;
        }

        //search 
        List<List<Integer>> res = new ArrayList<>();
        for (int i=0; i<n; i++){
            String word = words[i];
            TrieNode cur = root;

            int j=0;
            for (; j<word.length(); j++){
                if (isPalindrome(word.substring(j)) && cur.index!=-1){
                    res.add(Arrays.asList(i, cur.index));
                }

                int index = word.charAt(j) - 'a';
                if (cur.children[index] == null)
                    break;
                cur = cur.children[index];
            }

            if (j == word.length()){
                for (int k : cur.suffixs){
                    if (k != i)
                        res.add(Arrays.asList(i, k));
                }
            }
        }

        return res;
    }
}

class TrieNode {
    public TrieNode[] children;
    public int index;
    public List<Integer> suffixs;
    
    public TrieNode(){
        this.children = new TrieNode[26];
        this.index = -1;
        this.suffixs = new ArrayList<>();
    }
}

时间复杂度:O(n×k×k),因为k在一定范围内,所以这个问题优化为一个线性问题。
在这里插入图片描述
更多算法总结请关注我的微信公众号《Coder101》
在这里插入图片描述

### LeetCode 336 Palindrome Pairs 解析 对于给定单词列表 `words`,目标是从该列表中找出所有的索引对 `(i, j)`,使得由这两个单词组成的字符串是回文串。此问题可以通过构建字典树(Trie)来高效解决[^1]。 #### 方法一:暴力法 最直观的方法是对每一对可能的组合进行两两比较,检查它们连接后的字符串是否为回文串。这种方法的时间复杂度较高,达到 O(n^2 * k),其中 n 是数组长度而 k 表示平均单词长度。虽然简单易懂但是效率低下,在数据量较大时表现不佳。 #### 方法二:哈希表优化方案 为了提高性能,可以利用哈希表存储反转后的单词及其对应的下标位置。遍历每一个单词 w_i 并尝试将其拆分为前后缀 s 和 p (即 w_i = sp 或者 w_i=p+s) ,如果前缀s本身构成一个回文串,则只需要查询是否存在另一个单词等于p的逆序;同理当后缀p是一个有效的回文串时也做同样的处理。这样做的好处是可以显著减少不必要的匹配次数并加快检索速度。 ```cpp class Solution { public: unordered_map<string,int> mp; bool isPalindrome(string& str){ int l=0,r=str.length()-1; while(l<r && str[l]==str[r]) ++l,--r; return l>=r; } vector<vector<int>> palindromePairs(vector<string>& words) { vector<vector<int>> res; // 建立映射关系 for(int i=0;i<words.size();++i) mp[string(words[i].rbegin(),words[i].rend())]=i; for(int i=0;i<words.size();++i){ string word=words[i]; if(mp.count(word)&&mp[word]!=i) res.push_back({min(i,mp[word]),max(i,mp[word])}); for(int j=1;j<=word.size();++j){ auto prefix=word.substr(0,j); auto suffix=word.substr(j); if(isPalindrome(prefix) && mp.count(suffix)) res.push_back({mp[suffix],i}); if(isPalindrome(suffix) && mp.count(prefix)) res.push_back({i,mp[prefix]}); } } // 删除重复项 sort(res.begin(),res.end()); res.erase(unique(res.begin(),res.end()),res.end()); return res; } }; ``` 上述代码实现了基于哈希表的方式寻找所有符合条件的索引对,并通过去重操作确保最终结果不含有冗余条目。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值