LeetCode(336):回文对 Palindrome Pairs(Java)

2019.11.22 LeetCode 从零单刷个人笔记整理(持续更新)

github:https://github.com/ChopinXBP/LeetCode-Babel

这道题本质上是要切分字符串,使得字典中的两个单词

word1[0,end] + word2[0,j-1] + word2[j,end]
或者
word1[0,j-1] + word1[j,end] + word2[0,end]

恰能构成回文串。

也就是需要word1[0,end]与word2[j,end]互为翻转,且word2[0,j-1]能够构成回文子串(或word1[0,j-1]与word2[0,end]互为翻转,word1[j,end]构成回文子串)。

当j=0时,中间部分为空串,字典中恰有两个单词互为翻转。

因此,可以有两种思路:

1.字典树

从后往前反方向遍历所有单词,构建一棵后缀树(存储翻转的单词)。后缀树的有两个额外成员:reverseWordIdx用于存放翻转单词的单词序号,容器palindPrefixWords存放该结点处除去后缀其余部分能形成回文前缀[0,i]的单词序号。

从前往后遍历单词curWord的进行校验。

如果字典中恰存在一个与curWord[0,j-1]互为翻转的单词reverseWord,且剩余部分curWord[j,end]恰为回文子串,则一定能组合成

curWord[0,j-1]+curWord[j,end]+reverseWord[0,end]

的回文串。

若遍历结束结点仍不空,说明curWord[0,end]恰与字典中其他单词的部分后缀[j,end]互为翻转。除去翻转后缀后其余部分[0,j-1]能够形成回文的单词palindPrefixWord一定能与curWord构成

curWord[0,end]+palindPrefixWord[0,j-1]+palindPrefixWord[j,end]

的回文串(j-1=0时为空回文子串,字典中恰有两个单词互为翻转)。

2.哈希表

哈希表法思路比较简单清晰但也比较慢,第一轮遍历将所有字符串及其位置存入哈希表中,第二轮遍历依次截取字符串进行检验。有以下几种情况:

1.如果words里有空串,则配合当前字符串可以组成回文对。

2.如果当前串的翻转串也在words中(与本身不同),则可以组成回文对。

3.如果将字符串分割后有一半是回文串,且另一半的翻转在数组中,则可以组成回文对。


传送门:回文对

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.

给定一组唯一的单词, 找出所有不同 的索引对(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"]


import java.util.*;

/**
 *
 * 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.
 * 给定一组唯一的单词, 找出所有不同 的索引对(i, j),使得列表中的两个单词, words[i] + words[j] ,可拼接成回文串。
 *
 */

public class PalindromePairs {

    //字典树
    public List<List<Integer>> palindromePairs(String[] words) {
        List<List<Integer>> result = new ArrayList<>();
        TrieNode root = new TrieNode();
        for(int i = 0; i < words.length; i++){
            addWord(root, words[i], i);
        }
        for(int i = 0; i < words.length; i++){
            search(words, i, root, result);
        }
        return result;
    }

    private class TrieNode{
        TrieNode[] children;
        int reverseWordIdx;
        List<Integer> palindPrefixWords;
        TrieNode(){
            children = new TrieNode[26];
            reverseWordIdx = -1;
            palindPrefixWords = new ArrayList<>();
        }
    }

    //构建后缀树
    private void addWord(TrieNode root, String word, int curWordIdx){
        //从前往后遍历每一个字符i进行建树
        for(int i = word.length() - 1; i >= 0; i--){
            int loc = word.charAt(i) - 'a';
            if(root.children[loc] == null){
                root.children[loc] = new TrieNode();
            }
            //palindPrefixWords存放在i处除去后缀其余部分能形成回文前缀[0,i]的单词序号
            if(isPalindrome(word, 0, i)){
                root.palindPrefixWords.add(curWordIdx);
            }
            root = root.children[loc];
        }
        //考虑前缀为空串时[0,0]也是回文串,尾结点处palindPrefixWords添加curWordIdx
        root.palindPrefixWords.add(curWordIdx);
        //尾结点的reverseWordIdx存放翻转单词的单词序号
        root.reverseWordIdx = curWordIdx;
    }

    private void search(String[] words, int curWordIdx, TrieNode node, List<List<Integer>> result){
        //从前往后遍历单词curWord的进行校验
        for(int j = 0; j < words[curWordIdx].length(); j++){
            //如果字典中恰存在一个与curWord[0,j-1]互为翻转的单词reverseWord,且剩余部分curWord[j,end]恰为回文子串
            //则一定能组合成curWord[0,j-1]+curWord[j,end]+reverseWord[0,end]的回文串
            if(node.reverseWordIdx >= 0 && isPalindrome(words[curWordIdx], j, words[curWordIdx].length() - 1)){
                result.add(Arrays.asList(curWordIdx, node.reverseWordIdx));
            }
            node = node.children[words[curWordIdx].charAt(j) - 'a'];
            //若后缀路径遇到空值则停止校验并返回
            if(node == null){
                return;
            }
        }
        //若遍历结束结点仍不空,说明curWord[0,end]恰与字典中其他单词的部分后缀[j,end]互为翻转
        //除去翻转后缀后其余部分[0,j-1]能够形成回文的单词palindPrefixWord一定能与curWord构成
        //curWord[0,end]+palindPrefixWord[0,j-1]+palindPrefixWord[j,end]的回文串(j-1=0时为空回文子串,字典中恰有两个单词互为翻转)
        for(int palindPrefixWordIdx : node.palindPrefixWords){
            //排除自身构成回文的重复情况
            if(curWordIdx != palindPrefixWordIdx){
                result.add(Arrays.asList(curWordIdx, palindPrefixWordIdx));
            }
        }
    }

    private boolean isPalindrome(String word, int begin, int end){
        while(begin < end){
            if(word.charAt(begin++) != word.charAt(end--)){
                return false;
            }
        }
        return true;
    }

    //哈希表
    public List<List<Integer>> palindromePairs2(String[] words) {

        HashMap<String, Integer> map = new HashMap<>();
        int emptyLoc = -1;
        for(int i = 0; i < words.length; i++){
            if(words[i].equals("")){
                emptyLoc = i;
                continue;
            }
            map.put(words[i], i);
        }

        HashSet<List<Integer>> result = new HashSet<>();
        for(int i = 0; i < words.length; i++){
            String str = words[i];
            if(str.equals("")){
                continue;
            }
            //如果words里有空串,则配合当前字符串可以组成回文对
            if(emptyLoc != -1 && isPalindrome(str, 0, str.length() - 1)){
                result.add(Arrays.asList(emptyLoc, i));
                result.add(Arrays.asList(i, emptyLoc));
            }
            //如果当前串的翻转串也在words中(与本身不同),则可以组成回文对
            String revStr = reverseStr(str);
            if(map.containsKey(revStr) && !str.equals(revStr)){
                result.add(Arrays.asList(map.get(revStr), i));
                result.add(Arrays.asList(i, map.get(revStr)));
            }
            //如果将字符串分割后有一半是回文串,且另一半的翻转在数组中,则可以组成回文对
            for(int j = 1; j < str.length(); j++){
                String left = str.substring(0, j);
                String right = str.substring(j);
                if(isPalindrome(left, 0, left.length() - 1)){
                    String revRight = reverseStr(right);
                    if(map.containsKey(revRight)){
                        result.add(Arrays.asList(map.get(revRight), i));
                    }
                }
                if(isPalindrome(right, 0, right.length() - 1)){
                    String revLeft = reverseStr(left);
                    if(map.containsKey(revLeft)){
                        result.add(Arrays.asList(i, map.get(revLeft)));
                    }
                }
            }
        }

        return new ArrayList<>(result);
    }

    public String reverseStr(String str){
        StringBuilder result = new StringBuilder(str);
        return result.reverse().toString();
    }

}


#Coding一小时,Copying一秒钟。留个言点个赞呗,谢谢你#

发布了246 篇原创文章 · 获赞 316 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_20304723/article/details/103198153