代码随想录算法训练营第23天 || 669. 修剪二叉搜索树 || 108.将有序数组转换为二叉搜索树 || 538.把二叉搜索树转换为累加树 || 总结篇

代码随想录算法训练营第23天 || 669. 修剪二叉搜索树 || 108.将有序数组转换为二叉搜索树 || 538.把二叉搜索树转换为累加树 || 总结篇

669. 修剪二叉搜索树

给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。

示例 1:

代码随想录算法训练营第23天 || 669. 修剪二叉搜索树 || 108.将有序数组转换为二叉搜索树 || 538.把二叉搜索树转换为累加树 || 总结篇_第1张图片

输入:root = [1,0,2], low = 1, high = 2
输出:[1,null,2]

示例 2:

代码随想录算法训练营第23天 || 669. 修剪二叉搜索树 || 108.将有序数组转换为二叉搜索树 || 538.把二叉搜索树转换为累加树 || 总结篇_第2张图片

输入:root = [3,0,4,null,2,null,null,1], low = 1, high = 3
输出:[3,2,null,1]

个人思路:

递归法法三部曲

  • 确定参数和返回值
    • 参数:结点、左边界、右边界
    • 返回值:结点
  • 确定递归结束条件:结点为null
  • 确定单层逻辑
    • 先判断当前结点是否越界,若越界直接去处理可能未越界的那个孩子来返回
    • 若没越界,则左右孩子均处理
    • 处理完毕返回该节点即可
class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        return trimBST_(root, low, high);
    }

    public TreeNode trimBST_(TreeNode root, int low, int high) {
        if (root == null)
            return null;
        if (root.val < low)//只剩下左孩子可能在范围中,去左孩子中筛选返回
            return trimBST_(root.right, low, high);
        else if (root.val > high)//只剩下右孩子可能在范围中,去右孩子中筛选返回
            return trimBST_(root.left, low, high);
        //左右孩子均可能存在在范围的数,分别筛选再返回作为当前结点的左右孩子结点
        root.left = trimBST_(root.left, low, high);
        root.right = trimBST_(root.right, low, high);
        return root;
    }
}

题解中的迭代法:

因为二叉搜索树的有序性,不需要栈来模拟递归

三步:

  1. 调整根节点
  2. 调整左孩子
  3. 调整右孩子

难点:

  • 习惯了递归法之后较难跳出惯性思维,总想着函数解决一个问题
  • 因为返回结果是根节点,所以一开始就得先调整好根节点
  • 调整好根节点之后,左子树只需要判断左孩子情况,因为右孩子不可能大于根节点(根节点已调整好),并且左孩子node不符合的时候,自动连接到左孩子node的右孩子上;右子树的处理同理
class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        if (root == null)
            return null;
        //调整根节点
        while (root != null && (root.val > high || root.val < low)) {
            if (root.val > high)
                root = root.left;
            else if (root.val < low)
                root = root.right;
        }
        //调整左子树(左子树的右子树必然符合条件
        TreeNode node = root;
        while (node != null) {
            while (node.left != null && (node.left.val < low || node.left.val > high)) {
                node.left = node.left.right;
            }
            node = node.left;
        }
        //调整右子树(右子树的左子树必然符合条件
        node = root;
        while (node != null) {
            while (node.right != null && (node.right.val < low || node.right.val > high)) {
                node.right = node.right.left;
            }
            node = node.right;
        }
        return root;
    }
}

108.将有序数组转换为二叉搜索树

题目简介:

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。

高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

示例 1:

代码随想录算法训练营第23天 || 669. 修剪二叉搜索树 || 108.将有序数组转换为二叉搜索树 || 538.把二叉搜索树转换为累加树 || 总结篇_第3张图片

输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:

示例 2:

代码随想录算法训练营第23天 || 669. 修剪二叉搜索树 || 108.将有序数组转换为二叉搜索树 || 538.把二叉搜索树转换为累加树 || 总结篇_第4张图片

输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。

个人思路:

和之前做过的某道题有点类似,大概思路就是切割数组

切割的时候,尤其注意边界问题的处理

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return build(nums);
    }

    public TreeNode build(int[] nums) {
        if (nums.length == 0)
            return null;
        TreeNode node = new TreeNode(nums[nums.length / 2]);
        node.left = build(Arrays.copyOfRange(nums, 0, nums.length / 2));
        node.right = build(Arrays.copyOfRange(nums, nums.length / 2 + 1, nums.length));
        return node;
    }
}

538.把二叉搜索树转换为累加树

题目简介:

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。

  • 节点的右子树仅包含键 大于 节点键的节点。

  • 左右子树也必须是二叉搜索树。

  • 示例 1:

    代码随想录算法训练营第23天 || 669. 修剪二叉搜索树 || 108.将有序数组转换为二叉搜索树 || 538.把二叉搜索树转换为累加树 || 总结篇_第5张图片

    输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
    输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
    

    示例 2:

    输入:root = [0,null,1]
    输出:[1,null,1]
    

    示例 3:

    输入:root = [1,0,2]
    输出:[3,3,2]
    

    示例 4:

    输入:root = [3,2,4,1]
    输出:[7,9,4,10]
    

个人思路:

这是一棵二叉搜索树,根据题意,我们可以选择使用右 中 左遍历,这样遍历得到的便是从大到小的顺序。遍历过程中,我们要一直累加sum变量。

递归三部曲:

  • 确定参数和返回值:均为结点

  • 确定递归结束条件:结点为null

  • 确定单层递归逻辑:修改结点值和累计sum值

    root.val += sum;
    sum = root.val;
    
class Solution {
    public int sum = 0;

    public TreeNode convertBST(TreeNode root) {
        return build(root);
    }

    public TreeNode build(TreeNode root) {
        if (root == null)
            return null;
        //右 中 左遍历,即从大到小的遍历,sum进行累加
        root.right = build(root.right);

        root.val += sum;
        sum = root.val;

        root.left = build(root.left);
        return root;
    }
}

题解解析:

整体思路差不多,但递归的返回值其实可以为null

关于返回值为void的情况:通常不需要调整树的结构,只需要改变结点的值即可

class Solution {
    public int sum = 0;

    public TreeNode convertBST(TreeNode root) {
        build(root);
        return root;
    }

    public void build(TreeNode root) {
        if (root == null)
            return;
        //右 中 左遍历,即从大到小的遍历,sum进行累加
        build(root.right);
        root.val += sum;
        sum = root.val;
        build(root.left);
    }
}

总结篇

二叉树 理论基础
  • 二叉树的种类:满二叉树、完全二叉树、二叉搜索树
  • 存储方式:链式(几乎都是)、线性
  • 遍历方式
    • 深度优先:先序、中序、后序(递归或迭代)
    • 广度优先:层序(迭代法)
  • 定义方式:定义好结点类即可
二叉树的遍历方法
  • 深度优先遍历

    • 前中后序 (递归实现)

    • 前中后序 (模拟栈实现)

      • 先序容易实现
      • 后序只需要先调整为中右左遍历方式,得到result数组再反转即可
      • 中序:需要一个指针辅助,一路向左直到遇到null,弹出栈顶即为中,并转右,往返循环
      // 中序遍历顺序: 左-中-右 入栈顺序: 左-右
      class Solution {
          public List<Integer> inorderTraversal(TreeNode root) {
              List<Integer> result = new ArrayList<>();
              if (root == null){
                  return result;
              }
              Stack<TreeNode> stack = new Stack<>();
              TreeNode cur = root;
              while (cur != null || !stack.isEmpty()){
                 if (cur != null){
                     stack.push(cur);
                     cur = cur.left;
                 }else{
                     cur = stack.pop();
                     result.add(cur.val);
                     cur = cur.right;
                 }
              }
              return result;
          }
      }
      
    • 前中后序 (模拟栈并统一风格)

      • 总体上来说,就是使用一个NULL指针入栈标记,遇到出栈时遇到null才会处理
      • 顺序上的调整,只需要改变入栈当前结点 与 左右孩子的顺序即可
      class Solution {
          public List<Integer> preorderTraversal(TreeNode root) {
              List<Integer> result = new LinkedList<>();
              Stack<TreeNode> st = new Stack<>();
              if (root != null) st.push(root);
              while (!st.empty()) {
                  TreeNode node = st.peek();
                  if (node != null) {
                      st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                      
                      //只需要调整下面这三部分代码顺序即可改变遍历顺序
                      if (node.right!=null) st.push(node.right);  // 添加右节点(空节点不入栈)
                      if (node.left!=null) st.push(node.left);    // 添加左节点(空节点不入栈)
                      st.push(node);                          // 添加中节点
                      st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
                      
                  } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                      st.pop();           // 将空节点弹出
                      node = st.peek();    // 重新取出栈中元素
                      st.pop();
                      result.add(node.val); // 加入到结果集
                  }
              }
              return result;
          }
      }
      
  • 广度优先遍历

    • 层序遍历:使用队列和一个size变量记录当前层的结点数量(方便遍历每一层),实现也不难

下面是分类题型总结,若未涉及某种方法等二刷再补上

求二叉树的属性
  • 对称问题
    • 递归:左右子树(两个参数递归),分别递归比较内侧与外侧即可
    • 迭代:把要比较的结点一对一对放进去即可,到时候再成对弹出比较
  • 最大深度
    • 递归:遍历递归返回每一层的深度即可
    • 迭代:层序遍历解决
  • 最小深度
    • 递归:递归返回取每一层的较小深度即可
    • 迭代:层序遍历到某结点无左右结点即可
  • 所有路径
    • 递归:每次递归结束就回溯到之前的状态,找到路径就保存
  • 左叶子之和
    • 递归:遍历找到左孩子即可(其遍历到双亲结点的时候就要去判断,且判断其无孩子结点)
  • 求左下角的值
    • 递归:先序遍历,先遍历到的就是左孩子,我们只需要比较层数来保存即可,层数相同先遍历到的就是最左
  • 路径总和
    • 递归:先序遍历,递归结束完要回溯sum即可,这里int类型实际上隐式回溯了
二叉树的修改与构造
  • 翻转二叉树
    • 递归:遍历交换左右孩子即可
    • 迭代:层序遍历交换左右孩子
  • 构造二叉树
    • 递归:通过两个不同遍历顺序的数组,先找到根节点的位置,然后切割数组,分别递归构造左右孩子
  • 构造最大二叉树
    • 递归:通过数组最大值定位根节点位置,同样是切割数组递归构造左右孩子
  • 合并两个二叉树
    • 递归:遍历两棵树,创建新节点(val为两个树val之和),递归构造左右孩子即可,注意处理null结点

求二叉搜索树的属性

  • 二叉搜索树的搜索
    • 递归:比较选择方向递归即可
  • 验证二叉搜索树
    • 递归:中序遍历(得到就是从小到大的顺序),新增一个pre指针比较前后节点即可
  • 求二叉搜索树的最小绝对差
    • 递归:中序遍历,比较差值,得到最小的即可
  • 求二叉搜索树的众数
    • 递归:中序,记录一下当前数出现的次数与最大次数比较,超过最大次数则刷新再加入,等于最大次数直接加入
  • 二叉搜索树转换成累加树
    • 递归:右 中 左 (从大到小)遍历,遍历的过程不断累加sum值处理即可
二叉树的公共祖先问题
  • 二叉树的最近公共祖先问题
    • 递归:
      • 先序遍历得到路径并保存,之后比较路径的每一位即可
      • 后序遍历找到就返回结点,未找到返回null
  • 二叉搜索树的最近公共祖先问题
    • 递归:简便了许多判断,只需要与当前结点比较一下
      • pq都去右孩子或都去左孩子找
      • 一个在右孩子,另一个在左孩子,那么该节点就是最近公共祖先
二叉搜索树的修改与构造
  • 二叉搜索树的插入操作

    • 递归:比较后选择方向递归,遇到null就插入
  • 二叉搜索树的删除操作

    • 递归:多种情况慢慢分析
      • 左右孩子均为null
      • 一个孩子为null
      • 两个孩子均不为null(选择右孩子的一路向左孩子调位)
        • 删除结点的右孩子没有左孩子
        • 删除结点的右孩子有左孩子:使用pre指针辅助,找到一路向左孩子时,进行一系列的调整即可
  • 修剪二叉搜索树

    • 递归:比较当前结点,不在范围就选择性修剪往左或右孩子遍历返回替代当前结点;自身在范围内才去递归分别修剪左右孩子
  • 构造高度平衡二叉搜索树

    • 递归:中间的值即为根节点,切割数组递归左右孩子即可
最后总结:
  • 二叉树的构造:一般是先序,先有双亲才有孩子

  • 二叉树的属性:一般是后序,先得到返回值才能处理当前结点的要素

  • 二叉搜索树:一定是中序,有序性要利用起来

  • 二叉搜索树的插入操作

    • 递归:比较后选择方向递归,遇到null就插入
  • 二叉搜索树的删除操作

    • 递归:多种情况慢慢分析
      • 左右孩子均为null
      • 一个孩子为null
      • 两个孩子均不为null(选择右孩子的一路向左孩子调位)
        • 删除结点的右孩子没有左孩子
        • 删除结点的右孩子有左孩子:使用pre指针辅助,找到一路向左孩子时,进行一系列的调整即可
  • 修剪二叉搜索树

    • 递归:比较当前结点,不在范围就选择性修剪往左或右孩子遍历返回替代当前结点;自身在范围内才去递归分别修剪左右孩子
  • 构造高度平衡二叉搜索树

    • 递归:中间的值即为根节点,切割数组递归左右孩子即可
最后总结:
  • 二叉树的构造:一般是先序,先有双亲才有孩子
  • 二叉树的属性:一般是后序,先得到返回值才能处理当前结点的要素
  • 二叉搜索树:一定是中序,有序性要利用起来

你可能感兴趣的:(算法,数据结构,leetcode)