剑指offer(十八)

题目: 61. 二叉搜索树的第k个结点,62. 数据流中的中位数,63. 滑动窗口的最大值

61. 二叉搜索树的第k个结点

给定一颗二叉搜索树,请找出其中的第k大的结点(此处题目表达有些不准确,应该为第k小结点)。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按结点数值大小顺序第三个结点的值为4。

思路:二叉搜索树的中序遍历是排好序的,因此中序遍历输出第k个结点即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.Stack;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    
    }

}
*/
public class Solution {
    TreeNode KthNode(TreeNode pRoot, int k){
        if(pRoot == null || k == 0) return null;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode node = pRoot;
        int count = 0;
        do{
            if(node != null){
                stack.push(node);
                node = node.left;
            }
            else{
                node = stack.pop();
                count++;
                if(count == k) return node;
                node = node.right;
            }
        } while(!stack.isEmpty() || node!=null);
        return null;
    }
}

62. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

思路: 用一个最大堆和一个最小堆将数据流分成两部分,最大堆中的所有元素需要小于最小堆,两个堆的元素数量之差需要小于1,满足这两个条件之后,当元素总数为偶数时,两个堆定元素的平均数就是中位数;当元素总数是奇数时,元素数量多的堆的堆顶就是中位数。因此可以采用一种策略,用一个计数器,当放入的元素是第奇数个的时候,先将元素放入最大堆,调整完结构将最大堆顶放入最小堆;放入元素是偶数个的时候,先将元素放进最小堆,再取除最小堆的堆顶放入最大堆。这样就能确保两个堆的数量相等或差不超过1,且小堆元素总是大于大堆元素。

这里用到的容器是PriorityQueue,默认最小堆,最大堆需要重写compare方法。PriorityQueue具体可以看这篇文章:Java堆结构PriorityQueue完全解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
    private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
    private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(11, new Comparator<Integer>(){
        @Override
        public int compare(Integer o1, Integer o2){
            return o2.compareTo(o1);
        }
    });
    private static int count = 0;
    public void Insert(Integer num) {
        count++;
        if((count & 1) == 1){
            maxHeap.offer(num);
            minHeap.offer(maxHeap.poll());
        }
        else{
            minHeap.offer(num);
            maxHeap.offer(minHeap.poll());
        }
        

    }
    
    public Double GetMedian() {
        if(count == 0) {
            throw new RuntimeException("No numbers in stream!");
        }
        if((count & 1) == 0) return (double)(minHeap.peek() + maxHeap.peek())/2;
        else return (double)minHeap.peek();
    }
}

63. 滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

思路: 用一个双端队列存取数组下标di,当第i个元素进入队列,若尾部num[di]比num[i]小,弹出di,当队列头部元素与i的差值大于等于size时,说明头部元素已经不在窗口中,弹出头部元素。做完上述处理之后,将i存入队列。当i大于等于size - 1开始,将队头num[di]存入结果。

例,

数组[2, 3, 4, 2, 6, 2, 5, 1],窗口大小size = 3。

iteration1:

队列[0],进入元素num[1] = 3,num[1] > num[0],弹出队列尾部元素;

队列空,不判断;

队列加入1

iteration2:

队列[1],进入元素num[2] = 4,num[2] > num[1],弹出队列尾部元素;

队列空,不判断;

队列加入2;

2 >= size - 1,将队头元素对应的数组数字存入结果。res=[4]

iteration3:

队列[2],进入元素num[3] = 2,num[3] < num[2],无需操作队列;

队头元素2,此时3 - 2 < size,说明队头还在窗口内;

队列加入3;

3 >= size - 1,将队头元素对应的数组数字存入结果。res=[4,4]

iteration4:

队列[2,3],进入元素num[4] = 6,num[4] > num[3],弹出队列尾部元素;

num[4] > num[2],继续弹出队列尾部元素;

队头元素4,此时4 - 2 < size,说明队头还在窗口内;

队列加入4;

4 >= size - 1,将队头元素对应的数组数字存入结果。res=[4,4,6]

….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.ArrayList;
import java.util.LinkedList;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size){
        LinkedList<Integer> deque = new LinkedList<Integer>();
        ArrayList<Integer> res = new ArrayList<Integer>();
        if(num.length == 0 || size == 0) return res;
        for(int i = 0; i < num.length; i++){
            while(deque.size() > 0 && num[deque.getLast()] < num[i]) deque.removeLast();
            if(deque.size() > 0 && i - deque.getFirst() >= size) deque.removeFirst();
            deque.add(i);
            if(i >= size - 1) res.add(num[deque.getFirst()]);
        }
        return res;
    }
}