题目来源于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"]
第一眼看到,就想到是暴力解法。
结果就是超时了。
粗略计算,算法复杂度为O(n2*s)。n为字符串个数,s为字符串平均长度。因此需要换个思路,想想有没有更好的解法。
由于我想不到解法,这里就直接放官方攻略。
简单的概括几点我自己的理解。
我这里字典树的结构定义的不是很好,导致插入串,和查找串写出来的代码不够优雅。仅供参考。
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为字符串的平均长度。用于字典树。