leetcode 572. Subtree of Another Tree

题目概述

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

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值