Pair基础 在C++中,pair
是一个常用的模板类,用来将两个值组合成一个值对。它在处理关联数据时非常有用,例如在标准库中的map
和set
中常用到。以下是一些pair
的常见操作和用法:
定义和初始化 1. 创建一个pair
并初始化: 1 2 3 4 5 6 7 8 #include <iostream> #include <utility> int main () { std::pair<int , std::string> myPair (1 , "Hello" ) ; std::cout << "First: " << myPair.first << ", Second: " << myPair.second << std::endl; return 0 ; }
2. 使用std::make_pair
函数创建一个pair
: 1 2 3 4 5 6 7 8 #include <iostream> #include <utility> int main () { auto myPair = std::make_pair (1 , "Hello" ); std::cout << "First: " << myPair.first << ", Second: " << myPair.second << std::endl; return 0 ; }
访问元素 pair
中的元素可以通过first
和second
成员来访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <utility> int main () { std::pair<int , std::string> myPair (1 , "Hello" ) ; std::cout << "First: " << myPair.first << ", Second: " << myPair.second << std::endl; myPair.first = 2 ; myPair.second = "World" ; std::cout << "First: " << myPair.first << ", Second: " << myPair.second << std::endl; return 0 ; }
比较操作 pair
支持比较操作,包括相等、不等、小于、小于等于、大于和大于等于。比较操作按字典序进行,**即先比较first
,如果first
相等,再比较second
**。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <utility> int main () { std::pair<int , std::string> pair1 (1 , "Hello" ) ; std::pair<int , std::string> pair2 (2 , "World" ) ; std::pair<int , std::string> pair3 (1 , "Hello" ) ; std::cout << (pair1 == pair2) << std::endl; std::cout << (pair1 != pair2) << std::endl; std::cout << (pair1 < pair2) << std::endl; std::cout << (pair1 <= pair3) << std::endl; std::cout << (pair2 > pair1) << std::endl; std::cout << (pair2 >= pair3) << std::endl; return 0 ; }
使用在标准库容器中 pair
经常在标准库容器中使用,例如map
和set
。
1. 在map
中使用: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <map> #include <string> int main () { std::map<int , std::string> myMap; myMap.insert (std::make_pair (1 , "One" )); myMap.insert (std::make_pair (2 , "Two" )); for (const auto & elem : myMap) { std::cout << elem.first << ": " << elem.second << std::endl; } return 0 ; }
2. 在set
中使用: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <set> #include <utility> int main () { std::set<std::pair<int , std::string>> mySet; mySet.insert (std::make_pair (1 , "One" )); mySet.insert (std::make_pair (2 , "Two" )); for (const auto & elem : mySet) { std::cout << elem.first << ": " << elem.second << std::endl; } return 0 ; }
使用const auto& 来列举时,每个元素是const pair<>类型。
优先队列基础 priority_queue基础 CPP优先队列定义如下(需要#include<queue>
)
priority_queue<Type, Container, Functional>
Container是存放元素的容器,默认用vector, Functional指出优先级的比较标准。
CPP的优先队列默认是大顶堆,Functional默认为less,即:
1 2 3 priority_queue<int > a;
如果想构造小顶堆,只需将Functional变为greater即可:
1 priority_queue<int , vector<int >, greater<int > > c;
为什么默认为less就是大顶堆了呢?
默认情况下,std::priority_queue使用std::less作为比较函数,这相当于比较运算符<。这意味着,对于两个元素lhs和rhs (假设为bool operator()(const std::pair<int, int>& lhs, const std::pair<int, int>& rhs):
• 如果lhs < rhs返回true,则priority_queue认为rhs的优先级高于lhs。
也就是说,priority_queue默认认为返回true表示lhs应该在rhs之下,因此将较大的元素放在堆顶。
自定义比较函数 这个Functional是可以自定义的,如果我们想把pair中second较小的放在堆顶,我们可以自定义比较函数如下:
1 2 3 bool operator () (const std::pair<int , int >& lhs, const std::pair<int , int >& rhs) { return lhs.second > rhs.second; }
如此一来,当rhs.second小于lhs.second时,该函数返回true,rhs优先级比lhs高。
题目描述 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 , n 是数组的大小。
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。
思路解析 这题总的思想分为两部分: 统计频率,输出前k高元素。
统计频率 这题一开始我想的是空间换时间,即把nums数组中的元素看成下标,然后遍历数组,统计频率。这种方法只需要一个数组结构即可,但是由于我nums数组范围可以很大,导致可能会很浪费空间,于是改用map的方式统计频率。
map的方式也很简单明了。用哈希表的形式完成映射即可。
1 2 3 4 unordered_map<int ,int > um; for (int n:nums){ um[n] ++; }
输出前k高元素 现在我们的任务是,如何选出map中前k高的值(只根据值不根据键来选),然后输出对应的键,我们希望这个过程的时间复杂度仅仅是n(这里的n代表map中的元素个数。
在这里我们抽象一下问题,不把它当作map来看,如果我们有一个int数组,我们想输出前k大的元素,如何在n的时间复杂度内完成这个任务呢?
如果用暴力算法,很显然需要kn的时间复杂度,从简单的情况出发,如果我们需要输出前2大的元素,我们只需要双指针即可,可双指针的方法也需要kn的时间复杂度。所以我们需要用空间换时间,考虑用一个数据结构来存储已经遍历过的有可能是最后解的结果,所以堆
就成了这个数据结构的最佳选择。
我们构造一个大顶堆,将所有元素都插入到堆中,然后进行k次pop操作即可。但是除此之外,有一种更高效的方法,那就是构造一个k个元素的小顶堆,我们只需要将堆顶元素设置为第k大的元素即可,换句话说,只要将遍历到的比当前堆顶元素大的值插入到当前堆中,然后pop掉原来堆顶的值即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for (const auto & p: um){ if (minHeap.size () < k){ minHeap.push (p); } else { if (minHeap.top ().second < p.second){ minHeap.pop (); minHeap.push (p); } } } while (!minHeap.empty ()){ ans.push_back (minHeap.top ().first); minHeap.pop (); }
这里minHeap的优先级不是pair的默认先比较键再比较值,而是基于值的最小堆,所以我们需要自定义函数来定义优先级。
1 2 3 4 5 6 class myComparison {public : bool operator () (const pair<int ,int >& p1, const pair<int ,int >& p2) { return p1.second > p2.second; } };
然后在主函数中如下声明:
1 priority_queue<pair<int ,int >, vector<pair<int ,int >>, myComparison> minHeap;
本题思路不难,但是需要对CPP的STL容器进行融合使用,对练习STL容器来说是一道很好的习题。
类似习题 合并K个排序链表 • 题目链接 : LeetCode 23. Merge k Sorted Lists
• 思路 : 使用小顶堆将所有链表的头节点加入堆中,然后每次取出堆顶元素,将其后续节点再加入堆中,直到所有节点都被处理完。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution {public : class nodeComparison { public : bool operator () (const ListNode* n1, const ListNode* n2) { return n1->val > n2->val; } }; ListNode* mergeKLists (vector<ListNode*>& lists) { ListNode* ans = new ListNode (0 ); ListNode* p = ans; priority_queue<ListNode*, vector<ListNode*>, nodeComparison> minHeap; for (int i = 0 ; i < lists.size (); i++){ if (lists[i]) minHeap.push (lists[i]); } while (!minHeap.empty ()){ if (minHeap.top ()->next){ minHeap.push (minHeap.top ()->next); } p->next = minHeap.top (); p = p->next; minHeap.pop (); } return ans->next; } };
合并区间 合并区间
• 题目链接 : LeetCode 56. Merge Intervals
• 思路 : 虽然这道题不直接使用优先队列,但它的思想和堆排序有相似之处。首先对区间进行排序,然后合并重叠的区间。
我的做法使用了优先队列,两个小顶堆的做法虽然改变了原来的区间对应关系,但是对结果没有影响。当然,想来最好的做法还是先排序再遍历去重。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 vector<vector<int >> merge (vector<vector<int >>& intervals) { vector<vector<int >> ans; priority_queue<int ,vector<int >, greater<int >> pq1; priority_queue<int , vector<int >, greater<int >> pq2; for (int i = 0 ; i < intervals.size (); i++){ pq1.push (intervals[i][0 ]); pq2.push (intervals[i][1 ]); } while (!pq1.empty ()){ int a = pq1.top (); pq1.pop (); while (!pq1.empty () && pq1.top () <= pq2.top ()){ pq1.pop (); pq2.pop (); } ans.push_back (vector<int >{a,pq2.top ()}); pq2.pop (); } return ans; }
最佳做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : vector<vector<int >> merge (vector<vector<int >>& intervals) { sort (intervals.begin (), intervals.end ()); vector<vector<int >> merged; for (auto interval : intervals) { if (merged.empty () || merged.back ()[1 ] < interval[0 ]) { merged.push_back (interval); } else { merged.back ()[1 ] = max (merged.back ()[1 ], interval[1 ]); } } return merged; } };