代码随想录第19天|● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树

文章目录

  • 654. 最大二叉树
    • 思路
      • 代码:
  • 617. 合并二叉树
    • 思路:递归
      • 代码:
    • 思路2:迭代-层序遍历
      • 代码:
  • 700.二叉搜索树中的搜索
    • 思路:递归
      • 代码:
    • 思路2:迭代
  • 98.验证二叉搜索树
    • 思路:
      • 思路一:中序遍历变成递增数组
      • 思路一代码:定义long最小值做比较
      • 代码优化:定义最小值为前一个节点
    • 思路二:统一迭代法

654. 最大二叉树

代码随想录第19天|● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树_第1张图片

思路

构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。
// 在左闭右开区间[left, right),构造二叉树
注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。
代码随想录第19天|● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树_第2张图片

代码:

递归三部曲:

  1. 确定递归函数的参数和返回值
    参数传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。

  2. 终结点判断:数组是否为空

  3. 确定单层递归的逻辑
    这里有三步工作

  • 先要找到数组中最大的值和对应的下标, 最大的值构造根节点,下标用来下一步分割数组。
  • 最大值所在的下标左区间 构造左子树
    这里要判断maxValueIndex > 0,因为要保证左区间至少有一个数值。
  • 最大值所在的下标右区间 构造右子树
    判断maxValueIndex < (nums.size() - 1),确保右区间至少有一个数值
class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        return travesal(nums,0,nums.length);
    }
    public TreeNode travesal(int[] nums,int begin,int end){
        // if(nums.length==1){
        //     return new TreeNode(nums[0]);
        // }
        if(end-begin<=0){// 没有元素了
            return null;
        }
        int maxVal=-1;
        int maxIndex=0;
        // for(int i=0;i
        for(int i=begin;i<end;i++){
            if(nums[i]>maxVal){
                maxVal=nums[i];
                maxIndex=i;
            }
        }
        TreeNode node = new TreeNode(maxVal);
        node.left=travesal(nums,begin,maxIndex);
        node.right=travesal(nums,maxIndex+1,end);
        return node;
    }
}

617. 合并二叉树

代码随想录第19天|● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树_第3张图片

思路:递归

二叉树使用递归,就要想使用前中后哪种遍历方式?

本题使用哪种遍历都是可以的!
以前序遍历为例:

那么我们来按照递归三部曲来解决:

  1. 确定递归函数的参数和返回值:
    首先要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。

  2. 确定终止条件:
    因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。

    反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。

  3. 确定单层递归的逻辑:
    单层递归的逻辑就比较好写了,这里我们重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。

    那么单层递归中,就要把两棵树的元素加到一起。

代码:

class Solution {
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if(root1==null)return root2;
        if(root2==null)return root1;
        root1.val+=root2.val;
        root1.left=mergeTrees(root1.left,root2.left);
        root1.right=mergeTrees(root1.right,root2.right);
        return root1;
    }
}

思路2:迭代-层序遍历

迭代法中,一般一起操作两个树都是使用队列模拟类似层序遍历,同时处理两个树的节点,这种方式最好理解,如果用模拟递归的思路的话,要复杂一些。
将左右节点都放到队列里,都不为空则两个一起放进去,左为空右不为空则右边赋值给左边,左右都为空不用考虑,左不为空右为空也不用考虑(因为本来root1就有了)

代码:

注意区分if和while循环

class Solution {
    // 使用队列迭代
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if (root1 == null) return root2;
        if (root2 ==null) return root1;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root1);
        queue.offer(root2);
        while (!queue.isEmpty()) {
            TreeNode node1 = queue.poll();
            TreeNode node2 = queue.poll();
            // 此时两个节点一定不为空,val相加
            node1.val = node1.val + node2.val;
            // 如果两棵树左节点都不为空,加入队列
            if (node1.left != null && node2.left != null) {
                queue.offer(node1.left);
                queue.offer(node2.left);
            }
            // 如果两棵树右节点都不为空,加入队列
            if (node1.right != null && node2.right != null) {
                queue.offer(node1.right);
                queue.offer(node2.right);
            }
            // 若node1的左节点为空,直接赋值
            if (node1.left == null && node2.left != null) {
                node1.left = node2.left;
            }
            // 若node1的右节点为空,直接赋值
            if (node1.right == null && node2.right != null) {
                node1.right = node2.right;
            }
        }
        return root1;
    }
}

700.二叉搜索树中的搜索

代码随想录第19天|● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树_第4张图片

思路:递归

二叉搜索树是一个有序树:

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉搜索树
    代码随想录第19天|● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树_第5张图片

代码:

代码随想录第19天|● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树_第6张图片
java代码:

class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
        if(root==null||root.val==val)return root;
        // 判断1,需要有返回值
        // if(root.val
        // if(root.val>val)return searchBST(root.left,val);
        // return null;
        // 判断2
        if(root.val<val){
            return searchBST(root.right,val);
        }else{
            return searchBST(root.left,val);
        }
    }
}

思路2:迭代

对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向。

例如要搜索元素为3的节点,我们不需要搜索其他节点,也不需要做回溯,查找的路径已经规划好了。

中间节点如果大于3就向左走,如果小于3就向右走,如图:
代码随想录第19天|● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树_第7张图片

class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
        while(root!=null){
            if(root.val==val)return root;
            if(root.val<val){
                root=root.right;
            }else{
                root=root.left;
            }
        }
        return null;
    }
}

98.验证二叉搜索树

代码随想录第19天|● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树_第8张图片

思路:

遇到 搜索树,一定想着中序遍历,这样才能利用上特性。

思路一:中序遍历变成递增数组

  1. 确定递归函数,返回值以及参数
    要定义一个longlong的全局变量,用来比较遍历的节点是否有序,因为后台测试数据中有int最小值,所以定义为longlong的类型,初始化为longlong最小值。
    我们在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,立刻返回。

  2. 确定终止条件
    如果是空节点 是不是二叉搜索树呢?
    是的,二叉搜索树也可以为空!

  3. 确定单层递归的逻辑
    中序遍历,一直更新maxVal,一旦发现maxVal >= root->val,就返回false,注意元素相同时候也要返回false。

思路一代码:定义long最小值做比较

class Solution {
    private long prev = Long.MIN_VALUE;
    public boolean isValidBST(TreeNode root) {
        if(root==null)return true; 
        boolean left=isValidBST(root.left);    // 左
        if(root.val>prev){                     // 中
            prev=root.val;
        }else{
            return false;
        }
        boolean right= isValidBST(root.right); // 右
        return left&&right;
    }
}

代码优化:定义最小值为前一个节点

以上代码是因为后台数据有int最小值测试用例,所以都把maxVal改成了longlong最小值。

如果测试数据中有 longlong的最小值,怎么办?

不可能在初始化一个更小的值了吧。 建议避免 初始化最小值,如下方法取到最左面节点的数值来比较。

class Solution {
    private TreeNode pre;
    public boolean isValidBST(TreeNode root) {
        if(root==null)return true; 
        boolean left=isValidBST(root.left);    // 左
        if(!left){
            return false;
        }
        // 中 比较当前节点和前一个节点
        if(pre!=null&&root.val<=pre.val){
            return false;
        }
        pre = root;
        boolean right= isValidBST(root.right); // 右
        return right;
    }
}

思路二:统一迭代法

在弹出节点的时候(node==null)时,将这个节点的值和pre比较,如果为true则保存起来,和下一个进行比较

//统一迭代法,也是和前一个节点的最小值比较

class Solution {
    public boolean isValidBST(TreeNode root) {
        Stack<TreeNode> st = new Stack<>();
        TreeNode pre=null;
        if (root != null) st.push(root);
        while (!st.empty()) {
            TreeNode node = st.peek();
            if (node != null) {
                st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
                if (node.right!=null) {// 添加右节点(空节点不入栈)
                    // if(node.right.val<=node.val){
                    //     return false;
                    // } 不在这里判断
                    st.push(node.right);
                }  
                st.push(node);                          // 添加中节点
                st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
                if (node.left!=null){    // 添加左节点(空节点不入栈)
                    // if(node.left.val>=node.val){
                    //     return false;
                    // } 不在这里判断
                    st.push(node.left);
                }
            } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
                st.pop();           // 将空节点弹出
                node = st.peek();    // 重新取出栈中元素
                TreeNode temp=st.pop();
                if(pre!=null&&pre.val>=temp.val){
                    return false;
                }
                pre=temp;
            }
        }
        return true;
    }
}

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