优先队列总结

本文通过四个具体的编程问题展示了优先队列在解决实际问题中的应用,如课程表安排、会议室分配、找第K大元素和合并链表。优先队列常用于贪心算法,通过自定义比较器实现不同场景下的排序需求,如按结束时间升序排列会议、按课程截至时间升序选取等。在每个问题中,优先队列都起到了关键作用,确保了高效的时间复杂度和空间复杂度。
摘要由CSDN通过智能技术生成

优先队列在插入元素的时候,自动将其插入到合适的位置,实现动态的调整,底层是堆。创建优先队列时,可自己实现comparator接口的compare方法,用于队列中的元素排列规则。在使用优先队列时,有时候需要配合排序数组使用,也可以为Arrasy.sort()方法传入一个comparator接口的匿名内部类或lamda表达式,自定义数组排序规则。

力扣630. 课程表 III

思路:贪心+优先队列。每次选择一个截至时间最早的课程,判断该课程的持续时间与之前课程的持续时间之和是否超过了该课程的截至时间,如果没超过,则将该课程加入到队列中,并且总时间也加上该课程的持续时间。如果超过了,在判断该课程的持续时间是否小于 等于之前的课程中最大的持续时间,如果是,将该课程替换掉那个最大的课程。
所以,可以使用一个优先队列,保存的是已经选修的课程,队头是持续时间最大的课程。将所有课程按截至时间升序排序,每次选一个课程,将该课程的持续时间加上之前的持续时间之和,再和该课程的截至时间作比较。
代码:
//贪心+优先队列
//假设可以选多门课程,那么一定是这些课程的完成时间要小于最大的课程截至时间
//将课程按截至时间排序,先截止的先选

class Solution {
    public int scheduleCourse(int[][] courses) {
        //队列装的是课程完成时间
        PriorityQueue<Integer> cour = new PriorityQueue<Integer>((a, b) -> b - a);
        //将课程按截止时间排序
        Arrays.sort(courses, (a, b) -> a[1] - b[1]);
        //课程最大完成时间
        int count = 0;
        for(int[] course : courses){
            int duration = course[0], deadline = course[1];
            //如果当前课程 加上之前的总的完成时间小于课程的当前的课程截至时间,则将其加入队列
            if( duration + count <= deadline){
                count += duration;
                cour.add(duration);
                //如果当前课程的完成时间加上之前的完成时间比当前课程的截至时间要大,找到之前的课程的最大完成时间x,如果x大于duration,则将之前的移除,将现在的加入
            }else if(!cour.isEmpty() && cour.peek() > duration){
                count -= cour.peek();
                cour.poll();
                count += duration;
                cour.add(duration);
            }
        }
        return cour.size();
    }
}
复杂度分析:
时间: o(nlog(n)),调整一次需要log(n),需要n次
空间: o(n)。n元素

253. 会议室 II

思路:要安排会议室,一个很自然的想法是,会议开始的早的先安排,每当有新的会议需要 开的时候,就去已经安排的会议室查看是否空闲,如果不空,则增加新的会议。所以可以对会议按开始时间升序排序,优先队列存的是已经安排了的会议的结束时间,队头是截至时间最 小的,队列中有几个会议,就有几间会议室。当新的会议的开始时间比队头小时,就说明队头会议开完了,将其移除,加入新的会议,否则新开一间会议室,并将其移除。
代码:
//使用优先队列。
//让我们想想,安排会议室肯定是先安排最早开的,所以先按开始时间排序。
//每当有一个新会议的时候,我们就要去找,有没有已经结束的会议
//使用最小堆,堆顶元素是结束时间最快时间。当新会议的开始时间大于该结束时间,则可以加一个新会议,否则新建一个会议室。


class Solution {
    public int minMeetingRooms(int[][] intervals) {
        
        //base case
        if(intervals == null || intervals.length == 0)return 0;

        //优先队列存的是会议结束时间
        PriorityQueue<Integer> mettings = new PriorityQueue<>(
            intervals.length,
            new Comparator<Integer>(){
                public int compare(Integer a, Integer b){
                    return a - b;
                }
            });

        //对Intervals按开始时间排序
        //a - b为升序
        Arrays.sort(intervals,
        new Comparator<int[]>(){
            public int compare(int[] a, int[] b){
                return a[0] - b[0];
            }
        });

        //将第一个会议加入预优先队列
        mettings.add(intervals[0][1]);
        for(int i = 1; i < intervals.length; i++){
            //如果当前存在空的会议室,也就是当前会议的开始时间大于等于队头的结束时间,则将对头会议拿掉。
            //每次都要新加入一个会议
            if(mettings.peek() <= intervals[i][0]){
                mettings.poll();
            }
            mettings.add(intervals[i][1]);
        }
        return mettings.size();
    }
}
复杂度分析:
时间:o(nlog(n))
空间:o(n)

215. 数组中的第K个最大元素

思路:使用优先队列,移除k - 1次队头元素之后的队头元素就是目标元素。优先队列一般可以调用API,这里最好还是自己建立一个大根堆,调整k - 1之后的堆顶元素就是结果啦。
代码:
//方法1:优先队列
class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> q = new PriorityQueue<>(
            new Comparator<Integer>(){
                public int compare(Integer a, Integer b){
                    return b - a;
                }
            });
        for(int i = 0; i < nums.length; i++){
            q.add(nums[i]);
        }
        while(k > 1){
            q.poll();
            k--;
        }
        return q.peek();
    }
}

//方法2:建立大根堆,调整k-1次返回堆顶元素即可
class Solution {
    public int findKthLargest(int[] nums, int k) {
        //初始构建 一个大根堆
        buildMaxHeap(nums);
        for(int i = nums.length - 1; i > nums.length - k; i--){
            swap(nums, 0, i);
            adjust(nums, 0, i);
           
        }
        return nums[0];
    }


    void buildMaxHeap(int[] arr){
        //找到最后一个非叶子节点
        //从该节点自下而上的调整,建立一个初始大根堆。
        for(int i = arr.length / 2 - 1; i >= 0; i--){
            adjust(arr, i, arr.length);
        }
    }

    //调整函数:调整以当前节点为根的堆,使之成为一个大根堆
    void adjust(int[] arr,  int i, int heapsize){
        //保存最大元素的位置
        int largest = i;
        int left = 2 * i + 1;
        int right = left + 1;
        if(left < heapsize && arr[left] > arr[largest])largest = left;
        if(right < heapsize && arr[right] > arr[largest])largest = right;
        //如果最大元素不是父亲节点,则要将孩子节点与父亲节点做交换,再调整以该孩子节点为根的堆
        if(largest != i){
            swap(arr, i, largest); 
            adjust(arr, largest, heapsize);
        }
    }

    void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}
复杂度分析:
时间:o(n(log(n)))
空间:o(n)

23. 合并K个升序链表

思路:用优先队列保存每一个链表的表头,按链表元素升序排列。每次取链表头元素,加入到新链表中,并将取出的链表元素的下一个元素入队。当对为空时,就建好了链表。
代码:
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        //优先队列只存每个链表头就好了
        PriorityQueue<ListNode> queue = new PriorityQueue<>((a, b) -> a.val - b.val);
        for(ListNode list : lists){
            if(list != null){
                queue.offer(list);
        }
        }
        //重建链表
        ListNode dummy = new ListNode(0);
        ListNode curNode = dummy;
        while(!queue.isEmpty()){
            ListNode node = queue.poll();
            curNode.next = node;
            curNode = curNode.next;
            if(node.next != null){
                queue.offer(node.next);
            }
        }
        return dummy.next;
    }
}
复杂度分析:
时间:优先队列不超过k个元素,插入一个元素需要log(k),每条链表长度为n,复杂度为(nk log(k))
空间:o(k)。k条链表,最多k个链表头元素在队列中。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值