第五章--二叉树、并查集 板子
1、并查集(按秩合并+路径压缩优化)
板子
class query_set {//并查集,共三个函数
public:
//第一个函数:初始化函数
query_set(int n) {
this->n = n;
for (int i = 0; i <= n; i++) {
this->father[i] = i;//开始时候各自为战
this->rank[i] = 0;
}
}
//第二个函数:查找函数+路径压缩优化
int find(int item) {//找到item对应的集合的头目,每个集合的头目,找爹数组中都指向自己
if (item <= 0 || item > n) {//防止越界,从1-n存储节点
return 0;
}
if (father[item] == item) {
return item;
}
father[item] = find(father[item]);
return father[item];
}
//第三个函数,合并,找到x y对应集合的头目,把二者合并,合并时,把秩低的合并到秩高的,采用按秩合并优化
void merge(int x, int y) {
link(find(x), find(y));
}
private:
int father[1000];//找爹数组,看两个点是不是一伙的
int rank[1000];//记录每个节点的秩,表示以它为根节点的子树的高度最大值
int n;//表示总共的节点数
void link(int x, int y) {//根据秩进行两个树的融合,防止随意合并出现退化树(链表)的情况
if (rank[x] > rank[y]) {
father[y] = x;
}
else {
father[x] = y;
}
if (rank[x] == rank[y]) {//秩相等,合并之后,子树高度必加一
rank[y]++;//因为rank[x]<=rank[y]把x连在y上面,所以是以y为根
}
}
};
例题
力扣:547. 省份数量(题解在力扣里,自行寻找即可)
2、二叉排序树
计算成功/失败ASL
1、成功的
2、失败的
效率分析
删除节点の板子
1、在左面找最大节点
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr) {//走到头了
return nullptr;
}
if (root->val == key) {
if (root->left == nullptr || root->right == nullptr) {//前两种删除情况
return root->left == nullptr ? root->right : root->left;
}
TreeNode* item = findLeftNode(root->left);//找到左子树中用于替换的节点
root->left = deleteNode(root->left, item->val);//左子树中先删去他
item->left = root->left;//再用他换掉目标节点
item->right = root->right;
return item;
}
else {//不是目标节点
if (key < root->val) {
root->left = deleteNode(root->left, key);
}
else {
root->right = deleteNode(root->right, key);
}
}
return root;
}
private:
TreeNode* findLeftNode(TreeNode* root) {//左子树中最大,就是一直往右走
if (root->right == nullptr) {
return root;
}
return findLeftNode(root->right);
}
};
2、在右面找最小节点
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr) {
return nullptr;
}
if (root->val == key) {
if (root->left == nullptr || root->right == nullptr) {
return root->left == nullptr ? root->right : root->left;
}
TreeNode* item = findLeftNode(root->right);//找右儿子中最小
root->right = deleteNode(root->right, item->val);//从右子树中删除它
item->left = root->left;
item->right = root->right;
return item;
}
else {
if (key < root->val) {
root->left = deleteNode(root->left, key);
}
else {
root->right = deleteNode(root->right, key);
}
}
return root;
}
private:
TreeNode* findLeftNode(TreeNode* root) {//右子树中一直往左走
if (root->left == nullptr) {
return root;
}
return findLeftNode(root->left);
}
};
例题
力扣:450. 删除二叉搜索树中的节点(题解在力扣里,自行寻找即可)
增加节点の板子
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) {//到空节点了,说明该插入新节点
return new TreeNode(val, nullptr, nullptr);
}
if (root->val > val) {//找到合适应该继续往下走的路径
root->left = insertIntoBST(root->left, val);
}
else {
root->right = insertIntoBST(root->right, val);
}
return root;
}
};
例题
701. 二叉搜索树中的插入操作(题解在力扣里,自行寻找即可)
3、AVL树
增加/删除节点画图
判断是否是AVL树
class Solution {
public:
bool isBalanced(TreeNode* root) {
if (subHights(root) != -1) {//出现-1即为不平衡
return true;
}
return false;
}
private:
int subHights(TreeNode* root) {//用概念即可,任意一个节点左右高度差的绝对值小于1
if (root == nullptr) {
return 0;
}
int left = subHights(root->left);
int right = subHights(root->right);
if (left == -1 || right == -1) {//快速失败通道,一旦发现有一个节点不平衡,整个树都不平衡,快速返回-1退出
return -1;
}
if (abs(left - right) > 1) {
return -1;
}
int ans = max(left, right) + 1;//本层高度:左右高度最大值+1
return ans;
}
};
例题
力扣:面试题 04.04. 检查平衡性(题解在力扣里,自行寻找即可)
AVL树增加节点
注:AVL树重在"平衡"
1、AVL增加节点,最可能考的是,手画四种旋转LL、RR、LR、RL,所以这四种必须掌握画法,代码作为一种补充,以防万一让写出AVL加入节点方法。
2、这种方法是很取巧的,并没有旋转,而是利用二叉排序树(AVL树也是二叉排序树的一种)自身特点以及贪心策略重构了树,所以这种做法的前提是,他至少是二叉平衡树,才能用这个方法调平,并不是AVL树调平的通解。
3、考试如果要求AVL树加节点
<1> 调用二叉排序树插入节点方法,把目标节点全部插入
<2> 最后调用此函数一次性把二叉搜索树调平,成为AVL树即可。
class Solution {
public:
TreeNode* balanceBST(TreeNode* root) {
getInorder(root);
return build(0, size - 1);
}
void getInorder(TreeNode* root) {//获取中序序列,即升序序列
if (root == nullptr) {
return;
}
getInorder(root->left);
trees[size++] = root->val;
getInorder(root->right);
return;
}
TreeNode* build(int left, int right) {//贪心思想,AVL树左右平衡,所以尽可能每次吧中间当成根节点,这样左右节点数一样,子树也就平衡了
if (left > right) {//退出条件
return nullptr;
}
int mid = (left + right) / 2;
TreeNode* l = build(left, mid - 1);
TreeNode* r = build(mid + 1, right);
TreeNode* item = new TreeNode(trees[mid], l, r);
return item;
}
private:
int trees[10005];//存储 升序序列,具体大小按照考试当天来
int size = 0;//记录大小
};
例题
力扣1382. 将二叉搜索树变平衡(题解在力扣里,自行寻找即可)
4、利用“前序中序后序”来构造二叉树问题
!!!! 考试时候划分不明白,自己找个例子具体确定一下!!!!
前序后序构造二叉树(不唯一,给出一个正确的即可)
1、考试的时候,肯定会给传入,两个数组,以及对应的大小,为private里面的东西
2、注意边界值,用长度当边界值,当长度恰好为0,即一个节点,可以创建返回,当长度小于他,说明不合法,返回空,长度大于他说明还需要继续分治
3、具体理解过程看图:
class Solution {
public:
TreeNode* constructFromPrePost(vector<int>& preorder, vector<int>& postorder) {
for (int i = 0; i < preorder.size(); i++) {//因为考研不让用vector等容器,所以自己转化一下
this->preorder[size_pre++] = preorder[i];
}
for (int i = 0; i < postorder.size(); i++) {
this->postorder[size_post++] = postorder[i];
}
return build(0, 0, size_post - 1);
}
//接口解释:
//int start_pre:左子树(或右子树)preorder序列从何处开始
//int start_post:左子树(或右子树)postorder序列从何处开始
//int N:左子树(或右子树)序列长度,从零开始
//用子树序列长度判断边界值,长度最少为0,一个节点,创建返回即可,小于他,为非法序列返回空
TreeNode* build(int start_pre, int start_post, int N) {
if (N < 0) {//因为涉及子树序列长度问题,先处理不合法的,长度小于0即为错,返回空
return nullptr;
}
if (N == 0) {//序列长度为1,说明只有一个节点,直接创建返回即可
return new TreeNode(preorder[start_pre]);
}
//这里的理解看图即可。
int s = start_pre + 1;
int length = 0;
for (; length <= N; length++) {
if (preorder[s] == postorder[start_post + length]) {
break;
}
}
//自底向上建树过程
TreeNode* l = build(s, start_post, length);
TreeNode* r = build(s + length + 1, start_post + length + 1, N - length - 2);
return new TreeNode(preorder[start_pre], l, r);
}
int preorder[5000];//因为考研不让用vector等容器,所以自己转化一下
int postorder[5000];
int size_pre = 0;
int size_post = 0;
};
例题
力扣:889. 根据前序和后序遍历构造二叉树(题解在力扣里,自行寻找即可)
中序后序创建二叉树(递归法)
考试的时候,肯定会给传入,两个数组,以及对应的大小,为private里面的东西
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
for (int i = 0; i < postorder.size(); i++) {//因为考研不让用vector等容器,所以自己转化一下
this->postorder[size_p++] = postorder[i];
}
for (int i = 0; i < inorder.size(); i++) {
this->inorder[size_i++] = inorder[i];
}
return build(0, size_i - 1, 0, size_p - 1);
}
TreeNode* build(int in_left, int in_right, int post_left, int post_right) {//主函数
if (post_left > post_right) {//边界条件,只能是中序中,左大于右
return nullptr;
}
int root_pos = -1;//寻找中序中根位置
for (int i = in_left; i <= in_right; i++) {
if (postorder[post_right] == inorder[i]) {
root_pos = i;
break;
}
}
int right_num = in_right - root_pos;//右子树节点数量
TreeNode* l = build(in_left, root_pos - 1, post_left, post_right - 1 - right_num);//划分
TreeNode* r = build(root_pos + 1, in_right, root_pos, post_right - 1);
return new TreeNode(postorder[post_right], l, r);
}
private:
int postorder[5000];//因为考研不让用vector等容器,所以自己转化一下
int inorder[5000];
int size_p = 0;
int size_i = 0;
};
例题
力扣106. 从中序与后序遍历序列构造二叉树(题解在力扣里,自行寻找即可)
先序中序创建二叉树(递归法)
考试的时候,肯定会给传入,两个数组,以及对应的大小,为private里面的东西
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
for (int i = 0; i < preorder.size(); i++) {//因为考研不让用vector等容器,所以自己转化一下
this->preorder[size_p++] = preorder[i];
}
for (int i = 0; i < inorder.size(); i++) {
this->inorder[size_i++] = inorder[i];
}
return build(0, size_p - 1, 0, size_i - 1);
}
TreeNode* build(int pre_left, int pre_right, int in_left, int in_right) {//主函数
if (in_left > in_right) {//边界条件
return nullptr;
}
int root_pos = -1;//中序中根位置
for (int i = in_left; i <= in_right; i++) {
if (preorder[pre_left] == inorder[i]) {
root_pos = i;
break;
}
}
int left_num = root_pos - in_left;//左子树节点数
//开始分治
TreeNode* l = build(pre_left + 1, root_pos, in_left, root_pos - 1);
TreeNode* r = build(pre_left + 1 + left_num, pre_right, root_pos + 1, in_right);
return new TreeNode(preorder[pre_left], l, r);//合并
}
private:
int preorder[5000];//因为考研不让用vector等容器,所以自己转化一下
int inorder[5000];
int size_p = 0;
int size_i = 0;
};
例题
力扣105. 从前序与中序遍历序列构造二叉树(题解在力扣里,自行寻找即可)
5、前中后序遍历(迭代+递归)+层次遍历
题目链接
力扣145. 二叉树的后序遍历
力扣144. 二叉树的前序遍历
力扣94. 二叉树的中序遍历
力扣102. 二叉树的层序遍历
前中后序迭代法(统一模板法)
1、用栈实现
2、对于每个节点,第一次出栈为了把左右儿子和自己按照顺序加入栈,第二次出现是为了加入结果集,为了区分这两次出现,在第一次出现结束后,后面加一个"空节点"来标识,如果识别到了空,就把后面对应的节点加入结果集
3、图示如下:
后序(超重点)
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ans;
stack<TreeNode*> helping;
if (root != nullptr) {//边界一定注意好,不空才遍历
helping.push(root);
}
while (helping.size() != 0) {
TreeNode* item = helping.top();//先获取当前第一个元素
if (item != nullptr) {//顺序:先弹栈再入栈
helping.pop();//第一步弹栈
//按照后序以及栈的规律:根、右、左
helping.push(item);//加入根
helping.push(nullptr);
if (item->right != nullptr) {//右
helping.push(item->right);
}
if (item->left != nullptr) {//左
helping.push(item->left);
}
}
else {
helping.pop();
TreeNode* tmp = helping.top();
helping.pop();
ans.push_back(tmp->val);
}
}
return ans;
}
};
前序
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ans;
stack<TreeNode*> helping;
if (root != nullptr) {//边界条件
helping.push(root);
}
while (helping.size() != 0) {
TreeNode* item = helping.top();
if (item != nullptr) {
helping.pop();//顺序:先弹栈再入栈
//按照后序以及栈的规律:右、左、根
if (item->right != nullptr) {
helping.push(item->right);
}
if (item->left != nullptr) {
helping.push(item->left);
}
helping.push(item);
helping.push(nullptr);
}
else {
helping.pop();
ans.push_back(helping.top()->val);
helping.pop();
}
}
return ans;
}
};
前序补充方法(推荐)
1、因为前序遍历,第一个处理的就是根,所以,栈里面就只需要处理子节点就行,所以说,用栈类模拟队列的用法,先将右孩子入栈然后再将左孩子入栈,这样访问时就是前序的顺序。
2、这种方法也可以应用于N叉树的前序遍历,一定要记住,这种思路无法应用于非递归的中序和后序遍历。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
if (root == nullptr) return {};
vector<int> ans;
stack<TreeNode*> sta;
sta.push(root);
while (!sta.empty())
{
TreeNode* cur = sta.top();
sta.pop();
ans.push_back(cur->val);
if (cur->right) sta.push(cur->right);
if (cur->left) sta.push(cur->left);
}
return ans;
}
中序
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
stack<TreeNode*> help;
if (root != nullptr) {//边界一定注意好,不空才遍历
help.push(root);
}
while (help.size() > 0) {
TreeNode* item = help.top();
if (item != nullptr) {
help.pop();//先弹再入
//按照后序以及栈的规律:右、根、左
if (item->right != nullptr) {
help.push(item->right);
}
help.push(item);//根
help.push(nullptr);
if (item->left != nullptr) {
help.push(item->left);
}
}
else {
help.pop();
ans.push_back(help.top()->val);
help.pop();
}
}
return ans;
}
};
前中后序递归法
后序
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
postOrder(root);
return ans;
}
void postOrder(TreeNode* root) {
if (root == nullptr) {
return;
}
postOrder(root->left);
postOrder(root->right);
ans.push_back(root->val);
return;
}
private:
vector<int> ans;
};
前序
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
preOrder(root);
return ans;
}
void preOrder(TreeNode* root) {
if (root == nullptr) {
return;
}
ans.push_back(root->val);
preOrder(root->left);
preOrder(root->right);
return;
}
private:
vector<int> ans;
};
中序
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
inOrder(root);
return ans;
}
void inOrder(TreeNode* root) {
if (root == nullptr) {
return;
}
inOrder(root->left);
ans.push_back(root->val);
inOrder(root->right);
return;
}
private:
vector<int> ans;
};
层次遍历
分两种:
<1>一种就是单纯的获得层次遍历序列,用队列就行,每出一次,(如果有的话)加入两个子节点,一直到空为止
<2>第二种是要求分开打印每一层的节点,这样的话,每次出一层的节点,进入一层的节点,然后记录每层节点数。
本体为第二种
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
queue<TreeNode*> level_order;//用队列
if (root != nullptr) {
level_order.push(root);//一定注意边界值,空树不操作
}
int levelNum = 1;//每层节点数,初始为1
while (level_order.size() != 0) {
int tmpLevelNum = 0;//记录下一层节点数
vector<int> tmp;//记录该层节点信息
for (int i = 0; i < levelNum; i++) {//一次出一层,一次进一层
TreeNode* item = level_order.front();
level_order.pop();
tmp.push_back(item->val);
if (item->left != nullptr) {
level_order.push(item->left);
tmpLevelNum++;
}
if (item->right != nullptr) {
level_order.push(item->right);
tmpLevelNum++;
}
}
levelNum = tmpLevelNum;
ans.push_back(tmp);
}
return ans;
}
};
6、公共祖先问题(一次遍历法)
普通二叉树
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == q || root == p|| root == nullptr) {//出边界或者找到目标都是截止
return root;
}
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left == nullptr&&right == nullptr) {//两边都空,说明目标不在这一支上
return nullptr;
}
else if (left == nullptr || right == nullptr) {//有一边不空,说明他是目标的祖先,但不是最近的,只需要最近的祖先返回上去就行
return left == nullptr ? right : left;
}
return root;//左右都发现了,说明是最近的祖先
}
};
例题
二叉搜索树
1、因为二叉排序树自身特点,很多无意义节点不用找,所以不存在的情况不予以考虑,是有针对性的寻找。
2、这个是针对性的找,上面那个是回溯寻找
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode* ans = root;//初始化
while (true) {
if (ans->val > p->val&&ans->val > q->val) {//目标都在左面,答案也在左
ans = ans->left;
}
else if (ans->val < p->val&&ans->val < q->val) {//目标都在左面,答案也在左
ans = ans->right;
}
else {//左右都有即为答案
break;
}
}
return ans;
}
};
7、完全二叉树的判定
1、完全二叉树性质:除了最后一层以外完全填充,并且最后一层的节点都尽可能的出现在左侧。只有倒数第二层(如果存在)若干节点以及最后一层全部节点才可继续添加子节点,其余全部已经是两个子节点。
2、所以,只要是完全二叉树,所有节点必然是一个挨着一个的排布的,所以从上到下从左到右只要碰到了空节点,说明完全二叉树已经走完了,后面如果再碰到非空节点,说明不是完全二叉树。
#include <iostream>
#include <string>
#include <stack>
#include <queue>
#include <unordered_map>
#include <vector>
#include <algorithm>
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) {}
};
bool function_seven(TreeNode* root) {
if (root == nullptr) {//特殊情况,空树是完全二叉树
return true;
}
queue<TreeNode*> help;
help.push(root);//开始层序遍历
while (!help.empty()) {
TreeNode* tmp = help.front();
help.pop();
if (tmp == nullptr) {//遇到空节点,说明完全二叉树应该已经全都遍历完了
break;
}
help.push(tmp->left);//否则就是入队
help.push(tmp->right);
}
while (!help.empty()) {
TreeNode* tmp = help.front();
help.pop();
if (tmp != nullptr) {//再出现非空节点必错
return false;
}
}
return true;
}
8、无向图判定是否是一棵树
总思路:
给一个图,n个点(默认点的编号 0 -> n-1),让判定是否是树:遍历一遍图,统计走过了point个节点,edge条边 -> {point==n(防止出现森林)&& point == edge+1}
就是一棵树
统计策略:
1、遍历图:利用辅助数组visit每次只走没走过的
2、统计point:每走到了一个新的节点,就意味着走到了一个新的点 point++
3、统计edge:为了防止丢边,所以只要是到一个节点,我们把与他相连接的所有边都统计上,这样的话每条边统计了两次,只要除以2就是真正的edge
代码
#include <string>
#include <stack>
#include <queue>
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
//参数:图、记录访问情况的数组、当前走到哪一个节点、点数、边数(为了把数值带出去,所以传入指针,每次++需要访问到对应地址才可以)
void judge(vector<vector<int>> graph, bool visit[], int p, int *point, int *edge) {
visit[p] = true;//新走到一个节点,改一下访问状态
(*point)++;//遇到的点数++
for (int e : graph[p]) {//访问它所能到的所有目标点
(*edge)++;//重点:!!!!!为了不让统计的边少了,对于每个点而言,统计它所有的边(所以造成了一条边被统计两次)
if (!visit[e]) {//继续走那些没走到过的点
judge(graph, visit, e, point, edge);
}
}
return;
}
void function_six_one() {//共n个顶点,m对边的关系
//建图,节点编号默认为 0 -> n-1
int n, m;//共n个顶点,m对边的关系
cin >> n >> m;
vector<vector<int>> graph;//存储图的结构
graph.resize(n);
for (int i = 0; i < m; i++) {//根据关系存储图
int s = 0, e = 0;
cin >> s >> e;
graph[s].push_back(e);
graph[e].push_back(s);
}
bool visit[100];//用于辅助遍历,防止无限次遍历
for (int i = 0; i < n; i++) {
visit[i] = false;//最开始都默认未访问过
}
//建图结束,开始判定
//用于统计我一次遍历访问了多少个节点,此时我们是不知道图具体什么样的,所以都默认是0
int point = 0;
int edge = 0;
judge(graph, visit, 0, &point, &edge);//遍历
edge /= 2;//因为我的原则是见到边就算,且是无向图同一条边会在起点和终点个算一次,所以/2才是真正的边数
if (point == n && edge + 1 == point) {//点数==总点数(防止出现森林) && 边数+1==点数
cout << "1";//无向图是树
}
else {
cout << "0";
}
return;
}
int main()
{
function_six_one();
return 0;
}
9、补充
1、判断二叉树是否对称
经典习题:力扣101. 对称二叉树
思路
如图所示:
1、因为,判断是否是轴对称,必须两边一起看,只看一边的情况i下根本无从得知另一边的情况
2、拿来两个根节点(A、B),由上图所示,他们
<1>A的左子树和B的右子树一样
<2>A的右子树和B的左子树一样
<3>A和B本身的值也一样
3、三者都对,返回true,否则返回false
4、边界处理:同时走到空,为true,反之为false
代码
class Solution {
public:
bool isSymmetric(TreeNode* root) {
return dfs(root->left, root->right);
}
bool dfs(TreeNode *r1, TreeNode* r2) {
if (r1 == nullptr&&r2 == nullptr) {
return true;
}
else if (r1 == nullptr || r2 == nullptr) {
return false;
}
return dfs(r1->left, r2->right) && dfs(r1->right, r2->left) && (r1->val == r2->val);
}
};
2、二叉搜索树变形问题
二叉树节点变化
多了一个count,用来记录子树中节点个数
struct TreeNode {
int val;
int count;//以该节点为根的子树当中节点个数
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) {}
};
问题
在O(logn)内,查找二叉搜索树中第k(1<=k<=n)小节点
思路&&代码
(我不太会,直接背下来,一共处理六种情况即可)
六大情况:
1、判断k是否合法
2、左子树是空且k=1:root为目标答案,返回
3、左子树是空且k>1:往右走且用k-1
4、左子树不是空且左子树的count=k-1:root为目标答案,返回
5、左子树不是空且左子树的count>k-1:往左走且用k
6、左子树不是空且左子树的count<k-1:往右走且用k-1-左子树的count
#include <iostream>
#include <string>
#include <stack>
#include <queue>
#include <unordered_map>
#include <vector>
#include <algorithm>
using namespace std;
struct TreeNode {
int val;
int count;//以该节点为根的子树当中节点个数
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
};
//2021计学1(直接背下来就好,对二叉搜索树的节点改动了一下)
//把原有的二叉搜索树节点进行改动,加一个count记录子树节点个数
//logn内查找第k(1<=k<=n)小节点
//共六种情况
TreeNode* Find(TreeNode* root, int k) {
if (k<1 || k>root->count) {//第一种,对k先判断
return nullptr;
}
if (root->left == nullptr) {//第二类:左为空
if (k == 1) {//第二种情况,找到目标(左为空且k==1)
return root;
}
else {//第三种情况,左为空,还需要继续右走,K-1
return Find(root->right, k - 1);
}
}
else {//第三类情况,左面不是空,找他的count和k-1比较
if (root->left->count == k - 1) {
return root;
}
else if (root->left->count > k - 1) {
return Find(root->left, k);
}
else {
return Find(root->right, k - root->left->count - 1);
}
}
}