DAY15 二叉树2
1.层序遍历 *10
学会二叉树的层序遍历,可以一口气打完以下十题:
-
102.二叉树的层序遍历
-
107.二叉树的层次遍历II
-
199.二叉树的右视图
-
637.二叉树的层平均值
-
429.N叉树的层序遍历
-
515.在每个树行中找最大值
-
116.填充每个节点的下一个右侧节点指针
-
117.填充每个节点的下一个右侧节点指针II
-
104.二叉树的最大深度
-
111.二叉树的最小深度
102二叉树层序遍历
题目链接:102. 二叉树的层序遍历
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。——这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。
使用队列实现二叉树广度优先遍历,动画如下:
这样就实现了层序从左到右遍历二叉树。
代码如下:这份代码也可以作为二叉树层序遍历的模板,打十个就靠它了。
C++代码:
## 使用队列 class Solution { public: vector<vector<int>> levelOrder(TreeNode* root) { //数据结构的准备 queue<TreeNode*> que;//指针队列!不是数值的队列! if (root != NULL) que.push(root);//初始化是就要把根节点先入队列 vector<vector<int>> result; //开始操作 while (!que.empty()) { int size = que.size(); vector<int> vec;//存储树的每一层的元素的临时数组 // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的 for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); vec.push_back(node->val); if (node->left) que.push(node->left); if (node->right) que.push(node->right); } result.push_back(vec);//将存储每一层结点值的数组汇总起来,放到result数组中。 } return result; } };
### 递归法———不需要队列 class Solution { public: void order(TreeNode* cur, vector<vector<int>>& result, int depth) { if (cur == nullptr) return;//递归结束的条件,当cur->left/right一直往下走,最后一定为到cur==nullptr的情况,然后就直接return,执行归过程。 if (result.size() == depth) result.push_back(vector<int>());//这行代码有点巧妙 result[depth].push_back(cur->val); //下面两个连在一起的order的depth是一样的,所以他们对应的树结点值会被放在result数组中的同一数组中。 order(cur->left, result, depth + 1); order(cur->right, result, depth + 1); } vector<vector<int>> levelOrder(TreeNode* root) { vector<vector<int>> result;//result的大小这时为0 int depth = 0; order(root, result, depth);//递归 return result; } };
107.二叉树的层次遍历 II
题目链接:Loading Question... - 力扣(LeetCode)
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
思路:
相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。
C++代码:
class Solution { public: vector<vector<int>> levelOrderBottom(TreeNode* root) { queue<TreeNode*> que; if (root != NULL) que.push(root); vector<vector<int>> result; while (!que.empty()) { int size = que.size(); vector<int> vec; for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); vec.push_back(node->val); if (node->left) que.push(node->left); if (node->right) que.push(node->right); } result.push_back(vec); } reverse(result.begin(), result.end()); // 在这里反转一下数组即可 return result; } };
199.二叉树的右视图
题目链接:Loading Question... - 力扣(LeetCode)
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
思路:
层序遍历的时候,判断是否遍历到每一层的最后面的元素。如果是,就放进result数组中,随后返回result就可以了。
class Solution { public: vector<int> rightSideView(TreeNode* root) { queue<TreeNode*> que; if (root != NULL) que.push(root); vector<int> result; while (!que.empty()) { int size = que.size(); for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); if (i == (size - 1)) result.push_back(node->val); // 将每一层的最后元素放入result数组中 if (node->left) que.push(node->left); if (node->right) que.push(node->right); } } return result; } };
637.二叉树的层平均值
题目:637. 二叉树的层平均值 - 力扣(LeetCode)
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
思路:
本题就是层序遍历的时候把一层求个总和在取一个均值
class Solution { public: vector<double> averageOfLevels(TreeNode* root) { queue<TreeNode*> que; if (root != NULL) que.push(root); vector<double> result; while (!que.empty()) { int size = que.size(); double sum = 0; // 统计每一层的和 for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); sum += node->val; if (node->left) que.push(node->left); if (node->right) que.push(node->right); } result.push_back(sum / size); // 将每一层均值放进结果集 } return result; } };
429.N叉树的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
例如,给定一个 3叉树 :
返回其层序遍历:
[ [1], [3,2,4], [5,6] ]
思路:
这道题依旧是模板题,只不过一个节点有多个孩子了
C++代码:
class Solution { public: vector<vector<int>> levelOrder(Node* root) { queue<Node*> que; if (root != NULL) que.push(root); vector<vector<int>> result; while (!que.empty()) { int size = que.size(); vector<int> vec; for (int i = 0; i < size; i++) { Node* node = que.front(); que.pop(); vec.push_back(node->val); // 将node节点的全部孩子加入队列 for (int i = 0; i < node->children.size(); i++) { if (node->children[i]) {que.push(node->children[i]);} } } result.push_back(vec); } return result; } };
515.在每个树行中找最大值
您需要在二叉树的每一行中找到最大的值。
思路:
层序遍历,取每一层的最大值
C++代码:
class Solution { public: vector<int> largestValues(TreeNode* root) { queue<TreeNode*> que; if (root != NULL) que.push(root); vector<int> result; //时刻注意我们这里有2个数据结构,一个队列一个数组 while (!que.empty()) { int size = que.size(); int maxValue = INT_MIN; // 取每一层的最大值 for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); maxValue = node->val > maxValue ? node->val : maxValue; if (node->left) que.push(node->left); if (node->right) que.push(node->right); } result.push_back(maxValue); // 把最大值放进数组 } return result; } };
116.填充每个节点的下一个右侧节点指针
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node { int val; Node *left; Node *right; Node *next; }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
思路:本题依然是层序遍历,只不过在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了
C++代码:
class Solution { public: Node* connect(Node* root) { queue<Node*> que; if (root != NULL) que.push(root); //不需要返回结果集 while (!que.empty()) { int size = que.size(); // vector<int> vec; Node* nodePre; Node* node; for (int i = 0; i < size; i++) { if (i == 0) { nodePre = que.front();// 取出一层的头结点 que.pop(); node = nodePre; } else { node = que.front(); que.pop(); nodePre->next = node; // 本层前一个节点next指向本节点 nodePre = nodePre->next; } if (node->left) que.push(node->left); if (node->right) que.push(node->right); } nodePre->next = NULL; // 本层最后一个节点指向NULL } return root; } };
117.填充每个节点的下一个右侧节点指针II
思路:
与116题目不同之处在于这道题目说是二叉树;虽然116题目说是完整二叉树,但其实没有任何差别,一样的代码一样的逻辑一样的味道
C++代码:
class Solution { public: Node* connect(Node* root) { queue<Node*> que; if (root != NULL) que.push(root); while (!que.empty()) { int size = que.size(); vector<int> vec; Node* nodePre; Node* node; for (int i = 0; i < size; i++) { if (i == 0) { nodePre = que.front(); // 取出一层的头结点 que.pop(); node = nodePre; } else { node = que.front(); que.pop(); nodePre->next = node; // 本层前一个节点next指向本节点 nodePre = nodePre->next; } if (node->left) que.push(node->left); if (node->right) que.push(node->right); } nodePre->next = NULL; // 本层最后一个节点指向NULL } return root; } };
104.二叉树的最大深度
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。
思路:
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示:
所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。
总之不过就是再原来的层序遍历的代码里面多加了一个计数器depth!
C++代码如下:
class Solution { public: int maxDepth(TreeNode* root) { if (root == NULL) return 0; int depth = 0; queue<TreeNode*> que; que.push(root); while(!que.empty()) { int size = que.size(); depth++; // 记录深度 for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); if (node->left) que.push(node->left); if (node->right) que.push(node->right); } } return depth; } };
111.二叉树的最小深度
相对于 104.二叉树的最大深度 ,本题还也可以使用层序遍历的方式来解决,思路是一样的。
需要注意的是,当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点
代码如下:(详细注释)
class Solution { public: int minDepth(TreeNode* root) { if (root == NULL) return 0; int depth = 0; queue<TreeNode*> que; que.push(root); while(!que.empty()) { int size = que.size(); depth++; // 记录最小深度 for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); if (node->left) que.push(node->left); if (node->right) que.push(node->right); if (!node->left && !node->right) { // 当左右孩子都为空的时候,说明是最低点的一层了,退出 return depth; } } } return depth; } };
拓展
直觉上好像和求最大深度差不多,其实还是差不少的。
本题依然是前序遍历和后序遍历都可以,前序求的是深度,后序求的是高度。
-
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
-
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。
以下讲解中遍历顺序上依然采用后序遍历(因为要比较递归返回之后的结果,本文我也给出前序遍历的写法)。
本题还有一个误区,在处理节点的过程中,最大深度很容易理解,最小深度就不那么好理解,如图:
这就重新审题了,题目中说的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。,注意是叶子节点。
什么是叶子节点,左右孩子都为空的节点才是叶子节点!
(1)递归法
递归三部曲:
1.确定递归函数的参数和返回值
参数为要传入的二叉树根节点,返回的是int类型的深度。
代码如下:
int getDepth(TreeNode* node)
2.确定终止条件
终止条件也是遇到空节点返回0,表示当前节点的高度为0。
代码如下:
if (node == NULL) return 0;
3.确定单层递归的逻辑
这块和求最大深度可就不一样了,一些同学可能会写如下代码:
int leftDepth = getDepth(node->left); int rightDepth = getDepth(node->right); int result = 1 + min(leftDepth, rightDepth); return result;
这个代码就犯了此图中的误区:
如果这么求的话,没有左孩子的分支会算为最短深度。
所以,如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。
反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。 最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。
代码如下:
int leftDepth = getDepth(node->left); // 左 int rightDepth = getDepth(node->right); // 右 // 中 // 当一个左子树为空,右不为空,这时并不是最低点 if (node->left == NULL && node->right != NULL) { return 1 + rightDepth; } // 当一个右子树为空,左不为空,这时并不是最低点 if (node->left != NULL && node->right == NULL) { return 1 + leftDepth; } int result = 1 + min(leftDepth, rightDepth); return result;
遍历的顺序为后序(左右中),可以看出:求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。
整体递归代码如下:
class Solution { public: int getDepth(TreeNode* node) { if (node == NULL) return 0; int leftDepth = getDepth(node->left); // 左 int rightDepth = getDepth(node->right); // 右 // 中 // 当一个左子树为空,右不为空,这时并不是最低点 if (node->left == NULL && node->right != NULL) { return 1 + rightDepth; } // 当一个右子树为空,左不为空,这时并不是最低点 if (node->left != NULL && node->right == NULL) { return 1 + leftDepth; } int result = 1 + min(leftDepth, rightDepth); return result; } int minDepth(TreeNode* root) { return getDepth(root); } };
精简之后代码如下:
class Solution { public: int minDepth(TreeNode* root) { if (root == NULL) return 0; if (root->left == NULL && root->right != NULL) { return 1 + minDepth(root->right); } if (root->left != NULL && root->right == NULL) { return 1 + minDepth(root->left); } return 1 + min(minDepth(root->left), minDepth(root->right)); } };
精简之后的代码根本看不出是哪种遍历方式,所以依然还要强调一波:如果对二叉树的操作还不熟练,尽量不要直接照着精简代码来学。
前序遍历的方式:
class Solution { private: int result; void getdepth(TreeNode* node, int depth) { // 函数递归终止条件 if (root == nullptr) { return; } // 中,处理逻辑:判断是不是叶子结点 if (root -> left == nullptr && root->right == nullptr) { res = min(res, depth); } if (node->left) { // 左 getdepth(node->left, depth + 1); } if (node->right) { // 右 getdepth(node->right, depth + 1); } return ; } public: int minDepth(TreeNode* root) { if (root == nullptr) { return 0; } result = INT_MAX; getdepth(root, 1); return result; } };
(2)迭代法
相对于104.二叉树的最大深度 (opens new window),本题还可以使用层序遍历的方式来解决,思路是一样的。
如果对层序遍历还不清楚的话,可以看这篇:二叉树:层序遍历登场!(opens new window)
需要注意的是,只有当左右孩子都为空的时候,才说明遍历到最低点了。如果其中一个孩子不为空则不是最低点
代码如下:(详细注释)
class Solution { public: int minDepth(TreeNode* root) { if (root == NULL) return 0; int depth = 0; queue<TreeNode*> que; que.push(root); while(!que.empty()) { int size = que.size(); depth++; // 记录最小深度 for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); if (node->left) que.push(node->left); if (node->right) que.push(node->right); if (!node->left && !node->right) { // 当左右孩子都为空的时候,说明是最低点的一层了,退出 return depth; } } } return depth; } };
2.226-翻转二叉树
翻转一棵二叉树。
这道题目背后有一个让程序员心酸的故事,听说 Homebrew的作者Max Howell,就是因为没在白板上写出翻转二叉树,最后被Google拒绝了。
我的思路:我觉得用“层序遍历+翻转一个结点的左右孩子”就能做出来——不过我们一开始以为要记录迭代前的结点指针和迭代后的结点指针,然后再把他们swap;但是没有那么复杂,反而直接对一个结点的左右孩子直接使用swap就可以实现翻转的目的了。
思路
《代码随想录》算法视频公开课:听说一位巨佬面Google被拒了,因为没写出翻转二叉树 | LeetCode:226.翻转二叉树 (opens new window),相信结合视频在看本篇题解,更有助于大家对本题的理解。
想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。(和我的想法一样)
关键在于遍历顺序,前中后序应该选哪一种遍历顺序? (一些同学这道题都过了,但是不知道自己用的是什么顺序)
遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果。
注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!建议拿纸画一画,就理解了
那么层序遍历可以不可以呢?依然可以的!只要把每一个节点的左右孩子翻转一下的遍历方式都是可以的!
递归法
对于二叉树的递归法的前中后序遍历,已经在二叉树:前中后序递归遍历 (opens new window)详细讲解了。
我们下文以前序遍历为例,通过动画来看一下翻转的过程:
我们来看一下递归三部曲:
1.确定递归函数的参数和返回值
参数就是要传入节点的指针,不需要其他参数了,通常此时定下来主要参数,如果在写递归的逻辑中发现还需要其他参数的时候,随时补充。
返回值的话其实也不需要,但是题目中给出的要返回root节点的指针,可以直接使用题目定义好的函数,所以就函数的返回类型为TreeNode*
。
TreeNode* invertTree(TreeNode* root)
2.确定终止条件
当前节点为空的时候,就返回
if (root == NULL) return root;
3.确定单层递归的逻辑
因为是先前序遍历,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。
swap(root->left, root->right); invertTree(root->left); invertTree(root->right);
基于这递归三步法,代码基本写完,C++代码如下:
class Solution { public: TreeNode* invertTree(TreeNode* root) { if (root == NULL) return root; swap(root->left, root->right); // 中 invertTree(root->left); // 左 invertTree(root->right); // 右 return root; } };
迭代法
1.深度优先遍历
二叉树:听说递归能做的,栈也能做! (opens new window)中给出了前中后序迭代方式的写法,所以本题可以很轻松的写出如下迭代法的代码:
C++代码迭代法(前序遍历)
class Solution { public: TreeNode* invertTree(TreeNode* root) { if (root == NULL) return root; stack<TreeNode*> st; st.push(root); while(!st.empty()) { TreeNode* node = st.top(); // 中 st.pop(); //因为不需要返回遍历结果,只需要进行翻转,所以不需要像传统的前序遍历迭代法那样将node->value入栈 swap(node->left, node->right); if(node->right) st.push(node->right); // 右 if(node->left) st.push(node->left); // 左 } return root; } };
如果这个代码看不懂的话可以再回顾一下二叉树:听说递归能做的,栈也能做! (opens new window)。
我们在二叉树:前中后序迭代方式的统一写法 (opens new window)中介绍了统一的写法,所以,本题也只需将文中的代码少做修改便可。
C++代码如下迭代法&统一法(前序遍历)
class Solution { public: TreeNode* invertTree(TreeNode* root) { stack<TreeNode*> st; if (root != NULL) st.push(root); while (!st.empty()) { TreeNode* node = st.top(); if (node != NULL) { st.pop(); if (node->right) st.push(node->right); // 右 if (node->left) st.push(node->left); // 左 st.push(node); // 中 st.push(NULL); } else { st.pop(); node = st.top(); st.pop(); swap(node->left, node->right); // 节点处理逻辑 } } return root; } };
如果上面这个代码看不懂,回顾一下文章二叉树:前中后序迭代方式的统一写法 (opens new window)。
2.广度优先遍历
也就是层序遍历,层数遍历也是可以翻转这棵树的,因为层序遍历也可以把每个节点的左右孩子都翻转一遍,代码如下:
class Solution { public: TreeNode* invertTree(TreeNode* root) { queue<TreeNode*> que; if (root != NULL) que.push(root); while (!que.empty()) { int size = que.size(); for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); swap(node->left, node->right); // 交换就行——》超简单的 if (node->left) que.push(node->left); if (node->right) que.push(node->right); } } return root; } };
如果对以上代码不理解,或者不清楚二叉树的层序遍历,可以看这篇二叉树:层序遍历登场!(opens new window)
拓展(一刷没看)
文中我指的是递归的中序遍历是不行的,因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。
如果非要使用递归中序的方式写,也可以,如下代码就可以避免节点左右孩子翻转两次的情况:
class Solution { public: TreeNode* invertTree(TreeNode* root) { if (root == NULL) return root; invertTree(root->left); // 左 swap(root->left, root->right); // 中 invertTree(root->left); // 注意 这里依然要遍历左孩子,因为中间节点已经翻转了 return root; } };
代码虽然可以,但这毕竟不是真正的递归中序遍历了。
但使用迭代方式统一写法的中序是可以的。
代码如下:
class Solution { public: TreeNode* invertTree(TreeNode* root) { stack<TreeNode*> st; if (root != NULL) st.push(root); while (!st.empty()) { TreeNode* node = st.top(); if (node != NULL) { st.pop(); if (node->right) st.push(node->right); // 右 st.push(node); // 中 st.push(NULL); if (node->left) st.push(node->left); // 左 } else { st.pop(); node = st.top(); st.pop(); swap(node->left, node->right); // 节点处理逻辑 } } return root; } };
为什么这个中序就是可以的呢,因为这是用栈来遍历,而不是靠指针来遍历,避免了递归法中翻转了两次的情况,大家可以画图理解一下,这里有点意思的。
总结
(要点1,也是我烦的点)针对二叉树的问题,解题之前一定要想清楚究竟是前中后序遍历,还是层序遍历。
二叉树解题的大忌就是自己稀里糊涂的过了(因为这道题相对简单),但是也不知道自己是怎么遍历的。
这也是造成了二叉树的题目“一看就会,一写就废”的原因。
针对翻转二叉树,我给出了一种递归,三种迭代(两种模拟深度优先遍历,一种层序遍历)的写法,都是之前我们讲过的写法,融汇贯通一下而已。
大家一定也有自己的解法,但一定要成方法论,这样才能通用,才能举一反三
3.101-对称二叉树 2 (难)
给定一个二叉树,检查它是否是镜像对称的。
思路
《代码随想录》算法视频公开课:同时操作两个二叉树 | LeetCode:101. 对称二叉树 (opens new window),相信结合视频在看本篇题解,更有助于大家对本题的理解。
首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!
对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的——》本质就是我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。
那么如何比较呢?
比较的是两个子树的里侧和外侧的元素是否相等。如图所示:
那么遍历的顺序应该是什么样的呢?(二叉树题目的关键)
本题遍历只能是“后序遍历”,因为我们要通过先收集左右孩子的信息,然后在遍历中间结点的时候把这个结点的左右孩子的信息给返回!!——》只要是需要我们收集孩子信息向上返回的题目,都需要我们思考“后序遍历”。
正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是需要一个树的遍历顺序是左右中,另外一个树的遍历顺序是右左中。
tip:虽然一个树遍历的顺序是左右中,一个树的顺序是右左中,但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。
#递归法(一刷只看了这种)
递归三部曲
1.确定递归函数的参数和返回值
(1)参数:因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。
(2)返回值:自然是bool类型。
代码如下:
bool compare(TreeNode* left, TreeNode* right)
2.确定终止条件
要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。
节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点)
-
左节点为空,右节点不为空,不对称,return false
-
左不为空,右为空,不对称 return false
-
左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:
-
左右都不为空,比较节点数值,不相同就return false;相同就继续向下遍历、比较
此时左右节点不为空,且数值也不相同的情况我们也处理了。
代码如下:
if (left == NULL && right != NULL) return false; else if (left != NULL && right == NULL) return false; else if (left == NULL && right == NULL) return true; else if (left->val != right->val) return false; // 注意这里我没有使用else
注意上面最后一种情况,我没有使用else,而是else if, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。
3.确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
-
比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
-
比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
-
如果左右都对称就返回true ,有一侧不对称就返回false 。
代码如下:
bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右 bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左 bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理) return isSame; //tip:这个left->left, right->right的写法我竟然没想到,我好笨!!!~!
如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以我把这个遍历顺序也称之为“后序遍历”(尽管不是严格的后序遍历)。
最后递归的C++整体代码如下:
class Solution { public: bool compare(TreeNode* left, TreeNode* right) { // 首先排除空节点的情况 if (left == NULL && right != NULL) return false; else if (left != NULL && right == NULL) return false; else if (left == NULL && right == NULL) return true; // 排除了空节点,再排除数值不相同的情况 else if (left->val != right->val) return false; // 此时就是:左右节点都不为空,且数值相同的情况 // 此时才做递归,做下一层的判断 bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右 bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左 bool isSame = outside && inside; // 左子树:中、 右子树:中 (逻辑处理)(这个中以前也是其他结点的左或右,能把目前这个两个结点看作中,肯定是因为经过之前的比较他们相同,所以在这里中不代表要比较中的数值,而是看这两个的左右还是是否都相同) return isSame; } bool isSymmetric(TreeNode* root) { if (root == NULL) return true; return compare(root->left, root->right); } };
我给出的代码并不简洁,但是把每一步判断的逻辑都清楚的描绘出来了。
如果上来就看网上各种简洁的代码,看起来真的很简单,但是很多逻辑都掩盖掉了,而题解可能也没有把掩盖掉的逻辑说清楚。
盲目的照着抄,结果就是:发现这是一道“简单题”,稀里糊涂的就过了,但是真正的每一步判断逻辑未必想到清楚。
当然我可以把如上代码整理如下:
class Solution { public: bool compare(TreeNode* left, TreeNode* right) { if (left == NULL && right != NULL) return false; else if (left != NULL && right == NULL) return false; else if (left == NULL && right == NULL) return true; else if (left->val != right->val) return false; else return compare(left->left, right->right) && compare(left->right, right->left); } bool isSymmetric(TreeNode* root) { if (root == NULL) return true; return compare(root->left, root->right); } };
这个代码就很简洁了,但隐藏了很多逻辑,条理不清晰,而且递归三部曲,在这里完全体现不出来。
所以建议大家做题的时候,一定要想清楚逻辑,每一步做什么。把题目所有情况想到位,相应的代码写出来之后,再去追求简洁代码的效果。