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;
}