LeetCode系列之堆专题

1. 堆问题概述

https://leetcode-cn.com/tag/heap/

堆(heap)是一个可以被看成近似完全二叉树的数组。树上的每一个结点对应数组的一个元素。除了最底层外,该树是完全充满的,而且是从左到右填充。

常见操作有:

  • heapify:建堆
  • heappush:放入一个元素,并保持堆结构
  • heappop:取出最大/最小值,并保持堆结构
  • heapsort:堆排序

堆结构常应用于建立优先队列(priority queue)。

2. 典型题目

2.1 前k个高频元素

https://leetcode-cn.com/problems/top-k-frequent-elements/

vector<int> topKFrequent(vector<int>& nums, int k) {
    unordered_map<int, int> frequencyMap;
    for (int num : nums) {
        if (frequencyMap.count(num)) {
            frequencyMap[num]++;
        } else {
            frequencyMap[num] = 1;
        }
    }
    
    vector<int> ret;
    auto frequencyComparator = [&frequencyMap](int i, int j) {
        return frequencyMap[i] > frequencyMap[j];
    };
    std::make_heap(ret.begin(), ret.end(), frequencyComparator);
    
    for (const auto& kv : frequencyMap) {
        if (ret.size() < k) {
            ret.push_back(kv.first);
            push_heap(ret.begin(), ret.end(), frequencyComparator);
        } else if (kv.second > frequencyMap[ret[0]]) {
            ret.push_back(kv.first);
            push_heap(ret.begin(), ret.end(), frequencyComparator);
            pop_heap(ret.begin(), ret.end(), frequencyComparator);
            ret.pop_back();
        }
    }
    return ret;
}

C++里的堆的接口并不友好;所以,用priority_queue也是不错的。

时间复杂度O(N logK),数组长度为N,堆的大小为K;

空间复杂度O(N)。

2.2 合并k个升序链表

https://leetcode-cn.com/problems/merge-k-sorted-lists/

ListNode* mergeKLists(vector<ListNode*>& lists) {
    auto listNodeComparator = [](ListNode* a, ListNode* b) {
        return a->val > b->val;
    };
    priority_queue<ListNode*, std::vector<ListNode*>, decltype(listNodeComparator)> pq(listNodeComparator);
    for (ListNode* list : lists) {
        if (list) pq.push(list);
    }
    
    ListNode head;
    ListNode* tail = &head;
    while (!pq.empty()) {
        ListNode* current = pq.top();
        pq.pop();
        tail->next = current;
        tail = tail->next;
        if (current->next) pq.push(current->next);
    }
    return head.next;
}

这道题的解法很多,在堆(优先队列)这一章节,就采用优先队列的方法吧。

时间复杂度O(kn logk),空间复杂度O(logk)。

2.3 数组中的第k个最大元素

https://leetcode-cn.com/problems/kth-largest-element-in-an-array/

int findKthLargest(vector<int>& nums, int k) {
    priority_queue<int, std::vector<int>, std::greater<>> pq;
    for (int num : nums) {
        if (pq.size() < k) {
            pq.push(num);
        } else if (num > pq.top()) {
            pq.pop();
            pq.push(num);
        }
    }
    return pq.top();
}

时间复杂度O(n logk),空间复杂度O(k)。

2.4 滑动窗口最大值

https://leetcode-cn.com/problems/sliding-window-maximum/

vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    vector<int> maxSlidingWindow;
    if (nums.size() < k || k < 1) return maxSlidingWindow;

    deque<int> index;
    // 处理前k个元素
    for (int i = 0; i < k; i++) {
        while (!index.empty() && nums[i] >= nums[index.back()]) {
            index.pop_back();
        }
        index.push_back(i);
    }
    maxSlidingWindow.push_back(nums[index.front()]);
    // 处理其余元素
    for (int i = k; i < nums.size(); i++) {
        while (!index.empty() && nums[i] >= nums[index.back()]) {
            index.pop_back();
        }
        if (!index.empty() && index.front() <= i - k) {
            index.pop_front();
        }
        index.push_back(i);
        maxSlidingWindow.push_back(nums[index.front()]);
    }

    return maxSlidingWindow;
}

采用了《剑指Offer》上的做法,双端队列。官解里有一段分析,很精炼:

时间复杂度O(N),空间复杂度O(N)。

3. 总结

posted @ 2020-08-28 15:29  不写诗的诗人小安  阅读(174)  评论(0编辑  收藏  举报