【努力刷力扣】第十三天 --- 另一个树的子树解法二(KMP)
引言:
本人初次尝试写博客,希望各位看官大佬多多包容
有错误希望巨巨们提出来,我一定会及时改正,谢谢大家
在自己最好的年纪,找到了未来的目标
还有1年奋斗刷题,明年去面试实习,加油!
老样子,先看看题目要求:
DFS求先跟序列,再KMP匹配
定策略
1、我们想看T树在不在S树,我现在对树没有办法,不知道咋判断,所以:我们要把树给转化,把树换一种形式,再去比对。
2、树中的点在先根遍历中得到的序列是连续的,我们不妨把先把两棵树先转化成先跟序列,即转化成了两个串,如果T树的串在S树的串,那么为true。
分析过程:
First — 求先跟序列
先跟序列的求法自不必说,但是这里有一个注意的地方,就是比如 4为根节点,5 作为左儿子,先跟序列为45,如果5为右儿子,则先跟序列依然为45,所以必须要加以区分,故插入无意义节点。
Second — KMP
KMP才是这个算法的精髓,我得到两个串之后,利用KMP看T树能不能匹配的上。
这是第一点,利用两个串的比较巧妙地转移了问题。
第二点就是重中之重的KMP书写了。
首先我们应该写出来next数组,方法如下图:
next数组其实本质上就是两个相等的串进行匹配,成功向前走,失败之后回溯。
下一个进入KMP书写:如果next数组书写毫无问题,那么KMP其实就是把两个相同的串换成了不同的串,和next极为相像(next为相同的俩串),具体原理就是成功匹配就继续向下走,直到小串长度-1为止,不成功的分析和我上图一致,稍微有点不同在于这次找家长(回溯)了,家长想的是如果我后一个能和主串哪一个点的值匹配上了,就可以从那个点开始继续向下了,就可以实现一次向后走多个格子的目标了,而不是小串一次只向前走一步了。若k=-1了,小串就随着大串的指针走了。
代码如下:
class Solution {
public:
vector<int> target_one;//主串先序队列
vector<int> target_two;//副串先序队列
int LNULL = 1 << 20;//插入的无意义点的
int RNULL = 1 << 20;//插入的无意义点
bool isSubtree(TreeNode* s, TreeNode* t) {
find_path(s);
find_path_ano(t);
return KMP();
}
//以下开始KMP操作 ,这里的K值因为需要总比用于数组寻址时少一
vector<int> next(vector<int> item) {//这个是自己匹配自己,去看以每一个节点为结束的串前后缀多长
vector<int> temp(item.size(), -1);//存next数组
int len = item.size();//先求出长度,后面直接调用这个,用于加速
int k = -1;
for (int i = 1; i < len; i++) {
//对每一个节点求一个next值,代表
while (k > -1 && item[i] != item[k + 1]) {//这里k=-1,能匹配上就k++不行就直接把k值给出即可,没法回溯了
//因为匹配不上了,所以必须回溯了
k = temp[k];//匹配不上找家长
}
if (item[i] == item[k + 1]) {
k++;//匹配上了前缀结束位置加一
}
temp[i] = k;//记录以每个点结尾的字串他们前缀的结束位置,便于next 和KMP中找家长回溯,因为回溯找家长就是找上一次前缀结束位置
}
return temp;
}
bool KMP() {
vector<int> next0 = next(target_two);
int k = -1;
int len_s = target_one.size();//长度提前求便于加速
int len_t = target_two.size();
for (int i = 0; i < len_s; i++) {
while (k > -1 && target_one[i] != target_two[k + 1]) {
k = next0[k];//回溯找家长
}
if (target_one[i] == target_two[k + 1]) {
k++;//成功记一下长度
}
if (k == len_t - 1) {//达到目标长度即可
return true;
}
}
return false;
}
void find_path(TreeNode* item) {//很简单,注意递归时函数名别写错了,为空插入无意义点
target_one.push_back(item->val);
if (item->left != nullptr) {
find_path(item->left);
}
else {
target_one.push_back(LNULL);
}
if (item->right != nullptr) {
find_path(item->right);
}
else {
target_one.push_back(RNULL);
}
}
void find_path_ano(TreeNode* item) {
target_two.push_back(item->val);
if (item->left != nullptr) {
find_path_ano(item->left);
}
else {
target_two.push_back(LNULL);
}
if (item->right != nullptr) {
find_path_ano(item->right);
}
else {
target_two.push_back(RNULL);
}
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
1、经计算,该算法时间复杂度为O(∣s∣+∣t∣)
2、一定要时刻想着去转换,原来两个树之间看一个在另一个里面有没有,完全可以用同一种方法把二者转换成另一种形式在进行比较,把复杂的问题套以已知的模型,再注重一下特殊情况的处理