翻转二叉树
题目描述
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
解题思路
递归法
通过递归地交换每个节点的左右子树,从而实现整个二叉树的反转。从根节点开始,先交换其左右子树,然后分别递归地处理交换后的左右子树,直到处理完所有节点。
迭代法
深度优先遍历
使用栈来非递归地反转二叉树。从根节点开始,将节点压入栈中,然后循环从栈中取出节点,交换其左右子树,并将非空的左右子按序节点压回栈中,直至处理完所有节点。
统一写法
通过在栈中使用空指针作为标记,区分已入栈但尚未处理的节点和已处理过的节点。当遇到空指针标记时,表示栈顶的节点需要进行左右子树的交换。
层序遍历
通过广度优先搜索(BFS)的方式,逐层处理树的节点。在每一层中,先交换节点的左右子树,然后将非空的左右子节点加入队列,以便处理下一层。
代码实现
测试地址:https://leetcode.cn/problems/invert-binary-tree/description/
递归写法:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
//递归终止条件
if (root == nullptr)
return root;
//交换当前节点的左右子树
swap(root->left, root->right);
//递归反转左子树
invertTree(root->left);
//递归反转右子树
invertTree(root->right);
//返回反转后的根节点
return root;
}
};
栈写法:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
// 如果根节点为空,直接返回空指针
if (root == nullptr)
return root;
// 将根节点压入栈中
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;
}
};
统一迭代写法:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
// 如果根节点为空,直接返回空指针
if (root == nullptr)
return root;
// 创建一个栈用于辅助遍历
stack<TreeNode*> st;
// 将根节点压入栈中
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) {
// 如果根节点为空,直接返回空指针
if (root == nullptr)
return root;
// 创建一个队列用于辅助遍历
queue<TreeNode*> que;
// 将根节点加入队列
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;
}
};
对称二叉树
题目描述
给你一个二叉树的根节点 root
, 检查它是否轴对称。
解题思路
递归法
检查两个子树的节点值是否相等,然后递归地检查左子树的左子树和右子树的右子树,以及左子树的右子树和右子树的左子树是否相等。如果所有这些比较都返回true,则树是对称的。
迭代法
使用队列进行层次遍历,每次从队列中取出两个节点进行比较,如果它们的值相等且结构对称,则继续比较下一对节点。如果发现任何不对称的情况,立即返回false
。如果所有节点都比较完毕,且没有发现不对称的情况,则返回true
,表示树是对称的。
使用栈的写法和队列方式核心思路一致,代码可看下方代码实现。
代码实现
测试地址:https://leetcode.cn/problems/symmetric-tree/description/
递归写法:
class Solution {
public:
bool compareVal(TreeNode* leftNode, TreeNode* rightNode) {
// 如果左节点为空而右节点不为空,则不对称
if (leftNode == nullptr && rightNode != nullptr) {
return false;
}
// 如果左节点不为空而右节点为空,则不对称
else if (leftNode != nullptr && rightNode == nullptr) {
return false;
}
// 如果左右节点都为空,则对称
else if (leftNode == nullptr && rightNode == nullptr) {
return true;
}
// 如果左右节点的值不相等,则不对称
else if (leftNode->val != rightNode->val) {
return false;
}
// 递归比较左节点的左子树和右节点的右子树,以及左节点的右子树和右节点的左子树
else {
return compareVal(leftNode->left, rightNode->right) &&
compareVal(leftNode->right, rightNode->left);
}
}
bool isSymmetric(TreeNode* root) {
// 如果根节点为空,则树对称
if (root == nullptr)
return true;
// 调用辅助函数比较根节点的左右子树是否对称
return compareVal(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 && !rightNode) {
continue;
}
// 如果两个节点有一个为空或者值不相等,则树不对称
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;
}
};
迭代写法(栈):
class Solution {
public:
bool isSymmetric(TreeNode* root) {
// 如果根节点为空,则树对称
if (root == nullptr)
return true;
// 使用栈来辅助进行迭代遍历
stack<TreeNode*> st;
// 将根节点的左右子节点加入栈
st.push(root->left);
st.push(root->right);
// 当栈不为空时,继续检查
while (!st.empty()) {
// 从栈中取出两个节点进行比较
TreeNode* leftNode = st.top();
st.pop();
TreeNode* rightNode = st.top();
st.pop();
// 如果两个节点都为空,则继续下一轮比较
if (!leftNode && !rightNode) {
continue;
}
// 如果两个节点有一个为空或者值不相等,则树不对称
if ((!leftNode || !rightNode ||
(leftNode->val != rightNode->val))) {
return false;
}
// 将左节点的左子节点和右节点的右子节点,以及左节点的右子节点和右节点的左子节点成对加入栈
st.push(leftNode->left);
st.push(rightNode->right);
st.push(leftNode->right);
st.push(rightNode->left);
}
// 如果所有节点都比较完毕,且没有发现不对称的情况,则树对称
return true;
}
二叉树的最大深度
题目描述
给定一个二叉树 root
,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
解题思路
递归后序遍历思路
二叉树的节点深度&高度说明:
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
二叉树的节点深度实际上等同于二叉树的根节点到叶子结点的距离,也就是根节点所在的高度。
因此可以可以使用后序遍历的方式,从下往上递归地计算每个节点的左子树和右子树的最大高度,然后取两者中的最大值并加1(当前节点的高度),从而得到整个二叉树的最大高度,进而得到二叉树的最大深度。
递归前序遍历思路
从根节点开始,自顶向下递归到子节点时增加深度计数,并在返回时减少深度计数(回溯)。在每个节点,它比较当前深度和已知的最大深度,更新最大深度。
迭代法思路
使用广度优先搜索(BFS)来计算二叉树的最大深度。通过队列实现层次遍历,每次处理一层的节点,并在处理完一层后增加深度计数。
代码实现
测试地址:https://leetcode.cn/problems/maximum-depth-of-binary-tree/
递归写法(后序遍历正常版本):
class Solution {
public:
int maxDepth(TreeNode* cur) {
// 如果当前节点为空,表示到达叶子节点下方,返回0
if (cur == nullptr) {
return 0;
} else {
// 递归计算左子树的最大深度(左)
int leftHeight = maxDepth(cur->left);
// 递归计算右子树的最大深度(右)
int rightHeight = maxDepth(cur->right);
// 取左右子树深度的最大值,并加上当前节点(高度为1),得到当前树的最大深度(中)
int maxHeight = 1 + max(leftHeight, rightHeight);
// 返回计算出的最大深度
return maxHeight;
}
}
};
递归写法(后序遍历极简版本):
class Solution {
public:
int maxDepth(TreeNode* root) {
return root == nullptr
? 0
: max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
};
递归写法(前序遍历正常写法):
class Solution {
public:
// 用于存储最大深度的变量
int result;
// 递归函数,用于计算每个节点的深度
void getdepth(TreeNode* node, int depth) {
// 更新最大深度(中)
result = depth > result ? depth : result;
// 如果当前节点是叶子节点,则返回(左)
if (node->left == nullptr && node->right == nullptr)
return;
// 如果左子节点存在,增加深度并递归计算左子树的深度(右)
if (node->left) {
depth++;
getdepth(node->left, depth);
depth--; // 回溯,减少深度
}
// 如果右子节点存在,增加深度并递归计算右子树的深度
if (node->right) {
depth++;
getdepth(node->right, depth);
depth--; // 回溯,减少深度
}
return;
}
// 主函数,计算二叉树的最大深度
int maxDepth(TreeNode* root) {
result = 0; // 初始化最大深度为0
if (root == nullptr)
return result; // 如果树为空,返回0
getdepth(root, 1); // 从根节点开始计算深度
return result; // 返回最大深度
}
};
递归写法(前序遍历极简写法):
class Solution {
public:
int result;
void getdepth(TreeNode* node, int depth) {
result = depth > result ? depth : result;
if (node->left == nullptr && node->right == nullptr)
return;
if (node->left) {
getdepth(node->left, depth + 1);
}
if (node->right) {
getdepth(node->right, depth + 1);
}
return;
}
int maxDepth(TreeNode* root) {
result = 0;
if (root == nullptr)
return result;
getdepth(root, 1);
return result;
}
};
迭代法写法:
class Solution {
public: int maxDepth(TreeNode* root) {
// 如果根节点为空,返回0
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);
}
}
return depth; // 返回最大深度
}
};
二叉树的最小深度
题目描述
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
解题思路
递归后序遍历思路
递归地计算每个节点的左子树和右子树的最小高度,如果一个节点只有单边子树(左或右),则其最小深度应为该子树的最小高度加1。
递归先序遍历思路
从根节点开始,每次递归到子节点时增加深度计数。在每个节点,如果它是叶子节点,则比较当前深度和已知的最大深度,更新最小深度。
迭代法思路
使用BFS来计算二叉树的最小深度,通过队列实现层次遍历,每次处理一层的节点,并在处理过程中检查是否到达了叶子节点。一旦找到第一个叶子节点,即返回当前的深度作为最小深度
代码实现
测试地址:
递归后续遍历写法:
class Solution {
public:
int minDepth(TreeNode* root) {
// 如果根节点为空,返回0
if (root == nullptr)
return 0;
// 如果左子节点为空,但右子节点存在,则最小深度为右子树的最小高度加1
if (root->left == nullptr && root->right) {
return 1 + minDepth(root->right);
}
// 如果右子节点为空,但左子节点存在,则最小深度为左子树的最小高度加1
if (root->left && root->right == nullptr) {
return 1 + minDepth(root->left);
}
// 如果左右子节点都存在,则最小深度为左右子树最小高度的最小值加1
return 1 + min(minDepth(root->left), minDepth(root->right));
}
};
递归前序遍历写法:
class Solution {
public:
// 用于存储最小深度的变量
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;
}
// 主函数,计算二叉树的最小深度
int minDepth(TreeNode* root) {
// 如果根节点为空,返回0
if (root == nullptr)
return 0;
result = INT_MAX; // 初始化最小深度为最大整数值
getdepth(root, 1); // 从根节点开始计算深度
return result; // 返回最小深度
}
};
迭代法:
class Solution {
public:
int minDepth(TreeNode* root) {
// 如果根节点为空,返回0
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; // 返回最小深度
}
};