【刷题系列】堆(优先队列)

系列汇总:《刷题系列汇总》



——————《剑指offeer》———————

1. 最小的K个数

  • 题目描述:给定一个数组,找出其中最小的K个数。
    -

  • 思路:【不优秀,用堆实现】用优先级队列实现小根堆(升序排列)

import java.util.*;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if(k == 0 || input == null || input.length == 0 || k > input.length){
            return res;
        }
        PriorityQueue<Integer> q = new PriorityQueue<>((v1, v2) -> v1 - v2); //升序排列
        for(int i = 0;i < input.length;i++){
            q.add(input[i]);
        }
        while(k-- > 0){
            res.add(q.poll());
        }
        return res;
    }
}

2. 数据流中的中位数

  • 题目描述:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

  • 难点:数据流代表数据是实时更新的,所求的中位数也是实时的中位数。

  • 优秀思路:主要的思想是利用优先队列,优先队列分为大顶堆和小顶堆,默认维护的是小顶堆的优先队列。

    需要求的是中位数,如果我将 1 2 3 4 5 6 7 8定为最终的数据流
    此时的中位数是4+5求均值。为什么是4,为什么是5
    利用队列我们就可以看得很清楚,4是前半部分最大的值,肯定是维系在大顶堆
    而5是后半部分的最小值,肯定是维系在小顶堆
    问题就好理解了:
    使用小顶堆存大数据,使用大顶堆存小数据。这样堆顶一取出就是中位数了。

import java.util.*;
public class Solution {
    int count = 0;
    // 存储规则:
    // 1、小根堆存储较大部分的数,其队头元素即为较大部分的最小值 x1
    // 2、大根堆存储较小部分的数,其队头元素即为较小部分的最大值 x2
    // 3、总是将奇数项存储在大根堆里
    
    // 结果输出:
    // 1、偶数项时,中位数即为上述两值的平均值 (x1+x2)/2
    // 2、奇数项时,中位数即为 x2
    PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>() {
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });
	
	// 小根堆、大根堆储值
    public void Insert(Integer num) {
        count++;
        if (count % 2 != 0) { // 当前为奇数项,经小根堆筛选后输出最小值进入大根堆存储
            minHeap.offer(num);
            int cur = minHeap.poll();
            maxHeap.offer(cur);
        } else { // 当前为偶数项,经大根堆筛选后输出最大值进入小根堆存储
            maxHeap.offer(num);
            int cur = maxHeap.poll();
            minHeap.offer(cur);
        }
    }
	
	// 取出中位数
    public Double GetMedian() {
        if(count % 2 == 0) { //偶数项
            return new Double(minHeap.peek()+maxHeap.peek())/2;
        }else { // 奇数项
            return new Double(maxHeap.peek());
        }
    }
}

3. 滑动窗口的最大值

  • 题目描述:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。

    例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,针对数组{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]},他们的最大值分别为{4,4,6,6,6,5};
    窗口大于数组长度的时候,返回空

  • 我的思路(90%):使用大根堆存储当前的滑动窗口:当元素数量小于size时,往里添加元素即可;当元素数量不小于size时,大根堆输出最大值并保存在res里,并且大根堆删除窗口最前面的值num[i-size]

import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        ArrayList<Integer> res = new ArrayList<>();
        if(size == 0 || size > num.length) return res;
        // 建立大根堆
        PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>(){
            public int compare(Integer o1,Integer o2){
                return o2 - o1;
            }
        });
        for(int i = 0;i < num.length;i++){
            if(i < size){
                maxHeap.offer(num[i]);
            }else{
                res.add(maxHeap.peek()); // 存储当前滑动窗口的最大值,并删掉
                maxHeap.remove(num[i-size]);
                maxHeap.offer(num[i]);
            }
        }
        res.add(maxHeap.peek());
        return res;
    }
}
  • 优秀思路:设立双指针记录滑动窗口的左右位置,再写一个寻找最大值的函数
import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        ArrayList<Integer> list = new ArrayList<>();
        if(size <= 0|| size > num.length)return list;
        int left = 0;
        int right = size - 1;
        while(right<num.length){
            list.add(getMax(num,left,right));
            left++;
            right++;
        }
        return list;
    }
    
    public int getMax(int[] num,int left,int right){
        if(num.length==0 || left<0 || right>num.length)return -1;
        int max = Integer.MIN_VALUE;
        for(int i = left;i <= right;i++){
            max = max > num[i] ? max : num[i];
        }
        return max;
    }
}

——————《LeectCode》———————

1. 数组中的第K个最大元素(优秀思路待看)

  • 题目描述:在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
  • 我的思路 (47% - 43%):建立大根堆先存储所有的元素,再找到第k大的即可(删除前面的)
class Solution {
    public int findKthLargest(int[] nums, int k) {
        if(k > nums.length || k == 0) return 0;
        // 建立大根堆:降序排列
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1,o2) -> o2-o1);
        for(Integer moment:nums){
            maxHeap.offer(moment);
        }
        // 删去第k大之前的元素
        while(--k > 0){
            maxHeap.poll();
        }
        return maxHeap.peek();
    }
}
  • 我的思路改进(59% - 51%):在k较大时,从最小的方向进行搜索。即 k>len/2时,k=len-k+1,例如对于一个长度为6的数组来说,第5大=第2
class Solution {
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        PriorityQueue<Integer> Heap;
        if(k <= len/2){ // 建立大根堆:降序排列
            Heap = new PriorityQueue<>((o1,o2) -> o2-o1);
            for(Integer moment:nums) Heap.offer(moment);
            while(--k > 0) Heap.poll();
        }else{ // 建立小根堆:升序排列
            Heap = new PriorityQueue<>();
            for(Integer moment:nums) Heap.offer(moment);
            k = len-k+1; // 例如6个元素的数组,第5大=第2小
            while(--k > 0) Heap.poll();
        }
        return Heap.peek();
    }
}
  • 优秀思路(91%):自己动手写堆排
class Solution {
    public int findKthLargest(int[] nums, int k) {
        int heapSize = nums.length;
        buildMaxHeap(nums, heapSize);
        for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
            swap(nums, 0, i);
            --heapSize;
            maxHeapify(nums, 0, heapSize);
        }
        return nums[0];
    }

    public void buildMaxHeap(int[] a, int heapSize) {
        for (int i = heapSize / 2; i >= 0; --i) {
            maxHeapify(a, i, heapSize);
        } 
    }

    public void maxHeapify(int[] a, int i, int heapSize) {
        int l = i * 2 + 1, r = i * 2 + 2, largest = i;
        if (l < heapSize && a[l] > a[largest]) {
            largest = l;
        } 
        if (r < heapSize && a[r] > a[largest]) {
            largest = r;
        }
        if (largest != i) {
            swap(a, i, largest);
            maxHeapify(a, largest, heapSize);
        }
    }

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

2. 前 K 个高频元素

  • 题目描述:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
  • 优秀思路:建立hashmap存储元素及其出现次数,将出现次数存储到定制小根堆里。该小根堆的基本元素是数组,排序是根据数组的第二个值(即出现次数)进行排序,实时更新保持heap的大小为k,最后读出heap里的数组的第一个值(即元素)即可。
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        HashMap<Integer,Integer> map = new HashMap<>();
        int[] res = new int[k];
        for(int moment:nums){
            map.put(moment, map.getOrDefault(moment, 0) + 1);
        }
        // 根据本题需要重写小根堆:
        // int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] m, int[] n) {
                return m[1] - n[1]; //根据某值出现的次数进行升序排列
            }
        });
        for(int temp:map.keySet()){
            if(queue.size() < k){
                queue.offer(new int[]{temp,map.get(temp)});
            }else{
                if(map.get(temp) > queue.peek()[1]){
                    queue.poll();
                    queue.offer(new int[]{temp,map.get(temp)});
                }
            }
        }
        int index = 0;
        for(int[] temp:queue){
            res[index++] = temp[0];
        }
        return res;
    }
}

3. 合并K个升序链表(有个地方没理解)

  • 题目描述:给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
    在这里插入图片描述
  • 优秀思路(65%):【优先级队列】直接将lists数组的所有节点存入优先级队列,再逐个取出存入结果链表(有个地方没理解???
class Solution {
   public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;
        PriorityQueue<ListNode> queue = new PriorityQueue<>((o1,o2) -> o1.val - o2.val);
        ListNode dummy = new ListNode(0);
        ListNode p = dummy;
        // 把所有节点全部加到优先级队列里
        for (ListNode node : lists) {
            if (node != null) queue.add(node);
        }
        // 从队列里取出各个已排序好的节点,存入结果链表
        while (!queue.isEmpty()) {
            p.next = queue.poll();
            p = p.next;
            if (p.next != null) queue.add(p.next); // 这里没理解???
        }
        return dummy.next;
    }
}

4. 最小K个数

  • 题目描述:设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可
  • 普通思路(34%):利用小根堆实现,先全部存进去,再读出前k个
class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] res = new int[k];
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for(int num:arr){
            queue.offer(num);
        }
        for(int i = 0;i < k;i++){
            res[i] = queue.poll();
        }
        return res;
    }
}
  • 普通思路改进(38%):不全部存入,只存len-k个最大的,其余的就直接存到res
class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] res = new int[k];
        // 大根堆
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        int count = 0;
        for(int num:arr){
            if(queue.size() == arr.length-k){
                if(num <= queue.peek()) res[count++] = num;
                else{
                    res[count++] = queue.poll();
                    queue.offer(num);
                }
            }else{
                queue.offer(num);
            }
        }
        return res;
    }
}

5. 滑动窗口最大值

  • 题目描述:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
  • 一般思路(22%):【堆实现】若总是对优先队列进行元素删减会超时。所以将元素的索引+元素值一起存入优先队列,根据元素值的大小降序排列,每次输出当前滑动窗口的最大值时判断最大值的索引有没有在当前窗口范围内,若没在则抛出直至其出现在当前范围内。
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int len = nums.length;
        int count = 0;
        int[] res = new int[len-k+1];
        if(k == 0 || k > nums.length) return res;
        // 建立大根堆: 数组包含 元素的下标值+元素值
        PriorityQueue<int[]> maxHeap = new PriorityQueue<>(new Comparator<int[]>(){
            public int compare(int[] m,int[] n){
                return n[1] - m[1]; // 根据元素的值进行降序排序
            }
        });
        for(int i = 0;i < nums.length;i++){
            maxHeap.offer(new int[]{i,nums[i]});
            if(i >= k-1){
                while(maxHeap.peek()[0] <= i-k){ // 最大值不在当前滑动窗口范围内
                    maxHeap.poll();
                }
                res[count++] = maxHeap.peek()[1];
            }
        }
        return res;
    }
}
  • 优秀思路(93%):【双向队列】建立一个双向队列,存储元素的下标,保证其中的值按从大到小排列。若当前值>队尾值对应的元素值(队列中的最小值),则从队尾依次出队直至其小于队尾值对应元素,此时将当前值索引加入该队列。记录当前窗口的最大值时,判断队首是否在滑动窗口范围内,若不在则从队头依次出队,直至队头满足窗口范围。
    在这里插入图片描述
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums == null || nums.length < 2) return nums;
        int[] result = new int[nums.length-k+1];

        // 双向队列:保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序
        LinkedList<Integer> queue = new LinkedList();
        for(int i = 0;i < nums.length;i++){
            // 如果队尾的数小于当前值:则需要依次弹出,直至满足要求
            while(!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]){
                queue.pollLast(); // Last表示队尾(小的部分)
            }
            queue.addLast(i); // 添加当前值对应的数组下标(加在队尾)
            if(queue.peek() <= i-k){ // 当前队列中队首的值不在滑动窗口范围
                queue.poll();   
            } 
            // 当窗口长度为k时 保存当前窗口中最大值
            if(i+1 >= k){
                result[i+1-k] = nums[queue.peek()];
            }
        }
        return result;
    }
}

6. 丑数 II

  • 题目描述:给你一个整数 n ,请你找出并返回第 n 个 丑数 。丑数 就是只包含质因数 2、35 的正整数。
  • 一般思路(28%):【堆实现】利用最小堆,初始化加入1。后续每次将堆顶元素x的2x\3x\5x加入堆中,未避免重复,利用hashset筛选
class Solution {
    public int nthUglyNumber(int n) {
        // 核心:怎么避免数据溢出:计算时全部采用long,只在最后输出结果时转为int
        HashSet<Long> numSet = new HashSet<>();
        PriorityQueue<Long> minHeap = new PriorityQueue<>();
        minHeap.offer(1L);
        numSet.add(1L);
        int[] factor = {2,3,5};
        int res = 0;
        for(int i = 0;i < n;i++){
            // 每次取出最小堆中的堆顶元素x(最小),将其2x、3x、5x全部加入最小堆
            // 未避免重复,利用hashset筛选
            long cur = minHeap.poll();
            res = (int)cur; // res作为int不参与任何计算,只作为结果输出
            for(int temp:factor){
                long tempUgly = temp*cur;
                if(numSet.add(tempUgly)){ // hashset的add函数,若元素不存在则添加,且返回true,否则返回false
                    minHeap.offer(tempUgly);
                }
            }
        }
        return res;
    }
}

7. 数据流的中位数

  • 题目描述:中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。例如,[2,3,4] 的中位数是 3,[2,3] 的中位数是 (2 + 3) / 2 = 2.5。设计一个支持以下两种操作的数据结构:
    void addNum(int num) - 从数据流中添加一个整数到数据结构中。
    double findMedian() - 返回目前所有元素的中位数。
  • 优秀思路(70%):【双堆法】
class MedianFinder {
    // 小根堆存储较大的一半数据
    // 大根堆存储较小的一半数据
    // 奇数项存储在小根堆:先进入大根堆,大根堆输出最大元素放入小根堆
    // 偶数项存储在大根堆:先进入小根堆,小根堆输出最小元素放入大根堆
    int countNum = 0;
    PriorityQueue<Integer> minHeap;
    PriorityQueue<Integer> maxHeap;
    public MedianFinder() {
        minHeap = new PriorityQueue<>();
        maxHeap = new PriorityQueue<>((o1,o2)->o2-o1);
    }
    // 存储元素
    public void addNum(int num) {
        countNum++;
        if(countNum % 2 == 0){ //偶数项
            minHeap.offer(num);
            maxHeap.offer(minHeap.poll());
        }else{ // 奇数项
            maxHeap.offer(num);
            minHeap.offer(maxHeap.poll());
        }
    }
    // 获取中位数
    public double findMedian() {
        if(countNum % 2 == 0){
            return (double)((minHeap.peek() + maxHeap.peek()))/2;
        }else{
            return (double)minHeap.peek();
        }
    }
}

8. 重构字符串

  • 题目描述:给定一个字符串S,检查是否能重新排布其中的字母,使得两相邻的字符不同。若可行,输出任意可行的结果。若不可行,返回空字符串。
  • 优秀思路:维护最大堆存储字母,堆顶元素为出现次数最多的字母。首先统计每个字母的出现次数,然后将出现次数大于 00 的字母加入最大堆。当最大堆的元素个数大于 11 时,每次从最大堆取出两个字母,拼接到重构的字符串,然后将两个字母的出现次数分别减 1,并将剩余出现次数大于 0 的字母重新加入最大堆。由于最大堆中的元素都是不同的,因此取出的两个字母一定也是不同的,将两个不同的字母拼接到重构的字符串,可以确保相邻的字母都不相同。如果最大堆变成空,则已经完成字符串的重构。
  • 我的实现(23%):建立hashmap统计字母及出现次数,再转存到最大堆中,最大堆的基本元素为数组【字母ascii码+出现次数】,每次从堆中取出两个字母(也可能只剩一个),对应的次数减一,若减一后仍不为0,则又将其加入堆中,直至堆为空则停止。
class Solution {
    public String reorganizeString(String s) {
        // hashmap记录字符及其出现次数
        StringBuilder sb = new StringBuilder(); //用于保存结果
        PriorityQueue<int[]> maxHeap = new PriorityQueue<>(new Comparator<int[]>(){
            public int compare(int[] m,int[] n){
                return n[1] - m[1];
            }
        });
        char[] arr = s.toCharArray();
        Map<Integer,Integer> map = new HashMap<>(); // 作为泛型,Character首字母要大写
        for(char c:arr){
            map.put((int)c,map.getOrDefault((int)c,0)+1);
        }

        // 建立大根堆:存储字母对应的数字+字母出现次数,根据字母出现次数降序排列
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){
            int key = entry.getKey(),value = entry.getValue();
            maxHeap.offer(new int[]{key,value}); //不能只写{}
        }

        // 判断是否能重新排布:看出现次数最多的字母间隙能否被其他字母填满
        int maxCur = maxHeap.peek()[1];
        int nrest = arr.length - maxCur;
        if(nrest < maxCur-1){ // 无法重新排布
            return "";
        }else{ // 可重新排布:每次从堆顶区两个元素加入sb
            while(!maxHeap.isEmpty()){
                int[] temp = maxHeap.poll();
                int[] temp2 = new int[2];
                sb.append((char)temp[0]);
                if(!maxHeap.isEmpty()){//这个比较可以移到外面,只进行一次即可
                    temp2 = maxHeap.poll();
                    sb.append((char)temp2[0]);
                }
                if(temp[1] > 1)maxHeap.offer(new int[]{temp[0],temp[1]-1});
                if(temp2 != null && temp2[1] > 1) maxHeap.offer(new int[]{temp2[0],temp2[1]-1});
            }
        }
        return sb.toString();
    }
}
  • 可改进处:1、不用每次都比较;2、堆可以根据外部条件排序
  • 官方实现(50%)
class Solution {
    public String reorganizeString(String s) {
        if (s.length() < 2)  return s;
        
        // 建立数组记录字母出现次数:次数对应位置下标即为【字母-‘a’】
        int[] counts = new int[26];
        int maxCount = 0;
        int length = s.length();
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i);
            counts[c - 'a']++;
            maxCount = Math.max(maxCount, counts[c - 'a']);
        }
        if (maxCount > (length + 1) / 2) {
            return "";
        }

		// 最大堆存储字符:按字母的出现次数进行排列
        PriorityQueue<Character> queue = new PriorityQueue<Character>(new Comparator<Character>() {
            public int compare(Character letter1, Character letter2) {
                return counts[letter2 - 'a'] - counts[letter1 - 'a']; // 注意有count
            }
        });
        for (char c = 'a'; c <= 'z'; c++) {
            if (counts[c - 'a'] > 0) {
                queue.offer(c);
            }
        }
        StringBuffer sb = new StringBuffer();
        while (queue.size() > 1) {
            char letter1 = queue.poll();
            char letter2 = queue.poll();
            sb.append(letter1);
            sb.append(letter2);
            int index1 = letter1 - 'a', index2 = letter2 - 'a';
            counts[index1]--;
            counts[index2]--;
            if (counts[index1] > 0) {
                queue.offer(letter1);
            }
            if (counts[index2] > 0) {
                queue.offer(letter2);
            }
        }
        if (queue.size() > 0) {
            sb.append(queue.poll());
        }
        return sb.toString();
    }
}

9. 最后一块石头的重量

  • 题目描述:有一堆石头,每块石头的重量都是正整数。每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 xy,且 x <= y。那么粉碎的可能结果如下:如果 x == y,那么两块石头都会被完全粉碎;如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0
  • 我的思路(同优秀,56%):建立最大堆按题目描述过程进行,不再赘述
class Solution {
    public int lastStoneWeight(int[] stones) {
        if(stones == null || stones.length == 0) return 0;
        int res = 0;
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1,o2) -> o2-o1);
        for(int i:stones) maxHeap.offer(i);
        while(maxHeap.size() > 1){
            res = maxHeap.poll() - maxHeap.poll();
            if(res > 0) maxHeap.offer(res);
        }
        return maxHeap.size()==0? 0:maxHeap.poll();
    }
}

10. 有序矩阵中第 K 小的元素

  • 题目描述:给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。
  • 我的思路(22%):【堆实现】建立堆存储数据,根据k的大小选择是否最大还是最小(从短的那头开始搜索)
class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int n = matrix.length;
        if(k == 1 || n == 1) return matrix[0][0];

        int res = 0;
        PriorityQueue<Integer> heap;
        if(k*2 <= n*n){ // 建立最小堆
            heap = new PriorityQueue<>();
        }else{ // 建立最大堆
            heap = new PriorityQueue<>((o1,o2) -> o2-o1);
            k = n*n - k + 1;
        }
        
        //数组元素入堆
        for(int[] i:matrix){
            for(int j:i){
                heap.offer(j);
            }
        }

        // 输出结果
        while(k-- > 0){
            res = heap.poll();
        }
        return res;
    }
}
  • 我的思路改进(37%):【堆实现】每次将堆中弹出元素的右边和下边两个值入堆,故堆中元素数组的3位依次为:横坐标、纵坐标、元素值。弹出第k个最小值则停止
class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int n = matrix.length;
        if(k == 1 || n == 1) return matrix[0][0];
        if(k == n*n) return matrix[n-1][n-1];

        int res = 0;
        int curI = 0; // 当前弹出元素的坐标
        int curJ = 0;
        int count = 1;
        int factor = 1; //控制坐标的加减
        PriorityQueue<int[]> heap; // 堆元素数组的3位依次为:横坐标、纵坐标、元素值
        int[][] vis = new int[n][n];

        if(k*2 < n*n){ //从左上角开始搜索(较小端)
            heap = new PriorityQueue<>(new Comparator<int[]>(){
                public int compare(int[] m,int[] n){ // public 必须加
                    return m[2]-n[2];
                }
            });
        }else{
            curI = n-1; 
            curJ = n-1;
            factor = -1;
            k = n*n - k + 1;
            heap = new PriorityQueue<>(new Comparator<int[]>(){
                public int compare(int[] m,int[] n){ // public 必须加
                    return n[2]-m[2];
                }
            });
        }
        vis[curI][curJ] = 1;
        while(count < k){
            if(curI+factor < n && curI+factor >= 0 && vis[curI+factor][curJ] == 0){ //当前弹出最小值的下方元素
                vis[curI+factor][curJ] = 1;
                heap.offer(new int[]{curI+factor,curJ,matrix[curI+factor][curJ]});
            }
            if(curJ+factor < n && curJ+factor >= 0 && vis[curI][curJ+factor] == 0){ //当前弹出最小值的右方元素
                vis[curI][curJ+factor] = 1;
                heap.offer(new int[]{curI,curJ+factor,matrix[curI][curJ+factor]});
            }

            //弹出此轮最小值
            int[] temp = heap.poll();
            
            curI = temp[0];
            curJ = temp[1];
            res = temp[2];
            count++;
        }
        return res;
    }
}
  • 优秀思路:【二分查找】教程有时间看

11. 前K个高频单词

  • 题目描述:给一非空的单词列表,返回前 k 个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。
  • 优秀思路:哈希表+最大堆,
    • 1、hashmap存储字符串及其出现次数
    • 2、按【词频降序+字典序降序(词频相同时)】将字符串存入最大堆
    • 3、将前k个字符串存入list输出
class Solution {
    public List<String> topKFrequent(String[] words, int k) {
        List<String> res = new ArrayList<>();
        if(words.length == 0 || words == null) return res;

        // 记录字符串及其出现次数
        HashMap<String,Integer> map = new HashMap<>();
        for(String temp:words){
            map.put(temp,map.getOrDefault(temp,0)+1);
        }

        //按【词频降序+字典序降序(词频相同时)】将字符串存入最大堆
        PriorityQueue<String> maxHeap = new PriorityQueue<>(new Comparator<String>(){
            public int compare(String m,String n){
                return map.get(m) == map.get(n)? m.compareTo(n):map.get(n)-map.get(m);
            }
        });
        for(String key : map.keySet()){
            maxHeap.offer(key);
        }

        // 保存结果
        while(k-- > 0){
            res.add(maxHeap.poll());
        }

        return res;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值