每日算法刷题Day77:10.22:leetcode 二叉树bfs18道题,用时3h
十三、二叉树 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. 二叉树的层序遍历(中等)
102. 二叉树的层序遍历 - 力扣(LeetCode)
思想
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. 二叉树的右视图(中等)
199. 二叉树的右视图 - 力扣(LeetCode)
思想
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. 找树左下角的值(中等)
513. 找树左下角的值 - 力扣(LeetCode)
思想
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.二叉树的层平均值(简单)
637. 二叉树的层平均值 - 力扣(LeetCode)
思想
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.最大层内元素和(中等)
1161. 最大层内元素和 - 力扣(LeetCode)
思想
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.二叉树的堂兄弟节点(简单)
993. 二叉树的堂兄弟节点 - 力扣(LeetCode)
思想
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.奇偶树(中等)
1609. 奇偶树 - 力扣(LeetCode)
思想
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;elsex = 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. 在二叉树中增加一行(中等)
623. 在二叉树中增加一行 - 力扣(LeetCode)
思想
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. 完全二叉树插入器(中等)
919. 完全二叉树插入器 - 力扣(LeetCode)
思想
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;elsefaNode->left = newNode;return faNode->val;}TreeNode* get_root() { return curRoot; }
};
18. 958. 二叉树的完全性检验(中等)
958. 二叉树的完全性检验 - 力扣(LeetCode)
思想
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;}
};