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)。