【LeetCode】9.二叉树系列——遍历
总目录:
0.理论基础
0.1如何遍历
0.2.如何写递归
三板斧
(1)确定入参和返回值,如果需要搜索全部范围则不需要返回值,如果搜到目标值就需立即返回的话就必须返回值;
(2)确定终止条件;
(3)确定本层逻辑,包括数据处理和调用自身函数实现递归;
0.3.递归版DFS
以前序遍历为例:
1 void traversal(TreeNode* cur, vector<int>& vec) { 2 if (cur == NULL) return; 3 vec.push_back(cur->val); // 中 4 traversal(cur->left, vec); // 左 5 traversal(cur->right, vec); // 右 6 }
0.4.迭代版DFS
前、中、后序的结构不太一样,需要具体顺序具体分析。尤其是中序遍历。
具体代码见下方。
0.5.迭代版BFS
层序遍历,一层一层地从左到右处理节点。
1 class Solution { 2 public: 3 vector<vector<int>> levelOrder(TreeNode* root) { 4 queue<TreeNode*> que; 5 if (root != NULL) que.push(root); 6 vector<vector<int>> result; 7 while (!que.empty()) { 8 int size = que.size(); 9 vector<int> vec; 10 // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的 11 for (int i = 0; i < size; i++) { 12 TreeNode* node = que.front(); 13 que.pop(); 14 vec.push_back(node->val); 15 if (node->left) que.push(node->left); 16 if (node->right) que.push(node->right); 17 } 18 result.push_back(vec); 19 } 20 return result; 21 } 22 };
0.6.递归版BFS
输入层号,进行递归
1 # 递归法 2 class Solution { 3 public: 4 void order(TreeNode* cur, vector<vector<int>>& result, int depth) 5 { 6 if (cur == nullptr) return; 7 if (result.size() == depth) result.push_back(vector<int>()); 8 result[depth].push_back(cur->val); 9 order(cur->left, result, depth + 1); 10 order(cur->right, result, depth + 1); 11 } 12 vector<vector<int>> levelOrder(TreeNode* root) { 13 vector<vector<int>> result; 14 int depth = 0; 15 order(root, result, depth); 16 return result; 17 } 18 };
1.二叉树递归版前中后序遍历
1.1.问题描述
分别用递归实现二叉树的前中后序遍历
链接:
https://leetcode.cn/problems/binary-tree-preorder-traversal/
https://leetcode.cn/problems/binary-tree-inorder-traversal/
https://leetcode.cn/problems/binary-tree-postorder-traversal/
1.2.要点
递归三板斧、弄清楚根节点数据在哪个顺位被处理
1.3.代码实例
前
1 class Solution { 2 public: 3 void recurve(vector<int>& vec,const TreeNode* root){ 4 if(root==NULL){ 5 return; 6 } 7 8 vec.push_back(root->val); 9 recurve(vec,root->left); 10 recurve(vec,root->right); 11 } 12 vector<int> preorderTraversal(TreeNode* root) { 13 vector<int> vec; 14 if(root==NULL){ 15 return vec; 16 } 17 recurve(vec,root); 18 19 return vec; 20 } 21 };
中
1 class Solution { 2 public: 3 void recurve(vector<int>& vec,const TreeNode* root){ 4 if(root==NULL){ 5 return; 6 } 7 8 recurve(vec,root->left); 9 vec.push_back(root->val); 10 recurve(vec,root->right); 11 } 12 vector<int> inorderTraversal(TreeNode* root) { 13 vector<int> vec; 14 if(root==NULL){ 15 return vec; 16 } 17 recurve(vec,root); 18 19 return vec; 20 } 21 };
后
1 class Solution { 2 public: 3 void recurve(vector<int>& vec,const TreeNode* root){ 4 if(root==NULL){ 5 return; 6 } 7 8 recurve(vec,root->left); 9 recurve(vec,root->right); 10 vec.push_back(root->val); 11 } 12 vector<int> postorderTraversal(TreeNode* root) { 13 vector<int> vec; 14 if(root==NULL){ 15 return vec; 16 } 17 recurve(vec,root); 18 19 return vec; 20 } 21 };
2.二叉树迭代版前中后序遍历
2.1.问题描述
分别用迭代实现二叉树的前中后序遍历
链接:同上一题
2.2.要点
用迭代法来遍历树较为少用,关键还是其对栈的运用技巧,如何保存中间变量。
递归的本质就是将中间过程压栈弹栈,迭代时使用栈来实现。
前序迭代,访问顺序和处理顺序一致:
(1)while迭代判断栈是否为空;
(2)取栈顶元素,处理它的数据;
(3)先入栈其右子节点,如果有;再入栈其左子节点,如果有。
中序迭代,访问顺序和处理顺序不一致:记住固定套路。
后序迭代,前序迭代是中左右,后序迭代是左右中。将前序迭代改一下变成中右左,然后将结果翻转一下即可。
2.3.代码实例
前
1 class Solution { 2 public: 3 vector<int> preorderTraversal(TreeNode* root) { 4 stack<TreeNode*> st; 5 vector<int> result; 6 if (root == NULL) return result; 7 st.push(root); 8 9 while (!st.empty()) { 10 TreeNode* node = st.top(); // 中 11 st.pop(); 12 result.push_back(node->val); 13 if (node->right) st.push(node->right); // 右(空节点不入栈) 14 if (node->left) st.push(node->left); // 左(空节点不入栈) 15 } 16 return result; 17 } 18 };
中
1 class Solution { 2 public: 3 vector<int> inorderTraversal(TreeNode* root) { 4 vector<int> result; 5 stack<TreeNode*> st; 6 TreeNode* cur = root; 7 8 while (cur != NULL || !st.empty()) { 9 if (cur != NULL) { // 指针来访问节点,访问到最底层 10 st.push(cur); // 将访问的节点放进栈 11 cur = cur->left; // 左 12 } else { 13 cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据) 14 st.pop(); 15 result.push_back(cur->val); // 中 16 cur = cur->right; // 右 17 } 18 } 19 20 return result; 21 } 22 };
后
1 class Solution { 2 public: 3 vector<int> postorderTraversal(TreeNode* root) { 4 vector<int> vec; 5 if(root==NULL){ 6 return vec; 7 } 8 9 stack<TreeNode*> nodeSt; 10 nodeSt.push(root); 11 TreeNode* cur=NULL; 12 while(!nodeSt.empty()){ 13 cur=nodeSt.top(); 14 nodeSt.pop(); 15 16 vec.push_back(cur->val); 17 if(cur->left!=NULL){nodeSt.push(cur->left);} 18 if(cur->right!=NULL){nodeSt.push(cur->right);} 19 } 20 reverse(vec.begin(),vec.end()); 21 return vec; 22 } 23 };
3.二叉树的层序遍历,从上到下/从下到上
3.1.问题描述
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
链接:https://leetcode.cn/problems/binary-tree-level-order-traversal/
链接:https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/
3.2.要点
层序的不同最后再处理
1迭代法
2递归法
3.3.代码实例
迭代法
1 class Solution { 2 public: 3 vector<vector<int>> levelOrder(TreeNode* root) { 4 queue<TreeNode*> que; 5 if (root != NULL) que.push(root); 6 vector<vector<int>> result; 7 while (!que.empty()) { 8 int size = que.size(); 9 vector<int> vec; 10 // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的 11 for (int i = 0; i < size; i++) { 12 TreeNode* node = que.front(); 13 que.pop(); 14 vec.push_back(node->val); 15 if (node->left) que.push(node->left); 16 if (node->right) que.push(node->right); 17 } 18 result.push_back(vec); 19 } 20 return result; 21 } 22 };
递归版
1 class Solution { 2 public: 3 void recurve(vector<vector<int>>& vec,TreeNode* root,int levelId){ 4 if(root==NULL){ 5 return; 6 } 7 while(vec.size()<=levelId){ 8 vec.push_back(vector<int>()); 9 } 10 vec[levelId].push_back(root->val); 11 recurve(vec,root->left,levelId+1); 12 recurve(vec,root->right,levelId+1); 13 } 14 vector<vector<int>> levelOrder(TreeNode* root) { 15 vector<vector<int>> vec; 16 int levelId=0; 17 recurve(vec,root,0); 18 return vec; 19 } 20 };
4.二叉树的右视图
4.1.问题描述
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
输入: [1,2,3,null,5,null,4] 输出: [1,3,4]
链接:https://leetcode.cn/problems/binary-tree-right-side-view/
4.2.要点
迭代版层序遍历,每层只取最后一个
4.3.代码实例
迭代版层序遍历
1 class Solution { 2 public: 3 vector<int> rightSideView(TreeNode* root) { 4 queue<TreeNode*> que; 5 if (root != NULL) que.push(root); 6 vector<int> result; 7 while (!que.empty()) { 8 int size = que.size(); 9 // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的 10 for (int i = 0; i < size; i++) { 11 TreeNode* node = que.front(); 12 que.pop(); 13 if(i==(size-1)){ 14 result.push_back(node->val); 15 } 16 if (node->left) que.push(node->left); 17 if (node->right) que.push(node->right); 18 } 19 } 20 return result; 21 } 22 };
5.二叉树的各层平均值
5.1.问题描述
给定一个非空二叉树的根节点 root
, 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5
以内的答案可以被接受。
链接:https://leetcode.cn/problems/average-of-levels-in-binary-tree/
5.2.要点
求各层中节点之和后取平均值
1迭代式层序遍历
5.3.代码实例
迭代式层序遍历
1 /** 2 * Definition for a binary tree node. 3 * struct TreeNode { 4 * int val; 5 * TreeNode *left; 6 * TreeNode *right; 7 * TreeNode() : val(0), left(nullptr), right(nullptr) {} 8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 10 * }; 11 */ 12 class Solution { 13 public: 14 vector<double> averageOfLevels(TreeNode* root) { 15 queue<TreeNode*> que; 16 if (root != NULL) que.push(root); 17 18 vector<double> result; 19 double sum=0; 20 while (!que.empty()) { 21 int size = que.size(); 22 sum=0; 23 24 // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的 25 for (int i = 0; i < size; i++) { 26 TreeNode* node = que.front(); 27 que.pop(); 28 29 sum+=1.0*node->val; 30 31 if (node->left) que.push(node->left); 32 if (node->right) que.push(node->right); 33 } 34 35 result.push_back(sum/size); 36 } 37 return result; 38 } 39 };
6.N叉树的层序遍历
6.1.问题描述
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
链接:https://leetcode.cn/problems/n-ary-tree-level-order-traversal/
6.2.要点
处理每个节点的child集合,而不是左右子节点
1迭代式层序遍历
6.3.代码实例
迭代式层序遍历
1 /* 2 // Definition for a Node. 3 class Node { 4 public: 5 int val; 6 vector<Node*> children; 7 8 Node() {} 9 10 Node(int _val) { 11 val = _val; 12 } 13 14 Node(int _val, vector<Node*> _children) { 15 val = _val; 16 children = _children; 17 } 18 }; 19 */ 20 21 class Solution { 22 public: 23 vector<vector<int>> levelOrder(Node* root) { 24 queue<Node*> que; 25 if (root != NULL) que.push(root); 26 vector<vector<int>> result; 27 while (!que.empty()) { 28 int size = que.size(); 29 vector<int> vec; 30 // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的 31 for (int i = 0; i < size; i++) { 32 Node* node = que.front(); 33 que.pop(); 34 vec.push_back(node->val); 35 36 for(Node*& child:node->children){ 37 if (child) que.push(child); 38 } 39 } 40 result.push_back(vec); 41 } 42 return result; 43 } 44 };
7.在二叉树的每层找出最大值
7.1.问题描述
给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
链接:https://leetcode.cn/problems/find-largest-value-in-each-tree-row/
7.2.要点
1迭代式层序遍历
7.3.代码实例
略
8.填充每个节点的右侧节点
8.1.问题描述
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
1 struct Node { 2 int val; 3 Node *left; 4 Node *right; 5 Node *next; 6 }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
链接:https://leetcode.cn/problems/populating-next-right-pointers-in-each-node
链接:https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/
8.2.要点
1迭代式层序遍历
8.3.代码实例
略
9.二叉树的最大深度
9.1.问题描述
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
链接:https://leetcode.cn/problems/maximum-depth-of-binary-tree
9.2.要点
1迭代式层序遍历,levelCnt++
9.3.代码实例
略
10.二叉树的最小深度
10.1.问题描述
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
链接:https://leetcode.cn/problems/minimum-depth-of-binary-tree/
10.2.要点
遇到叶子节点就要比较一下深度最小值
1迭代式层序遍历,levelCnt++,与levelMin作比较
10.3.代码实例
略
11.翻转二叉树
11.1.问题描述
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
链接:https://leetcode.cn/problems/invert-binary-tree/
11.2.要点
递归交换左右节点,不可使用中序遍历,可以使用前序和后序遍历。
11.3.代码实例
递归
1 class Solution { 2 public: 3 TreeNode* invertTree(TreeNode* root) { 4 if(root==NULL){ 5 return NULL; 6 } 7 8 TreeNode* tmp=root->left; 9 root->left=root->right; 10 root->right=tmp; 11 12 invertTree(root->left); 13 invertTree(root->right); 14 15 return root; 16 } 17 };
12.遍历总结
12.1.遍历方式
策略上可以分为深度优先、广度优先,
具体实现上可以分为递归和迭代,迭代一般借助队列或栈。
深度优先的迭代实现方式较为麻烦,而且前、中、后的实现策略不一样,这一块需要特别练习。
12.2实现
递归、栈迭代、队列迭代并不对立,都有各自的实现逻辑,具体实现例子可以参考
xxx.问题
xxx.1.问题描述
111
xxx.2.要点
222
xxx.3.代码实例
333