二叉搜索树

267 阅读7分钟

问题总览

类型问题描述完成
构造BST95. 不同的二叉搜索树 II
96. 不同的二叉搜索树
108. 将有序数组转换为二叉搜索树
109. 有序链表转换二叉搜索树
利用中序遍历230. 二叉搜索树中第K小的元素
剑指 Offer 54. 二叉搜索树的第k大节点
173. 二叉搜索树迭代器
510. 二叉搜索树中的中序后继 II
剑指 Offer 33. 二叉搜索树的后序遍历序列
转换累加树538. 把二叉搜索树转换为累加树
1038. 从二叉搜索树到更大和树
转换链表426. 将二叉搜索树转化为排序的双向链表
剑指 Offer 36. 二叉搜索树与双向链表
基础操作98. 验证二叉搜索树
700. 二叉搜索树中的搜索
701. 二叉搜索树中的插入操作
450. 删除二叉搜索树中的节点
Moss遍历99. 恢复二叉搜索树
1214. 查找两棵二叉搜索树之和

题解

基本操作

[搜索]700. 二叉搜索树中的搜索

class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
        if (root == null || root.val == val) {
            return root;
        }
        if (root.val > val) {
            return searchBST(root.left, val);
        } else {
            return searchBST(root.right, val);
        }
    }
}
// 时间复杂度:O(h),类似于二分查找了,就是树的高度
// 空间复杂度:O(h)

[插入]701. 二叉搜索树中的插入操作

貌似并没有考虑要对树进行平衡。

class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) {
            return new TreeNode(val);
        }
        if (root.val < val) {
            root.right = insertIntoBST(root.right, val);
        }
        if (root.val > val) {
            root.left = insertIntoBST(root.left, val);
        }
        return root;
    }
}
// 时间复杂度:O(h),和查找一样的
// 空间复杂度:O(h)

[删除]450. 删除二叉搜索树中的节点

class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) {
            return root;
        }
        if (root.val == key) {
            // 找到了
            if (root.left == null && root.right == null) {
                // 这个节点没有子节点,那直接删除这个节点就可以
                return null;
            }
            if (root.left == null) {
                // 这个节点只有右节点,这个时候把它的右节点顶上来,代替root,相当于就是删除了root
                return root.right;
            }
            if (root.right == null) {
                // 这个节点只有左节点,这个时候把它的左节点顶上来,代替root,相当于就是删除了root
                return root.left;
            }
            // 这个节点同时有左右子树,此时
            TreeNode minNode = getMin(root.right);
            root.val = minNode.val;
            root.right = deleteNode(root.right, minNode.val);
        } else if (root.val < key) {
            // 去右子树继续找
            root.right = deleteNode(root.right, key);
        } else {
            // 去左子树继续找
            root.left = deleteNode(root.left, key);
        }
        return root;
    }

    private TreeNode getMin(TreeNode root) {
        TreeNode p = root;
        while (p.left != null) {
            p = p.left;
        }
        return p;
    }
}

[验证]98. 验证二叉搜索树

class Solution {
    boolean ans = true;

    public boolean isValidBST(TreeNode root){
        dfs(root, null, null);
        return ans;
    }

    public void dfs(TreeNode root, Integer leftMax, Integer rightMin) {
        if (root == null) {
            return;
        }
        if (leftMax != null && leftMax <= root.val) {
            ans = false;
            return;
        }
        if (rightMin != null && rightMin >= root.val) {
            ans = false;
            return;
        }
        dfs(root.left, root.val, rightMin);
        dfs(root.right, leftMax, root.val);
    }
}

构造二叉搜索树

95. 不同的二叉搜索树 II

class Solution {
    int[][] memo;
    List<TreeNode> ans;

    public List<TreeNode> generateTrees(int n) {
        ans = new ArrayList<>();
        return dfs(1, n);
    }

    public List<TreeNode> dfs(int left, int right) {
        List<TreeNode> res = new LinkedList<>();
        if (left > right) {
            res.add(null);
            return res;
        }
        for (int i = left; i <= right; i++) {
            List<TreeNode> lefts = dfs(left, i - 1);
            List<TreeNode> rights = dfs(i + 1, right);
            for (TreeNode l : lefts) {
                for (TreeNode r : rights) {
                    TreeNode root = new TreeNode();
                    root.left = l;
                    root.right = r;
                    root.val = i;
                    res.add(root);
                }
            }
        }
        return res;
    }
}

96. 不同的二叉搜索树

class Solution {
    int[][] memo;

    public int numTrees(int n) {
        // 用备忘录删除重复问题
        memo = new int[n + 1][n + 1];
        return count(1, n);
    }

    public int count(int left, int right) {
        // 空也是一种情况,所以需要返回1
        if (left > right) {
            return 1;
        }
        if (memo[left][right] != 0) {
            return memo[left][right];
        }
        int res = 0;
        for (int i = left; i <= right; i++) {
            // 左子树和右子树的可能数量相乘,就是当前节点可能的二叉搜索树
            res += count(left, i - 1) * count(i + 1, right);
        }
        memo[left][right] = res;
        return res;
    }
}

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

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        int n = nums.length;
        // 已经按照升序排列
        // 转换成高度平衡的二叉树,返回的是根节点
        // 1,2,3,4,5,6,7,8
        // 1,2,3,4,5,6,7
        return build(nums, 0, n - 1);
    }

    public TreeNode build(int[] nums, int start, int end) {
        if (start > end) {
            return null;
        }
        int mid = start + (end - start) / 2;
        TreeNode root = new TreeNode(nums[mid]);
        root.left = build(nums, start, mid - 1);
        root.right = build(nums, mid + 1, end);
        return root;
    }
}

109. 有序链表转换二叉搜索树

比有序数组转换二叉搜索树难的地方在于,链表不是随机读取的。

class Solution {
    public TreeNode sortedListToBST(ListNode head) {
        return build(head, null);
    }

    public TreeNode build(ListNode left, ListNode right) {
        // 正因为选择了左闭又开,这边的退出条件在相等的时候需要返回null,否则相等应该返回自身
        if (left == right) {
            return null;
        }

        ListNode mid = getMid(left, right);
        TreeNode root = new TreeNode(mid.val);
        // 选择了左闭右开的结构,因为找到上一个节点有点麻烦
        // 如果是左闭右闭,这边的写法应该是mid.pre
        root.left = build(left, mid);
        root.right = build(mid.next, right);
        return root;
    }

    public ListNode getMid(ListNode left, ListNode right) {
        ListNode slow = left;
        ListNode fast = left;
        while (fast != right && fast.next != right) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}
  • 时间复杂度:O(nlogn),因为每一个元素都需要二分查找中点,查找一次是logn查找n次就是nlogn
  • 空间复杂度:O(logn)
// 优化的解法
class Solution {
    ListNode globalHead;

    public TreeNode sortedListToBST(ListNode head) {
        globalHead = head;
        int length = getLength(head);
        return buildTree(0, length - 1);
    }

    public int getLength(ListNode head) {
        int len = 0;
        while (head != null) {
            len++;
            head = head.next;
        }
        return len;
    }

    // 利用中序遍历的原理,因为一个二叉搜索树中序遍历的结果肯定是升序的
    // 我们反过来想,我们构造的这个二叉搜索树如果中序遍历得到的结果就是入参的head
    // 而buildTree不断的二分递归,找到的第一个树,其实就是二叉搜索树最左边的元素,也就是head,依次往后面中序遍历,每一个节点和入参元素的位置一一对应
    public TreeNode buildTree(int left, int right) {
        if (left > right) {
            return null;
        }
        int mid = (left + right + 1) / 2;
        TreeNode root = new TreeNode();
        root.left = buildTree(left, mid - 1);
        // 只能在中序遍历的位置赋值
        root.val = globalHead.val;
        globalHead = globalHead.next;
        root.right = buildTree(mid + 1, right);
        return root;
    }
}
  • 时间复杂度:O(logn)
  • 空间复杂度:O(logn)

利用中序遍历

230. 二叉搜索树中第K小的元素

class Solution {
    int ans = 0;
    int rank = 0;
    // 利用二叉搜索树中序遍历为有序的特性
    public int kthSmallest(TreeNode root, int k) {
        dfs(root, k);
        return ans;
    }

    public void dfs(TreeNode root, int k) {
        if (root == null) {
            return;
        }
        dfs(root.left, k);
        //中序遍历的位置
        rank++;
        if (k == rank) {
            ans = root.val;
            return;
        }
        dfs(root.right, k);
    }
}

剑指 Offer 54. 二叉搜索树的第k大节点

class Solution {
    int ans = 0;
    int rank = 0;

    public int kthLargest(TreeNode root, int k) {
        dfs(root, k);
        return ans;
    }

    public void dfs(TreeNode root, int k) {
        if (root == null) {
            return;
        }
        dfs(root.right, k);
        rank++;
        if (rank == k) {
            ans = root.val;
            return;
        }
        dfs(root.left, k);
    }
}

173. 二叉搜索树迭代器

class BSTIterator {

    TreeNode cur;
    Deque<TreeNode> stack;

    public BSTIterator(TreeNode root) {
        stack = new LinkedList<>();
        //初始化必须为root节点,不然无法进行dfs
        cur = root;
    }

    public int next() {
        while (cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        // 从左/中的节点中取
        cur = stack.pop();
        int res = cur.val;
        // cur作为左已经是最左了,下一个要找cur的中,那就是要去stack里pop
        // 在此之前,cur作为中,需要检查cur的right在不在
        cur = cur.right;
        return res;
    }

    public boolean hasNext() {
        // cur不等于null,说明肯定有next,因为遍历完一次后,cur就指向right了
        // cur等于null,只能说明没有right不能说明没有next
        // stack为空,可能这个树全是右节点
        return cur != null || !stack.isEmpty();
    }
}

510. 二叉搜索树中的中序后继 II

class Solution {
    public Node inorderSuccessor(Node x) {
        // 如果有右子树,那么直接返回右子树中最小的节点
        if (x.right != null) {
            x = x.right;
            while (x.left != null) x = x.left;
            return x;
        }

        // 如果没有右子树,此时需要找祖先节点,但是如果当前节点是祖先节点的右子树,说明这个祖先节点是前驱不是后继,那么得继续往上找
        // 走到根节点(x.parent == null)也没发现合适的,那么返回null
        // 因为如果当前节点位于根节点的左子树中,那么肯定可以找到根节点
        // 只有当前节点位于根节点的右侧时,才找不到根节点
        while (x.parent != null && x == x.parent.right) x = x.parent;
        return x.parent;
    }
}
  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)

转换为其他数据结构

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

相同问题:1038. 从二叉搜索树到更大和树

class Solution {
    public TreeNode convertBST(TreeNode root) {
        dfs(root);
        return root;
    }

    int sum = 0;
    // 利用倒序的中序遍历,是将元素从大到小
    // 累加树的需求是计算大于或等于该元素的所有元素的和
    public void dfs(TreeNode root) {
        if (root == null) {
            return;
        }
        dfs(root.right);
        sum += root.val;
        root.val = sum;
        dfs(root.left);
    }
}

剑指 Offer 33. 二叉搜索树的后序遍历序列

利用了二叉搜索树的特性,左边的子树都比根节点小,右边的子树都比根节点大,而后续遍历是左、右、中,根节点肯定是末尾的元素。我们只需要找到左子树和右子树,再按照这个条件去检查子树是否符合二叉搜索树的条件就可以。

class Solution {
    public boolean verifyPostorder(int[] postorder) {
        if (postorder == null) {
            return true;
        }
        return verifyPostorder(postorder, 0, postorder.length - 1);
    }

    public boolean verifyPostorder(int[] postorder, int start, int end) {
        // base条件,不会存在start小于end的情况,因为rightRoot都是大于等于start的
        if (start >= end) {
            return true;
        }
        int root = end;
        int rightRoot = start;
        for (int i = end - 1; i >= start; i--) {
            // 找到右子树的根节点
            if (postorder[i] < postorder[root]) {
                rightRoot = i + 1;
                break;
            }
        }
        for (int i = start; i < rightRoot; i++) {
            // 左子树必须都小于根节点
            if (postorder[i] > postorder[root]) {
                return false;
            }
        }
        // 左右子树都需要满足后序
        return verifyPostorder(postorder, start, rightRoot - 1) && verifyPostorder(postorder, rightRoot, end - 1);
    }
}
// 时间复杂度:O(n^2),最坏的情况是,每一轮都要遍历所有节点
// 空间复杂度:O(height),栈的空间

1214. 查找两棵二叉搜索树之和

// 无脑遍历
class Solution {
    List<Integer> list2;
    boolean ans;

    public boolean twoSumBSTs(TreeNode root1, TreeNode root2, int target) {
        list2 = new ArrayList<>();
        dfs2(root2);
        dfs1(root1, target);
        return ans;
    }

    private void dfs1(TreeNode root1, int target) {
        if (root1 == null) {
            return;
        }
        if (list2.contains(target - root1.val)) {
            ans = true;
            return;
        }
        dfs1(root1.left, target);
        dfs1(root1.right, target);
    }

    private void dfs2(TreeNode root2) {
        if (root2 == null) {
            return;
        }
        list2.add(root2.val);
        dfs2(root2.left);
        dfs2(root2.right);
    }
}
// 时间复杂度:O(logn)
// 空间复杂度:O(n)

Morris遍历

99. 恢复二叉搜索树

// 显式的中序遍历,先将中序遍历的结果用数组保存下来
class Solution {
    public void recoverTree(TreeNode root) {
        List<Integer> nums = new ArrayList<>();
        dfs(root, nums);
        int x = -1, y = -1;
        int len = nums.size();
        for (int i = 0; i < len - 1; i++) {
            if (nums.get(i) > nums.get(i + 1)) {
                y = i + 1;
                if (x == -1) {
                    x = i;
                }
            }
        }
        x = nums.get(x);
        y = nums.get(y);
        dfs(root, x, y);
    }

    private void dfs(TreeNode root, List<Integer> nums) {
        if (root == null) {
            return;
        }
        dfs(root.left, nums);
        nums.add(root.val);
        dfs(root.right, nums);
    }

    private void dfs(TreeNode root, int x, int y) {
        if (root == null) {
            return;
        }
        dfs(root.left, x, y);
        if (root.val == x) {
            root.val = y;
        } else if (root.val == y) {
            root.val = x;
        }
        dfs(root.right, x, y);
    }
}
  • 时间复杂度:O(N)
  • 空间复杂度:O(H)
// 该方法属于隐士中序遍历,在遍历的过程中记录下需要交换的节点
class Solution {
    TreeNode x = null;
    TreeNode y = null;
    TreeNode pre = null;

    public void recoverTree(TreeNode root) {
        dfs(root);
        // 交换两个节点
        swap(x, y);
    }

    private void dfs(TreeNode root) {
        if (root == null) {
            return;
        }
        dfs(root.left);
        // 判断pre和root的关系,如果pre大于root,说明找到了逆序的
        if (pre != null && pre.val > root.val) {
            y = root;
            // 因为是中序遍历,首先遍历到的肯定是第一个逆序对,此时给x赋值
            if (x == null) {
                x = pre;
            }
        }
        // pre始终指向root的前一个节点
        pre = root;
        dfs(root.right);
    }

    public void swap(TreeNode x, TreeNode y) {
        int tmp = x.val;
        x.val = y.val;
        y.val = tmp;
    }
}