[leetcode每日一题2020/8/6]336. 回文对

回文对

  • 题目
  • 思路
  • 代码
      • 未优化算法
      • 优化算法
  • 算法复杂度分析
  • 本代码结果

题目来源于leetcode,解法和思路仅代表个人观点。传送门。
难度:困难
用时:03:00:00(看攻略了,这题解法确实没想到)

题目

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

思路

第一眼看到,就想到是暴力解法。

  1. 对于给定的字符串
  2. 两个循环中。依次判断两两组合的情况是否是回文串

结果就是超时了。
粗略计算,算法复杂度为O(n2*s)。n为字符串个数,s为字符串平均长度。因此需要换个思路,想想有没有更好的解法。


由于我想不到解法,这里就直接放官方攻略。

简单的概括几点我自己的理解。

  1. 建立字典树。 建立一个以空串为根节点的字典树。结构如下。每个节点都有26个子节点。
    [leetcode每日一题2020/8/6]336. 回文对_第1张图片
  2. 对于每个给定的字符串,都分成两部分。左半部分右半部分
    (1)如果左半部分是回文串,右半部分在字典树中可以找到一个逆序串(不是自己)。那么把找到的逆序串放在该串的左边,可以构成一个回文串。
    (2)如果右半部分是回文串,左半部分在字典树中可以找到一个逆序串(不是自己)。那么把找到的逆序串放在该串的右边,可以构成一个回文串。
  3. 那么我们只需要写好下面几个方法,方便调用即可。
    (1)判断一个串是否是回文串
    (2)把一个串插入到字典树中
    (3)判断一个串是否在字典树中

我这里字典树的结构定义的不是很好,导致插入串,和查找串写出来的代码不够优雅仅供参考

代码

未优化算法

115 / 134 个通过测试用例(大量字符串导致超时)

class Solution {
    public List<List<Integer>> palindromePairs(String[] words) {
        List<List<Integer>> ans = new ArrayList<>();
        for(int i=0;i<words.length;i++){
            for(int j=0;j<words.length;j++){
                if(i==j){
                    continue;
                }
                if(isPalindrome(words[i],words[j])){
                    List<Integer> result = new ArrayList<>();
                    result.add(i);
                    result.add(j);
                    ans.add(result);
                }
            }
        }
        return ans;
    }
    public boolean isPalindrome(String s1,String s2){
        String s3 = s1 + s2;
        int p1=0;
        int p2=s3.length()-1;
        for(p1=0,p2=s3.length()-1;p1<p2;p1++,p2--){
            if(s3.charAt(p1)!=s3.charAt(p2)){
                return false;
            }
        }
        return true;
    }
}

优化算法

124 / 134 个通过测试用例(特殊情况)

输入:
["a",""]
输出:
[[0,1]]
预期:
[[0,1],[1,0]]

特殊情况添加以下代码:

//如果左半部分是空串,那么结果是【相互】的
if(s1.equals("")){
    ans.add(Arrays.asList(leftId, i));
}
class Solution {
    //字典树
    class Node{
        //char的默认值为''
        //char ch;
        //int的默认值为0,记录该点的id
        //-1表示没有值,-2表示中间值,正整数表示串id
        int id;
        //26个子节点
        Node[] child;
        public Node(){
            id = -1;
        }
    }
    //从‘’开始的字典树
    Node tree = new Node();

    public List<List<Integer>> palindromePairs(String[] words) {
        List<List<Integer>> ans = new ArrayList<>();
        //1.构建字典树
        for(int i=0;i<words.length;i++){
            insert(tree,words[i],-1,i);
        }
        //2.遍历每一个串
        for(int i=0;i<words.length;i++){
            //对于每一个串
            for(int j=0;j<words[i].length();j++){
                //把该串分割成两部分
                //一部分找回文串,一部分找逆序串
                //左侧部分
                String s1 = words[i].substring(0,j);
                //右侧部分
                String s2 = words[i].substring(j,words[i].length());
                if (isPalindrome(s2)) {
                    int leftId = findNode(tree,new StringBuilder(s1).reverse().toString(),-1);
                    if (leftId != -1 && leftId != i) {
                        ans.add(Arrays.asList(i, leftId));
                        //如果左半部分是空串,那么结果是【相互】的
                        if(s1.equals("")){
                            ans.add(Arrays.asList(leftId, i));
                        }
                    }
                }
                if (isPalindrome(s1)) {
                    int rightId = findNode(tree,new StringBuilder(s2).reverse().toString(),-1);
                    if (rightId != -1 && rightId != i) {
                        ans.add(Arrays.asList(rightId, i));
                    }
                }
            }
        }
        return ans;
    }
    //判读其是否为回文串
    public boolean isPalindrome(String s){
        int p1=0;
        int p2=s.length()-1;
        for(p1=0,p2=s.length()-1;p1<p2;p1++,p2--){
            if(s.charAt(p1)!=s.charAt(p2)){
                return false;
            }
        }
        return true;
    }
    //在字典树中查找字符串s。index为字符串当前指针
    public int findNode(Node tree,String s,int index){
        //如果没有节点了 或者 该节点没有串走过
        if(tree == null || tree.id == -1){
            return -1;
        }
        //遍历到尽头,且找到存在
        if(index == s.length()-1){
            if(tree.id >= 0){
                return tree.id;
            }
            //如果遍历到尽头,但是找到不存在
            else{
                return -1;
            }
        }
        //如果没有走到尽头,且该节点有串走过,进入下一层
        int next = s.charAt(index+1)-'a';
        if(tree.child == null) {
        	return -1;
        }
        return findNode(tree.child[next],s,index+1);
    }
    
    //插入将该字符串s插入字典树
    public void insert(Node tree,String s,int index,int id){
        //如果该字符串已经插入了树中
        if(index >= s.length()){
            return;
        }
        //index == -1 时需要判断其是否时空串
        if(index == -1){
            //如果该串是空串
            if(s.equals("")){
                tree.id = id;
                return;
            }
            //如果不是空串执行下一层
            else{
                //如果子节点为空,则生成子节点
                if(tree.child == null){
                    Node[] child = new Node[26];
                    tree.child = child;
                }
                //修改tree.id
                if(tree.id <0){
                    tree.id = -2;
                }
                int next = s.charAt(index+1)-'a';
                if(tree.child[next] == null) {
	            	tree.child[next] = new Node();
	            }
                insert(tree.child[next],s,index+1,id);
            }
        }
        //如果不是空串
        else{
            //修改tree.id
            if(tree.id < 0){
                //标记为走过
                tree.id = -2;
            }
            //该字符串插入树中
	        if(index >= s.length()-1){
	            tree.id = id;
	            return;
	        }
            //如果子节点为空,则生成子节点
            if(tree.child == null && index < s.length()-1){
                Node[] child = new Node[26];
                tree.child = child;
            }
            
            int next = s.charAt(index+1)-'a';
            
            if(tree.child[next] == null) {
            	tree.child[next] = new Node();
            }
            insert(tree.child[next],s,index+1,id);
        }
        return;
    }
}

算法复杂度分析

以下分析来自leetcode官方

时间复杂度: O(n * m2)。其中n为字符串的个数,m为字符串的平均长度。遍历n个字符串,对于每一个字符串,我们需要 O(m2)地判断其所有前缀与后缀是否是回文串,并 O(m2) 地寻找其所有前缀与后缀是否在给定的字符串序列中出现。
空间复杂度: O(n * m)。其中n为字符串的个数,m为字符串的平均长度。用于字典树。

本代码结果

[leetcode每日一题2020/8/6]336. 回文对_第2张图片

你可能感兴趣的:(算法)