题目:树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
1 代码准备
首先在 vscode 中建立二叉树,代码如下
#include<vector>
#include<queue>
#include<iostream>
using namespace std;
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) {}
};
TreeNode* creatTree(vector<int> nums, int i) {
if (i >= nums.size() || nums[i] == -1)
return NULL;//判断输入是否有效
TreeNode* root = new TreeNode(nums[i]);
root->left = creatTree(nums, 2 * i + 1);
root->right = creatTree(nums, 2 * i + 2);
return root;
}
void printTree(TreeNode* root) {
if (root == nullptr)
return;
queue<TreeNode*> Q;
Q.push(root);
int count = 0;
while (!Q.empty()) {
count++;
cout << "第" << count << "层节点:";
int size = Q.size();
for (int i = 0; i < size; i++)
{
TreeNode* node = Q.front();
Q.pop();
cout << node->val << " ";
if (node->left) Q.push(node->left);
if (node->right) Q.push(node->right);
}
cout << endl;
}
}
int main()
{
vector<int>nums = { 8,13,4,-1,-1,5,1 };
TreeNode* root = creatTree(nums, 0);
printTree(root);
vector<vector<int>> result;
result = levelOrder(root);
}
2 思考
首先应该是广度搜索就可以,然后回顾广度的思路:
- 放入出发点作为队列头
- 循环(如果列表为空则退出)
3. 找邻居:找队列头的下一步点,将所有可能的下一步点放入队列
4. 实际操作:本题是建立二维的数组
5. 删除自己:当那个点的下一步点全部入队列后,删除那个点
写的时候有一些问题:
- 一般的广度不需要记录深度,这个好像需要,毕竟二维数组的每个小数组维度不一样
- 循环结束后,临时变量里面还剩点东西, 最后非要额外把剩下的放到大数组里面去,有点啰嗦
vector<vector<int>> levelOrder2(TreeNode* root) {
vector<vector<int>> result;
TreeNode* temp_node;
int t = 1;
// 1 广度优先毕然建立队列
queue<pair<TreeNode*, int>> q;
q.push(make_pair(root, 1)); // 问题1:需要记录深度
vector<int> temp_int;
//temp_int.push_back(root->val);
while (q.empty() == false) {
// 准备一些变量
temp_node = q.front().first;
// 2 获取相邻值
if (temp_node->left != nullptr)
{
q.push(make_pair(temp_node->left, q.front().second + 1));
}
if (temp_node->right != nullptr)
{
q.push(make_pair(temp_node->right, q.front().second + 1));
}
if (t != q.front().second) // 表明下一层了,执行:把现有的vector结束了,建立新的
{
result.push_back(temp_int);
temp_int.clear();
t = q.front().second;
}
temp_int.push_back(temp_node->val);
// 3 队列弹出
q.pop();
}
if (temp_int.size() != 0) // 问题2:非要最后来一下
{
result.push_back(temp_int);
}
return result;
}
然而别人的参考代码十分简单,这两个都没有出现过
vector<vector<int>> levelOrder(TreeNode* root) {
vector <vector <int>> ret;
if (!root) {
return ret;
}
queue <TreeNode*> q;
q.push(root);
while (!q.empty()) {
int currentLevelSize = q.size(); // ?
ret.push_back(vector <int>());
for (int i = 1; i <= currentLevelSize; ++i) {
auto node = q.front(); q.pop();
ret.back().push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return ret;
}
分析:
- [巧妙利用队列长度,不记录深度] 广度算法中,当一层结束的时候,队列的长度=下一层节点数量,这样就不需要记录深度了。
- [不用临时变量] 由于我的算法是发现节点切换到下一层的时候,才把临时变量放到总变量中,但最后一层的时候,不会切换到下一层了,所以得额外来一下。直接放到总变量的最后一个数组即可 ret.back().push_back(node->val);
也可以使用深度遍历。其实这种题目就是:遍历方法(广度或深度)+ 实际操作(放到二维数组中去)
class Solution {
public:
vector<vector<int>> ret;
void level(TreeNode* root, int lev) {
if(!root) return;
if (lev >= ret.size()) {
ret.push_back(vector<int>());
}
ret[lev].push_back(root -> val); // 放到二维数组中去
level(root -> left, lev + 1);
level(root -> right, lev + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
level(root, 0);
return ret;
}
};
完整代码记录
#include<vector>
#include<queue>
#include<iostream>
using namespace std;
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) {}
};
TreeNode* creatTree(vector<int> nums, int i) {
if (i >= nums.size() || nums[i] == -1)
return NULL;//判断输入是否有效
TreeNode* root = new TreeNode(nums[i]);
root->left = creatTree(nums, 2 * i + 1);
root->right = creatTree(nums, 2 * i + 2);
return root;
}
void printTree(TreeNode* root) {
if (root == nullptr)
return;
queue<TreeNode*> Q;
Q.push(root);
int count = 0;
while (!Q.empty()) {
count++;
cout << "第" << count << "层节点:";
int size = Q.size();
for (int i = 0; i < size; i++)
{
TreeNode* node = Q.front();
Q.pop();
cout << node->val << " ";
if (node->left) Q.push(node->left);
if (node->right) Q.push(node->right);
}
cout << endl;
}
}
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
TreeNode* temp_node;
int t = 0;
// 1 广度优先毕然建立队列
queue<pair<TreeNode*, int>> q;
q.push(make_pair(root,1));
vector<int> temp_int;
temp_int.push_back(root->val);
while (q.empty() == false) {
// 准备一些变量
temp_node = q.front().first;
if (t != q.front().second) // 表明下一层了,执行:把现有的vector结束了,建立新的
{
result.push_back(temp_int);
temp_int.clear();
t = q.front().second;
}
// 2 获取相邻值
if (temp_node->left != nullptr)
{
q.push(make_pair(temp_node->left,t+1));
}
if (temp_node->right != nullptr)
{
q.push(make_pair(temp_node->right, t + 1));
}
}
}
};
vector<vector<int>> levelOrder2(TreeNode* root) {
vector<vector<int>> result;
TreeNode* temp_node;
int t = 1;
// 1 广度优先毕然建立队列
queue<pair<TreeNode*, int>> q;
q.push(make_pair(root, 1));
vector<int> temp_int;
//temp_int.push_back(root->val);
while (q.empty() == false) {
// 准备一些变量
temp_node = q.front().first;
// 2 获取相邻值
if (temp_node->left != nullptr)
{
q.push(make_pair(temp_node->left, q.front().second + 1));
}
if (temp_node->right != nullptr)
{
q.push(make_pair(temp_node->right, q.front().second + 1));
}
if (t != q.front().second) // 表明下一层了,执行:把现有的vector结束了,建立新的
{
result.push_back(temp_int);
temp_int.clear();
t = q.front().second;
}
temp_int.push_back(temp_node->val);
// 3 队列弹出
q.pop();
}
if (temp_int.size() != 0)
{
result.push_back(temp_int);
}
return result;
}
// 1. ? 广度是否需要记录深度?
vector<vector<int>> levelOrder(TreeNode* root) {
vector <vector <int>> ret;
if (!root) {
return ret;
}
queue <TreeNode*> q;
q.push(root);
while (!q.empty()) {
int currentLevelSize = q.size(); // ?
ret.push_back(vector <int>());
for (int i = 1; i <= currentLevelSize; ++i) {
auto node = q.front(); q.pop();
ret.back().push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return ret;
}
int main()
{
vector<int>nums = { 8,13,4,-1,-1,5,1 };
TreeNode* root = creatTree(nums, 0);
printTree(root);
vector<vector<int>> result;
result = levelOrder(root);
}
二. 题目:
- 翻转二叉树
在学习了对称二叉树后,感觉这类题目用嵌套十分简答
TreeNode* invertTree(TreeNode* root) {
TreeNode* temp = 0;
if (root == nullptr) return root;
temp = root->right;
root->right = root->left;
root->left = temp;
invertTree(root->left);
invertTree(root->right);
return root;
}
思考
一开始我们学的前序、中序有什么用呢?为什么一直没有用到?其实用到了,只是我们不知道。比如上述代码中,你是先嵌套左还是右边,是先处理根节点的交换还是先嵌套?这几个顺序其实无所谓,但每种都对应了一种遍历方法。上述就是中序遍历。
三. 题目
- 路径总和
典型的深度遍历算法就可以
bool lujin(TreeNode* root, int targetSum, int current)
{
if ( root->left == nullptr && root->right == nullptr)
{
if (targetSum == current) return true; // 可以改进
else return false;
}
bool t = 0;
if (root->left != nullptr)
t = lujin(root->left, targetSum, current + root->left->val);
if (t) return true;
if (root->right != nullptr) // 可以改进
t = lujin(root->right, targetSum, current + root->right->val);
if (t) return true;
return false;
}
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr) return false;
int sum_ = root->val;
return lujin(root, targetSum, sum_);
}
思考
逻辑没有问题,但是写的太乱了!
注意几个简便的写法
if (targetSum == current) return true; else return false;// 改为 return (targetSum == current)
hasPathSum(root->left, sum - root->val) ||
hasPathSum(root->right, sum - root->val);
四、题目
二叉搜索树的搜索
其实我也知道嵌套的
TreeNode* searchBST(TreeNode* root, int val) {
TreeNode* temp = 0;
if (root == nullptr) return root;
if (root->val == val) return root;
if (val < root->val)
{
temp = searchBST(root->left, val); //? 有无简便写法?
}
else
{
temp = searchBST(root->right, val);
}
if (temp != nullptr) return temp;
return nullptr;
}
但是写的不美观,看看别人的代码
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val)
{
if(root==nullptr) return nullptr; // 注意 1 这里不是返回root,是nullptr
if(root->val==val) return root;
if(root->val>val) return searchBST(root->left,val);
if(root->val<val) return searchBST(root->right,val); // 简便的写法
//讲道理走不到这里
return nullptr;
}
};
五、题目
- 验证二叉搜索树
本来以为只要 左<中<右 ,但是发现还得满足父的要求;也就是说,下面的子节点是有一个取值范围的。
先来看看我的
bool isValidBST2(TreeNode* root, long long up, long long low) {
if (root == nullptr) return true; //? 返回什么,就是这个,因为不影响“和”的判断
// 两个叶节点需要在范围内;左边 中 右边;嵌套函数并更新上下界限
bool r1=1,r2=1;
if (root->left)
{
if ((root->left->val >= root->val) || (root->left->val >= up) || (root->left->val <= low))
return false;
r1 = isValidBST2(root->left, root->val, low);
}
if (root->right)
{
if ((root->right->val <= root->val) || (root->right->val >= up) || (root->right->val <= low))
return false;
r2 = isValidBST2(root->right, up, root->val);
}
return r1 && r2;
}
bool isValidBST(TreeNode* root) {
if (root == nullptr) return true; //? 返回什么,就是这个,因为不影响“和”的判断
return isValidBST2(root, LONG_MAX, LONG_MIN); //
}
注意
- 一开始是 负无穷 - 正无穷,c++没有这个,所以用 LONG_MAX, LONG_MIN 这两个常量来写,注意类型不是 int ,而是 long long .
- 好像不需要什么 “两个叶节点需要在范围内;左边 中 右边;嵌套函数并更新上下界限”,只要比较当前节点的值是否在范围内部就好。
- 在嵌套的时候注重于当前节点就好,不需要判断是否有左右节点(如下面的代码)
- 大致就几步
- [结束调节] 省去是否有左右节点的判断
- [实际操作] 判断 当前节点是否超出范围
- [嵌套左右]
所以看别人的代码
bool isValidBST(TreeNode *root, long left = LONG_MIN, long right = LONG_MAX) {
if (root == nullptr)
return true;
long x = root->val;
return left < x && x < right &&
isValidBST(root->left, left, x) &&
isValidBST(root->right, x, right);
}
作者:endlesscheng
链接:https://leetcode.cn/problems/validate-binary-search-tree/solution/qian-xu-zhong-xu-hou-xu-san-chong-fang-f-yxvh/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
};
思考:各种遍历的关系、用法和区别
看到这篇解答给出的分别用前序中序后序写的代码,发现这三种区别挺大的(也就是说不一定有:维护一个取值范围 这种操作,而且只有前序遍历需要一个动态的范围)。
首先,这三种有什么区别?
-
首先,前中后都是对于父来说的,永远都是先左后右;
-
前序遍历的思想(也是我想到的。虽然不是刻意的,但是我看到这题写出来的代码后,发现就是前序遍历)。看到题目,大概是递归,但是关键在于递归中的实际操作,自然的我们会把树画出来,然后找到某个节点,比如“24”,发现他首先要比21大,还要比28小(其实第二个条件我一开始没想出来)。所以就知道节点的值是在一个范围内的。因为树都是从根节点开始的吗,所以就先看看根节点(这个想发就是前序遍历)。根节点是没有限制的,但是越往下就限制越大,所以递归的时候:1判断值是否在范围内部;2 更新这个范围给子树。
-
中序遍历的代码如下:
- 嵌套左
- 判断自己是否小于一个值
- 更新这个值
- 嵌套右
但是你会问,你怎么想到这样做的?大概就是先一路向左找到全局最小,左、中、右依次更新这个值(没有就跳过),然后用这个值去给中节点的父节点设置下限。
bool isValidBST(TreeNode* root) {
if (root == nullptr)
return true;
if (!isValidBST(root->left))
{
return false;
}
if (root->val <= pre)
{
return false;
}
pre = root->val;
return isValidBST(root->right);
}
- 后续遍历的代码有点逆天,不知道怎么才能想出来。就不分析了。
六、题目
- 二叉搜索树的最近公共祖先
我实在是不清楚如何做,只能把2点的父节点全部打印出来,然后找最小的那个。找所有父节点的方法是
[实际操作] 包括:判断相等 + 放入堆栈 + 弹出堆栈,但是具体作的时候这些步骤可能和嵌套代码穿插着来,也就是顺序要对!比如下面的代码顺序
- [实际操作1] 无论怎样,先把自己放到自己的父节点列表中(一般来说父节点是不包括自己的,但是这里严格说是找到某节点的路径列表)
- [实际操作2] 看看当前是不是要找的值,是就返回True(表明已经把目标节点放入路径中),不是就继续;
- [嵌套1] 看看基于当前的路径列表,往左走试试。如果函数判断为真(表明已经把目标节点放入路径中),就可以返回了(就是说当前函数的任务已完成);
- [嵌套2] 看看基于当前的路径列表,往右边走试试。如果函数判断为真(表明已经把目标节点放入路径中),就可以返回了(就是说当前函数的任务已完成);
- [实际操作3] 都不行的时候只能掉头了(弹出当前节点); 这个操作的位置要注意,要在两个嵌套之后
- [最后返回] 没办法告诉前面的,我这个函数没找到目标节点。
bool lowest(TreeNode* root, TreeNode* p, vector<TreeNode*> & vec)
{
if (root == nullptr) return false; // 这个返回什么?应该是对的,就是告诉上面的,我没找到呀!
vec.push_back(root);
if (root->val == p->val) return true;
if (lowest(root->left, p, vec)) return true;
if (lowest(root->right, p, vec)) return true;
vec.pop_back();
return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (p->val > q -> val)
{
swap(p, q);
}
vector<TreeNode*> vec;
vector<TreeNode*> vec2;
//vec.push_back(root);
TreeNode* result;
lowest(root, p, vec);
lowest(root, q, vec2); // 某个节点所有父节点
int i = 0;
for (; i < min(vec.size(), vec2.size()); i++)
{
if (vec[i]->val != vec2[i]->val)
{
return vec[i - 1];
}
}
return vec[i-1 ];
}
所以上面的代码可以命名为:找出到目标节点的路径 算法。
但是我没有利用“搜索二叉树”的前提,如果用的话,找路径可能定会简单许多(见下面官方代码一)。更强的还可以利用“分岔点”思想(代码二)
// 官方代码一
class Solution {
public:
vector<TreeNode*> getPath(TreeNode* root, TreeNode* target) {
vector<TreeNode*> path;
TreeNode* node = root;
while (node != target) {
path.push_back(node);
if (target->val < node->val) {
node = node->left;
}
else {
node = node->right;
}
}
path.push_back(node);
return path;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
vector<TreeNode*> path_p = getPath(root, p);
vector<TreeNode*> path_q = getPath(root, q);
TreeNode* ancestor;
for (int i = 0; i < path_p.size() && i < path_q.size(); ++i) {
if (path_p[i] == path_q[i]) {
ancestor = path_p[i];
}
else {
break;
}
}
return ancestor;
}
};
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/solution/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-26/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
// “分叉点”官方代码二
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode* ancestor = root;
while (true) {
if (p->val < ancestor->val && q->val < ancestor->val) {
ancestor = ancestor->left;
}
else if (p->val > ancestor->val && q->val > ancestor->val) {
ancestor = ancestor->right;
}
else {
break;
}
}
return ancestor;
}
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/solution/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-26/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。