题目
给定一组独特的单词, 找出在给定列表中不同 的索引对(i, j),使得关联的两个单词,例如:words[i] + words[j]
形成回文。
示例 1:
给定words = ["bat", "tab", "cat"]
返回 [[0, 1], [1, 0]]
回文是["battab", "tabbat"]
示例 2:
给定 words = ["abcd", "dcba", "lls", "s", "sssll"]
返回[[0, 1], [1, 0], [3, 2], [2, 4]]
回文是 ["dcbaabcd", "abcddcba", "slls", "llssssll"]
思路解析
这道题和214 最短回文串有点像,可以转化成类似的做法,214 最短回文串的解法(https://blog.csdn.net/lv1224/article/details/81052102)。
对于该题,比如s=s1+s2
,如果s1
是回文串,那么只要保证s2
反转之后在words
中,那么就可以构成回文对,同理如果s2
是回文串,那么只要保证s1
反转之后在words
中,那么也可以构成回文对。
区别在于,214 最短回文串那题只要找出包含起始位置的最长回文串即可,而这题需要考虑包含起始位置的回文串和包含末位置的回文串,同时还要考虑字符串本省反转之后与原字符串构成回文对。但是具体做法和最短回文串方法一样。
这里有个小技巧,如果s1
是回文串,而s2
的长度不在words
的字符串长度中,那么反转之后肯定也不在words
中了。
方法一 马拉车算法
static const auto __ = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();
class Solution {
public:
void findpalindrome(int idx,vector<string> &words,vector<vector<int>> &res,unordered_map<string, int> &m,unordered_set<int> &se){
string &s=words[idx];
int ns=s.size();
string tmp(2*ns+1,'*');
for(int i=0;i<ns;i++) tmp[2*i+1]=s[i];
int pos=0,curlen=0,n=tmp.size();
vector<int> slen(n,0);
for(int i=0;i<n;i++){
int mi=2*pos-i,r=mi>=0?slen[mi]:0;
if(i<pos+curlen&&i+r<pos+curlen){
slen[i]=slen[mi];
continue;
}
if(i>=pos+curlen) r=1;
else r=pos+curlen-i;
while(i+r<tmp.size()&&i-r>=0&&(tmp[i+r]==tmp[i-r])) r++;
slen[i]=r-1;
pos=i;
curlen=r-1;
if(i-slen[i]==0&&se.find(ns-slen[i])!=se.end()){
int sr=slen[i];
string s1=s.substr(sr,ns-sr);
reverse(s1.begin(),s1.end());
if(m.find(s1)!=m.end()&&m[s1]!=idx){
res.push_back({m[s1],idx});
}
}
if(i+slen[i]==n-1&&se.find(ns-slen[i])!=se.end()){
if(i==n-1) continue; //这一行是为了避免重复,比如`abcd`和`dbca`都存在的情况下
int sr=slen[i];
string s1=s.substr(0,ns-sr);
reverse(s1.begin(),s1.end());
if(m.find(s1)!=m.end()&&m[s1]!=idx){
res.push_back({idx,m[s1]});
}
}
}
}
vector<vector<int>> palindromePairs(vector<string>& words) {
vector<vector<int>> res;
int n=words.size();
unordered_map<string, int> m;
unordered_set<int> se;
for(int i=0;i<words.size();i++){
string &s=words[i];
se.insert(s.size());
m[s]=i;
}
for(int i=0;i<words.size();i++){
findpalindrome(i,words,res,m,se);
}
return res;
}
};
方法二
这种方法就是考虑s=s1+s2
,然后考虑s1
是回文串的情况下,s2
反转是否在words
中,同理考虑s2
是回文串的情况下,s1
反转是否在words
中。注意这里s2
或者s1
并不需要考虑所有可能组合,而是只需要考虑长度在words
中
举个例子:words = ["abcd", "dcba", "lls", "s", "sssll"]
,长度有1,3,4,5
那么对于abcd
只需要考虑长度为1,3,4
的子串,对于ab
这种组合就不需考虑了,因为words
中不存在长度为2
的字符串。
static const auto __ = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();
class Solution {
public:
bool ispalindrome(string s){
int n=s.size();
for(int i=0,j=n-1;i<j;i++,j--){
if(s[i]!=s[j]) return false;
}
return true;
}
vector<vector<int>> palindromePairs(vector<string>& words) {
vector<vector<int>> res;
int n=words.size();
unordered_map<string,int> m;
set<int> se;
for(int i=0;i<n;i++){
m[words[i]]=i;
se.insert(words[i].size());
}
for(int i=0;i<n;i++){
int len=words[i].size();
string str=words[i];
//特别注意这里是有等号的(*it)<=len
for(auto it=se.begin();it!=se.end()&&(*it)<=len;it++){
string s1=str.substr(0,len-(*it)),s2=str.substr(len-(*it),*it);
if(ispalindrome(s1)){
reverse(s2.begin(),s2.end());
if(m.find(s2)!=m.end()&&m[s2]!=i){
res.push_back({m[s2],i});
}
}
}
//特别注意这里是没有等号的(*it)<len,这里是为了避免重复,否则`abcd`和`dbca`会发生重复
for(auto it=se.begin();it!=se.end()&&(*it)<len;it++){
string s1=str.substr(*it,len-(*it)),s2=str.substr(0,*it);
if(ispalindrome(s1)){
reverse(s2.begin(),s2.end());
if(m.find(s2)!=m.end()&&m[s2]!=i){
res.push_back({i,m[s2]});
}
}
}
}
return res;
}
};