题目

题目

思路

该题很简单,想办法将其遍历再判断就行,不过遍历的方法有很多种。

1. 常规思路:先中后遍历

先序、中序、后序遍历是最基本的遍历,这里用先序。

    void travelTree(TreeNode* tree1, TreeNode* tree2) {
        if (tree1 == nullptr || tree2 == nullptr) {
            if (tree1 != tree2) {
                this->isPair = false;
            }
            return ;
        }

        // 先序
        if (tree1->val != tree2->val) {
            this->isPair = false;
            return ;
        }
        travelTree(tree1->left, tree2->left);
        travelTree(tree1->right, tree2->right);
    }

此处的算法复杂度是:O(min(M,N))M,N分别是树的结点数。

虽然简单,但我们应该总结下该思路。此处仅仅做了两个判断,一个是关于tree本身的,另一个是对两个结点的值进行判断的。而其他是递归的操作。

对于该题,或者是树相关的题目,我们一般思路就是递归,递归在可读性上强与循环遍历,解决问题的过程中只需要关注我们最终的目的就行了,剩下的事情就交给函数本身了。如果需要再深的分析就需要自己再多琢磨一下了。

而该代码实现中,新建了一个函数,为了展现常规的思路,我们还可以在解决问题的函数中使用递归解决本题。

2. 代码优化->深度优先搜索

深度优先搜索(DFS)是树和图中的关键遍历方法,顾名思义,就是从一个结点出发,知道到达该结点的最后,再往回走。所谓的深度优先,可以理解为一条路走到底,再去走另外一条路。在当中,就是一个分支走到底,然后再返回到另一个分支上去。

如果理解到位了,我们就可以继续写代码了:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        // 首先判空 :: 递归的返回条件
        if (p == nullptr || q == nullptr) {
            return p == q;
        }

        // 非空必有其值 :: 递归的返回条件
        // 而一旦遇到值不相等,撤
        if (p->val != q->val) {
            return false;
        }

        // 值相等,往下走
        return isSameTree(p->left, q->left) /* 左边这个支路 */ /* 由于递归,这条支路会一路到底,深度优先 */ &&
            isSameTree(p->right, q->right); /* 右边支路 */

        // 仅仅考虑需要解决的问题,p和q对应的结点存在的值是否相等,遍历交给递归去做。
    }
};

同样,此处使用了递归,只考虑核心的问题,对应节点的值是否相等,其他交给函数本身的特性。

虽然理论上与前者的时间复杂度一致,但实际执行较快。

除了深度优先,我们也听说过广度优先,那么如何实现呢?

2. 再来一次->广度优先

所谓的广度优先(BFS),在当中,就是每条支路都走。一个二叉树,有两条路,那么我左右走一遍,再回去,下一层。就是层次优先,参照完全二树标点的时候。比如一栋楼,我先走完这一层,再往上走。

实现起来比较麻烦,解决这道题,如果没有必要,广度优先是不需要使用的。

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        // 确保非空
        if (p == nullptr || q == nullptr) {
            return p == q;
        }

        // BFS
        // 需要记录每一层的结点 本处使用对列
        queue<TreeNode *> layerP, layerQ;

        layerP.push(p), layerQ.push(q);

        // 获取队列长度 记录每一层队列的长度
        auto sizeP = layerP.size(),
            sizeQ = layerQ.size();

        // 层都不空 进入循环
        while(!layerP.empty() && !layerQ.empty()) {
            // 再获取size
            sizeP = layerP.size(),
            sizeQ = layerQ.size();

            // 所在的一层,结点数不一值
            if (sizeP != sizeQ) {
                return false;
            }

            // 遍历
            while(sizeP-- && sizeQ--) {
                // 获取节点
                TreeNode* pNode = layerP.front();
                TreeNode* qNode = layerQ.front();
                // 弹出
                layerP.pop(), layerQ.pop();

                // 判断
                if (pNode->val != qNode->val) {
                    return false;
                }

                // 左右子树
                auto pLeft = pNode->left,
                    pRight = pNode->right, 
                    qLeft = qNode->left,
                    qRight = qNode->right;

                // 若下一层不等,则不需要再对比
                if (pLeft == nullptr ^ qLeft == nullptr) {
                    return false;
                }  
                if (pRight == nullptr ^ qRight == nullptr) {
                    return false;
                }

                // 输入
                // layerP 存储左子树 layerQ 存右子树 顺序不能乱
                if (pLeft != nullptr) {
                    layerP.push(pLeft);
                }
                if (pRight != nullptr) {
                    layerP.push(pRight);
                }
                if (qLeft != nullptr) {
                    layerQ.push(qLeft);
                }
                if (qRight != nullptr) {
                    layerQ.push(qRight);
                }
            }
        }

        // 已经遍历完成 确保队列中再无元素
        return layerP.empty() && layerQ.empty();
    }
};

思路很简单,就找个方法把当前层的东西记住,然后就逐个比较。

总结

对于树的比较,可以扩展到很多相对应的题目,其核心解题思路就是BFSDFS先中后遍历。有这三样,树的问题基本解决。再高的难度也不在话下了。

而找到了核心问题,将繁杂的问题化为主要解决的问题,再借助递归,可读性高且容易理解。