【左程云算法全讲7】二叉树基础算法及递归套路

news2024/11/25 23:15:10

系列综述:
💞目的:本系列是个人整理为了秋招面试的,整理期间苛求每个知识点,平衡理解简易度与深入程度。
🥰来源:材料主要源于左程云算法课程进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验自证。
🤭结语:如果有帮到你的地方,就点个赞关注一下呗,谢谢🎈🎄🌷!!!
🌈【C++】秋招&实习面经汇总篇


文章目录

    • 二叉树理论基础
      • 基本知识
      • 二叉树的递归套路例题
      • 二叉树深度优先遍历*
      • 二叉树广度优先遍历*
      • 二叉树最大深度
      • 二叉树最小深度
      • 求树中结点的数量
      • 判断是否为平衡二叉树
    • 相关题目
      • 翻转二叉树
      • 二叉树是否对称
      • 二叉树的所有路径
      • 左叶子之和
      • 求二叉树最左下的叶子
      • 符合总和的路径
    • 构建二叉树
      • 树的序列化
      • 105. 从前序与中序遍历序列构造二叉树
      • 106. 从中序与后序遍历序列构造二叉树
      • 654. 构建二叉树*
      • 二叉树的双指针遍历
      • 654. 最大二叉树
    • 二叉搜索树
      • 查找二叉搜索树的指定值
      • 98. 验证二叉搜索树
      • 530. 二叉搜索树的最小绝对差
      • 236. 二叉树的最近公共祖先
      • 235. 二叉搜索树的最近公共祖先
      • 450. 删除二叉搜索树中的节点
      • 669. 修剪二叉搜索树
      • 108. 将有序数组转换为二叉搜索树
      • 669. 修剪二叉搜索树
      • [LeetCode] 333. 最大 BST 子树
    • 参考博客


😊点此到文末惊喜↩︎


二叉树理论基础

基本知识

  1. 二叉树数据结构
    struct TreeNode {
        int val;
        TreeNode *left;
        TreeNode *right;
        TreeNode(int x) : val(x), left(NULL), right(NULL) {}
    };
    
  2. 二叉树递归套路
    • 建立Info结构体:数据元素为向左右子树索要的信息的集合
    • 递归出口:考虑递归到底部应该如何返回info信息
    • 进行左右子树的递归处理
    • 修改info信息:一般为选择和不选择两种情况进行考虑
    • 返回info信息:返回的实际是整合后的一颗树的info信息
  3. 模板例题
    • 题目:给定一颗二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树的头节点。搜索二叉树指的是整棵树没有重复的值,并且每颗子树都是左小右大。
// 思路:
// 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)

}




二叉树的递归套路例题

  1. 题目链接:【二叉树的直径】
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);
	
}

二叉树深度优先遍历*

  1. Leetcode题目链接
  2. 递归序
    • 原理:递归出口、递归左子树、递归右子树:三个部分中间及后面的范围成为其递归处理范围。
    • 过程:每个结点都会被经历三次,第一次是根结点处理范围、左子树处理范围和右子树处理范围
    • 本质:递归出口和子递归函数,每一部分都是递归+该递归处理范围
    // 递归序
    void f(TreeNode *root) {
      if (root == nullptr) return ;
      // 根结点处理范围
      f(root->left);  
      // 左子树处理范围
      f(root->right); 
      // 右子树处理范围
    }
    
  3. 递归式
    • 前序遍历:任何子树的处理顺序都是,先根节点、再左子树,然后右子树
    • 中序遍历:任何子树的处理顺序都是,先左子树、再根节点,然后右子树
    • 后序遍历:任何子树的处理顺序都是,先左子树、再右子树,然后根节点
    // 前序遍历
    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);// 中
    }
    
  4. 递归和非递归
    • 任何递归函数都可以通过自己设计压栈的方式改成非递归
  5. 非递归:将前序、中序和后序统一化处理,将遍历核心顺序进行逆序转化
    • 初始化:声明结果容器、栈、根非空则入栈
    • 算法:栈非空,每次取栈顶元素。判断结点是否为空,若为空则弹出并逆序压入对应元素,若非空则弹出结点和空结点并进行处理
    vector<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;
    }
    

二叉树广度优先遍历*

  1. Leetcode题目链接
  2. 递归法
    // 递归参数,如果需要修改要进行引用传递
    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;
    }
    
  3. 非递归法
    vector<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;
    }
    

二叉树最大深度

  1. 递归法
    // 递归只考虑当前层,不要过于考虑整体
    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;
    }
    
  2. 非递归法
    int 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;
    }
    

二叉树最小深度

  1. 递归法
    • 二叉树的五种形态
      • 空二叉树
      • 只有根节点
      • 只有左子树
      • 只有右子树
      • 左右子树都有
    int 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));
    }
    
  2. 非递归法
    • 找到第一个左右孩子均为空的,即为最小深度
    int 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;
    }
    

求树中结点的数量

  1. 递归法
    • 递归法要只考虑单层的逻辑
    int 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;
    }
    
  2. 非递归法
    int 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;
    }
    

判断是否为平衡二叉树

  1. 递归法
    • 后序遍历和求树的高度的模板改进
// 初始化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);
}

相关题目

翻转二叉树

  1. 翻转二叉树
    • 对于二叉树的操作都是从二叉树的遍历衍生出来的
      在这里插入图片描述
    // 前序遍历
    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;
    }
    

二叉树是否对称

  1. 101. 对称二叉树
    • 对称二叉树要比较的不是左右节点,而是比较根节点的左右子树是否值相等
      在这里插入图片描述
    bool 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);
    }
    
    
  2. 有点东西的写法(这是哪个题的?)判断链表的入口的
    ListNode *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;
    }
    

二叉树的所有路径

  1. 递归
    • 数字转化成字符串to_string(number)
    • 字符串后追加子串str.append(subStr)
    • 字符串删除某个位置之后的字符str.erase(position)
    // 数字型
    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;
    }
    

左叶子之和

  1. 求二叉树的左叶子之和
    • 遍历所有节点,对所求的特殊节点进行约束求值
    void 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;
    }
    
  2. 迭代法
    int 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;
    }
    

求二叉树最左下的叶子

  1. 513. 找树左下角的值
    • 层次遍历最后一层的第一个,就是最左下的叶子
    int 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;
    }
    

符合总和的路径

  1. 112. 路径总和
    • 增加结点就加值,不符合就回溯进行减值。
    bool 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;
    }
    

构建二叉树

树的序列化

  1. 树的序列化和反序列化
    • 序列化:树的遍历在输出时,将所有结点的左右孩子补全,没有的使用null代替
    • 反序列化:按照什么遍历方式序列化,就按照什么遍历方式反序列化
  2. 深度优先的的序列化和反序列化
// 序列化
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. 从前序与中序遍历序列构造二叉树

  1. 105. 从前序与中序遍历序列构造二叉树

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. 从中序与后序遍历序列构造二叉树

  1. 106. 从中序与后序遍历序列构造二叉树
    • 通过始末位置指示容器范围,避免每次调用的vector创建开销
    // 中序区间:[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. 构建二叉树*

  1. 654. 最大二叉树
    • 通过始末位置指示容器范围,避免每次调用的vector创建开销
    // 在左闭右开区间[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;
    }
    

二叉树的双指针遍历

  1. 530. 二叉搜索树的最小绝对差
    • 注意INT_MAX的溢出问题
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. 最大二叉树

  1. 617. 合并二叉树
    • 如果两颗树有个相同位置的节点一个为空,另一个不是。则应该直接链接过去,因为这样可以保证后面的也过去
    // 递归
    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;
    }
    

二叉搜索树

查找二叉搜索树的指定值

  1. 利用二叉搜索树的左小右大
    • 栈、队列和树中元素的访问要注意判空,防止访问溢出
    bool 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. 验证二叉搜索树

  1. 98. 验证二叉搜索树
    • 中序遍历下,输出的二叉搜索树节点的数值是有序序列
    // **********中序遍历,形成一个递增数组**************
    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. 二叉搜索树的最小绝对差

  1. 530. 二叉搜索树的最小绝对差
    • 思路:中序遍历下,输出的二叉搜索树节点的数值是有序序列。顺序判断相邻值的绝对值,保存最小的即可
    • 双指针在树内应用,双指针本质是对于一个序列的遍历。
    int 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. 二叉树的最近公共祖先

  1. 236. 二叉树的最近公共祖先
    • 后序遍历是一个天然的自低向上的回溯过程
    • 状态的向上传递:通过判断左右子树是否出现了p和q,如果出现p或q则通过回溯值上传到父节点
        TreeNode* 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. 二叉搜索树的最近公共祖先

  1. 235. 二叉搜索树的最近公共祖先
    • 思路:自上而下搜索,遇到的第一个节点值在p和q之间的值即为最近公共祖先
    TreeNode* 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. 删除二叉搜索树中的节点

  1. 450. 删除二叉搜索树中的节点
    • 思路:框架
    TreeNode* 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. 修剪二叉搜索树

  1. 669. 修剪二叉搜索树
    // 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. 将有序数组转换为二叉搜索树

  1. 108. 将有序数组转换为二叉搜索树
    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. 修剪二叉搜索树

  1. 669. 修剪二叉搜索树
    // 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 子树

  1. 代码
    // 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;
    }
    


少年,我观你骨骼清奇,颖悟绝伦,必成人中龙凤。
不如点赞·收藏·关注一波


🚩点此跳转到首行↩︎

参考博客

  1. 代码随想录
  2. letcode

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1208349.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据仓库】数仓分层方法详解与层次调用规范

文章目录 一. 数仓分层的意义1. 清晰数据结构。2. 减少重复开发3. 方便数据血缘追踪4. 把复杂问题简单化5. 屏蔽原始数据的异常6. 数据仓库的可维护性 二. 如何进行数仓分层&#xff1f;1. ODS层2. DW层2.1. DW层分类2.2. DWD层2.3. DWS 3. ADS层 4、层次调用规范 一. 数仓分层…

前端学习笔记--面试题系列总结

event loop它的执行顺序&#xff1a; 一开始整个脚本作为一个宏任务执行执行过程中同步代码直接执行&#xff0c;宏任务进入宏任务队列&#xff0c;微任务进入微任务队列当前宏任务执行完出队&#xff0c;检查微任务列表&#xff0c;有则依次执行&#xff0c;直到全部执行完执…

vue3 el-menu初始化时选中没有高亮的问题(default-active和index的问题)

首先看官方文档的示例&#xff1a; 需要注意的是&#xff1a; 1、default-active的值是字符串&#xff0c;那么index绑定的值也要是字符串&#xff0c;且数字对应。不能default-avtive绑定的是1&#xff0c;而menu-item的index绑定的是45 2、default-active的值是当前选中me…

产品运营的场景和运营策略

一、启动屏 1&#xff0e;概念 启动屏&#xff0c;特指 APP 产品启动时即显示的界面&#xff0c;这个界面一般会停留几秒钟时间&#xff0c;在这个时间内 APP 会在后台加载服务框架、启动各种服务 SDK 、获取用户地理位置、判断有无新版本、判断用户账户状态以及其他系统级别的…

2023.11.13 Spring Bean 的生命周期

目录 Spring 执行流程 Bean 的生命周期 五个阶段 深入理解 Bean 初始化 实例理解 总结梳理 经典面试题 Spring 执行流程 Bean 的生命周期 Spring 中 Bean 的生命周就是 Bean 在 Spring 中从创建到销毁的整个过程 五个阶段 1. 实例化 Bean 为 Bean 对象分配内存空间 …

如何在Photoshop 中创建橡皮图章效果

如何在 Photoshop 中制作橡皮图章。只需几个快速步骤即可将任何照片变成橡皮图章图像 1. 如何创建垃圾纸背景 步骤1 让我们开始学习如何制作自定义印章。创建一个新的850 x 550 像素 文档。当然&#xff0c;您可以为 PSD 文件使用其他尺寸&#xff0c; 但必须按比例调整本教程…

合肥中科深谷嵌入式项目实战——基于ARM语音识别的智能家居系统(二)

目录 基于ARM语音识别的智能家居系统 练习一 一、程序编译 练习二&#xff1a; 二、文件IO 三、文件IO常用API接口函数 1、打开文件 open&#xff08;&#xff09; 2、将数据内容写入文件 write&#xff08;&#xff09; 3、关闭&#xff08;保存&#xff09;文件 四、…

spring-cloud 简介

springcloud 定义 1.定义&#xff1a;springcloud为开发人员提供了在分布式系统中快速构建一些通用模式的工具&#xff08;例如配置管理、服务发现、断路器、路由、控制总线等&#xff09;2.微服务:基于单体应用&#xff0c;基于业务进行拆分&#xff0c;每个服务都是独立应用…

应用层——HTTPS协议

文章目录 一.HTTPS协议介绍二.关于加密1.什么是"加密"2.为什么要加密3.常见的加密方式4.数据摘要 && 数据指纹 三.HTTPS的工作过程探究1.方案1 —— 只使用对称加密&#xff08;明文传输不可取&#xff09;2.方案2 —— 只使用非对称加密&#xff08;仅单向安…

图文示例:Python程序的运行原理解读

文章目录 一、编译型语言&#xff08;C语言为例&#xff09;二、动态型语言三、程序是如何运行起来的&#xff1f;四、分析五、dir 函数六、def 指令七、pyc文件1.pyc文件三大作用 八、import 指令总结关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三…

zabbix中图形可视化页面中文乱码解决

在window 电脑中的 C:\Windows\Fonts 里面是字体文件&#xff0c;里面有一个 SIMKAI.TTF &#xff08;有的是小写&#xff09; 这个是楷体 将该文件复制到虚拟机中 怎么导入应该不需要我说吧 查看zabbix的字体文件在哪个目录下 [rootlocalhost /]# find / -name fonts /boo…

【Android】画面卡顿优化列表流畅度五之下拉刷新上拉加载更多组件RefreshLayout修改

之前也写过类似组件的介绍&#xff1a; 地址&#xff1a;下拉刷新&上拉加载更多组件SmartRefreshLayout 本来打算用这个替换的&#xff0c;但在进行仔细研究发现不太合适。功能都很好&#xff0c;但嵌入不了当前的工程体系里。原因就是那啥体制懂的都懂。这样的组件需要改…

力扣 225. 用队列实现栈(C语言实现)

目录 1.解题思路2.代码实现 1.解题思路 这道题如果使用C会好写的多&#xff0c;因为可以使用C提供的队列来实现&#xff0c;但如果使用C语言则必须手写一个队列来实现&#xff0c;在这里我用了我前面文章中实现好的队列来解答&#xff0c;首先因为队列是先进先出&#xff0c;而…

揭秘视频号创作者分成计划,带你玩赚视频号流量主,保姆级教程

项目介绍 今天给大家分享一个视频号流量主的一个项目&#xff0c;也就是视频号创作者广告分成计划。这个项目在目前来说是一个蓝海赛道&#xff0c;做的人是比较少&#xff0c;作为副业来说还是非常适合个人来做的。如果大家有工作室的话&#xff0c;也可以进行批量操作&#…

2021年06月 Scratch(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

一、单选题(共25题,每题2分,共50分) 第1题 小猫位置在舞台中心,点击一次小猫后能前进10步的程序为? A: B: C: D: 答案:B 第2题 快速切换到下一个背景图片应该使用哪个积木? A: B:

MySQL库的操作『增删改查 ‖ 编码问题 ‖ 备份与恢复』

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; MySQL 学习 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 &#x1f381;软件版本&#xff1a; MySQL 5.7.44 文章目录 1.创建数据库2.数据库中的编码问题2.1.字符集与校验集2.3.支持的字符…

环保气膜建筑的运维成本在哪几个方面

作为一种环保建筑&#xff0c;气膜结构在工业和文体领域得到了广泛认可。尽管气膜建筑在经济上具有明显的优势&#xff0c;但对于不了解它的人来说&#xff0c;他们可能会下意识地认为在运营和维护过程中会产生大量费用。今天&#xff0c;让我们一起了解一下气膜建筑在运营维护…

【蓝桥杯选拔赛真题68】Scratch打地鼠游戏 少儿编程scratch图形化编程 蓝桥杯创意编程选拔赛真题解析

目录 scratch打地鼠游戏 一、题目要求 编程实现 二、案例分析 1、角色分析

未来之路:大模型技术在自动驾驶的应用与影响

本文深入分析了大模型技术在自动驾驶领域的应用和影响&#xff0c;万字长文&#xff0c;慢慢观看~ 文中首先概述了大模型技术的发展历程&#xff0c;自动驾驶模型的迭代路径&#xff0c;以及大模型在自动驾驶行业中的作用。接着&#xff0c;详细介绍了大模型的基本定义、基础功…

【论文阅读】(CTGAN)Modeling Tabular data using Conditional GAN

论文地址&#xff1a;[1907.00503] Modeling Tabular data using Conditional GAN (arxiv.org) 摘要 对表格数据中行的概率分布进行建模并生成真实的合成数据是一项非常重要的任务&#xff0c;有着许多挑战。本文设计了CTGAN&#xff0c;使用条件生成器解决挑战。为了帮助进行公…