系列综述:
💞目的:本系列是个人整理为了
秋招面试
的,整理期间苛求每个知识点,平衡理解简易度与深入程度。🥰来源:材料主要源于左程云算法课程进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验自证。
🤭结语:如果有帮到你的地方,就点个赞和关注一下呗,谢谢🎈🎄🌷!!!
文章目录
-
- 二叉树理论基础
- 相关题目
- 构建二叉树
- 二叉搜索树
-
- 查找二叉搜索树的指定值
- [98. 验证二叉搜索树](#98. 验证二叉搜索树)
- [530. 二叉搜索树的最小绝对差](#530. 二叉搜索树的最小绝对差)
- [236. 二叉树的最近公共祖先](#236. 二叉树的最近公共祖先)
- [235. 二叉搜索树的最近公共祖先](#235. 二叉搜索树的最近公共祖先)
- [450. 删除二叉搜索树中的节点](#450. 删除二叉搜索树中的节点)
- [669. 修剪二叉搜索树](#669. 修剪二叉搜索树)
- [108. 将有序数组转换为二叉搜索树](#108. 将有序数组转换为二叉搜索树)
- [669. 修剪二叉搜索树](#669. 修剪二叉搜索树)
- [[LeetCode] 333. 最大 BST 子树](#[LeetCode] 333. 最大 BST 子树)
- 参考博客
二叉树理论基础
基本知识
-
二叉树数据结构
cppstruct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) {} };
-
二叉树递归套路
- 建立Info结构体:数据元素为向左右子树索要的信息的集合
- 递归出口:考虑递归到底部应该如何返回info信息
- 进行左右子树的递归处理
- 修改info信息:一般为选择和不选择两种情况进行考虑
- 返回info信息:返回的实际是整合后的一颗树的info信息
-
模板例题
- 题目:给定一颗二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树的头节点。搜索二叉树指的是整棵树没有重复的值,并且每颗子树都是左小右大。
cpp
// 思路:
// 1. 与X结点无关:询问左右子树最大搜索二叉树的size
// 2. 与X结点有关:左右子树均为搜索二叉树 && 左max_val < X->val && 右边min_val > X->val
/*
1. 建立Info结构体
- 向左子树索要的信息:左子树中最大搜索二叉树的size、左子树是否在搜索二叉树、左子树上的最大值
- 向右子树索要的信息:右子树中最大搜索二叉树的size、右子树是否在搜索二叉树、右子树上的最小值
- 对于任何子树的Info结构体为左右子树所需信息的合集,如下:
*/
struct Info {
bool is_all_BST; // 子树整体是否为搜索二叉树
int max_sub_BST_size; // 满足搜索二叉树的最大搜索子树大小
int min; // 左子树的最大值
int max; // 右子树的最小值
Info(bool is, int size, int mi, int ma):
is_all_BST(is), max_sub_BST_size(size), min(mi), max(ma){}
};
Info Process(Node X) {
// 2. 递归出口:若递归出口不好设置,则可设置为nullptr,但使用前要判空
if (X == nullptr) return nullptr;
// 3. 递归左右子树
Info left_info = Process(X.left);
Info right_info = Process(X.right);
// 4. 修改info信息
int min = X.value;
int max = X.value;
// 加工min和max:将本结点的min和max修改为左右子树的最大和最小值
if (left_info != nullptr) {
min = min(min, left_info.min);
max = max(max, left_info.max);
}
if (right_info != nullptr) {
min = min(min, right_info .min);
max = max(max, right_info .max);
}
bool is_all_BST = false;
int max_sub_BST_size = 0;
// 与X无关时, max_sub_BST_size设置为左右子树的最大size
if (left_info != nullptr) {
max_sub_BST_size = left_info.max_sub_BST_size;
}
if (right_info != nullptr) {
max_sub_BST_size = max(right_info.max_sub_BST_size, max_sub_BST_size );
}
// 与X有关时,判断左右子树均为搜索二叉树 && 左小右大
if (
(left_info == nullptr ? true : left_info.is_all_BST) &&
(right_info == nullptr ? true : right_info.is_all_BST) &&
(leftInfo == nullptr ? true : leftInfo.max < X.value) &&
(right_info == nullptr ? true : right_info.max < X.value)
) {
max_sub_BST_size =
(leftInfo == nullptr ? 0: leftInfo.max_sub_BST_size) +
(leftInfo == nullptr ? 0: leftInfo.max_sub_BST_size) +
1;
is_all_BST = true;
}
// 5. 返回info信息
return new info(is_all_BST, max_sub_BST_size, min, max)
}
二叉树的递归套路例题
- 题目链接:【二叉树的直径】
cpp
struct Info {
int max_distance;
int height;
Info(int dis, int h) : max_distance(dis), height(h){}
};
Info Process(Node *root) {
// 递归出口:叶子结点时,高度和最大距离都为0
if (root == nullptr)
return new Info(0, 0);
// 获取左右子树的信息
Info left_info = Process(root->left);
Info right_info = Process(root->right);
// 根据左右子树信息构建本树的信息
int height = max(left_info.height, right_info.height) + 1;
int max_distance =
max(max(left_info.max_distance, right_info.max_distance),
left_info.height + right_info.height + 1)
// 返回本树info信息
return new Info(max_distance, height);
}
二叉树深度优先遍历*
-
递归序
- 原理:递归出口、递归左子树、递归右子树:三个部分中间及后面的范围成为其递归处理范围。
- 过程:每个结点都会被经历三次,第一次是根结点处理范围、左子树处理范围和右子树处理范围
- 本质:递归出口和子递归函数,每一部分都是
递归+该递归处理范围
cpp// 递归序 void f(TreeNode *root) { if (root == nullptr) return ; // 根结点处理范围 f(root->left); // 左子树处理范围 f(root->right); // 右子树处理范围 }
-
递归式
- 前序遍历:任何子树的处理顺序都是,先根节点、再左子树,然后右子树
- 中序遍历:任何子树的处理顺序都是,先左子树、再根节点,然后右子树
- 后序遍历:任何子树的处理顺序都是,先左子树、再右子树,然后根节点
cpp// 前序遍历 void Traversal(TreeNode *root) { if (root == nullptr) return ; Doing(root->val); // 中 Traversal(root->left); // 左 Traversal(root->right); // 右 } // 中序遍历 void Traversal(TreeNode *root) { if (root == nullptr) return ; Traversal(root->left); // 左 Doing(root->val); // 中 Traversal(root->right); // 右 } // 后序遍历 void Traversal(TreeNode *root, vector<int> vec) { if (root == nullptr) return ; Traversal(root->left); // 左 Traversal(root->right); // 右 vec.emplace_back(root->val);// 中 }
-
递归和非递归
- 任何递归函数都可以通过自己设计压栈的方式改成非递归
-
非递归:将前序、中序和后序统一化处理,将遍历核心顺序进行
逆序转化
- 初始化:声明结果容器、栈、根非空则入栈
- 算法:栈非空,每次取栈顶元素。判断结点是否为空,若为空则弹出并逆序压入对应元素,若非空则弹出结点和空结点并进行处理
cppvector<int> Traversal(TreeNode* root) { // 初始化 vector<int> result; // 结果容器 stack<TreeNode*> st; // 深度的栈 if (root != NULL) // 根非空则入栈 st.push(root); // 遍历源容器 while (!st.empty()) { TreeNode* node = st.top(); // if (node != NULL) { st.pop(); // 算法变化的部分,遍历的逆序 // 中 st.push(node); st.push(NULL); // 右 if (node->right) st.push(node->right); // 左 if (node->left) st.push(node->left); } else { // 对值节点的处理 st.pop();// 弹出空值结点 node = st.top(); st.pop(); // 结点处理 result.push_back(node->val); } } return result; }
二叉树广度优先遍历*
-
递归法
cpp// 递归参数,如果需要修改要进行引用传递 void traversal(TreeNode* cur, vector<vector<int>>& result, int depth) { // 递归出口 if (cur == nullptr) return; // 递归体 if (result.size() == depth) // 扩容 result.push_back(vector<int>());// 原地构建数组 result[depth].push_back(cur->val);// 顺序压入对应深度的数组中 order(cur->left, result, depth + 1); order(cur->right, result, depth + 1); } vector<vector<int>> levelOrder(TreeNode* root) { // 初始化:一般为递归形参 vector<vector<int>> result; int depth = 0; // 递归调用 traversal(root, result, depth); // 返回结果 return result; }
-
非递归法
cppvector<vector<int>> levelOrder(TreeNode* root) { // 初始化 vector<vector<int>> result; // 结果容器 queue<TreeNode*> que; // 广度的队列 if(root != nullptr) // 根非空则入列 que.push(root); // 算法 while (!que.empty()) { // 队列非空 vector<int> vec; // 结果存放 TreeNode* node; // 过程记录 int size = que.size(); // 初始化:记录每层要遍历的根节点数量 for (int i = 0; i < size; i++) { // que.size()会变化 // 处理结点 node = que.front(); // 先记录后弹出,避免复杂逻辑 que.pop(); if (node->left) que.push(node->left); if (node->right) que.push(node->right); // 对每个结点的处理 vec.push_back(node->val); } // 对每层的处理 result.push_back(vec); } // 输出 return result; }
二叉树最大深度
-
递归法
cpp// 递归只考虑当前层,不要过于考虑整体 int depth(TreeNode* root) { // 1. 如果当前 root 为 null,说明当前层的深度就是 0 if (!root) { return 0; } // 2. 分别计算左子树和右子树的深度 int L = depth(root->left); int R = depth(root->right); // 3. 获取当前树的左子树和右子树深度的较大值,加 1 (本层深度) return max(L,R) + 1; } // 简略版 int depth(TreeNode* cur) { //计算最大深度 return (cur == nullptr) ? 0 : max(depth(cur->left), depth(cur->right)) + 1; }
-
非递归法
cppint MaxDepth(TreeNode *root) { int depth = 0; // 结果 queue<TreeNode*> que; // 队列 if (root != nullptr) // 根入列 que.push(root); while (!que.empty()) { TreeNode *node; // 层次遍历 int size = que.size(); for (int i = 0; i < size; ++i) { node = que.front(); que.pop(); if (node->left) que.push(node->left); if (node->right) que.push(node->right); } // 层数+1 ++depth; } return depth; }
二叉树最小深度
-
递归法
- 二叉树的五种形态
- 空二叉树
- 只有根节点
- 只有左子树
- 只有右子树
- 左右子树都有
cppint minDepth(TreeNode* root) { // 空二叉树 if (root == NULL) return 0; // 只有左子树 if (root->left != NULL && root->right == NULL) { return 1 + minDepth(root->left); } // 只有右子树 if (root->left == NULL && root->right != NULL) { return 1 + minDepth(root->right); } // 左右子树都非空 return 1 + min(minDepth(root->left), minDepth(root->right)); }
- 二叉树的五种形态
-
非递归法
- 找到第一个左右孩子均为空的,即为最小深度
cppint minDepth(TreeNode* root) { if (root == NULL) return 0; int depth = 0; queue<TreeNode*> que; que.push(root); while(!que.empty()) { int size = que.size(); depth++; // 记录最小深度 for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); if (!node->left && !node->right) { // 第一个左右孩子均空,为最小深度 return depth; if (node->left) que.push(node->left); if (node->right) que.push(node->right); } } } return depth; }
求树中结点的数量
-
递归法
- 递归法要只考虑单层的逻辑
cppint getNodesNum(TreeNode* cur) { if (cur == NULL) return 0; int leftNum = getNodesNum(cur->left); // 左 int rightNum = getNodesNum(cur->right); // 右 int treeNum = leftNum + rightNum + 1; // 中 return treeNum; }
-
非递归法
cppint CountNodes(TreeNode *root) { int count = 0; // 结果 queue<TreeNode*> que; // 队列 if (root != nullptr) // 根入队 que.push(root); // 队列非空则执行 while (!que.empty()) { TreeNode * node; int size = que.size(); // 该层宽度 for (int i = 0; i < size; ++i) { // 层次遍历 ++count; // 结点的处理 node = que.front(); que.pop(); if (node->left) que.push(node->left); if (node->right) que.push(node->right); } } return count; }
判断是否为平衡二叉树
- 递归法
- 后序遍历和求树的高度的模板改进
cpp
// 初始化ans为true,最后看ans是否为false即可
int depth(TreeNode* root, bool &ans) {
if(!root) return 0;
// 后序遍历
int left=1+depth(root->left, ans);
int right=1+depth(root->right, ans);
if(abs(left-right)>1)ans=false;// 对根结点的处理
// 递归出口
return max(left,right); // 返回树的高度
}
// 尾递归优化:效率高
bool isBalanced(TreeNode* root) {
if (root == nulllptr) return true;
return abs(depth(root->left) - depth(root->right)) <= 1
&& isBalanced(root->left)
&& isBalanced(root->right);
}
相关题目
翻转二叉树
-
- 对于二叉树的操作都是从二叉树的遍历衍生出来的
cpp// 前序遍历 void Traversal(TreeNode *cur){ if(cur == nullptr) return ; swap(cur->left, cur->right); // 树的本质是地址的值 if(cur->left) Traversal(cur->left); if(cur->right) Traversal(cur->right); } // 调用函数 TreeNode* invertTree(TreeNode* root) { if(root == nullptr) return nullptr; Traversal(root); return root; }
- 对于二叉树的操作都是从二叉树的遍历衍生出来的
二叉树是否对称
-
- 对称二叉树要比较的不是左右节点,而是比较根节点的左右子树是否值相等
cppbool compare(TreeNode* left, TreeNode* right) { if (left == NULL && right != NULL) return false; else if (left != NULL && right == NULL) return false; else if (left == NULL && right == NULL) return true; else if (left->val != right->val) return false; // 左右都不空才能访问值 else return compare(left->left, right->right) && compare(left->right, right->left); } bool isSymmetric(TreeNode* root) { if (root == NULL) return true; return compare(root->left, root->right); }
- 对称二叉树要比较的不是左右节点,而是比较根节点的左右子树是否值相等
-
有点东西的写法(这是哪个题的?)判断链表的入口的
cppListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { ListNode *A = headA, *B = headB; // 核心在于交换头节点 while (A != B) { A = A != nullptr ? A->next : headB; B = B != nullptr ? B->next : headA; } return A; }
二叉树的所有路径
-
递归
- 数字转化成字符串
to_string(number)
- 字符串后追加子串
str.append(subStr)
- 字符串删除某个位置之后的字符
str.erase(position)
cpp// 数字型 void dfs(TreeNode*root,vector<int>path, vector<vector<int>> &res) { if(!root) return; //根节点为空直接返回 // 中 path.push_back(root->val); //作出选择 if(!root->left && !root->right) //如果到叶节点 { res.push_back(path); return; } // 左 dfs(root->left,path,res); //继续递归 // 右 dfs(root->right,path,res); } // 字符型 void binaryTree(TreeNode* root,string path,vector<string>&res) { if(root==NULL) return ; path.append(to_string(root->val)); path.append("->"); if(root->left==NULL&&root->right==NULL{ path.erase(path.length()-2); res.push_back(path); } binaryTree(root->left,path,res); binaryTree(root->right,path,res); } vector<string> binaryTreePaths(TreeNode* root) { string path; vector<string>res; binaryTree(root,path,res); return res; }
- 数字转化成字符串
左叶子之和
-
- 遍历所有节点,对所求的特殊节点进行约束求值
cppvoid postorder(TreeNode *root, int &result){ if(root == nullptr) return ; if(root->left) postorder(root->left, result); if(root->right) postorder(root->right, result); // 中 if(root->left != nullptr && root->left->left == nullptr &&root->left->right == nullptr ) result += root->left->val; }
-
迭代法
cppint sumOfLeftLeaves(TreeNode* root) { // 初始化 stack<TreeNode*> st; if(root != nullptr) st.push(root); int res = 0; // 迭代 while(!st.empty()){ TreeNode* cur = st.top(); if(cur != nullptr){ st.pop(); st.push(cur); st.push(nullptr); if(cur->right) st.push(cur->right); if(cur->left) st.push(cur->left); }else{ st.pop(); cur = st.top(); st.pop(); if(cur->left != nullptr && cur->left->left == nullptr && cur->left->right == nullptr ) res += cur->left->val; } } // 结果处理 return res; }
求二叉树最左下的叶子
-
- 层次遍历最后一层的第一个,就是最左下的叶子
cppint findBottomLeftValue(TreeNode* root) { queue<TreeNode *> q; if(root != nullptr) q.push(root); int res = 0; while(!q.empty()){ int size = q.size(); for(int i= 0; i < size; ++i){ TreeNode * cur = q.front(); q.pop(); // 每层的第一个,即最左的节点 if(i == 0) res = cur->val; if(cur->left) q.push(cur->left); if(cur->right) q.push(cur->right); } } return res; }
符合总和的路径
-
- 增加结点就加值,不符合就回溯进行减值。
cppbool hasPathSum(TreeNode* root, int targetSum) { // 初始化 stack<TreeNode*> st; if(root != nullptr) st.push(root); int sum = 0; // 迭代 while(!st.empty()){ TreeNode *cur = st.top(); if(cur != nullptr){ st.pop(); st.push(cur); st.push(nullptr); sum += cur->val; if(cur->right) st.push(cur->right); if(cur->left) st.push(cur->left); }else{ st.pop(); cur = st.top(); st.pop(); // 节点判断 if(sum == targetSum&& cur->left == nullptr && cur->right == nullptr){ return true; }else{// 回溯 sum -= cur->val; } } } return false; }
构建二叉树
树的序列化
- 树的序列化和反序列化
- 序列化:树的遍历在输出时,将所有结点的左右孩子补全,没有的使用null代替
- 反序列化:按照什么遍历方式序列化,就按照什么遍历方式反序列化
- 深度优先的的序列化和反序列化
cpp
// 序列化
void Serialize(Node *head, queue<string> &que) {
if (head == nullptr) {
que.push("-1"); // 空标记
} else {
que.push(to_string(head->val));
Serialize(head->left, que);
Serialize(head->right, que);
}
}
// 反序列化
Node *Build(queue<string> &que) {
int val = atoi(que.front());
que.pop();
if (val == -1) return nullptr;
Node *root = new Node(val);
root->left = Build(que);
root->right = Build(que);
return root; // 建立完成返回
}
105. 从前序与中序遍历序列构造二叉树
cpp
TreeNode* traversal(vector<int>& preorder, vector<int>& inorder) {
// 递归出口
if (preorder.empty() == true)
return nullptr;
// 建立根结点
TreeNode *root = new TreeNode(preorder[0], nullptr, nullptr);
// 查找当前结点在中序序列中的位置
vector<int>::iterator itr = find(inorder.begin(), inorder.end(), preorder[0]);
// 划分中序序列
vector<int> inorder_left(inorder.begin(), itr); // key:左闭右开
vector<int> inorder_right(itr + 1, inorder.end());
// 划分前序序列:根据左右子树的数量
vector<int> preorder_left( preorder.begin()+1,
preorder.begin()+1+(itr - inorder.begin()));
vector<int> preorder_right( preorder.begin()+1+(itr - inorder.begin()),
preorder.end());
//创建左右子树, 并将它们的根节点赋值给当前节点的指针
root->left = buildTree(preorder_left, inorder_left);
root->right = buildTree(preorder_right, inorder_right);
return root;
}
106. 从中序与后序遍历序列构造二叉树
-
- 通过始末位置指示容器范围,避免每次调用的vector创建开销
cpp// 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd) TreeNode* traversal ( vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd ){ // 每次都是先从后序找,所以后序没有即完成 if (postorderBegin == postorderEnd) return NULL; // 分界点为后序最后一个 int rootValue = postorder[postorderEnd - 1]; TreeNode* root = new TreeNode(rootValue); if (postorderEnd - postorderBegin == 1) return root; // 查找前序序列中的分界下标 int delimiterIndex; for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) { if (inorder[delimiterIndex] == rootValue) break; } // 切割中序数组 // 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd) int leftInorderBegin = inorderBegin; int leftInorderEnd = delimiterIndex; // 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd) int rightInorderBegin = delimiterIndex + 1; int rightInorderEnd = inorderEnd; // 切割后序数组 // 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd) int leftPostorderBegin = postorderBegin; int leftPostorderEnd = postorderBegin + delimiterIndex - inorderBegin; // 终止位置是 需要加上 中序区间的大小size // 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd) int rightPostorderBegin = postorderBegin + (delimiterIndex - inorderBegin); int rightPostorderEnd = postorderEnd - 1; // 排除最后一个元素,已经作为节点了 // root->left = traversal(inorder, leftInorderBegin, leftInorderEnd, postorder, leftPostorderBegin, leftPostorderEnd); root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd); return root; } TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) { if (inorder.size() == 0 || postorder.size() == 0) return NULL; // 左闭右开的原则 return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size()); }
654. 构建二叉树*
-
- 通过始末位置指示容器范围,避免每次调用的vector创建开销
cpp// 在左闭右开区间[left, right),构造二叉树 TreeNode* traversal(vector<int>& nums, int left, int right) { // 构建完成 if (left >= right) return nullptr; // 分割点下标:maxValueIndex int maxValueIndex = left; for (int i = left + 1; i < right; ++i) { if (nums[i] > nums[maxValueIndex]) maxValueIndex = i; } // 创建节点 TreeNode* root = new TreeNode(nums[maxValueIndex]); // 左闭右开:[left, maxValueIndex) root->left = traversal(nums, left, maxValueIndex); // 左闭右开:[maxValueIndex + 1, right) root->right = traversal(nums, maxValueIndex + 1, right); return root; }
二叉树的双指针遍历
- 530. 二叉搜索树的最小绝对差
- 注意
INT_MAX
的溢出问题
- 注意
cpp
int getMinimumDifference(TreeNode* root) {
// 基本初始化
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
int result = INT_MAX;
TreeNode* prior = new TreeNode(-100000); // 给根节点前面一个初始化条件
// 迭代
while (!st.empty()) {
TreeNode* cur = st.top();
if (cur != NULL) {
// 弹出根节点再重排序
st.pop();
if (cur->right) st.push(cur->right);
st.push(cur);
st.push(NULL);
if (cur->left) st.push(cur->left);
}
else {
st.pop();// 出null
cur = st.top();
st.pop();
// 节点处理
result = min(result, cur->val - prior->val);
prior = cur;// 迭代条件要放在最后
}
}
return result;
}
654. 最大二叉树
-
- 如果两颗树有个相同位置的节点一个为空,另一个不是。则应该直接链接过去,因为这样可以保证后面的也过去
cpp// 递归 TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { if (t1 == NULL) return t2;// 其中一个为空则返回另一个 if (t2 == NULL) return t1; // 重新定义新的节点,不修改原有两个树的结构 TreeNode* root = new TreeNode(0); root->val = t1->val + t2->val; root->left = mergeTrees(t1->left, t2->left);// 直接链接 root->right = mergeTrees(t1->right, t2->right); return root; } // 非递归方式 TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { if (t1 == NULL) return t2; if (t2 == NULL) return t1; queue<TreeNode*> que; que.push(t1); que.push(t2); while(!que.empty()) { TreeNode* node1 = que.front(); que.pop(); TreeNode* node2 = que.front(); que.pop(); // 此时两个节点一定不为空,val相加 node1->val += node2->val; // 如果两棵树左节点都不为空,加入队列 if (node1->left != NULL && node2->left != NULL) { que.push(node1->left); que.push(node2->left); } // 如果两棵树右节点都不为空,加入队列 if (node1->right != NULL && node2->right != NULL) { que.push(node1->right); que.push(node2->right); } // 当t1的左节点 为空 t2左节点不为空,就赋值过去 if (node1->left == NULL && node2->left != NULL) { node1->left = node2->left; } // 当t1的右节点 为空 t2右节点不为空,就赋值过去 if (node1->right == NULL && node2->right != NULL) { node1->right = node2->right; } } return t1; }
二叉搜索树
查找二叉搜索树的指定值
-
利用二叉搜索树的
左小右大
- 栈、队列和树中元素的访问要注意
判空
,防止访问溢出
cppbool handleNode(TreeNode* root, int key) { // 健壮性检查 if (root == nullptr) return false; // 双指针 TreeNode *cur = root; TreeNode *prev = root; while(cur != nullptr){ // 结点的处理 if (cur->val == key) { Doing(); } // 指针移动 prev = cur; if (key < cur->val) { if (cur->left) cur = cur->left; else return root; } else { if (cur->right) cur = cur->right; else return root; } } return root; }
- 栈、队列和树中元素的访问要注意
98. 验证二叉搜索树
-
- 中序遍历下,输出的二叉搜索树节点的数值是有序序列
cpp// **********中序遍历,形成一个递增数组************** vector<int> vec; void inorder(TreeNode *root){ if(root == nullptr) return ; inorder(root->left); vec.push_back(root->val); inorder(root->right); } // 判断是否中序遍历的数组是递增的 bool isValidBST(TreeNode* root){ inorder(root); for(int i = 0; i < vec.size()-1; ++i){ if(vec[i] >= vec[i+1])// 二叉搜索树的中序排列是严格递增的 return false; } return true; } // *********************纯递归********************** bool isValid(TreeNode* current,long left,long right){ // 单层逻辑 if(current==nullptr) return true; else if(current->val<=left||current->val>=right) return false; // 递归 return isValid(current->left,left,current->val) &&isValid(current->right,current->val,right); } bool isValidBST(TreeNode* root) { return isValid(root,LONG_MIN,LONG_MAX); }
530. 二叉搜索树的最小绝对差
-
- 思路:中序遍历下,输出的二叉搜索树节点的数值是有序序列。顺序判断相邻值的绝对值,保存最小的即可
- 双指针在树内应用,双指针本质是对于一个序列的遍历。
cppint getMinimumDifference(TreeNode* root) { // 初始化条件 stack<TreeNode*> st; if(root != nullptr) st.push(root); int res = INT_MAX; TreeNode *prior = new TreeNode(-1000000); while(!st.empty()){ TreeNode* cur = st.top(); if(cur != nullptr){ st.pop(); // 中序遍历 if(cur->right) st.push(cur->right); st.push(cur); st.push(nullptr); if(cur->left) st.push(cur->left); }else{ st.pop(); cur = st.top(); st.pop(); // 节点处理 res = min(res, cur->val - prior->val); prior = cur;// 迭代条件 } } return res; }
236. 二叉树的最近公共祖先
-
- 后序遍历是一个天然的
自低向上
的回溯过程 - 状态的向上传递:通过判断左右子树是否出现了p和q,如果出现p或q则通过回溯值上传到父节点
cppTreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if(root == NULL) return NULL; // 每次对返回的结点进行 if(root == p || root == q) return root; TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q); // 结点的处理是:尽量返回结点 if(left == NULL) return right; if(right == NULL) return left; if(left && right) // p和q在两侧 return root; return NULL; // 必须有返回值 }
- 后序遍历是一个天然的
235. 二叉搜索树的最近公共祖先
-
- 思路:自上而下搜索,遇到的第一个节点值在p和q之间的值即为最近公共祖先
cppTreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { while(root) { if (root->val > p->val && root->val > q->val) { root = root->left; } else if (root->val < p->val && root->val < q->val) { root = root->right; } else return root; } return NULL; }
450. 删除二叉搜索树中的节点
-
- 思路:框架
cppTreeNode* deleteNode(TreeNode* root, int key) { // 健壮性检查 if(root == nullptr) return nullptr; // 基本初始化 TreeNode *cur = root; TreeNode *prior = root; while (cur != nullptr){ // 符合条件值的处理 if(cur->val == key){ if(cur->left == nullptr || cur->right == nullptr){ // 两个都空 if(cur->left == nullptr && cur->right == nullptr) return nullptr; // 被删除节点只有一个孩子或均为空 if(key < prior->val){// cur是左子树 prior->left = cur->right; return root; }else{ prior->right n = cur->right; return root; } }else{ // 被删除节点有两个孩子 TreeNode *curLeft = cur->left; cur = cur->right; while(cur->left != nullptr){ cur = cur->left; } cur->left = curLeft; if(key < prior->val){// cur是左子树 prior->left = prior->left->right; return root; }else{ prior->right = prior->right->right; return root; } } } prior = cur;// 前迭代 // 左右节点处理 if(key < cur->val){ if(cur->left){ cur = cur->left; }else{// 找不到 return root; } }else{ if(cur->right){ cur = cur->right; }else{// 找不到 return root; } } } return root; }
669. 修剪二叉搜索树
-
cpp
// 1. 确定递归函数的返回类型及参数,返回类型是递归算法的输出值类型,参数是递归算法的输入 TreeNode* trimBST(TreeNode* root, int low, int high) { // 2. 递归终止条件 if (root == nullptr ) return nullptr; // 3.节点处理:return保留的状态 if (root->val < low) {// 保留更大的右半部分 TreeNode* right = trimBST(root->right, low, high); return right; } if (root->val > high) {// 保留更小的左半部分 TreeNode* left = trimBST(root->left, low, high); return left; } // 4.迭代条件 root->left = trimBST(root->left, low, high); // root->left接入符合条件的左孩子 root->right = trimBST(root->right, low, high); // root->right接入符合条件的右孩子 return root; }
108. 将有序数组转换为二叉搜索树
-
cpp
TreeNode* traversal(vector<int>& nums, int left, int right) { // 递归出口 if (left > right) return nullptr; // 运算 int mid = left + ((right - left) / 2);// 防止求和溢出 TreeNode* root = new TreeNode(nums[mid]); // 递归迭代 root->left = traversal(nums, left, mid - 1); root->right = traversal(nums, mid + 1, right); return root; } // 主调函数 TreeNode* sortedArrayToBST(vector<int>& nums) { TreeNode* root = traversal(nums, 0, nums.size() - 1); return root; }
669. 修剪二叉搜索树
-
cpp
// 1. 确定递归函数的返回类型及参数,返回类型是递归算法的输出值类型,参数是递归算法的输入 TreeNode* trimBST(TreeNode* root, int low, int high) { // 2. 递归终止条件 if (root == nullptr ) return nullptr; // 3.节点处理:return保留的状态 if (root->val < low) {// 保留更大的右半部分 TreeNode* right = trimBST(root->right, low, high); return right; } if (root->val > high) {// 保留更小的左半部分 TreeNode* left = trimBST(root->left, low, high); return left; } // 4.迭代条件 root->left = trimBST(root->left, low, high); // root->left接入符合条件的左孩子 root->right = trimBST(root->right, low, high); // root->right接入符合条件的右孩子 return root; }
[LeetCode] 333. 最大 BST 子树
-
代码
cpp// 1. 确定递归函数的返回类型及参数,返回类型是递归算法的输出值类型,参数是递归算法的输入 TreeNode* trimBST(TreeNode* root, int low, int high) { // 2. 递归终止条件 if (root == nullptr ) return nullptr; // 3.节点处理:return保留的状态 if (root->val < low) {// 保留更大的右半部分 TreeNode* right = trimBST(root->right, low, high); return right; } if (root->val > high) {// 保留更小的左半部分 TreeNode* left = trimBST(root->left, low, high); return left; } // 4.迭代条件 root->left = trimBST(root->left, low, high); // root->left接入符合条件的左孩子 root->right = trimBST(root->right, low, high); // root->right接入符合条件的右孩子 return root; }
少年,我观你骨骼清奇,颖悟绝伦,必成人中龙凤。 不如点赞·收藏·关注一波