226.翻转二叉树
递归法
前序递归遍历或者后序递归遍历都是可以的,因为无论一开始就交换左右孩子,还是最后交换左右孩子,都可以。但对于中序遍历来说就不一样了,因为交换发生在中间,交换之前的右子树,交换之后就变成左子树了,要特殊处理
// 前序递归
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) return root;
swap(root->left, root->right); // 交换左右孩子
invertTree(root->right); // 递归原左子树
invertTree(root->left); // 递归原右子树
return root;
}
};
// 后序递归
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) return root;
invertTree(root->left); // 递归左子树
invertTree(root->right); // 递归右子树
swap(root->left, root->right); // 交换左右孩子
return root;
}
};
// 中序递归
class Solution {
public:
// 中序递归
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) 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 != nullptr) st.push(root);
while(!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
if (node->right) st.push(node->right); // 右
if (node->left) st.push(node->left); // 左
st.push(node); // 中
st.push(nullptr);
} else {
st.pop();
node = st.top();
st.pop();
swap(node->left, node->right);
}
}
return root;
}
};
// 中序迭代
// 左中右的顺序,那么入栈的顺序就是右,中,左
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
while(!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
if (node->right) st.push(node->right); // 右
st.push(node); // 中
st.push(nullptr);
if (node->left) st.push(node->left); // 左
} else {
st.pop();
node = st.top();
st.pop();
swap(node->left, node->right);
}
}
return root;
}
};
// 后序迭代
// 左右中的顺序,那么入栈的顺序就是中右左
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
while(!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
// st.pop();
// st.push(node); // 中
st.push(nullptr);
if (node->right) st.push(node->right); // 右
if (node->left) st.push(node->left); // 左
} 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 != nullptr) 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;
}
};
101.对称二叉树
本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。
但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。
递归法
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
// 首先排除存在空节点的情况
if (left == nullptr && right != nullptr) return false;
else if (right == nullptr && left != nullptr) return false;
else if (right == nullptr && left == nullptr) return true;
// 都不是空节点,再排除数值不相同的情况
else if (left->val != right->val) return false;
// 此时就是:左右节点都不为空,且数值相同的情况
// 此时才做递归,做下一层的判断
bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左
return outside && inside; // 左子树:中、 右子树:中 (逻辑处理)
}
bool isSymmetric(TreeNode* root) {
if (root == nullptr) return true;
return compare(root->left, root->right);
}
};
迭代法
这道题目我们也可以使用迭代法,但要注意,这里的迭代法可不是前中后序的迭代写法,因为本题的本质是判断两个树是否是相互翻转的,其实已经不是所谓二叉树遍历的前中后序的关系了。
在迭代法的统一写法中,我们使用栈来记录遍历过的节点
这里先用队列,判断条件和递归法是一样的
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == nullptr) return true;
queue<TreeNode*> que;
// 同时考虑左右两个节点
que.push(root->left); // 将左子树头结点加入队列
que.push(root->right); // 将右子树头结点加入队列
while(!que.empty()) { // 接下来就要判断这两个树是否相互翻转
TreeNode* leftNode = que.front(); que.pop();
TreeNode* rightNode = que.front(); que.pop();
// 左孩子右孩子都为空,对称,且已到底层,不要新的底层元素入队,不做处理,
if (leftNode == nullptr && rightNode == nullptr) {
continue;
}
// 左右一个节点不为空,或者都不为空但数值不相同
// 迭代过程中遇到,直接返回false,中止迭代
if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
return false;
}
que.push(leftNode->left);
que.push(rightNode->right);
que.push(leftNode->right);
que.push(rightNode->left);
}
// 没有提前中止迭代,说明完全对称
return true;
}
};
细心的话,其实可以发现,这个迭代法,其实是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。
只要把队列原封不动的改成栈就可以了
104.二叉树的最大深度
层序遍历
在之前二叉树的层序遍历一节,已经说明只要每遍历一层,depth++, 遍历所有层就可以统计出来最大深度。
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;
}
};
递归法
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。
先用后序遍历(左右中)来计算树的高度。
// 后序递归遍历获取节点高度
class Solution {
public:
int getHeight(TreeNode* node) {
// 后序递归遍历,递归到最左侧的叶子节点开始返回
if (node == nullptr) return 0; // 空指针说明递归到了最底层的下一层
int left_Height = getHeight(node->left); // 左
int right_Height = getHeight(node->right);// 右
int Height = 1 + max(left_Height, right_Height); // 中
return Height;
}
int maxDepth(TreeNode* root) {
// 根节点高度就是二叉树的最大深度
return getHeight(root);
}
};
本题当然也可以使用前序,代码如下:(充分表现出求深度回溯的过程) 我暂时看不懂
class Solution {
public:
int result;
void getdepth(TreeNode* node, int depth) {
result = depth > result ? depth : result; // 中
if (node->left == NULL && node->right == NULL) return ;
if (node->left) { // 左
depth++; // 深度+1
getdepth(node->left, depth);
depth--; // 回溯,深度-1
}
if (node->right) { // 右
depth++; // 深度+1
getdepth(node->right, depth);
depth--; // 回溯,深度-1
}
return ;
}
int maxDepth(TreeNode* root) {
result = 0;
if (root == NULL) return result;
getdepth(root, 1);
return result;
}
};
111.二叉树的最小深度
本题还有一个误区,在处理节点的过程中,最大深度很容易理解,最小深度就不那么好理解,如图:
这就重新审题了,题目中说的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。注意是叶子节点。
什么是叶子节点,左右孩子都为空的节点才是叶子节点!
层序遍历
和最大深度一样,每遍历一层,depth++,区别只是,如果当前层存在叶子节点,就直接返回
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == nullptr) 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;
}
};
递归法
这道题看似和求最大深度类似,采用后序递归,只需要把Height = 1 + max(left_Height, right_Height)修改为Height = 1 + min(left_Height, right_Height)。但这样写就无法处理一开始提到的左右子树其中之一不存在的情况。
class Solution {
public:
int getHeight(TreeNode* node) {
if (node == nullptr) return 0;
int leftHeight = getHeight(node->left); // 左
int rightHeight = getHeight(node->right); // 右
// 当左子树为空,右子树不为空,此时左子树不能算最低点
if (node->left == nullptr && node->right != nullptr) {
return 1 + rightHeight;
}
// 当左子树不为空,右子树为空,此时左子树不能算最低点
if (node->left != nullptr && node->right == nullptr) {
return 1 + leftHeight;
}
return 1 + min(leftHeight, rightHeight);
}
int minDepth(TreeNode* root) {
return getHeight(root);
}
};
前序遍历的方式,暂时还看不懂
class Solution {
private:
int result;
void getdepth(TreeNode* node, int depth) {
// 函数递归终止条件
if (node == nullptr) {
return;
}
// 中,处理逻辑:判断是不是叶子结点
if (node -> left == nullptr && node->right == nullptr) {
result = min(result, 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;
}
};