144、145、94 二叉树遍历
这三道题分别考察二叉树的前序遍历、后序遍历、中序遍历。
二叉树的遍历问题是二叉树较为基础的一类问题,通常来讲,都是使用递归算法来实现的。而递归算法的关键就在于,确定递归函数的参数以及返回值、终止条件,再加上每一层的递归逻辑。
先来看看二叉树的结构体:
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) {}
};
关于二叉树的遍历问题,其实我们所需要做的莫过于传入结点以及存放结果的容器,而终止条件则是当前结点为空时,说明以及遍历完毕。而其中单层递归的逻辑则决定了遍历的顺序。而中间结点的遍历先后顺序,其实就决定了前中后遍历的顺序。
当然,除了递归法,通过使用迭代法,使用栈这种数据结构,我们同样可以完成二叉树的遍历。
144. 前序遍历
Given the root of a binary tree, return the preorder traversal of its nodes’ values.
示例:
Input: root = [1,null,2,3]
Output: [1,2,3]
递归法:
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec)
{
if (cur == NULL)
{
return;
}
vec.push_back(cur->val);
traversal(cur->left, vec);
traversal(cur->right, vec);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
迭代法:
利用迭代法,不使用递归法同样可以解决问题,此处使用的是一个while循环,通过结点的入栈出栈,利用栈先进后出的特性,完成按顺序的遍历。当栈空时则停止循环。
class Solution2 {
public:
vector<int> preorderTraversal(TreeNode* root)
{
stack<TreeNode*> stnode;
vector<int> result;
//考虑空树情况
if (root == NULL)
{
return result;
}
stnode.push(root);//根节点进栈
while (!stnode.empty())
{
TreeNode* node = stnode.top();//获取当前结点
stnode.pop();
result.push_back(node->val);//中
if (node->right)
{
stnode.push(node->right); //先入栈右节点,先出左节点
}
if (node->left)
{
stnode.push(node->left); //后入栈左节点,后出
}
}
return result;
}
};
145. 后序遍历
Given the root of a binary tree, return the postorder traversal of its nodes’ values.
示例:
Input: root = [1,null,2,3]
Output: [3,2,1]
递归法:
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec)
{
if (cur == NULL)
{
return;
}
traversal(cur->left, vec);
traversal(cur->right, vec);
vec.push_back(cur->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
迭代法:
后序遍历的迭代法其实和前序遍历尤为相似,在前序遍历的基础上,我们只需更改入栈的顺序,使其按照中、右、左的顺序出栈,进而在最后对所得到的的result数组进行翻转操作,即可得到以左右中顺序遍历的结果。
class Solution2 {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
if (node->right) st.push(node->right); // 空节点不入栈
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
94. 中序遍历
Given the root of a binary tree, return the inorder traversal of its nodes’ values.
示例:
Input: root = [1,null,2,3]
Output: [1,3,2]
递归法:
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec)
{
if (cur == NULL)
{
return;
}
traversal(cur->left, vec);
vec.push_back(cur->val);
traversal(cur->right, vec);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
迭代法:
中序遍历的迭代法和以上两种遍历都完全不同,其特殊性在于:是以左中右的顺序进行遍历,这和我们自上而下的访问顺序是不一致的。
所以我们选择采用指针的形式进行操作:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != NULL || !st.empty()) {
if (cur != NULL) { // 指针来访问节点,访问到最底层
st.push(cur); // 将访问的节点放进栈
cur = cur->left; // 左
} else {
cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
st.pop();
result.push_back(cur->val); // 中
cur = cur->right; // 右
}
}
return result;
}
};
迭代法通解:
除此以外,针对迭代法,其实也是存在于类似递归法的通解的,只需更换两行代码的顺序即可改变。但同样的,这种方式的代码比较难懂,其精髓是通过加入空指针来标记中间结点。
我们会将检索到的每一个结点都先存入栈中,接着,当检索到空指针NULL时,我们才更新result数组。此时只要调整入栈顺序即可完成不同方式的遍历。
前序遍历:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
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();
result.push_back(node->val);
}
}
return result;
}
};
中序遍历:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
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();
result.push_back(node->val); // 加入到结果集
}
}
return result;
}
};
后序遍历:
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root != NULL) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != NULL) {
st.pop();
st.push(node); // 中
st.push(NULL);
if (node->right) st.push(node->right); // 右
if (node->left) st.push(node->left); // 左
} else {
st.pop();
node = st.top();
st.pop();
result.push_back(node->val);
}
}
return result;
}
};
参考:代码随想录
往期回顾:
LeetCode18. 四数之和
LeetCode15. 三数之和
LeetCode383. 赎金信
LeetCode454. 四数相加 II
LeetCode1. 两数之和
LeetCode202. 快乐数
LeetCode350. 两个数组的交集 II
LeetCode349. 两个数组的交集
LeetCode1002. 查找共用字符