每日算法刷题Day70:10.13:leetcode 二叉树10道题,用时2h
一般有三种遍历方式:
- 先序遍历:见 §2.2 自顶向下 DFS。
- 中序遍历:见 §2.9 二叉搜索树。
- 后序遍历:见 §2.3 自底向上 DFS。
带着问题去做下面的题目: - 1.一般来说,DFS 的递归边界是空节点。在什么情况下,要额外把叶子节点作为递归边界?
- 2.在什么情况下,DFS 需要有返回值?什么情况下不需要有返回值?
- 3.在什么情况下,题目更适合用自顶向下的方法解决?什么情况下更适合用自底向上的方法解决?
一、遍历二叉树
1.套路
2.题目描述
3.学习经验
1. 144. 二叉树的前序遍历(简单,学习)
144. 二叉树的前序遍历 - 力扣(LeetCode)
思想
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. 二叉树的中序遍历(简单,学习迭代版本)
94. 二叉树的中序遍历 - 力扣(LeetCode)
思想
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. 二叉树的后序遍历(简单,学习迭代版本)
145. 二叉树的后序遍历 - 力扣(LeetCode)
思想
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. 叶子相似的树(简单)
872. 叶子相似的树 - 力扣(LeetCode)
思想
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.开幕式焰火
LCP 44. 开幕式焰火 - 力扣(LeetCode)
思想
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.左叶子之和(简单,理解递归)
404. 左叶子之和 - 力扣(LeetCode)
思想
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;} elsegetSum(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;elsereturn pq.top();}
};
二、自顶向下 DFS(先序遍历)
在「递」的过程中维护值(理解)。
有些题目自顶向下和自底向上都可以做。有些题目也可以用 BFS 做。
「在写递归函数时,可以假设递归返回的结果一定是正确的」。其实这种说法本质上就是数学归纳法。
1.套路
2.题目描述
3.学习经验
1. 104. 二叉树的最大深度(简单)
104. 二叉树的最大深度 - 力扣(LeetCode)
思想
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的区别)
111. 二叉树的最小深度 - 力扣(LeetCode)
思想
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. 路径总和(简单)
112. 路径总和 - 力扣(LeetCode)
思想
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);}
};