LeetCode 230, 222, 215

377 阅读3分钟

LeetCode 230 Kth Smallest Element in a BST

链接:leetcode.com/problems/kt…

方法1:栈

时间复杂度:O(k)

空间复杂度:O(n)

想法:用二叉树栈遍历的方法,使用BST的性质,对BST进行中序遍历,循环k - 1次之后找栈顶的元素。因为中序遍历是有序的,因此循环k-1次之后,栈顶元素就是要找的元素。

代码:

class Solution {
    public int kthSmallest(TreeNode root, int k) {
        Stack<TreeNode> stack = new Stack<>();
        
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        
        for (int i = 0; i < k - 1; i++) {
            TreeNode node = stack.pop();
            if (node.right != null) {
                node = node.right;
                while (node != null) {
                    stack.push(node);
                    node = node.left;
                }
            }
        }
        
        return stack.peek().val;
    }
}

方法2:递归

时间复杂度:O(n2)

空间复杂度:O(n)

想法:用count,数左子树的节点个数。如果左子树节点个数>=k,那就说明这个节点在左子树里,就对左子树递归。如果<k - 1说明在右子树。否则说明就是当前的节点。但其实这种写法原则上来说count是比较低效的,每次都得把子树节点全过一遍。我不知道为什么这题test case这么松以至于这种写法能过。

代码:

class Solution {
    public int kthSmallest(TreeNode root, int k) {
        int l = count(root.left);
        if (k <= l) {
            return kthSmallest(root.left, k);
        }
        if (k > l + 1) {
            return kthSmallest(root.right, k - l - 1);
        }
        
        return root.val;
    }
    
    private int count(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return count(root.left) + count(root.right) + 1;
    }
}

Follow up: If the BST is modified often (i.e., we can do insert and delete operations) and you need to find the kth smallest frequently, how would you optimize?

修改树节点Class,使其除了包含val, left, right之外,还包含一个int变量count,代表以它为根节点的子树总共有多少个节点。先使用O(n)建树,然后每次add/delete或查询,都按照BST的方法来做,时间复杂度都是O(h)。

LeetCode 222 Count Complete Tree Nodes

链接:leetcode.com/problems/co…

方法1:递归1

时间复杂度:O(logn)

空间复杂度:O(logn)

想法:题目说能不能写一个算法比O(n)更好,对于平衡二叉树既然比O(n)更好那大概率就是O(h)或者说O(logn)了。想法是拉一个指针一直往左走,另一个指针一直往右走,然后看这棵树最左边的深度和最右边的深度是不是一样大。如果一样,说明最后一层全满,直接算出来节点个数就完了。如果不相等,说明不满,因此需要对左右两边分别递归1 + countNodes(root.left) + countNodes(root.right);。为什么时间复杂度是O(logn)?因为假设说最后一层不满,可以想象最后一层的若干个节点,因为只能先填满左边再填右边,所以要么root的左子树最后一层也不满,这时候右子树这一层会没有元素,而上一层满,因此右子树节点可以直接算;要么root的左子树最后一层满了,这样的话root的左子树的节点个数可以直接算。因此两头递归下去,其中一个肯定要马上返回,真正在一直往下递归的只能是一个分支,所以是O(logn)。

代码:

class Solution {
    public int countNodes(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        TreeNode l = root, r = root;
        int lh = 0, rh = 0;
        
        while (l != null) {
            lh++;
            l = l.left;
        }
        while (r != null) {
            rh++;
            r = r.right;
        }
        
        if (lh == rh) {
            return (int) (Math.pow(2, lh)) - 1;
        }
        
        return 1 + countNodes(root.left) + countNodes(root.right);
    }
}

方法:递归2

时间复杂度:O(logn)

空间复杂度:O(logn)

想法:这种想法在于,真正地算树高。每次算出root和root.right的树高,假设分别为h和rh,因为rh要么等于h-1,要么等于h-2,因此等于h-1的时候就对应着root的左子树最后一层满了,所以需要对root.right继续递归,返回值为(1 << h) + countNodes(root.right)。其中1<<h来源于左子树的所有节点,加上root节点。如果rh等于h-2,那就说明root.right最后一层根本没有节点,所以root.right所有节点加上root节点一共有(1 << h - 1)个,此时返回值是(1 << h - 1) + countNodes(root.left)

代码:

class Solution {
    public int countNodes(TreeNode root) {
        if (root == null) {
            return 0;
        }
        
        int h = getHeight(root);
        int rh = getHeight(root.right);
        
        if (rh == h - 1) {
            return (1 << h) + countNodes(root.right);
        }
        
        return (1 << h - 1) + countNodes(root.left);
    }
    
    private int getHeight(TreeNode root) {
        return root == null ? -1 : getHeight(root.left) + 1;
    }
}

LeetCode 215 Kth Largest Element in an Array

链接:leetcode.com/problems/kt…

方法:分治

时间复杂度:O(klogk)

空间复杂度:O(1)

想法:经典快选。写这个题是因为我发现我之前用的wikipedia那个写法在LeetCode上跑得不是很快,于是参考了九章的写法www.jiuzhang.com/solutions/k… 。相向双指针模板来把比pivot大的全扔到左边,比pivot小的全扔到右边,然后分情况递归。

代码:

class Solution {
    public int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return -1;
        }
        return quickSelect(nums, k, 0, nums.length - 1);
    }
    
    private int quickSelect(int[] nums, int k, int start, int end) {
        if (start == end) {
            return nums[start];
        }
        
        int left = start, right = end;
        int pivot = nums[(start + end) / 2];
        while (left <= right) {
            while (left <= right && nums[left] > pivot) left++;
            while (left <= right && nums[right] < pivot) right--;
            if (left <= right) {
                swap(nums, left, right);
                left++;
                right--;
            }
        }
        
        if (start + k - 1 <= right) {
            return quickSelect(nums, k, start, right);
        }
        if (start + k - 1 >= left) {
            return quickSelect(nums, k - (left - start), left, end);
        }
        
        return nums[(left + right) / 2];
    }
    
    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}