leetcode-探索二叉搜索树

(1)像普通的二叉树一样,我们可以按照前序、中序和后序来遍历一个二叉搜索树。 但是值得注意的是,对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。因此,中序遍历是二叉搜索树中最常用的遍历方法。

(2)删除二叉搜索树中的节点
删除要比我们前面提到过的两种操作复杂许多。有许多不同的删除节点的方法,这篇文章中,我们只讨论一种使整体操作变化最小的方法。我们的方案是用一个合适的子节点来替换要删除的目标节点。根据其子节点的个数,我们需考虑以下三种情况:

  1. 如果目标节点没有子节点,我们可以直接移除该目标节点。
  2. 如果目标节只有一个子节点,我们可以用其子节点作为替换。
  3. 如果目标节点有两个子节点,我们需要用其中序后继节点或者前驱节点来替换,再删除该目标节点。

(3)问题描述:设计一个类,求一个数据流中第k大的数。

一个很显而易见的解法是,先将数组降序排列好,然后返回数组中第k个数。

但这个解法的缺点在于,为了在O(1)时间内执行搜索操作,每次插入一个新值都需要重新排列元素的位置。从而使得插入操作的解法平均时间复杂度变为O(N)。因此,算法总时间复杂度会变为O(N^2)。

鉴于我们同时需要插入和搜索操作,为什么不考虑使用一个二叉搜索树结构存储数据呢?

我们知道,对于二叉搜索树的每个节点来说,它的左子树上所有结点的值均小于它的根结点的值,右子树上所有结点的值均大于它的根结点的值。

换言之,对于二叉搜索树的每个节点来说,若其左子树共有m个节点,那么该节点是组成二叉搜索树的有序数组中第m + 1个值。

(4)利用数组实现大顶堆和小顶堆(大顶堆用来进行升序操作,小顶堆用来进行降序操作)

(5)平衡二叉树

一个高度平衡的二叉搜索树(平衡二叉搜索树)是在插入和删除任何节点之后,可以自动保持其高度最小。也就是说,有 N 个节点的平衡二叉搜索树,它的高度是 logN 。并且,每个节点的两个子树的高度不会相差超过 1。

但一个普通的二叉搜索树,在最坏的情况下,它可以退化成一个链。

因此,具有 N 个节点的二叉搜索树的高度在 logN 到 N 区间变化。也就是说,搜索操作的时间复杂度可以从 logN 变化到 N 。这是一个巨大的性能差异。

所以说,高度平衡的二叉搜索树对提高性能起着重要作用。

高度平衡的二叉搜索树在实际中被广泛使用,因为它可以在 O(logN) 时间复杂度内执行所有搜索、插入和删除操作。

平衡二叉搜索树的概念经常运用在 Set 和 Map 中。Set 和 Map 的原理相似。 我们将在下文中重点讨论 Set 这个数据结构。

Set(集合)是另一种数据结构,它可以存储大量 key(键)而不需要任何特定的顺序或任何重复的元素。 它应该支持的基本操作是将新元素插入到 Set 中,并检查元素是否存在于其中。

通常,有两种最广泛使用的集合:散列集合(Hash Set)和 树集合(Tree Set)。

树集合,Java 中的 Treeset 或者 C++ 中的 set ,是由高度平衡的二叉搜索树实现的。因此,搜索、插入和删除的时间复杂度都是 O(logN) 。

散列集合,Java 中的 HashSet 或者 C++ 中的 unordered_set ,是由哈希实现的,但是平衡二叉搜索树也起到了至关重要的作用。当存在具有相同哈希键的元素过多时,将花费 O(N) 时间复杂度来查找特定元素,其中N是具有相同哈希键的元素的数量。 通常情况下,使用高度平衡的二叉搜索树将把时间复杂度从 O(N) 改善到 O(logN) 。

哈希集和树集之间的本质区别在于树集中的键是有序的。

public class Heap {
    
    
    public void heapSort(int[] num) {
    
    
        int n = num.length;
        
        //从i=n/2开始进行判断是否需要进行调整
        for (int i = n / 2; i >= 0; i--) {
    
    
            heapAdjust(num, i, n);
        }
        //将堆顶点元素与末尾元素进行换位,然后继续进行调整
        for (int j = n - 1; j >= 0; j--) {
    
    
            swap(num, 0, j);
            heapAdjust(num, 0, j);
        }
    }

    public void swap(int[] num, int i, int j) {
    
    
        int temp = num[i];
        num[i] = num[j];
        num[j] = temp;
    }

    public void heapAdjust(int[] num, int begin, int n) {
    
    
        //注意这里的i值
        for (int i = begin; i < n;  ) {
    
    
            
            int max = i;
            //2*i+1:判断该结点有左子树,num[2*i+1]>num[max]:左子树的值比父节点值大
            if ((2 * i + 1) < n && num[2 * i + 1] > num[max]) {
    
    
                max = 2 * i + 1;
            }
             //2*i+2:判断该结点有右子树,num[2*i+2]>num[max]:右子树的值比父节点值大
            if ((2 * i + 2) < n && num[2 * i + 2] > num[max]) {
    
    
                max = 2 * i + 2;
            }
            if (max != i) {
    
    
                swap(num, max, i);
                i = max;
            } else {
    
    
                break;
            }
        }
    }
}

1.验证二叉搜索树
/*
执行用时:2 ms, 在所有 Java 提交中击败了31.63%的用户
内存消耗:38.1 MB, 在所有 Java 提交中击败了99.33%的用户
*/
解题思路:如上面所示,二叉搜索树经过中序排序之后能得到一个递增的有序序列,我们判断经过中序遍历的序列是否是有序的就可。
class Solution {
    
    
    public boolean isValidBST(TreeNode root) {
    
    
        ArrayList<Integer> temp = new ArrayList<>();
        if (root==null){
    
    
            return true;
        }
        inorderTraversal(root,temp);
        for (int i = 1;i<temp.size();i++){
    
    
            if(temp.get(i)<=temp.get(i-1)){
    
    
                return false;
            }
        }
        return true;
    }

    private void inorderTraversal(TreeNode root, ArrayList<Integer> temp) {
    
    
        if (root.left!=null){
    
    
            inorderTraversal(root.left, temp);
        }
        temp.add(root.val);
        if (root.right!=null){
    
    
            inorderTraversal(root.right, temp);
        }
    }
}



2.二叉搜索树迭代器
/*
执行用时:24 ms, 在所有 Java 提交中击败了70.91%的用户
内存消耗:43.2 MB, 在所有 Java 提交中击败了99.35%的用户
*/
class BSTIterator {
    
    
    int count = 0;
    ArrayList<Integer> res = new ArrayList<>();

    public BSTIterator(TreeNode root) {
    
    
        if (root != null) {
    
    
            travelsal(root, res);
        }

    }

    private void travelsal(TreeNode root, ArrayList<Integer> res) {
    
    

        if (root.left != null) {
    
    
            travelsal(root.left, res);
        }
        res.add(root.val);
        if (root.right != null) {
    
    
            travelsal(root.right, res);
        }
    }

    /**
     * @return the next smallest number
     */
    public int next() {
    
    
        return res.get(count++);


    }

    /**
     * @return whether we have a next smallest number
     */
    public boolean hasNext() {
    
    
        if (count + 1 <= res.size()) {
    
    
            return true;
        } else {
    
    
            return false;
        }
    }
}
3.二叉搜索树中的搜索
/*
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:39 MB, 在所有 Java 提交中击败了93.66%的用户
*/
    
class Solution {
    
    
    public TreeNode searchBST(TreeNode root, int val) {
    
    
        if(root==null){
    
    
            return null;
        }
        return treeTravel(root,val);
    }

    private TreeNode treeTravel(TreeNode root, int val) {
    
    
        if (root==null){
    
    
            return null;
        }
        if (root.val==val){
    
    
            return root;
        }else if (root.val>val){
    
    
            return treeTravel(root.left, val);
        }else {
    
    
            return treeTravel(root.right, val);
        }
    }
}
4.二叉搜索树中的插入操作
/*
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:39.3 MB, 在所有 Java 提交中击败了91.47%的用户
*/

class Solution {
    
    
    public TreeNode insertIntoBST(TreeNode root, int val) {
    
    
        if (root==null){
    
    
            return new TreeNode(val);
        }
        treeTravel(root,val);
        return root;
    }

    private void treeTravel(TreeNode root, int val) {
    
    

        if (root.val>val){
    
    
            if (root.left==null){
    
    
                TreeNode temp = new TreeNode(val);
                root.left=temp;
            }else {
    
    
                 treeTravel(root.left, val);
            }
        }else {
    
    
            if (root.right==null){
    
    
                TreeNode temp = new TreeNode(val);
                root.right = temp;
            }else {
    
    
                 treeTravel(root.right, val);
            }
        }
    }
}
4.删除二叉搜索树中的节点



5.数据流中的第K大元素

/*
评论区大佬做法,利用PriorityQueue实现一个小顶堆。
执行用时:16 ms, 在所有 Java 提交中击败了98.02%的用户
内存消耗:42.4 MB, 在所有 Java 提交中击败了99.00%的用户
*/
class KthLargest {
    
    
    final PriorityQueue<Integer> q ;
    final int k;
    public KthLargest(int k, int[] nums) {
    
    
        this.k = k;
        q = new PriorityQueue<Integer>(k);
        for(int i: nums) {
    
    
            add(i);
        }
    }
    
    public int add(int val) {
    
    
        if(q.size() < k) {
    
    
            q.offer(val);
            
        }
        else if(q.peek() < val) {
    
    
            q.poll();
            q.offer(val);
        }
        return q.peek();
    }
}
6.二叉搜索树的最近公共祖先
/*
执行用时:6 ms, 在所有 Java 提交中击败了99.89%的用户
内存消耗:39.2 MB, 在所有 Java 提交中击败了98.77%的用户
*/

解题思路:二叉搜索树本身具有左子树值比父结点值小,右子树值比父结点值大的特点。而p、q两值的存在有三种情况。
    1.一个在左子树,一个在右子树,返回根节点
    2.都在左子树
    3.都在右子树
class Solution {
    
    
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    
    
        if (root==null){
    
    
            return root;
        }
        while (root!=null){
    
    
            //当p、q值都比根节点的值大时,说明p、q都在根节点的右边
            if (p.val>root.val&&q.val>root.val){
    
    
                root = root.right;
            }
            //当p、q值都比根节点的值小时,说明p、q都在根节点的左边
            else if (p.val<root.val&&q.val<root.val){
    
    
                root = root.left;
            }
            else {
    
    
                return root;
            }
        }
        return root;
    }
}


/*
执行用时:7 ms, 在所有 Java 提交中击败了39.54%的用户
内存消耗:39.2 MB, 在所有 Java 提交中击败了98.38%的用户
*/
解题思路:针对于普通二叉树的解题思路,没有用到二叉搜索树的特性
class Solution {
    
    
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    
    
        if(root==null||root==p||root==q){
    
    
            return root;
        }
        TreeNode leftTree = lowestCommonAncestor(root.left,p,q);
        TreeNode rightTree = lowestCommonAncestor(root.right,p,q);
        if(leftTree!=null&&rightTree!=null){
    
    
            return root;
        }else if(leftTree==null){
    
    
            return rightTree;
        }else if(rightTree==null){
    
    
            return leftTree;
        }
        return null;
    }
}
7.存在重复元素 III

8.平衡二叉树
/*
执行用时:1 ms, 在所有 Java 提交中击败了99.90%的用户
内存消耗:38.5 MB, 在所有 Java 提交中击败了95.75%的用户
*/
class Solution {
    
    
    public boolean isBalanced(TreeNode root) {
    
    
        if(root==null){
    
    
            return true;
        }
        if (Math.abs(getMaxHigh(root.left)-getMaxHigh(root.right))<=1){
    
    
            return isBalanced(root.right)&&isBalanced(root.left);
        }
        return false;
    }

    private int getMaxHigh(TreeNode root) {
    
    
        if(root==null) return 0;
        return (Math.max(getMaxHigh(root.left),getMaxHigh(root.right))+1);
    }
}
9.将有序数组转换为二叉搜索树
/*
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:38.4 MB, 在所有 Java 提交中击败了93.74%的用户
*/
解题思路:考察二分查找以及二叉搜索树的定义

class Solution {
    
    
    public TreeNode sortedArrayToBST(int[] nums) {
    
    
        if (nums==null||nums.length==0){
    
    
            return null;
        }
        return  bulidBst(nums,0,nums.length-1);
    }

    private TreeNode bulidBst(int[] nums, int left, int right) {
    
    
        if (left>right) {
    
    
            return null;
        }
        int mid = left+(right-left)/2;
        TreeNode root = new TreeNode(nums[mid]);
        root.left = bulitBst(nums, left, mid-1);
        root.right = bulitBst(nums,mid+1,right);
        return root;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_41458842/article/details/109236563