[C++] LeetCode 336. 回文对

题目

给定一组独特的单词, 找出在给定列表中不同 的索引对(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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值