看起来简单实际上却很牛的KMP算法:LeetCode572-另一棵树的子树
题目描述
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
暴力解法
从s的根节点开始遍历,查看该节点下的子树是否与t相同。方法是同步对s和t进行遍历,一旦出现s和t有不同(包括只有其中一个为NULL,或都不为NULL时value不同),就返回为false。如果最终返回给调用比较函数的地方是false,那么就继续为s的下一个节点重新遍历。
class Solution {
public:
bool isSameTree(TreeNode * sNow, TreeNode * tNow)
{
if(!sNow && !tNow) return true;
if((!sNow && tNow) || (sNow && !tNow) || (sNow->val != tNow->val))
return false;
//这里直接返回的是:当左子树和右子树全部相等时,就是匹配的。
//为什么没有比较本节点?因为假如本节点value不一样,就已经在上面一个if被返回false了,不会执行到最后的return
return isSameTree(sNow->left, tNow->left) && isSameTree(sNow->right, tNow->right);
}
bool dfs(TreeNode * sNow, TreeNode * tNow)
{
if(!sNow) return false;
//由于||和&&的短路特性,||后面的表达式只有前面为false才会被运行
return isSameTree(sNow, tNow) || dfs(sNow->left, tNow) || dfs(sNow->right, tNow);
}
bool isSubtree(TreeNode* s, TreeNode* t) {
bool isSub = dfs(s, t);
return isSub;
}
};
显然暴力解法的复杂度太高了。其时间复杂度为O(|s|*|t|),其中|s|和|t|是s树和t树的节点数量。空间复杂度方面,递归栈最大为O(max{ds, dt}),其中ds, dt分别是s和t的最大深度。
遍历后匹配字符串
为了解决暴力解法复杂度高的问题,一个容易想到的思路是首先对s和t前序遍历之后形成数组(字符串),然后再来进行字符串的匹配。
这样会出现一个显而易见的问题,当t是s的“一部分”而并非子树时,也就是t的“底端”比s多一些节点时,会出现匹配失误的情况。为了解决该问题,可以将叶节点左、右节点的NULL也插入到字符串中,这样可以保证匹配唯一。
遍历过后,就成为了一个字符串匹配问题。我们把较长的需要寻找子串的一个叫做文本串S(此处是s树得来的),匹配的目标叫做模式串P(此处是t树得来的)。
直接匹配
进行字符串匹配时,首先可以从暴力匹配想起。其思路是:对于S[i], P[j],如果匹配,则继续往后一位匹配。如果失配,则S退回到i = i-j+1,也就是与j开始匹配的后一位,P退回到j = 0,重新开始匹配过程。
此算法的浪费之处在于,假如失配时S[i-j+1]与S[i-j]并不相同,那么P重新从S[i-j+1]开始匹配时必然是失配的,在找到下一个匹配开始点前的操作都是重复的。
KMP算法匹配
KMP算法原理
KMP算法的核心在于利用了已经匹配过的信息。其关键在于加入了一个next数组。
next数组的意义是:next[k]表示从模式串的子串P[0]到P[k]中,相同前缀后缀的最大长度。例如:P = ABCDFABGF,那么next[6] = 2,即子串ABCDFAB的相同前缀后缀最长是AB,长度为2。
有了next数组,如果首位失配则S[i]后移(i++),如果非首位失配,则下次迭代只要从S[i]和P[next[j]]开始即可。因为在失配处的前面子串里,长度为next[j]的后缀是跟前缀一样的,这next[j]位必定是匹配的,无需再次迭代。
next数组求解
那么还剩下一个问题,next数组如何求解?
首先直观的方式是直接索引长度为1、2、3……的开头和结尾串,直到长度j-1的子串。其计算的重复性也是显而易见的。
较好的方法是使用动态规划。思考一下,当已知next[0...j]时,如何求出next[j+1]的值?
令k = next[j],即从开头算起长度为j的子串,最长的相同前缀后缀长度为k。此时有两种情况:
- p[k] = p[j]:即之前的前后缀再往后看一格,也是相同的。所以此时这个长度就增加了1,即next[j+1] = k + 1。
- p[k] ≠ p[j]:则令k = next[k],即在原本的最长相同前缀子串里再寻找它的最长相同前缀,重复第一步。意思是说,对于p[j+1],如果之前的相同前后缀再加一位是不相同的,那么再到这个前缀里去找,能不能找到?这样循环往复,直到最开始为止。
class Solution {
private:
int maxElement,lNULL, rNULL;
vector<int> sValues, tValues;
public:
void getMaxElement(TreeNode *p)
{
if(!p) return;
maxElement = max(maxElement, p->val);
getMaxElement(p->left);
getMaxElement(p->right);
}
void traverse(TreeNode * p, vector<int> & stack)
{
if(!p) return;
stack.push_back(p->val);
if(p->left == NULL)
stack.push_back(lNULL);
else
traverse(p->left, stack);
if(p->right == NULL)
stack.push_back(rNULL);
else
traverse(p->right, stack);
}
bool kmp()
{
int i = 0, j = 0;
int sLen = sValues.size(), tLen = tValues.size();
vector<int> next(tLen, 0);
//首先计算next数组
for(int k = 1; k < tLen; k++)
{
int index = k;
while(index > 0)
{
if(tValues[k] == tValues[next[index - 1]])
{
next[k] = next[index - 1] + 1;
break;
}
else
{
index = next[index - 1];
}
}
if(index <= 0) next[k] = 0;
}
//从头开始匹配字符串,失配后使用next数组重新尝试匹配
while(i < sLen && j < tLen)
{
if(j == 0)
{
while(sValues[i] != tValues[j] && i < sLen)
i++;
}
if(sValues[i] == tValues[j])
{
i++;
j++;
}
else
{
if(j > 0)
j = next[j - 1];
else
j = 0;
}
}
if(j == tLen)
return true;
return false;
}
bool isSubtree(TreeNode* s, TreeNode* t) {
maxElement = INT_MIN;
getMaxElement(s); //找出s和t中的最大值,分别+1 +2作为左右NULL值
getMaxElement(t);
lNULL = maxElement + 1;
rNULL = maxElement + 2;
traverse(s, sValues); //对s,t进行前序遍历
traverse(t, tValues);
return kmp();
}
};
看起来简单实际上却很牛的KMP算法:LeetCode572-另一棵树的子树的更多相关文章
- 听着好像很牛的特效——幽灵按钮DOM
给大家分享一个听着好像很牛的东西——幽灵按钮,这个玩意对于艺术设计细胞在高中决定不在考试试卷上画画的我来说,实在不感冒.但是这个按钮的设计元素很流行,一个网页东西不做几个,光放上几个按钮就会显得很高端 ...
- iPhone不为人知的功能常用技巧,看完后才发现很多用iPhone的人实在是愧对乔布斯! - imsoft.cnblogs
很多人花了四五千买部苹果,结果只用到四五百块钱的普通手机功能. iPhone不为人知的功能,常用技巧: 网上搜集整理的iPhone快捷键操作,虽然表面上iPhone按键只有一个HOME键,大部分操作都 ...
- 你很牛B,面试却没过,为什么?
点击标题下「飞测」可快速关注 坚持的是分享,搬运的是知识,图的是大家的进步,没有收费的培训,没有虚度的吹水,喜欢就关注.转发(免费帮助更多伙伴)等来交流,想了解的知识请留言,给你带来更多价值,是我们期 ...
- 一个很牛的计算pi的c程序!
C语言是面向过程的一种高级程序设计语言,它在世界范围内使用很广泛,而且很流行.很多大型的应用软件,基本上是用C语言所编写的.在对操作系统以及系统使用程序.需要对硬件进行操作的场合,C语言较其他的高级语 ...
- Javascript中几个看起来简单,却不一定会做的题
Javascript作为前端开发必须掌握的一门语言,因为语言的灵活性,有些知识点看起来简单,在真正遇到的时候,却不一定会直接做出来,今天我们就一起来看看几道题目吧 题目1 var val = 'smt ...
- 零元学Expression Blend 4 - Chapter 31 看如何简单的把SampleData 绑进ListBox里
原文:零元学Expression Blend 4 - Chapter 31 看如何简单的把SampleData 绑进ListBox里 前面几章连续讲到ListBox的运用,本章要讲得是如何简单的把Sa ...
- 不推荐别的了,IDEA 自带的数据库工具就很牛逼!
MySQL 等数据库客户端软件市面上非常多了,别的栈长就不介绍了, 其实 IntelliJ IDEA 自带的数据库工具就很牛逼,不信你继续往下看. 本文以 IntelliJ IDEA/ Mac 版本作 ...
- KMP算法再解 (看毛片算法真是人如其名,哦不,法如其名。)
KMP算法主要解决字符串匹配问题,其中失配数组next很关键: 看毛片算法真是人如其名,哦不,法如其名. 看了这篇博客,转载过来看一波: 原博客地址:https://blog.csdn.net/sta ...
- 很详尽KMP算法(厉害)
作者:July时间:最初写于2011年12月,2014年7月21日晚10点 全部删除重写成此文,随后的半个多月不断反复改进.后收录于新书<编程之法:面试和算法心得>第4.4节中. 1. 引 ...
- 2020牛客寒假算法基础集训营2 J题可以回顾回顾
2020牛客寒假算法基础集训营2 A.做游戏 这是个签到题. #include <cstdio> #include <cstdlib> #include <cstring ...
随机推荐
- CentOS 7.x 升级OpenSSH
升级SSH 存在中断风险,如果SSH 升级失败将会导致终端无法登录,建议在使用本地虚拟机进行测试后对线上生产环境进行升级操作!!! 三级等保评测中对主机进行漏洞扫描发现linux主机存在高危漏洞,查看 ...
- 监控elasticsearch
转载自:https://cloud.tencent.com/developer/article/1655489 注意:上半截跟下半截是采用的不同的方式,建议采用下半截的方式,上半截的方式据说获取不到数 ...
- 1_MySQL
一. 什么是数据库 概念: 数据库是按照数据结构来组织, 存储和管理数据的仓库, 是一个长期存储在计算机内的, 有组织的, 有共享的, 统一管理的数据集合 分类: 网状结构数据库: 美国通用汽车公司I ...
- MySQL实战,SQL语句
student数据库 student学生表,course课程表表,sc成绩表 -- 1.找出成绩为95分的学生的姓名 SELECT Sname FROM student WHERE Sno IN( S ...
- Docker 部署 Kibana
Docker 部署 Kibana 本篇主要介绍 使用 Docker 部署 kibana 用于操作 Elasticsearch 使用. 1. 前置准备 1.1 Elasticsearch 准备 可以先准 ...
- VS Code For Web 深入浅出 -- 进程间通信篇
在上一篇中,我们一起分析了 VS Code 整体的代码架构,了解了 VS Code 是由前后端分离的方式开发的.且无论前端是基于 electron 还是 web,后端是本地还是云端,其调用方式并无不同 ...
- MAUI 初体验 联合 WinForm 让家里废弃的手机当做电脑副品用起来
软件效果图 软件架构草图 效果解释:运行 winform 端后 使用 ctrl+c 先复制任何词语,然后ctrl+空格 就可以将翻译结果显示在 安卓,IOS,windows 甚至 mac 任意客户端 ...
- CH58X服务修改
在对ble系列应用时,很多时候拿手机充当主机.在使用ble 调试助手时常会用到write.read.notify等功能.有时可能会根据自己的需求对这些服务进行修改.下图是官方例程体现出的service ...
- 【Azure 环境】Azure 云环境对于OpenSSL 3.x 的严重漏洞(CVE-2022-3602 和 CVE-2022-3786)的处理公告
问题描述 引用报告:(OpenSSL3.x曝出严重漏洞 : https://www.ctocio.com/ccnews/37529.html ) 最近OpenSSL 3.x 爆出了严重安全漏洞,分别是 ...
- 树上启发式合并(dsu on tree)
树上启发式合并属于暴力的优化,复杂度O(nlogn) 主要解决的问题特点在于: 1.对于树上的某些信息进行查询 2.一般问题的解决不包含对树的修改,所有答案可以离线解决 算法思路:这类问题的特点在于父 ...