一般有三种遍历方式:
- 先序遍历:见 §2.2 自顶向下 DFS。
- 中序遍历:见 §2.9 二叉搜索树。
- 后序遍历:见 §2.3 自底向上 DFS。
带着问题去做下面的题目: - 1.一般来说,DFS 的递归边界是空节点。在什么情况下,要额外把叶子节点作为递归边界?
- 2.在什么情况下,DFS 需要有返回值?什么情况下不需要有返回值?
- 3.在什么情况下,题目更适合用自顶向下 的方法解决?什么情况下更适合用自底向上的方法解决?
一、遍历二叉树
1.套路
2.题目描述
3.学习经验
1. 144. 二叉树的前序遍历(简单,学习)
思想
1.给你二叉树的根节点 root
,返回它节点值的 前序 遍历。
2.前序遍历:根-左-右
3.重要的是
(1)树怎么构建?
struct TreeNode{
int val;
TreeNode* left;
TreeNode* right;
TreeNode():val(0),left(nullptr),right(nullptr){}
TreeNode(int _val):val(_val),left(nullptr),right(nullptr){}
TreeNode(int _val,TreeNode* _left,TreeNode* _right):val(_val),left(_left),right(_right){}
};
疑问,算法竞赛中怎么输入树?
(2)递归三要素:
- 1.递归函数返回值和参数
- 2.递归函数终止条件
- 3,单层递归逻辑
代码
递归版本:
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution {
public:
void preOrder(TreeNode* cur, vector<int>& res) {
// 递归终止条件
if (cur == nullptr)
return;
res.push_back(cur->val); // 根
preOrder(cur->left, res); // 左
preOrder(cur->right, res); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
preOrder(root, res);
return res;
}
};
迭代版本:
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<TreeNode*> stk; // 显式栈
vector<int> res;
if (root == nullptr)
return {};
stk.push_back(root);
while (!stk.empty()) {
auto cur = stk.back();
stk.pop_back();
res.push_back(cur->val);
// 入栈先右后左
if (cur->right)
stk.push_back(cur->right);
if (cur->left)
stk.push_back(cur->left);
}
return res;
}
};
2. 94. 二叉树的中序遍历(简单,学习迭代版本)
思想
1.给你二叉树的根节点 root
,返回它节点值的 中序 遍历。
2.有两个操作:
- 处理:将节点元素放进result数组中
- 访问:遍历节点
3.中序为左-根-右,所以当前遍历访问的根不是要处理的节点,所以要再用一个指针来进行显示访问,而栈用来处理节点元素
- (1)指针为空,指针为栈顶元素,指针元素进入答案数组,指针更新为右节点
- (2)指针不为空,指针入栈,指针更新为左节点
代码
递归版本:
class Solution {
public:
void inOrder(TreeNode* cur,vector<int>& res){
if(cur==nullptr) return;
inOrder(cur->left,res);
res.push_back(cur->val);
inOrder(cur->right,res);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
inOrder(root,res);
return res;
}
};
迭代版本:
vector<int> inorderTraversal(TreeNode* root) {
vector<TreeNode*> stk; // 显式栈,处理顺序
vector<int> res;
if (root == nullptr)
return {};
TreeNode* cur = root; // 访问顺序
while (cur != nullptr || !stk.empty()) {
if (cur != nullptr) {
stk.push_back(cur);
cur = cur->left; // 左
} else {
cur = stk.back();
stk.pop_back(); // 处理元素
res.push_back(cur->val); // 中
cur = cur->right; // 右
}
}
return res;
}
3. 145. 二叉树的后序遍历(简单,学习迭代版本)
思想
1.给你二叉树的根节点 root
,返回它节点值的 后序 遍历。
2.迭代版本:
后序:左右中->(反转)中右左->按照先序迭代版本改动即可
代码
递归版本
class Solution {
public:
void postOrder(TreeNode* cur, vector<int>& res) {
if (cur == nullptr)
return;
postOrder(cur->left, res);
postOrder(cur->right, res);
res.push_back(cur->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
postOrder(root, res);
return res;
}
};
迭代版本:
vector<int> postorderTraversal(TreeNode* root) {
if (root == nullptr)
return {};
vector<int> res;
vector<TreeNode*> stk;
stk.push_back(root);
while (!stk.empty()) {
TreeNode* cur = stk.back();
stk.pop_back();
// 中-右-左
res.push_back(cur->val);
if (cur->left)
stk.push_back(cur->left);
if (cur->right)
stk.push_back(cur->right);
}
// 反转,左-右-中
reverse(res.begin(), res.end());
return res;
}
4. 872. 叶子相似的树(简单)
思想
1.请考虑一棵二叉树上所有的叶子,这些叶子的值按从左到右的顺序排列形成一个 叶值序列 。
举个例子,如上图所示,给定一棵叶值序列为 (6, 7, 4, 9, 8)
的树。
如果有两棵二叉树的叶值序列是相同,那么我们就认为它们是 叶相似 的。
如果给定的两个根结点分别为 root1
和 root2
的树是叶相似的,则返回 true
;否则返回 false
。
代码
class Solution {
public:
void find(TreeNode* cur, vector<int>& res) {
if (cur->left == nullptr && cur->right == nullptr) {
res.push_back(cur->val);
return;
}
if (cur->left)
find(cur->left, res);
if (cur->right)
find(cur->right, res);
}
bool leafSimilar(TreeNode* root1, TreeNode* root2) {
vector<int> res1, res2;
find(root1, res1);
find(root2, res2);
return res1 == res2;
}
};
5. LCP 44.开幕式焰火
思想
1.「力扣挑战赛」开幕式开始了,空中绽放了一颗二叉树形的巨型焰火。 给定一棵二叉树 root
代表焰火,节点值表示巨型焰火这一位置的颜色种类。请帮小扣计算巨型焰火有多少种不同的颜色。
代码
class Solution {
public:
void dfs(TreeNode* cur, set<int>& res) {
if (cur == NULL)
return;
res.insert(cur->val);
dfs(cur->left, res);
dfs(cur->right, res);
}
int numColor(TreeNode* root) {
set<int> res;
dfs(root, res);
return res.size();
}
};
6. 404.左叶子之和(简单,理解递归)
思想
1.给定二叉树的根节点 root
,返回所有左叶子之和。
2.获取左右子树的答案,再把当前节点加入当前节点答案中
代码
class Solution {
public:
bool isLeft(TreeNode* cur) {
return cur->left == nullptr && cur->right == nullptr;
}
void getSum(TreeNode* cur, int& sum) {
if (cur == nullptr)
return;
if (cur->left != nullptr && isLeft(cur->left)) {
sum += cur->left->val;
} else
getSum(cur->left, sum);
getSum(cur->right, sum);
}
int sumOfLeftLeaves(TreeNode* root) {
int res = 0;
getSum(root, res);
return res;
}
};
更简洁:
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (root == nullptr)
return 0;
int sum = sumOfLeftLeaves(root->left) + sumOfLeftLeaves(root->right);
TreeNode* l = root->left;
if (l != nullptr && l->left == nullptr && l->right == nullptr) {
sum += l->val;
}
return sum;
}
};
7. 671. 二叉树中第二小的节点(简单)
671. 二叉树中第二小的节点 - 力扣(LeetCode)
思想
1.给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2
或 0
。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。
更正式地说,即 root.val = min(root.left.val, root.right.val)
总成立。
给出这样的一个二叉树,你需要输出所有节点中的 第二小的值 。
如果第二小的值不存在的话,输出 -1 。
代码
class Solution {
public:
void find(TreeNode* cur, priority_queue<int>& pq, set<int>& st) {
if (cur == nullptr)
return;
if (cur->left == nullptr && cur->right == nullptr) {
if (!st.count(cur->val)) {
pq.push(cur->val);
if (pq.size() > 2)
pq.pop();
st.insert(cur->val);
}
return;
}
find(cur->left, pq, st);
find(cur->right, pq, st);
}
int findSecondMinimumValue(TreeNode* root) {
priority_queue<int> pq;
set<int> st;
find(root, pq, st);
if (pq.size() < 2)
return -1;
else
return pq.top();
}
};
二、自顶向下 DFS(先序遍历)
在「递」的过程中维护值(理解)。
有些题目自顶向下和自底向上都可以做。有些题目也可以用 BFS 做。
「在写递归函数时,可以假设递归返回的结果一定是正确的」。其实这种说法本质上就是数学归纳法。
1.套路
2.题目描述
3.学习经验
1. 104. 二叉树的最大深度(简单)
思想
1.给定一个二叉树 root
,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
代码
自顶向下:
class Solution {
public:
int res;
void dfs(TreeNode* cur, int sum) {
if (cur == nullptr)
return;
// 访问该节点
sum += 1;
res = max(res, sum);
dfs(cur->left, sum);
dfs(cur->right, sum);
}
int maxDepth(TreeNode* root) {
res = 0;
dfs(root, 0);
return res;
}
};
自底向上:
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr)
return 0;
return max(maxDepth(root->left), maxDepth(root->right)) + 1; // 后处理节点
}
};
2. 111. 二叉树的最小深度(简单,理解自底向上与1的区别)
思想
1.给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
2.自底向上空节点返回0,因为是min,所以直接用左右节点的min会取到空节点的0 (max就无影响),所以要分类讨论左右节点是否为空
代码
自顶向下:
class Solution {
public:
int res;
void dfs(TreeNode* cur, int sum) {
if (cur == nullptr || ++sum>=res) // 最优性剪枝
return;
if (cur->left == nullptr && cur->right == nullptr) {
res = min(res, sum);
return;
}
dfs(cur->left, sum);
dfs(cur->right, sum);
}
int minDepth(TreeNode* root) {
res = INT_MAX;
dfs(root, 0);
if (res == INT_MAX)
return 0;
return res;
}
};
自底向上:
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == nullptr)
return 0;
if (root->right == nullptr) {
return minDepth(root->left) + 1;
}
if (root->left == nullptr) {
return minDepth(root->right) + 1;
}
return min(minDepth(root->left), minDepth(root->right)) + 1;
}
};
3. 路径总和(简单)
思想
1.给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
叶子节点 是指没有子节点的节点。
代码
自顶向下:
class Solution {
public:
bool dfs(TreeNode* cur, int sum, int targetSum) {
sum += cur->val;
if (cur->left == nullptr && cur->right == nullptr) {
return sum == targetSum;
}
if ((cur->left != nullptr && dfs(cur->left, sum, targetSum)) ||
(cur->right != nullptr && dfs(cur->right, sum, targetSum)))
return true;
return false;
}
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr)
return false;
return dfs(root, 0, targetSum);
}
};
简洁(不是自底向上,因为是先处理节点,再调用,在递时维护变量):
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr)
return false;
targetSum -= root->val; // 先处理节点
if (root->left == nullptr && root->right == nullptr) {
return targetSum == 0;
}
return hasPathSum(root->left, targetSum) ||
hasPathSum(root->right, targetSum);
}
};