Leetcode 336.回文对 c++ 字典树

Leetcode 336.回文对 c++ 字典树

题目在这https://leetcode-cn.com/problems/palindrome-pairs/
大意是给你给你一连串互相不一样(unique)的字符串,问哪两个字符串前后拼接在一起会是回文串(不能自己拼接自己)
思路是用trie树(字典树)。
不会trie树的去百度一下OTZ 很简单的就是一个储存字符串的结构

trie树部分代码

trie树的结构如下 注意节点的end默认为-1 如果不为-1就表示某个反串在这个节点结尾

struct node
{
    char c;  //当前节点字符
    int end; //当前节点是哪个串的结尾
    node *next[26];
};

这样创建新的节点

node *CreateNew(char c)//创建新的节点
{
    node *p = new node();
    p->c = c;
    p->end = -1;
    for (int i = 0; i < 26; i++)
        p->next[i] = NULL;
    return p;
}

字符串反向加入树

void add(string &ss, node *head, int pos) //字符串反向插入树
{
    int len = ss.length();
    node *p = head;
    for (int i = len - 1; i >= 0; i--)
    {
        if (p->next[ss[i] - 'a'] == NULL)//如果创建字典树时当前节点的下一层节点不存在的话创造 不然不用创造直接往下走
            p->next[ss[i] - 'a'] = CreateNew(ss[i]);
        p = p->next[ss[i] - 'a'];
    }
    p->end = pos;//记录当前结尾节点是哪个字符串的结尾
    return;
}

递归清空字典树

void destroy(node *head)//递归清空字典树
{
    for (int i = 0; i < 26; i++)
    {
        if (head->next[i])
            destroy(head->next[i]);
    }
    delete head;
    return;
}

先分类讨论。

1.a,b串都不存在空串。

如果a,b个串拼接起来可以成回文串,那么a的前缀一定和b串反过来(比如b串是abcd,那么b串反过来就是dcba)有公共前缀,所以用字典树,插入每个非空字符串反过来的串,再遍历每个串,遍历每个串的每个字符同时匹配trie树往下走。如果走到了一个trie树的节点,这个节点的end标记不为-1,就表示有一个串在这个节点结尾,也就是匹配到了b串,能走到这个位置就意味着a串的长度肯定>=b串的长度,这时候看a串剩余部分是否为回文串,如果剩余部分是回文串或者空串就意味着a+b串是回文串,加入结果。如果a串已经匹配完,但是在字典树中没有走到根节点,那么可能匹配上的b串长度比a串长度要长,这时候从a串匹配完的trie树节点开始dfs(深度优先搜索),寻找这个节点到所有的单词结尾所组成的串有哪些是回文串,就意味着a+b是回文串。

for (int i = 0; i < words.size(); i++) //遍历每个单词
    {
        if(i==nullString) continue;//空串额外讨论
        node *p = head;
        bool complete = true; //判断单词是否走完
        int len = words[i].size();
        for (int j = 0; j < len; j++)//对于这个单词 开始走字典树
        {
            if (p->next[words[i][j] - 'a'] == NULL) //字典树中没有匹配的 就是这个单词没走完
            {
                complete = false;
                break;
            }
            p = p->next[words[i][j] - 'a'];//有匹配的 往下走
            if (p->end != -1 && p->end != i) //这个节点是字典树某个反向单词的结尾 并且这个节点不是自己
            {
                //判断单词words[i]从位置[j+1]到结尾是否回文
                if (words[i].length() - 1 == j) //如果是空串 说明这两个刚好互补匹配上
                {
                    ret.push_back({i, p->end});
                }
                else //不是空串 判断单词words[i]从位置[j+1]到结尾是否回文
                {
                    bool flag = true;
                    for (int k = 0; k < (len - 1 - j) / 2; k++)
                    {
                        if (words[i][j + 1 + k] != words[i][len - 1 - k])
                        {
                            flag = false;
                            break;
                        }
                    }
                    if (flag) //words[i]从位置[j+1]到结尾是回文 加入答案
                    {
                        ret.push_back({i, p->end});
                    }
                }
            }
        }
        if (complete) //当前单词走完了 但是字典树不一定走完
        {
            string ss;
            ss.clear();
            solve(i, p, ss);
        }
    }

solve函数是用来dfs解决从一个节点出发遍历,寻找这个节点到所有的单词结尾所组成的串。

void solve(int ch_int /*当前哪个字符*/, node *curr /*当前到了哪个节点*/, string &ss)//solve函数的意义在于递归遍历剩下节点组成的树有几个回文串
{
    if ((curr->end != -1 && ss.size() != 0))//如果当前节点的end标记不为-1 就表示有单词在这个节点结束 并且不能重复输出 所以ss的size不能为0 空串额外讨论
    {
        if (is_palindrome(ss))
        {
            ret.push_back({ch_int, curr->end});
        }
    }
    for (int i = 0; i < 26; i++)//递归遍历剩余子树 其实就是dfs
    {
        if (curr->next[i])
        {
            ss.push_back((char)('0' + i));
            solve(ch_int, curr->next[i], ss);
            ss.pop_back();
        }
    }
    return;
}

然后is_palindrome函数就是用来快速判断一个串是否是回文串

bool is_palindrome(string &ss)//判断是否回文
{
    int len = ss.length();
    for (int i = 0; i < (0 + len) / 2; i++)
    {
        if (ss[i] != ss[len - 1 - i])
            return false;
    }
    return true;
}
2.存在空串
int nullString=-1
for (int i = 0; i < words.size(); i++)
    {
        if(words[i]=="")//空串额外讨论
        {
            nullString=i;
            continue;
        }
        add(words[i], head, i); //倒过来加入字典树
    }

直接遍历一遍所有的串(不包括空串,因为自己不能和自己串在一起)如果有一个串是回文串,那么空串+回文串或者回文串+空串也是回文串,加入结果

if(nullString!=-1)//空串额外讨论
    {
        for(int i=0;i<words.size();i++)
        {
            if(i==nullString) continue;
            if(is_palindrome(words[i]))
            {
                ret.push_back({nullString,i});
                ret.push_back({i,nullString});
            }
        }
    }

完整的代码如下

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;
vector<vector<int>> ret; //返回的vector
struct node
{
    char c;  //当前节点字符
    int end; //当前节点是哪个串的结尾
    node *next[26];
};
node *CreateNew(char c)//创建新的节点
{
    node *p = new node();
    p->c = c;
    p->end = -1;
    for (int i = 0; i < 26; i++)
        p->next[i] = NULL;
    return p;
}
void add(string &ss, node *head, int pos) //字符串反向插入树
{
    int len = ss.length();
    node *p = head;
    for (int i = len - 1; i >= 0; i--)
    {
        if (p->next[ss[i] - 'a'] == NULL)//如果创建字典树时当前节点的下一层节点不存在的话创造 不然不用创造直接往下走
            p->next[ss[i] - 'a'] = CreateNew(ss[i]);
        p = p->next[ss[i] - 'a'];
    }
    p->end = pos;//记录当前结尾节点是哪个字符串的结尾
    return;
}
void destroy(node *head)//递归清空字典树
{
    for (int i = 0; i < 26; i++)
    {
        if (head->next[i])
            destroy(head->next[i]);
    }
    delete head;
    return;
}
bool is_palindrome(string &ss)//判断是否回文
{
    int len = ss.length();
    for (int i = 0; i < (0 + len) / 2; i++)
    {
        if (ss[i] != ss[len - 1 - i])
            return false;
    }
    return true;
}
void solve(int ch_int /*当前哪个字符*/, node *curr /*当前到了哪个节点*/, string &ss)//solve函数的意义在于递归遍历剩下节点组成的树有几个回文串
{
    if ((curr->end != -1 && ss.size() != 0))//如果当前节点的end标记不为-1 就表示有单词在这个节点结束 并且不能重复输出 所以ss的size不能为0 空串额外讨论
    {
        if (is_palindrome(ss))
        {
            ret.push_back({ch_int, curr->end});
        }
    }
    for (int i = 0; i < 26; i++)//递归遍历剩余子树 其实就是dfs
    {
        if (curr->next[i])
        {
            ss.push_back((char)('0' + i));
            solve(ch_int, curr->next[i], ss);
            ss.pop_back();
        }
    }
    return;
}
vector<vector<int>> palindromePairs(vector<string> &words)
{
    node *head = CreateNew(' ');//创建一个根节点
    int nullString=-1;
    for (int i = 0; i < words.size(); i++)
    {
        if(words[i]=="")//空串额外讨论
        {
            nullString=i;
            continue;
        }
        add(words[i], head, i); //倒过来加入字典树
    }
    ret.clear();
    for (int i = 0; i < words.size(); i++) //遍历每个单词
    {
        if(i==nullString) continue;//空串额外讨论
        node *p = head;
        bool complete = true; //判断单词是否走完
        int len = words[i].size();
        for (int j = 0; j < len; j++)//对于这个单词 开始走字典树
        {
            if (p->next[words[i][j] - 'a'] == NULL) //字典树中没有匹配的 就是这个单词没走完
            {
                complete = false;
                break;
            }
            p = p->next[words[i][j] - 'a'];//有匹配的 往下走
            if (p->end != -1 && p->end != i) //这个节点是字典树某个反向单词的结尾 并且这个节点不是自己
            {
                //判断单词words[i]从位置[j+1]到结尾是否回文
                if (words[i].length() - 1 == j) //如果是空串 说明这两个刚好互补匹配上
                {
                    ret.push_back({i, p->end});
                }
                else //不是空串 判断单词words[i]从位置[j+1]到结尾是否回文
                {
                    bool flag = true;
                    for (int k = 0; k < (len - 1 - j) / 2; k++)
                    {
                        if (words[i][j + 1 + k] != words[i][len - 1 - k])
                        {
                            flag = false;
                            break;
                        }
                    }
                    if (flag) //words[i]从位置[j+1]到结尾是回文 加入答案
                    {
                        ret.push_back({i, p->end});
                    }
                }
            }
        }
        if (complete) //当前单词走完了 但是字典树不一定走完
        {
            string ss;
            ss.clear();
            solve(i, p, ss);
        }
    }
    if(nullString!=-1)//空串额外讨论
    {
        for(int i=0;i<words.size();i++)
        {
            if(i==nullString) continue;
            if(is_palindrome(words[i]))
            {
                ret.push_back({nullString,i});
                ret.push_back({i,nullString});
            }
        }
    }
    destroy(head);//清空字典树
    return ret;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值