十三、二叉树 BFS
1.套路
1.每轮队列弹出弹出这一层 的(写代码遍历时i必须从大到小,因为队列大小在变化,遍历终止条件得是常数0)
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
if (root == nullptr) // 根节点为空单独判断
return {};
vector<vector<int>> res;
queue<TreeNode*> que;
que.push(root);
int dep=0; // 深度
while (!que.empty()) {
// 弹出这一层
vector<int> tmp;
// int len=que.size(); // 大小得提前记录
for (int i = que.size()-1; i >=0; --i) {
TreeNode* cur = que.front();
que.pop();
tmp.push_back(cur->val);
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
res.push_back(tmp);
++dep;
}
return res;
}
};
2.另一种遍历方式(记录下一层节点,然后数组指针移动(队列用数组实现))(方便遍历父节点,处理子节点,处理堂兄弟问题,也可以两次遍历):
vector<TreeNode*> que={root}; // 改成数组实现
while (!que.empty()) {
// 记录下一层节点
vector<TreeNode*> nxt;
for(auto node:que){
if(node->left) nxt.push_back(node->left);
if(node->right) nxt.push_back(node->right);
}
que=move(nxt); // 精髓
}
2.题目描述
3.学习经验
1. 102. 二叉树的层序遍历(中等)
思想
1.给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
代码
class Solution {
public:
typedef pair<TreeNode*, int> PTI;
vector<vector<int>> levelOrder(TreeNode* root) {
if (root == nullptr)
return {};
vector<vector<int>> res;
queue<PTI> que;
que.push({root, 0});
while (!que.empty()) {
auto x = que.front();
que.pop();
TreeNode* cur = x.first;
int dep = x.second;
if (res.empty() || res.size() - 1 < dep) {
res.push_back({cur->val});
} else {
res[dep].push_back(cur->val);
}
if (cur->left)
que.push({cur->left, dep + 1});
if (cur->right)
que.push({cur->right, dep + 1});
}
return res;
}
};
2. 103.二叉树的锯齿层序遍历(中等)
103. 二叉树的锯齿形层序遍历 - 力扣(LeetCode)
思想
1.给你二叉树的根节点 root
,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
代码
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
if (root == nullptr)
return {};
vector<vector<int>> res;
queue<TreeNode*> que;
que.push(root);
int isL = 0;
while (!que.empty()) {
// 这一层全遍历
vector<int> tmp;
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
tmp.push_back(cur->val);
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
if (isL)
reverse(tmp.begin(), tmp.end());
res.push_back(tmp);
isL ^= 1;
}
return res;
}
};
3. 107. 二叉树的层序遍历II(中等)
107. 二叉树的层序遍历 II - 力扣(LeetCode)
思想
1.给你二叉树的根节点 root
,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
代码
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
if (root == nullptr)
return {};
vector<vector<int>> res;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
vector<int> tmp;
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
tmp.push_back(cur->val);
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
res.push_back(tmp);
}
reverse(res.begin(), res.end());
return res;
}
};
4. 199. 二叉树的右视图(中等)
思想
1.给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
代码
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
if (root == nullptr)
return {};
vector<int> res;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
if (i == 0)
res.push_back(cur->val);
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
}
return res;
}
};
5. 513. 找树左下角的值(中等)
思想
1.给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
代码
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
int res;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int tmp = que.size() - 1;
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
if (i == tmp)
res = cur->val;
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
}
return res;
}
};
6. 515. 在每个树行中找最大值(中等)
515. 在每个树行中找最大值 - 力扣(LeetCode)
思想
1.给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
代码
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
if (root == nullptr)
return {};
vector<int> res;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int maxn = INT_MIN;
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
maxn = max(maxn, cur->val);
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
res.push_back(maxn);
}
return res;
}
};
7. 637.二叉树的层平均值(简单)
思想
1.定一个非空二叉树的根节点 root
, 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5
以内的答案可以被接受。
代码
class Solution {
public:
typedef long long ll;
vector<double> averageOfLevels(TreeNode* root) {
if (root == nullptr)
return {};
vector<double> res;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
ll sum=0;
int len=que.size();
for (int i = que.size()-1; i >=0; --i) {
TreeNode* cur = que.front();
que.pop();
sum+=cur->val;
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
res.push_back(1.0*sum/len);
}
return res;
}
};
8. 1161.最大层内元素和(中等)
思想
1.给你一个二叉树的根节点 root
。设根节点位于二叉树的第 1
层,而根节点的子节点位于第 2
层,依此类推。
请返回层内元素之和 最大 的那几层(可能只有一层)的层号,并返回其中 最小 的那个。
代码
class Solution {
public:
typedef long long ll;
int maxLevelSum(TreeNode* root) {
int res;
queue<TreeNode*> que;
que.push(root);
ll maxn = LLONG_MIN;
int dep = 0;
while (!que.empty()) {
// 弹出这一层
ll sum = 0;
++dep;
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
sum += cur->val;
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
if (sum > maxn) {
maxn = sum;
res = dep;
}
}
return res;
}
};
9. 993.二叉树的堂兄弟节点(简单)
思想
1.在二叉树中,根节点位于深度 0
处,每个深度为 k
的节点的子节点位于深度 k+1
处。
如果二叉树的两个节点深度相同,但 父节点不同 ,则它们是一对堂兄弟节点。
我们给出了具有唯一值的二叉树的根节点 root
,以及树中两个不同节点的值 x
和 y
。
只有与值 x
和 y
对应的节点是堂兄弟节点时,才返回 true
。否则,返回 false
。
代码
class Solution {
public:
typedef pair<TreeNode*, int> PTI;
bool isCousins(TreeNode* root, int x, int y) {
queue<PTI> que;
que.push({root, -1});
while (!que.empty()) {
bool isX = false, isY = false;
int faX = -1, faY = -1;
for (int i = que.size() - 1; i >= 0; --i) {
auto tmp = que.front();
que.pop();
TreeNode* cur = tmp.first;
int fa = tmp.second;
if (cur->val == x) {
isX = true;
faX = fa;
} else if (cur->val == y) {
isY = true;
faY = fa;
}
if (cur->left)
que.push({cur->left, cur->val});
if (cur->right)
que.push({cur->right, cur->val});
}
if ((isX && !isY) || (!isX && isY))
return false;
if (isX && isY) {
return faX != faY;
}
}
return false;
}
};
10. 2583.二叉树中的第K大层和(中等)
2583. 二叉树中的第 K 大层和 - 力扣(LeetCode)
思想
1.给你一棵二叉树的根节点 root
和一个正整数 k
。
树中的 层和 是指 同一层 上节点值的总和。
返回树中第 k
大的层和(不一定不同)。如果树少于 k
层,则返回 -1
。
注意 ,如果两个节点与根节点的距离相同,则认为它们在同一层。
2.top-k问题
代码
class Solution {
public:
typedef long long ll;
long long kthLargestLevelSum(TreeNode* root, int k) {
ll res;
queue<TreeNode*> que;
que.push(root);
priority_queue<ll, vector<ll>, greater<ll>> pq;
while (!que.empty()) {
// 弹出这一层
ll sum = 0;
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
sum += cur->val;
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
pq.push(sum);
if (pq.size() > k)
pq.pop();
}
if (pq.size() < k)
return -1;
return pq.top();
}
};
11. 1302.层数最深叶子节点的和(中等)
1302. 层数最深叶子节点的和 - 力扣(LeetCode)
思想
1.给你一棵二叉树的根节点 root
,请你返回 层数最深的叶子节点的和 。
代码
class Solution {
public:
int deepestLeavesSum(TreeNode* root) {
if (root == nullptr) // 根节点为空单独判断
return {};
int res;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
// 弹出这一层
int sum = 0;
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
sum += cur->val;
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
res = sum;
}
return res;
}
};
12. 2415.反转二叉树的奇数层(中等)
2415. 反转二叉树的奇数层 - 力扣(LeetCode)
思想
1.给你一棵 完美 二叉树的根节点 root
,请你反转这棵树中每个 奇数 层的节点值。
- 例如,假设第 3 层的节点值是
[2,1,3,4,7,11,29,18]
,那么反转后它应该变成[18,29,11,7,4,3,1,2]
。
反转后,返回树的根节点。
完美 二叉树需满足:二叉树的所有父节点都有两个子节点,且所有叶子节点都在同一层。
节点的 层数 等于该节点到根节点之间的边数。
代码
class Solution {
public:
TreeNode* reverseOddLevels(TreeNode* root) {
if (root == nullptr) // 根节点为空单独判断
return nullptr;
queue<TreeNode*> que;
que.push(root);
int dep = 0;
while (!que.empty()) {
vector<TreeNode*> nodes;
vector<int> vals;
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
nodes.push_back(cur);
vals.push_back(cur->val);
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
if (dep % 2) {
reverse(vals.begin(), vals.end());
for (int j = 0; j < nodes.size(); ++j) {
nodes[j]->val = vals[j];
}
}
++dep;
}
return root;
}
};
13. 1609.奇偶树(中等)
思想
1.如果一棵二叉树满足下述几个条件,则可以称为 奇偶树 :
- 二叉树根节点所在层下标为
0
,根的子节点所在层下标为1
,根的孙节点所在层下标为2
,依此类推。 - 偶数下标 层上的所有节点的值都是 奇 整数,从左到右按顺序 严格递增
- 奇数下标 层上的所有节点的值都是 偶 整数,从左到右按顺序 严格递减
给你二叉树的根节点,如果二叉树为 奇偶树 ,则返回true
,否则返回false
。
代码
class Solution {
public:
bool isEvenOddTree(TreeNode* root) {
queue<TreeNode*> que;
que.push(root);
int dep = 0;
while (!que.empty()) {
// 弹出这一层
int x;
if (dep % 2)
x = INT_MAX;
else
x = INT_MIN;
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
if (dep % 2) {
if (cur->val % 2 != 0 || cur->val >= x)
return false;
} else {
if (cur->val % 2 != 1 || cur->val <= x)
return false;
}
x = cur->val;
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
++dep;
}
return true;
}
};
14. 623. 在二叉树中增加一行(中等)
思想
1.给定一个二叉树的根 root
和两个整数 val
和 depth
,在给定的深度 depth
处添加一个值为 val
的节点行。
注意,根节点 root
位于深度 1
。
加法规则如下:
- 给定整数
depth
,对于深度为depth - 1
的每个非空树节点cur
,创建两个值为val
的树节点作为cur
的左子树根和右子树根。 cur
原来的左子树应该是新的左子树根的左子树。cur
原来的右子树应该是新的右子树根的右子树。- 如果
depth == 1
意味着depth - 1
根本没有深度,那么创建一个树节点,值val
作为整个原始树的新根,而原始树就是新根的左子树。
代码
class Solution {
public:
TreeNode* addOneRow(TreeNode* root, int val, int depth) {
if (depth == 1)
return new TreeNode(val, root, nullptr);
queue<TreeNode*> que;
que.push(root);
int dep = 1; // 深度
while (!que.empty()) {
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
if (dep == depth - 1) {
cur->left = new TreeNode(val, cur->left, nullptr);
cur->right = new TreeNode(val, nullptr, cur->right);
} else {
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
}
++dep;
}
return root;
}
};
15. 2471.逐层排序二叉树所需的最少操作数目(中等)
2471. 逐层排序二叉树所需的最少操作数目 - 力扣(LeetCode)
思想
1.给你一个 值互不相同 的二叉树的根节点 root
。
在一步操作中,你可以选择 同一层 上任意两个节点,交换这两个节点的值。
返回每一层按 严格递增顺序 排序所需的最少操作数目。
节点的 层数 是该节点和根节点之间的路径的边数。
2.本质是[[十三.图论算法-并查集和最小生成树#5. 3551. 数位和排序需要的最小交换次数(中等,学习,技巧结论)]]的置换环问题,可以用并查集做,也可以用vis
数组实现
代码
并查集:
class Solution {
public:
int cnt;
vector<int> fa;
int find(int x) {
if (fa[x] != x)
fa[x] = find(fa[x]);
return fa[x];
}
void merge(int from, int to) {
int x = find(from), y = find(to);
if (x == y)
return;
fa[x] = y;
--cnt;
}
int minimumOperations(TreeNode* root) {
int res = 0;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
map<int, int> valToId;
int id = 0;
vector<int> tmp;
for (int i = que.size() - 1; i >= 0; --i) {
TreeNode* cur = que.front();
que.pop();
valToId[cur->val] = id++;
tmp.push_back(cur->val);
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
sort(tmp.begin(), tmp.end());
int n = tmp.size();
cnt = n;
fa.clear();
fa.resize(cnt);
for (int i = 0; i < n; ++i)
fa[i] = i;
for (int i = 0; i < n; ++i) {
int j = valToId[tmp[i]];
merge(i, j);
}
res += n - cnt;
}
return res;
}
};
vis数组:
sort(tmp.begin(), tmp.end());
int n = tmp.size();
map<int, int> idToId;
for (int i = 0; i < n; ++i) {
int j = valToId[tmp[i]];
idToId[i] = j; // 下标映射,构建环
}
res += n;
vector<bool> vis(n, false);
for (int i = 0; i < n; ++i) {
if (vis[i])
continue;
// 显式走环
while (!vis[i]) {
vis[i] = true;
i = idToId[i]; // 走到下一个
}
res -= 1; // 减去一个环
}
16. 2641. 二叉树的堂兄弟节点II(中等,xuex)
2641. 二叉树的堂兄弟节点 II - 力扣(LeetCode)
思想
1.给你一棵二叉树的根 root
,请你将每个节点的值替换成该节点的所有 堂兄弟节点值的和 。
如果两个节点在树中有相同的深度且它们的父节点不同,那么它们互为 堂兄弟 。
请你返回修改值之后,树的根 root
。
注意 ,一个节点的深度指的是从树根节点到这个节点经过的边数。
2.一次遍历要map记录父节点与子节点关系,会错误,应该两次遍历,第一次记录孩子那一层总和,第二次遍历记录孩子总和并赋值
代码
一次遍历:
class Solution {
public:
typedef long long ll;
typedef pair<TreeNode*, int> PTI;
TreeNode* replaceValueInTree(TreeNode* root) {
queue<PTI> que;
que.push({root, -1});
while (!que.empty()) {
// 弹出这一层
ll sum = 0;
set<int> fas;
map<int, vector<TreeNode*>> faToChis;
map<int, ll> faToSums;
for (int i = que.size() - 1; i >= 0; --i) {
auto x = que.front();
que.pop();
TreeNode* cur = x.first;
int fa = x.second;
sum += cur->val;
faToChis[fa].push_back(cur);
faToSums[fa] += cur->val;
fas.insert(fa);
if (cur->left)
que.push({cur->left, cur->val});
if (cur->right)
que.push({cur->right, cur->val});
}
for (auto& fa : fas) {
auto chis = faToChis[fa];
ll chiSum = faToSums[fa];
ll newVal = sum - chiSum;
for (auto& cur : chis) {
cur->val = newVal;
}
}
}
return root;
}
};
两次遍历:
class Solution {
public:
typedef long long ll;
TreeNode* replaceValueInTree(TreeNode* root) {
root->val = 0;
vector<TreeNode*> que;
que.push_back(root);
while (!que.empty()) {
vector<TreeNode*> nxt;
ll sum = 0;
for (auto node : que) {
if (node->left) {
nxt.push_back(node->left);
sum += node->left->val;
}
if (node->right) {
nxt.push_back(node->right);
sum += node->right->val;
}
}
for (auto node : que) {
ll chiSum = 0;
if (node->left) {
chiSum += node->left->val;
}
if (node->right) {
chiSum += node->right->val;
}
if (node->left) {
node->left->val = sum - chiSum;
}
if (node->right) {
node->right->val = sum - chiSum;
}
}
que = move(nxt);
}
return root;
}
};
17. 919. 完全二叉树插入器(中等)
思想
1.完全二叉树 是每一层(除最后一层外)都是完全填充(即,节点数达到最大)的,并且所有的节点都尽可能地集中在左侧。
设计一种算法,将一个新节点插入到一棵完全二叉树中,并在插入后保持其完整。
实现 CBTInserter
类:
CBTInserter(TreeNode root)
使用头节点为root
的给定树初始化该数据结构;CBTInserter.insert(int v)
向树中插入一个值为Node.val == val
的新节点TreeNode
。使树保持完全二叉树的状态,并返回插入节点TreeNode
的父节点的值;CBTInserter.get_root()
将返回树的头节点。
2.利用完全二叉树的性质 :
根节点序号x,左孩子2*x
,右孩子2*x+1
,(x从1开始计数)
3.或者队列实现,初始化层序遍历存储叶子节点(倒数第二层的右侧和最后一层的左侧),然后新插入节点进入队尾,队首若有左右子树则出队列
代码
class CBTInserter {
public:
TreeNode* curRoot;
int size;
vector<TreeNode*> nodes;
CBTInserter(TreeNode* root) {
curRoot = root;
nodes.push_back(nullptr);
size = 0;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
TreeNode* cur = que.front();
que.pop();
++size;
nodes.push_back(cur);
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
}
int insert(int val) {
++size;
int fa = size / 2;
TreeNode* faNode = nodes[fa];
TreeNode* newNode = new TreeNode(val);
nodes.push_back(newNode);
if (size % 2)
faNode->right = newNode;
else
faNode->left = newNode;
return faNode->val;
}
TreeNode* get_root() { return curRoot; }
};
18. 958. 二叉树的完全性检验(中等)
思想
1.给你一棵二叉树的根节点 root
,请你判断这棵树是否是一棵 完全二叉树 。
在一棵 完全二叉树 中,除了最后一层外,所有层都被完全填满,并且最后一层中的所有节点都尽可能靠左。最后一层(第 h
层)中可以包含 1
到 2h
个节点。
2.利用一个bool
变量来划分非叶子节点与叶子节点
代码
class Solution {
public:
bool isCompleteTree(TreeNode* root) {
bool isLeaf = false;
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
TreeNode* cur = que.front();
que.pop();
if (isLeaf) {
if (cur->left || cur->right)
return false;
} else {
if (cur->left == nullptr && cur->right != nullptr) {
return false;
} else if (
cur->right ==
nullptr) { // 只要右子树为空(左子树无所谓),则该节点之后剩下节点都是叶子节点
isLeaf = true;
}
if (cur->left)
que.push(cur->left);
if (cur->right)
que.push(cur->right);
}
}
return true;
}
};