【Warrior刷题笔记】剑指offer 32. 三道题,让你学会二叉树的深度广度优先遍历与递归迭代技术

时间:2023-03-09 22:32:34
【Warrior刷题笔记】剑指offer 32. 三道题,让你学会二叉树的深度广度优先遍历与递归迭代技术

题目一 剑指 Offer 32 - I. 从上到下打印二叉树

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/

1.描述

从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

2.示例

  • 示例 1:
给定二叉树: [3,9,20,null,null,15,7],

    3
/ \
9 20
/ \
15 7
返回: [3,9,20,15,7]

解法一 广度优先遍历+辅助队列

解题思路

看到题不难想到最简单的办法就是借助一个队列,对二叉树进行广度优先遍历。

1.如果根节点为空,直接返回空数组,否则入队根节点;

2.将队头节点值加入答案数组;

3.入队队头节点的非空子节点,将队头节点出队。

4.重复2,3过程直到队列为空。

代码

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> levelOrder(TreeNode* root) {
vector<int> ans;//存储答案
if(!root) return ans;//如果根节点为空,返回空答案数组
queue<TreeNode*> q;//辅助队列
q.push(root);//根节点入队
while(!q.empty()){//只要队列不为空
ans.push_back(q.front()->val);//将队头节点值加入答案数组
//入队队头节点的非空子节点
if(q.front()->left) q.push(q.front()->left);
if(q.front()->right) q.push(q.front()->right);
q.pop();//将队头节点出队
}
return ans;//返回答案
}
};

复杂度分析

时间复杂度: O(m)m二叉树节点数,遍历整棵二叉树需要O(m)时间。

空间复杂度: O(m)。存储答案和使用辅助队列的空间消耗。

解法二 深度优先搜索+辅助数组

解题思路

除了解法一较为直观的广度优先搜索外,我们也可以使用深度优先搜索+辅助二维数组解决本题。

具体的,我们对二叉树进行深度优先搜索,同时维护一个层数level,初始时level0。每进入一层递归,也即从当前节点遍历孩子节点时,层数就加一,然后通过层数作为下标访问二维数组将节点值加入对应层的一维数组,最后再将二维数组按层捋直即为最终答案。

代码

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> levelOrder(TreeNode* root) {
vector<int> ans;//存储答案
if(!root) return ans;//如果根节点为空,返回空数组
vector<vector<int>> aid;//辅助数组
dfs(root, 0, aid);//深度优先遍历
for(auto a : aid) ans.insert(ans.end(), a.begin(), a.end());//将二维数组按层捋直
return ans; //返回答案
} void dfs(TreeNode* root, int level, vector<vector<int>>& aid){
if(!root) return;//如果节点为空,返回
if(aid.size()<=level){//如果该层数组还未创建,创建之
vector<int> temp;
aid.push_back(temp);
}
aid[level].push_back(root->val);//将节点值加入对应层数组
dfs(root->left, level+1, aid);//遍历左子节点,同时level+1
dfs(root->right, level+1, aid);//遍历右子节点,同时level+1
}
};

复杂度分析

时间复杂度: O(m)。递归以及把二维数组捋直的时间消耗

空间复杂度: O(m)。递归以及辅助数组、答案数组的空间消耗。

题目二 剑指 Offer 32 - II. 从上到下打印二叉树 II

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/

1.描述

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

2.示例

  • 示例 1:
给定二叉树: [3,9,20,null,null,15,7],

    3
/ \
9 20
/ \
15 7
返回其层次遍历结果: [
[3],
[9,20],
[15,7]
]

解法一 广度优先搜索+辅助队列

解题思路

参考题目一,我们可以写出广度优先搜索+辅助队列的版本。

不过略有不同的是,我们需要选择合适的方式进行分层。这里使用两个额外变量countsize进行分层操作,对每一层,size为层大小,初始时为1,即只有根节点的时候队列的大小。每记录一个节点,count值加一,当count等于size时,表示当前层已遍历完毕,则更新层大小,重置count0

1.如果根节点为空,直接返回空数组,否则入队根节点;

2.将队头节点值加入答案数组;

3.入队队头节点的非空子节点,将队头节点出队;

4.将count加一,若count等于size,则更新size,并重置count0

5.重复2,34过程直到队列为空。

代码

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;//存储答案
if(!root) return ans;//如果根节点为空,返回空数组
queue<TreeNode*> q;//辅助队列
q.push(root);//入队根节点
int size = q.size(), count = 0;//初始化size和count
vector<int> temp;//辅助数组
while(!q.empty()){//只要队列不为空
temp.push_back(q.front()->val);//记录队头节点值
//入队队头节点的非空子节点
if(q.front()->left) q.push(q.front()->left);
if(q.front()->right) q.push(q.front()->right);
q.pop();//出队队头节点
++count;//将count+1
if(count == size){//若count==size,表示当前层已记录完毕
ans.push_back(temp);//将temp加入答案数组
temp.clear();//清空temp
count = 0;//重置count为0
size = q.size();//更新size
}
}
return ans;//返回答案
}
};

复杂度分析

时间复杂度: O(m)m为二叉树节点数,遍历整棵二叉树需要O(m)时间。

空间复杂度: O(m)。辅助队列,答案数组,辅助数组的空间消耗。

解法二 深度优先搜索+辅助数组

解题思路

除了解法一较为直观的广度优先搜索外,我们也可以使用深度优先搜索+辅助二维数组解决本题。

具体的,我们对二叉树进行深度优先搜索,同时维护一个层数level,初始时level0。每进入一层递归,也即从当前节点遍历孩子节点时,层数就加一,然后通过层数作为下标访问二维数组将节点值加入对应层的一维数组,遍历完成后即为最终答案。

代码

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;//存储答案
if(!root) return ans;//如果根节点为空,返回空数组
dfs(root, 0, ans);//深度优先遍历
return ans;//返回答案
} void dfs(TreeNode* root, int level, vector<vector<int>>& aid){//深度优先遍历
if(!root) return;//如果节点为空,直接返回
if(aid.size()<=level){//如果当前层数组还未创建,创建之
vector<int> temp;
aid.push_back(temp);
}
aid[level].push_back(root->val);//存入当前节点值到对应层
dfs(root->left, level+1, aid);//遍历左孩子,层数加一
dfs(root->right, level+1, aid);//遍历右孩子,层数加一
}
};

复杂度分析

时间复杂度: O(m)。遍历二叉树的时间消耗。

空间复杂度: O(m)。存储答案和递归的栈空间消耗。

题目三 剑指 Offer 32 - III. 从上到下打印二叉树 III

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/

1.描述

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

2.示例

  • 示例 1:
给定二叉树: [3,9,20,null,null,15,7],

    3
/ \
9 20
/ \
15 7
返回其层次遍历结果: [
[3],
[20,9],
[15,7]
]

解法一 广度优先搜索+辅助队列

解题思路

参考题目二,我们可以写出广度优先搜索+辅助队列的版本。

我们需要选择合适的方式进行分层。这里使用两个额外变量countsize进行分层操作,对每一层,size为层大小,初始时为1,即只有根节点的时候队列的大小。每记录一个节点,count值加一,当count等于size时,表示当前层已遍历完毕,则更新层大小,重置count0。不过略有不同的是,我们需要在遍历完毕后对结果数组进行处理,也即翻转偶数层数组。处理完毕后即为答案。

1.如果根节点为空,直接返回空数组,否则入队根节点;

2.将队头节点值加入答案数组;

3.入队队头节点的非空子节点,将队头节点出队;

4.将count加一,若count等于size,则更新size,并重置count0

5.重复2,34过程直到队列为空;

6.翻转偶数层结果数组。

代码

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;//存储答案
if(!root) return ans;//如果根节点为空,返回空数组
queue<TreeNode*> q;//辅助队列
q.push(root);//入队根节点
int size = q.size(), count = 0;//初始化size和count
vector<int> temp;//辅助数组
while(!q.empty()){//只要队列不为空
temp.push_back(q.front()->val);//记录队头节点值
//入队队头节点的非空子节点
if(q.front()->left) q.push(q.front()->left);
if(q.front()->right) q.push(q.front()->right);
q.pop();//出队队头节点
++count;//将count+1
if(count == size){//若count==size,表示当前层已记录完毕
ans.push_back(temp);//将temp加入答案数组
temp.clear();//清空temp
count = 0;//重置count为0
size = q.size();//更新size
}
}
for(int i = 0; i < m; ++i) if(i%2!=0) reverse(ans[i].begin(), ans[i].end());//翻转偶数层
return ans;//返回答案
}
};

复杂度分析

时间复杂度: O(m)m为二叉树节点数,遍历整棵二叉树和翻转偶数层需要O(m)时间。

空间复杂度: O(m)。辅助队列,答案数组,辅助数组的空间消耗。

解法二 深度优先遍历+辅助数组

解题思路

这里也提供深度优先遍历的版本。

代码

/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;//存储答案
if(!root) return ans;//如果根节点为空,返回空数组
dfs(root, 0, ans);//深度优先遍历
for(int i = 0; i < m; ++i) if(i%2!=0) reverse(ans[i].begin(), ans[i].end());//翻转偶数层
return ans;//返回答案
} void dfs(TreeNode* root, int level, vector<vector<int>>& aid){//深度优先遍历
if(!root) return;//如果节点为空,直接返回
if(aid.size()<=level){//如果当前层数组还未创建,创建之
vector<int> temp;
aid.push_back(temp);
}
aid[level].push_back(root->val);//存入当前节点值到对应层
dfs(root->left, level+1, aid);//遍历左孩子,层数加一
dfs(root->right, level+1, aid);//遍历右孩子,层数加一
}
};

复杂度分析

时间复杂度: O(m)。遍历二叉树和翻转偶数层的时间消耗。

空间复杂度: O(m)。存储答案和递归的栈空间消耗。

一般说来,广度优先遍历更直观,但是代码较长;而深度优先遍历代码简洁,但是较难理解。要多用多练,以掌握之。

更多知识内容分享:

博客园个人主页https://home.cnblogs.com/u/newCoderTheWarrior

力扣个人主页https://leetcode-cn.com/profile/articles/

【Warrior刷题笔记】剑指offer 32. 三道题,让你学会二叉树的深度广度优先遍历与递归迭代技术