第十七天|二叉树的修改与构造,求二叉搜索树的属性| 654. 最大二叉树, 617. 合并二叉树,700.二叉搜索树中的搜索, 98.验证二叉搜索树

目录

654. 最大二叉树

617. 合并二叉树

递归法

迭代法_层序遍历

使用队列迭代

使用栈迭代

700. 二叉搜索树中的搜索

递归法

迭代法(简单)

98. 验证二叉搜索树

递归法

迭代法


注意二叉搜索树的性质。利用好会简单很多。

654. 最大二叉树

654与106极其类似,甚至更简单。

注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。

class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        if (nums.length == 0) return null;
        return constructMaximumBinaryTree1(nums, 0, nums.length);
    }
    private TreeNode constructMaximumBinaryTree1(int[] nums, int numsStart, int numsEnd) {
        if (numsStart == numsEnd) return null;
        int max = nums[numsStart];
        int rootIndex = numsStart;
        for (int i = numsStart; i < numsEnd; i++) {
            if (nums[i] > max){
                max = nums[i];
                rootIndex = i;
            }
        }
        TreeNode root = new TreeNode(max);
        //左闭右开
        root.left = constructMaximumBinaryTree1(nums, numsStart, rootIndex);
        root.right = constructMaximumBinaryTree1(nums, rootIndex + 1, numsEnd);
        return root;
    }
}

617. 合并二叉树

本题使用递归超级简单。

递归法

对于每个节点,我们检查两棵树中该节点是否存在:

  1. 如果两个节点都存在,则将它们的值相加,并递归地合并它们的左子树和右子树。
  2. 如果某一棵树的节点不存在,则直接返回另一棵树的节点。
  3. 如果两个节点都不存在,则返回 null
class Solution {
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        // 如果其中一棵树为空,返回另一棵树
        if (root1 == null) return root2;
        if (root2 == null) return root1;

        // 如果两个节点都存在,则将它们的值相加
        TreeNode mergedRoot = new TreeNode(root1.val + root2.val);

        // 递归合并左子树和右子树
        mergedRoot.left = mergeTrees(root1.left, root2.left);
        mergedRoot.right = mergeTrees(root1.right, root2.right);

        return mergedRoot;
    }
}

直接修改root1,不用重新定义一个树:

前序(中左右)最好理解。其实中序(左中右)后序(左右中)都可以,直接换个位置即可。

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;
    }
}

迭代法_层序遍历

层序遍历来合并两棵二叉树的思路:通过队列来同时遍历两棵树的节点,将它们的值相加。如果其中某一棵树的某个位置没有节点,直接使用另一棵树的节点。整个过程类似于 BFS(广度优先搜索)。

时间复杂度:

  • 每个节点最多会被访问一次,时间复杂度为 O(n),其中 n 是两棵树节点的总数。

空间复杂度:

  • 空间复杂度取决于队列中的最大节点数。最坏情况下,队列可能会同时存储树的一层节点数,因此空间复杂度为 O(n)。

使用队列迭代

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(new TreeNode[]{root1, root2});
            // 遍历队列,直到所有节点处理完
            while (!queue.isEmpty()) {
                // 取出两个节点
                TreeNode[] current = queue.poll();
                TreeNode node1 = current[0];
                TreeNode node2 = current[1];
                // 将两棵树的节点值相加
                node1.val += node2.val;
                // 处理左子树
                if (node1.left!=null && node2.left != null){
                    // 两棵树的左子树都存在,放入队列
                    queue.offer(new TreeNode[]{node1.left, node2.left});
                } else if (node1.left == null) {
                    // 如果node1的左子树为空,直接用node2的左子树
                    node1.left = node2.left;
                }
                // 处理右子树
                if (node1.right!=null && node2.right != null){
                    queue.offer(new TreeNode[]{node1.right , node2.right});
                } else if (node1.right == null) {
                    node1.right = node2.right;
                }
            }
            return root1;
        }
    }

使用栈迭代

class Solution {
    // 使用栈迭代
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if (root1 == null) {
            return root2;
        }
        if (root2 == null) {
            return root1;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root2);
        stack.push(root1);
        while (!stack.isEmpty()) {
            TreeNode node1 = stack.pop();
            TreeNode node2 = stack.pop();
            node1.val += node2.val;
            if (node2.right != null && node1.right != null) {
                stack.push(node2.right);
                stack.push(node1.right);
            } else {
                if (node1.right == null) {
                    node1.right = node2.right;
                }
            }
            if (node2.left != null && node1.left != null) {
                stack.push(node2.left);
                stack.push(node1.left);
            } else {
                if (node1.left == null) {
                    node1.left = node2.left;
                }
            }
        }
        return root1;
    }
}

700. 二叉搜索树中的搜索

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

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉搜索树

本题居然迭代法要简单很多!

递归法

利用好二叉搜索树的性质。

如果root->val > val,搜索左子树,如果root->val < val,就搜索右子树,最后如果都没有搜索到,就返回NULL。

    class Solution {
        public TreeNode searchBST(TreeNode root, int val) {
//        // 递归法 普通二叉树
//        if (root == null || root.val == val) return root;
//        // 递归查找左子树
//        TreeNode leftResult = searchBST(root.left, val);
//        if (leftResult != null) {
//            return leftResult; // 如果在左子树找到了结果,则返回
//        }
//        // 如果左子树没有找到,再递归查找右子树
//        return searchBST(root.right, val);

            // 递归法 二叉搜索树
            if (root == null || root.val == val) return root;
            if (val < root.val) return searchBST(root.left, val);
            else return searchBST(root.right, val);
        }
    }

迭代法(简单)

因为二叉搜索树的特殊性,也就是节点的有序性,可以不使用辅助栈或者队列就可以写出迭代法

对于一般二叉树,递归过程中还有回溯的过程,例如走一个左方向的分支走到头了,那么要调头,在走右分支。

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

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

中间节点如果大于3就向左走,如果小于3就向右走。

class Solution {
    // 迭代,利用二叉搜索树特点,优化,可以不需要栈
    public TreeNode searchBST(TreeNode root, int val) {
        while (root != null)
            if (val < root.val) root = root.left;
            else if (val > root.val) root = root.right;
            else return root;
        return null;
    }
}
class Solution {
    // 迭代,普通二叉树
    public TreeNode searchBST(TreeNode root, int val) {
        if (root == null || root.val == val) {
            return root;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode pop = stack.pop();
            if (pop.val == val) {
                return pop;
            }
            if (pop.right != null) {
                stack.push(pop.right);
            }
            if (pop.left != null) {
                stack.push(pop.left);
            }
        }
        return null;
    }
}

98. 验证二叉搜索树

思路:

要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列

有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。

这个题有2个陷阱:

  • 陷阱1:(需要尤其注意,我就是掉进了这个坑

不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了

写出了类似这样的代码:

if (root->val > root->left->val && root->val < root->right->val) {
    return true;
} else {
    return false;
}

我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点所以以上代码的判断逻辑是错误的。

例如:节点10大于左节点5,小于右节点15,但右子树里出现了一个6 这就不符合了!

  • 陷阱2:

样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的。

此时可以初始化比较元素为longlong的最小值。

递归法

递归的思路

  • 中序遍历是按照“左 -> 中 -> 右”的顺序进行的。
  • 当遍历左子树时,期望左子树中的所有节点都比当前节点小;然后处理当前节点,期望它比左子树的最大节点大;接着遍历右子树,期望右子树中的所有节点都比当前节点大。
    class Solution {
        TreeNode max; // 记录当前遍历过程中前一个节点的值
        public boolean isValidBST(TreeNode root) {
            //递归
            if (root == null) return true;
            // 左 // 左子树递归
            boolean left = isValidBST(root.left);
            if (!left) return false;
            // 中 // 中间节点处理
            if (max != null && root.val <= max.val) return false;
            max = root; // 更新 max 为当前节点
            // 右 // 右子树递归
            boolean right = isValidBST(root.right);
            return right; // 返回右子树的验证结果
        }
    }

递归法简洁实现

// 简洁实现·递归解法
class Solution {
    public boolean isValidBST(TreeNode root) {
        return validBST(Long.MIN_VALUE, Long.MAX_VALUE, root);
    }
    boolean validBST(long lower, long upper, TreeNode root) {
        if (root == null) return true;
        if (root.val <= lower || root.val >= upper) return false;
        return validBST(lower, root.val, root.left) && validBST(root.val, upper, root.right);
    }
}

中序遍历简洁实现

// 简洁实现·中序遍历
class Solution {
    private long prev = Long.MIN_VALUE;
    public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return true;
        }
        if (!isValidBST(root.left)) {
            return false;
        }
        if (root.val <= prev) { // 不满足二叉搜索树条件
            return false;
        }
        prev = root.val;
        return isValidBST(root.right);
    }
}

迭代法

1.使用统一迭代法

思路:

  • 使用中序遍历来遍历二叉树的节点,确保遍历过程中当前节点的值始终大于之前访问过的节点的值。
  • 采用栈(stack)的方式来模拟递归的中序遍历,避免使用系统递归调用。
  • 每次从栈中取出一个节点时,如果这个节点不是 null,说明我们还没有处理它,而是要先处理它的左子树。因此,将它重新压栈并插入一个 null 作为标记,之后先处理左子树。
  • 当我们遇到 null 时,表示左子树已经处理完毕,接下来我们可以处理当前节点了。
    class Solution {
        public boolean isValidBST(TreeNode root) {
            //使用統一迭代法
            Stack<TreeNode> stack = new Stack<>();
            TreeNode pre = null; // 记录前一个访问的节点,用来和当前节点进行比较
            if (root != null) stack.add(root);
            while (!stack.isEmpty()) {
                TreeNode curr = stack.peek(); // 查看栈顶的节点,不弹出
                if (curr != null) { // 如果栈顶的节点不是 null,说明是正常的节点
                    stack.pop(); // 弹出栈顶的节点(即将要处理当前节点)
                    if (curr.right != null)  // 右
                        stack.add(curr.right); // 中序遍历:右子树稍后处理

                    stack.add(curr); // 将当前节点重新压入栈,稍后处理(需要在遍历完左子树后处理)
                    stack.add(null); // 在当前节点之后压入一个 null 标记,表示这个节点稍后需要处理

                    if (curr.left != null)  // 左
                        stack.add(curr.left);
                } else {  // 如果栈顶是 null,说明当前节点的左子树已经遍历完毕,可以处理当前节点了
                    stack.pop(); // 弹出 null 标记
                    TreeNode temp = stack.pop();
                    if (pre != null && pre.val >= temp.val)
                        return false;
                    pre = temp;
                }
            }
            return true;
        }
    }

迭代法2:(中序遍历)

    class Solution {
        public boolean isValidBST(TreeNode root) {
            // 迭代法
            if (root == null) return true;
            Stack<TreeNode> stack = new Stack<>();
            TreeNode pre = null;
            while(root != null || !stack.isEmpty()) {
                // 先遍历左子树,将左边所有节点压栈
                while(root != null) {
                    stack.push(root);
                    root = root.left; // 左
                }
                // 处理栈顶节点  中
                TreeNode pop = stack.pop();
                // 判断当前节点值是否大于前一个访问的节点
                if (pre != null && pop.val <= pre.val)
                    return false;
                pre = pop;

                // 遍历右子树
                root = pop.right; // 右
            }
            return true;
        }
    }

第十七天的总算是结束了,直冲Day18!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值