每日算法刷题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.有两个操作:

  1. 处理:将节点元素放进result数组中
  2. 访问:遍历节点
    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) 的树。

如果有两棵二叉树的叶值序列是相同,那么我们就认为它们是 叶相似 的。

如果给定的两个根结点分别为 root1root2 的树是叶相似的,则返回 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;
        } 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.给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 20。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。

更正式地说,即 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. 二叉树的最大深度(简单)

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);
    }
};
相关推荐
ghie90903 小时前
基于MATLAB的遗传算法优化支持向量机实现
算法·支持向量机·matlab
朝新_4 小时前
【优选算法】第一弹——双指针(上)
算法
艾莉丝努力练剑4 小时前
【C++STL :stack && queue (一) 】STL:stack与queue全解析|深入使用(附高频算法题详解)
linux·开发语言·数据结构·c++·算法
CoovallyAIHub4 小时前
ICLR 2026 惊现 SAM 3,匿名提交,实现“概念分割”,CV领域再迎颠覆性突破?
深度学习·算法·计算机视觉
IT古董4 小时前
【第五章:计算机视觉-计算机视觉在工业制造领域中的应用】1.工业缺陷分割-(2)BiseNet系列算法详解
算法·计算机视觉·制造
电鱼智能的电小鱼4 小时前
服装制造企业痛点解决方案:EFISH-SBC-RK3588 预测性维护方案
网络·人工智能·嵌入式硬件·算法·制造
yan8626592465 小时前
于 C++ 的虚函数多态 和 模板方法模式 的结合
java·开发语言·算法
小此方5 小时前
C语言自定义变量类型结构体理论:从初见到精通(下)
c语言·数据结构·算法
_poplar_6 小时前
15 【C++11 新特性】统一的列表初始化和变量类型推导
开发语言·数据结构·c++·git·算法