目录
🌼前序与中序遍历构造二叉树
105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)
AC 递归
1,递归出口,区间不存在 2,明确 pre, in 区间的左右端点( dfs()的参数 )
3,递归建树,令 root->left = dfs(), root->right = dfs(), return root
4,借助 unordered_map,找到中序 in,根节点位置
class Solution {
public:
unordered_map<int, int> index; // 中序 <值, 位置>
TreeNode* dfs(const vector<int>& pre, const vector<int>&in,
int pre_left, int pre_right, int in_left, int in_right) {
// 只需判断 pre 区间为空,因为 in 区间根据 得出
if (pre_left > pre_right)
return nullptr; // 递归出口
// in 区间,根节点位置
int in_root = index[pre[pre_left]];
int num = in_root - in_left; // 左子树节点数
// 根据上述变量,明确 pre 和 in 区间的范围
// 递归建树,注意 root->left / right = ...
TreeNode* root = new TreeNode(pre[pre_left]); // 当前根节点
root->left = dfs(pre, in, pre_left + 1, pre_left + num, in_left, in_left + num - 1);
root->right = dfs(pre, in, pre_left + num + 1, pre_right, in_root + 1, in_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
for (int i = 0; i < n; ++i)
index[inorder[i]] = i; // 中序 <值, 位置>
return dfs(preorder, inorder, 0, n - 1, 0, n - 1);
}
};
🥤路径总和 III
AC 暴力DFS
每个节点 10^9,10^3个节点,最大可达 10^12,将所有 int 改为 long long
时间 O(n^2):每个节点最大遍历节点数 n,共 n 个节点
空间 O(n):递归栈深度
class Solution {
public:
// 当前节点出发的路径总数
long long rootSum(TreeNode* root, long long targetSum) {
if (!root) return 0; // 递归出口
long long ret = 0;
if (root->val == targetSum)
ret++; // 当前路径/节点符合
// 递归左右子树
// 初始 ret += 的是 root 开始,整条路径上所有符合条件的点
ret += rootSum(root->left, targetSum - root->val);
ret += rootSum(root->right, targetSum - root->val);
return ret;
}
// 整个二叉树的路径总数
long long pathSum(TreeNode* root, long long targetSum) {
if (!root) return 0; // 递归出口
// 根节点路径数
long long ret = rootSum(root, targetSum);
// 递归左右子树
ret += pathSum(root->left, targetSum);
ret += pathSum(root->right, targetSum);
return ret;
}
};
AC 前缀和
解析
1,哈希表:得到路径和的时间,O(n) 降到 O(1)
unordered_map<long long, int> prefix,键 -- 前缀和,值 -- 该前缀和出现次数
这里的前缀和,和一维数组的前缀和一样,就是累积和,即“根节点出发的累积和”
举个例子:根节点出发,到节点 c 的前缀和为 10,且 prefix[10] == 3
表示 root节点 到 c节点 这条路径上,前缀和为 10 的不同路线有 3 段
2,逻辑:前缀和的差 == targetSum
当前节点 a 的前缀和 curr,目标路径和 targetSum
curr:root 到 a 的路径和,假设另一个节点 b 的路径和为 curr_b(b 在 root 和 a 之间)
那么当 curr - curr_b == targetSum 时,表示 a--->b 这条路线,满足要求
所以 prefix[curr]++; 表示 根节点 root 到 当前节点 a 之间,满足条件的路线 +1
而 b 是在遍历到 a 之前,就加入了哈希表 prefix 的,可以直接 prefix.count(...)
3,回溯
当前节点处理完后,需要从哈希表 prefix 移除这一前缀和,再进入下一分支路径
即,左右子树递归完后,需要回溯,来保证,当前路径的前缀和,不会作为另一路径的前提
而本题,“路径方向必须是向下的(只能从父节点到子节点)”,只能算一条往下的路线,而不能中途分叉,限制了访问的方向,分叉会影响结果,需要多次经过同一个节点
所以需要回溯,需要 prefex[curr]--; 来取消标记
4,初始化
prefix[0] = 1;
后续匹配的前提,当 curr - targetSum == 0,表示当前节点前缀和 == targetSum
prefix.count(curr - targetSum) >= 1
此时 ret = prefix[0],即 ret = 1,然后才是 prefix[0]++
防止包含根节点时找不到
代码
时间 O(n),空间 O(n)
class Solution {
public:
unordered_map<long long, int> prefix; // <前缀和, 相同前缀和的路径数>
int dfs(TreeNode* root, long long curr, int targetSum) {
if (!root) return 0; // 递归出口
curr += root->val; // 当前节点 前缀和
int ret = 0;
// 节点 a, b 间的路径和 满足要求
if (prefix.count(curr - targetSum))
ret = prefix[curr - targetSum];
// 标记
prefix[curr]++; // 路径数 +1
// 递归
ret += dfs(root->left, curr, targetSum);
ret += dfs(root->right, curr, targetSum);
// 取消标记(回溯)
prefix[curr]--;
return ret; // 返回累加结果
}
int pathSum(TreeNode* root, int targetSum) {
prefix[0] = 1; // 初始化:包含根节点的路径
return dfs(root, 0, targetSum);
}
};
🎂二叉树的最近公共祖先
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
AC 递归
灵神讲的太好了👇
时间 O(n),空间 O(n)
/**
* 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:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// 递归出口,返回当前节点
if (!root || root == p || root == q)
return root;
// 递归
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
// 1) 分别在左右子树找到 p, q
if (left && right)
return root; // 返回当前节点
// 2) 只有左子树找到 p, q
if (left)
return left;
// 3) 只有右子树找到 p, q
if (right)
return right;
// 左右子树都没有 p, q
return nullptr; // 返回空节点
}
};
🌼二叉树的最大路径和
124. 二叉树中的最大路径和 - 力扣(LeetCode)
AC 递归
解析
题目中 “一条路径” 的意思是,不能分叉,只能一次从头走到尾
所有树的题目,都可以将大的树,想象成一棵小树:根,左,右
按本题,一棵只有 3 个节点的树,只有以下 6 种情况:
不用向上累加
case 1: 根 左 右
case 2: 左
case 3: 右
向上累加
case 4: 根 左
case 5: 根 右
case 6: 根
是否向上累加,是因为,题目要求 “一条路径”
最后将 不用向上累加 和 向上累加 取最大值
代码
时间 O(n),空间 O(n)
class Solution {
public:
int ans = -1000; // case 1,2,3(不用向上累加) 最大值
int dfs(TreeNode* root, int& acc) {
// 递归出口
if (!root) return -1000; // 递归出口
// 递归
int left = dfs(root->left, acc);
int right = dfs(root->right, acc);
// case 1,2,3 (根左右,左,右)
int temp = max(root->val + left + right, max(left, right));
ans = max(ans, temp);
// case 4,5,6(根左,根右,根)
acc = root->val + max(left, right);
return max(acc, root->val);
}
int maxPathSum(TreeNode* root) {
int acc = dfs(root, acc); // case 4,5,6(向上累加)最大值
return max(acc, ans); // 返回两种情况最大值
}
};