【Leetcode】336. Palindrome Pairs

题目地址:

https://leetcode.com/problems/palindrome-pairs/

给定一个长 n n n的字符串数组 A A A,题目保证字符串各不相同。返回所有的数对 ( i , j ) (i,j) (i,j),使得 A [ i ] + A [ j ] A[i]+A[j] A[i]+A[j]是个回文串。

我们可以枚举 A [ i ] + A [ j ] A[i]+A[j] A[i]+A[j]里较长的那个字符串(其实就是遍历 A A A的时候枚举每个 A [ i ] A[i] A[i]作为拼接里的较长的字符串,看存不存在较短的串与之拼接为回文串),如果 s = A [ i ] s=A[i] s=A[i]作为一个较长串,存在另一个串 t t t与之拼接可以是回文串,我们需要枚举两种情况:
1 1 1种情况: s + t s+t s+t是回文串,那么说明存在分界点 k k k使得 s [ k : l s − 1 ] s[k:l_s-1] s[k:ls1]是回文串,并且 s [ 0 : k − 1 ] s[0:k-1] s[0:k1] t t t镜像对称。前者可以暴力判断,后者可以用字符串哈希的方式加速,具体做法是,用“左高右低”的方式,将每个字符串的哈希值和其下标存入一个HashMap里以供查询,然后遍历 s s s的时候,从左到右遍历,用右高左低的方式求 s s s前缀的哈希值,这样就可以直接在哈希表里查是否有和 s s s前缀镜像对称的字符串了。
注意, k k k是可以取 l A l_A lA的,此时 s [ k : l A − 1 ] s[k:l_A-1] s[k:lA1]是空串,相当于 s s s整个作为左半边,右边接一个 s s s的镜像作为回文串,这是可以的,需要特判一下。
2 2 2种情况, t + s t+s t+s是回文串,那么说明存在分界点 k k k使得 s [ 0 : k ] s[0:k] s[0:k]是回文串,并且 s [ k + 1 : l s − 1 ] s[k+1:l_s-1] s[k+1:ls1] t t t镜像对称,这时要计算 s [ k + 1 : l s − 1 ] s[k+1:l_s-1] s[k+1:ls1]的哈希值,由于我们已经存了每个字符串的左高右低的哈希值,这里从右向左遍历 s s s的时候,就要算右高左低的哈希值,原理是类似的。这里也需要注意 k k k是可以取 − 1 -1 1的,对应的是 s s s整个作为右半部分,左半部分的 t t t作为 s s s的镜像接在前面得到一个回文串。
还需要注意的是,如果 s s s t t t长度相同, s + t s+t s+t是回文串的话,那么会在枚举 s s s在前和 t t t在后的时候都会枚举到 s + t s+t s+t,这会使得答案里有重复。所以在枚举到 t t t的时候我们可以直接略过与之长度相等的字符串。代码如下:

import java.util.*;

public class Solution {
    public List<List<Integer>> palindromePairs(String[] words) {
        List<List<Integer>> res = new ArrayList<>();
        // 将每个字符串的“左高右低”的哈希值及其下标存入一个哈希表里以供查询
        Map<Long, Integer> map = new HashMap<>();
        for (int i = 0; i < words.length; i++) {
            map.put(hash(words[i]), i);
        }
        
        long P = 131L;
        // 枚举长的
        for (int i = 0; i < words.length; i++) {
            String s = words[i];
            // 初始hash为0,表示空串的哈希值
            long pow = 1, hash = 0;
            // 长在前,枚举分界点k,并且考虑s[k : ]是回文串的情况,找s[0 : k - 1]镜像
            for (int k = 0; k <= s.length(); k++) {
            	// 这里计算哈希值的方式是从左到右、左低右高
                if (k >= 1) {
                    hash += s.charAt(k - 1) * pow;
                    pow *= P;
                }
                
                if (isPalin(s, k, s.length() - 1)) {
                    int idx = map.getOrDefault(hash, -1);
                    if (idx != -1 && idx != i) {
                        res.add(Arrays.asList(i, idx));
                    }
                }
            }
            
            hash = 0;
            // 长在后,枚举分界点k,并且考虑s[0 : k-1]是回文串的情况,找s[k : ]的镜像
            for (int k = s.length(); k >= 0; k--) {
                // 这里计算哈希值的方式是从右到左、左低右高
                if (k < s.length()) {
                    hash = hash * P + s.charAt(k);
                }
                
                if (isPalin(s, 0, k - 1)) {
                    int idx = map.getOrDefault(hash, -1);
                    if (idx != -1 && idx != i) {
                        if (words[idx].length() == s.length()) {
                            continue;
                        }
                        
                        res.add(Arrays.asList(idx, i));
                    }
                }
            }
        }
        
        return res;
    }
    
    private boolean isPalin(String s, int l, int r) {
        while (l < r) {
            if (s.charAt(l) != s.charAt(r)) {
                return false;
            }
            l++;
            r--;
        }
        
        return true;
    }
    
    private long hash(String s) {
        long hash = 0, P = 131L;
        for (int i = 0; i < s.length(); i++) {
            hash = hash * P + s.charAt(i);
        }
        
        return hash;
    }
}

时间复杂度 O ( n l 2 ) O(nl^2) O(nl2) l l l是最长字符串长度,空间 O ( n ) O(n) O(n)

不用字符串哈希的话会更好写一些,直接枚举 s s s的分界点 k k k,形成两个字符串 a = s [ 0 : k − 1 ] a=s[0:k-1] a=s[0:k1]还有 b = s [ k : ] b=s[k:] b=s[k:],那么 s s s在前相当于 b b b回文并且存在 a a a的镜像, s s s在后相当于 a a a回文并且存在 b b b的镜像,所以可以把每个字符串翻转后存入一个HashMap。代码如下:

import java.util.*;

public class Solution {
    public List<List<Integer>> palindromePairs(String[] words) {
        List<List<Integer>> res = new ArrayList<>();
        Map<String, Integer> map = new HashMap<>();
        for (int i = 0; i < words.length; i++) {
            map.put(new StringBuilder(words[i]).reverse().toString(), i);
        }
        
        for (int i = 0; i < words.length; i++) {
            String s = words[i];
            for (int k = 0; k <= s.length(); k++) {
                String left = s.substring(0, k), right = s.substring(k);
                if (isPalin(right) && map.containsKey(left) && map.get(left) != i) {
                    res.add(Arrays.asList(i, map.get(left)));
                }
                if (isPalin(left) && map.containsKey(right) && map.get(right) != i && words[map.get(right)].length() != s.length()) {
                    res.add(Arrays.asList(map.get(right), i));
                }
            }
        }
        
        return res;
    }
    
    private boolean isPalin(String s) {
        for (int i = 0, j = s.length() - 1; i < j; i++, j--) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
        }
        
        return true;
    }
}

时空复杂度一样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值