代码随想录算法训练营第十一天--二叉树2 || 226.翻转二叉树 / 101.对称二叉树 / 104.二叉树的最大深度 / 111.二叉树的最小深度
226.翻转二叉树(优先掌握递归)
解题思路
我们要翻转的不只是数字,而是指针。
递归法
/**
* 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:
TreeNode* invertTree(TreeNode* root) {
if(root == NULL) return root;
swap(root -> left, root -> right); // 中
invertTree(root -> left); // 左
invertTree(root -> right); // 右
return root;
}
};
上述代码使用的前序遍历法,当然也可以使用后序遍历法,但是**不能使用中序遍历法。
因为我们先翻转左子树,然后翻转中间节点,原来翻转过来的左子树翻转到右侧,我们又一次翻转右子树,那么把原来翻转过来的左子树又翻转回去了。而原来右子树没有进行翻转。
中序遍历代码如下:
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) {
if (root == NULL) return root;
stack<TreeNode*> st;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
swap(node -> left, node -> right);
if (node -> right) st.push(node -> right); // 右
if (node -> left) st.push(node -> left); // 左
}
return root;
}
};
统一迭代法
/**
* 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:
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;
}
};
层序遍历 – 广度优先搜索
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
while (!que.empty()) {
int size = que.size();
while (size --) {
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;
}
};
101.对称二叉树(优先掌握递归)
解题思路
对于要搜集左右孩子,返回到中间节点的情况,我们一般采取后序遍历,这个非常重要。
这道题的关键是要判断外侧相等,里侧相等,比较根节点的左右两棵子树能否互相翻转。
递归法
递归三部曲
1.先判断返回值和传入的参数:
返回bool类型,参数就是左右节点
2.确定终止条件:
- 左节点为空,右节点不为空,不对称,return false
- 左不为空,右为空,不对称 return false
- 左右都为空,对称,返回true
- 左右都不为空,左右值不同,return false
- 左右都不为空,左右值相同,进入单层递归
3.确定单层递归逻辑
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
- 如果左右都对称就返回true ,有一侧不对称就返回false 。
完整代码
/**
* 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 compare(TreeNode* left, TreeNode* right) {
// 分五种情况
if (left == NULL && right != NULL) return false; // 1.左空,右非空
else if (left != NULL && right == NULL) return false; // 2.左非空,右空
else if (left == NULL && right == NULL) return true; // 3.左空,右空
else if (left -> val != right -> val) return false; // 4.左值 不等于 右值
// 5.左值 = 右值
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 isSymmetric(TreeNode* root) {
if (root == NULL) return true;
queue<TreeNode*> que;
que.push(root -> left); // 将左子树头节点入队
que.push(root -> right); // 将右子树头节点入队
while (!que.empty()) { // 判断两个树是否相互翻转
TreeNode* l = que.front(); que.pop();
TreeNode* r = que.front(); que.pop();
// 左右皆空,对称
if (l == NULL && r == NULL) continue;
// 左右一个为空 or 都不空但值不相等,不对称
if (l == NULL || r == NULL || l -> val != r -> val)
return false;
que.push(l -> left); // 左节点左孩子
que.push(r -> right); // 右节点右孩子
que.push(l -> right); // 左节点右孩子
que.push(r -> left); // 右节点左孩子
}
return true;
}
};
练习1
class Solution {
public:
bool cmp(TreeNode* p, TreeNode* q) {
if (p == NULL && q == NULL) return true;
else if (p == NULL || q == NULL || p -> val != q -> val) return false;
bool isl = cmp(p -> left, q -> left);
bool isr = cmp(p -> right, q -> right);
return isl && isr;
}
bool isSameTree(TreeNode* p, TreeNode* q) {
return cmp(p, q);
}
};
练习2:
class Solution {
public:
bool cmp(TreeNode* root, TreeNode* subRoot) {
if (root == NULL && subRoot == NULL) return true;
else if (root == NULL || subRoot == NULL || root -> val != subRoot -> val)
return false;
bool isl = cmp(root -> left, subRoot -> left);
bool isr = cmp(root -> right, subRoot -> right);
return isl && isr;
}
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if (root == NULL) return false;
return cmp(root, subRoot) ||
isSubtree(root -> left, subRoot) ||
isSubtree(root -> right, subRoot);
}
};
这道题首先要判断根节点非空,防止对空节点进行操作。
然后我们就像100.相同的树一样去比较是否相等,不相等我们就递归isSubtree函数,直到找到相等的子树。
104.二叉树的最大深度
递归法
我们要分清高度和深度的概念,对于一个二叉树,最上面的根节点的深度为1,高度就是深度的最大值,也就是说高度和深度是反转关系的。
一般使用后序遍历来求二叉树的最大高度,也就是二叉树的最大深度
public:
int getDepth(TreeNode* node) {
if (node == NULL) return 0; // 终止条件,最底层的下一层高度为0
int leftDepth = getDepth(node -> left); // 左
int rightDepth = getDepth(node -> right); // 右
int depth = 1 + max(leftDepth, rightDepth); // 中
return depth;
}
int maxDepth(TreeNode* root) {
return getDepth(root);
}
};
代码可以精简
class Solution {
public:
int getDepth(TreeNode* node) {
if (node == NULL) return 0; // 终止条件,最底层的下一层高度为0
return 1 + max(getDepth(node -> left), getDepth(node -> right));
}
int maxDepth(TreeNode* root) {
return getDepth(root);
}
};
层序遍历–迭代法
class Solution {
public:
int maxDepth(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
int depth = 0;
while (!que.empty()) {
int size = que.size();
depth ++; // 记录深度
while (size --) {
TreeNode* node = que.front();
que.pop();
if (node -> left) que.push(node -> left);
if (node -> right) que.push(node -> right);
}
}
return ans;
}
};
练习:
迭代法–层序遍历
class Solution {
public:
int maxDepth(Node* root) {
queue<Node*> que;
if (root != NULL) que.push(root);
int depth = 0;
while (!que.empty()) {
int size = que.size();
depth ++;
while (size --) {
Node* node = que.front();
que.pop();
for (int i = 0; i < node -> children.size(); i++) {
if (node -> children[i]) que.push(node -> children[i]);
}
}
}
return depth;
}
};
递归法
class Solution {
public:
int getDepth(Node* node) {
if (node == NULL) return 0;
int depth = 0;
for (int i = 0; i < node -> children.size(); i++) {
depth = max(depth, getDepth(node -> children[i]));
}
return depth + 1;
}
int maxDepth(Node* root) {
return getDepth(root);
}
};
最后为什么要加1呢,想想之前的二叉树,是左右,然后中,中是1 + max(左,右)。这里for循环就先当于左右,最后到中需要加1。
111.二叉树的最小深度
审题
这道题的最小深度,指的是叶子节点是指没有子节点的节点,有一个节点的,不叫最小深度,必须左右两个节点都无才算。
递归法
class Solution {
public:
int getDepth(TreeNode* node) {
int res = 0;
if (node == NULL) return 0;
int left = getDepth(node -> left); // 左
int right = getDepth(node -> right); // 右
// 中
// 当一个左子树为空,右不为空,这时并不是最低点
// 当一个右子树为空,左不为空,这时并不是最低点
if (node -> left == NULL && node -> right != NULL) {
res = 1 + right;
} else if (node -> left != NULL && node -> right == NULL) {
res = 1 + left;
} else {
res = 1 + min(left, right);
}
return res;
}
int minDepth(TreeNode* root) {
return getDepth(root);
}
};
迭代法–层序遍历
class Solution {
public:
int minDepth(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
int depth = 0;
while (!que.empty()) {
int size = que.size();
depth ++;
while (size --) {
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;
}
};
注意一下depth
的位置,层序遍历是从上到下遍历,当左右无节点,那就直接返回。