题目概述
Leetcode版本:
牛客版(左程云版):
解题思路
首先我们理解一下题意:题目的大意是说,给定两个二叉树t1和t2,判断t2是否是t1的子树。
Leetcode版对于子树的定义比较严格,它认为子树是包含了NULL节点在内的;而牛客网的版本则只考虑到非NULL节点。
我们先考虑一下,如何暴力地求解。直观的做法就是遍历t1和t2这两个树的节点,只要它俩根节点的值相等,就开始遍历接下来的整个树。这样的时间复杂度是O(M * N)。
接下来我们考虑一下,有没有更快的求解策略呢?
对于Leetcode版的题目来说,有一种比较容易想到的思路,就是把字符串直接转换成一个字符串,因为关于判断子字符串/数组的算法是有很多线性复杂度的优化方案的(比如KMP算法)。
这里我们可以采用序列化二叉树,使之变为一个字符串的方式:先序遍历一棵树,并令每个叶结点的子节点NULL的值为#,转化成字符串时,每个节点的值写在前面,后跟一个感叹号“!”。
例如Example1:
root:
3!4!1!#!#!2!#!#!5!#!#!
subroot:
4!1!#!#!2!#!#!
subroot = root[2:15]
root:
3!4!1!#!#!2!0!#!#!#!5!#!#!
subroot:
4!1!#!#!2!#!#!
subroot不是root的子串了。
此时,算法的整体复杂度为O(M+N)。
下面考虑一下牛客网的这种情况,能否给出线性复杂度的方法呢?
首先我们发现,之前这种线性复杂度的做法不再奏效了。因为Example 2在牛客网的题目定义下,是符合要求的。可是,如果用不上寻找子串的方法,改成寻找子序列的方法,那复杂度就上O(M * N)了(相当于有N个指针,每个指针指向t2字符串的每个位置,找到第一个匹配的子序列以后,从后往前,从左往右移动指针去寻找新的匹配的子序列,复杂度为O(M * N))。这种方法的复杂度就和直接比较节点没什么区别了。
方法性能
先看一下O(M * N)时间复杂度方法的性能:
我只想说很离谱哇。。。这还优化个锤子_(:з」∠)_
下面是O(M + N)时间复杂度方法的性能:
示例代码
1. O(M * N)时间复杂度的版本:
class Solution {
public:
bool isEq(TreeNode* A, TreeNode* B)
{
if(A == NULL && B == NULL)
return true;
else if(A != NULL && B != NULL)
return (A->val == B->val) && isEq(A->left, B->left) && isEq(A->right, B->right);
return false;
}
bool isSubtree(TreeNode* root, TreeNode* subRoot)
{
if(root == NULL)
return false;
bool res = false;
res |= isEq(root, subRoot);
res |= isSubtree(root->left, subRoot);
res |= isSubtree(root->right, subRoot);
return res;
}
};
2. O(M + N)时间复杂度的版本(leetcode 28 KMP算法 + leetcode 297序列化字符串):
这里有一个点需要注意,就是我们对于单个节点的情况需要重新考虑一下。因为树t1/t2只有单个节点的时候,很容易出现Example 4这种情况:这时subroot并不是root的子串,但是如果按照之前的序列化字符串的做法,我们会把subroot误判为root的子串。一种解决方法是在序列化的时候,在根节点对应的元素之前加上一个特殊符号,比如“!”。
Example 4:
root: 123!#!#!
123
subroot:23!#!#!
23
class Solution {
public:
int strStr(string haystack, string needle)
{
int hay_len = haystack.length(), nee_len = needle.length(), i = 0, j = 0;
if (hay_len < nee_len)
return -1;
if (hay_len == 0 && nee_len == 0)
return 0;
if (hay_len != 0 && nee_len == 0)
return 0;
int *next = new int[nee_len + 1];
next[1] = 0;
int p = 0, q = 1;
while (q < nee_len)
{
if (p == 0 || (p >= 1 && q >= 1 && needle[p-1] == needle[q-1]))
{
++p;
++q;
next[q] = p;
}
else
p = next[p];
}
while (i <= hay_len && j <= nee_len)
{
if (j == 0 || haystack[i - 1] == needle[j - 1])
{
++i;
++j;
}
else
j = next[j];
}
return j - 1 == nee_len ? (i-nee_len-1) : -1;
}
string cvtPreorderStr(TreeNode* root)
{
stack<TreeNode*> nodes;
nodes.push(root);
TreeNode *heads;
string res = "";
while(!nodes.empty())
{
heads = nodes.top();
nodes.pop();
if (heads == NULL)
res += '#';
else
{
res += to_string(heads->val);
nodes.push(heads->left);
nodes.push(heads->right);
}
res += '!';
}
return res;
}
bool isSubtree(TreeNode* root, TreeNode* subRoot)
{
string rs = '!' + cvtPreorderStr(root);
string ss = '!' + cvtPreorderStr(subRoot);
return strStr(rs, ss) != -1;
}
};