双非本科准备秋招(14.1)—— 力扣刷题

今天做两个有点难度的题。

1、295. 数据流的中位数

手写堆实现:

        加入元素:

        如何维护一个中位数?我们考虑一下堆的特点,大顶堆堆顶是一个最大值,小顶堆堆顶是一个最小值,那么,如果我们可以把数据流的数据按顺序地平均地存放在两个堆中,一个大顶堆,一个小顶堆,那么大顶堆和小顶堆的堆顶不就是中位数吗?

        就像下图这样,我们依次加入数据流,最后需要形成这样的堆。

        还需要考虑一个问题,我们怎样加入元素?肯定是加一下大顶堆,再加一下小顶堆,这样依次加入元素,但是能直接就加吗?

        不可以的,因为我们的大顶堆需要存储小的那部分元素,小顶堆需要存储大的那部分元素。

        这里需要一个巧妙地操作,比如我们每次要往大顶堆添加元素,先把这个元素加到小顶堆,然后把小顶堆的堆顶加到大顶堆中。

        比如,此时来了个100,两个堆size相同,我们想加到大顶堆中

但是100很大,所以我们先加入小顶堆,然后小顶堆堆顶移到大顶堆

这样就实现了元素加入大顶堆并且大小有序。

加入代码如下:h1大顶堆,h2小顶堆。

        if(h1.size == h2.size){
            h2.offer(num);
            h1.offer(h2.poll());
        }
        else{
            h1.offer(num);
            h2.offer(h1.poll());
        }

        返回答案

          如果元素相同,返回堆头之和除以2;如果不同,那肯定是大顶堆h1多一个元素,直接返回就行。注意结果是double类型。

        if(h1.size == h2.size){
            return (h1.peek() + h2.peek())/2.0;
        }
        return 1.0*h1.peek();

这里我手写一下堆,复习一下堆的基本写法。

 h1 = new Heap(10, true);
 h2 = new Heap(10, false);

第二个参数是true代表大顶堆,是false代表小顶堆,我将大小顶堆代码合并了。

class MedianFinder {
    Heap h1 = null;
    Heap h2 = null;

    public MedianFinder() {
        h1 = new Heap(10, true);
        h2 = new Heap(10, false);
    }
    
    public void addNum(int num) {
        if(h1.size == h2.size){
            h2.offer(num);
            h1.offer(h2.poll());
        }
        else{
            h1.offer(num);
            h2.offer(h1.poll());
        }
    }
    
    public double findMedian() {
        if(h1.size == h2.size){
            return (h1.peek() + h2.peek())/2.0;
        }
        return 1.0*h1.peek();
    }
}
class Heap{
    private int[] array;
    int size;
    Boolean m;

    public Heap(int capacity, Boolean m) {
        this.array = new int[capacity];
        this.m = m;
    }

    public int peek(){
        return size==0 ? -999 : array[0];
    }

    public void offer(int offered){
        if(size == array.length) {
            resize();
        }
        up(offered);
        size++;
    }

    private void resize() {
        int capacity = size + (size>>1);
        int[] newArr = new int[capacity];
        System.arraycopy(array, 0, newArr, 0, size);
        array = newArr;
    }

    private void up(int offered) {
        int child = size;
        while(child > 0){
            int parent = (child - 1) / 2;
            if(m ? offered > array[parent] : offered < array[parent]){
                array[child] = array[parent];
            }
            else {
                break;
            }
            child = parent;
        }
        array[child] = offered;
    }

    public int poll(){
        int top = array[0];
        swap(0, size-1);
        size--;
        down(0);
        return top;
    }

    private void down(int i) {
        int lc = 2*i+1;
        int rc = lc+1;
        int now = i;
        if(lc < size && (m ? array[lc] > array[now] : array[lc] < array[now])){
            now = lc;
        }
        if(rc < size && (m ? array[rc] > array[now] : array[rc] < array[now])){
            now = rc;
        }
        if(now != i){
            swap(now, i);
            down(now);
        }
    }

    private void swap(int top, int bottom) {
        int t = array[top];
        array[top] = array[bottom];
        array[bottom] = t;
    }
}

利用优先队列:

优先队列底层就是堆嘛,所以直接用java提供的优先队列也行。( •̀ ω •́ )y

class MedianFinder {
    PriorityQueue<Integer> q1;
    PriorityQueue<Integer> q2;

    public MedianFinder() {
        q1 = new PriorityQueue<>((o1, o2)->o2-o1);
        q2 = new PriorityQueue<>();
    }
    
    public void addNum(int num) {
        if(q1.size() == q2.size()){
            q2.offer(num);
            q1.offer(q2.poll());
        }
        else{
            q1.offer(num);
            q2.offer(q1.poll());
        }
    }
    
    public double findMedian() {
        return (q1.size()==q2.size() ? 
                    1.0*(q1.peek()+q2.peek())/2 :
                        1.0*q1.peek());
    }
}

2、239. 滑动窗口最大值

        第一想法,直接来个优先队列,但是想想不对,因为优先队列会将窗口中的数据排序,排完序之后窗口继续移动,那我就无法知道应该pop掉哪个元素了。

        我们需要一个数据结构,这个数据结构和队列很像,它能push加入队尾,pop删除队头,getMaxValue获取最大值。但是并没有这种现成的数据结构。

        实现上面需求的数据结构叫做单调队列

        单调队列中存着窗口中的元素,队列头就是当前窗口最大值,为什么能是最大值呢?因为这个队列并不是存储所有的元素,而是有选择的存,单调队列每次新加元素时,都会比较与队尾的值,如果队尾小,就一直pop,直到能添加进去,所以,如果此时新加的元素是最大值,那么它会pop掉所有队元素,成为队头。

        所以,想要在单调队列中存活,需要满足两个条件:

  • 在窗口的范围内
  • 没有被新来的元素干掉,也就是不比新来的元素小

        java中,我使用LinkedList,LinkedList实现了双端队列接口,用双端队列来模拟单调队列。

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        LinkedList<Integer> list = new LinkedList<>();
        //nums.length-k+1
        int len = nums.length;
        int[] ans = new int[len-k+1];
        int cnt = 0;
        for(int i = 0; i < len; i++){
            //弹出过时的
            while(!list.isEmpty() && list.peek() < i-k+1){
                list.poll();
            }
            //弹出末尾不够大的
            while(!list.isEmpty() && nums[list.peekLast()] < nums[i]){
                list.pollLast();
            }
            list.offer(i);
            if(i >= k-1){
                ans[cnt++] = nums[list.peek()];
            }
        }

        return ans;
    }
}
  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值