1 排序
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
1.1冒泡排序
冒泡排序就是重复地走访过要排序的元素列,依次比较两个相邻的元素。
第一个循环是冒泡次数,为数组大小-1,第二个循环是比较相邻元素,只需要比较数组大小-1-i次
代码:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
if (n == 0 or n == 1) return nums;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n - i - 1; ++j) {
if (nums[j] > nums[j + 1]){
int temp = nums[j + 1];
nums[j + 1] = nums[j];
nums[j] = temp;
}
}
}
return nums;
}
冒泡排序改进:
在每轮循环前设置一个flag表明本轮是否发生了元素交互,若一轮循环下来,都没有发生元素交互,则说明已经排序完成,直接break
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
if (n < 2) return nums;
for (int i = 0; i < n; ++i) {
bool flag = false;
for (int j = 0; j < n - i - 1; ++j) {
if (nums[j] > nums[j + 1]) {
swap(nums[j], nums[j + 1]);
flag = true;
}
}
if (!flag) break;//说明这轮循环内,没有发生元素交互,说明已经排好序了
}
return nums;
}
正反冒泡
void Bubble_sort(int a[],int size)
{
int low = 0;
int high = size-1;
while(high > low)
{
for (int i = low; i != high; ++i)//正向冒泡,确定最大值
{
if (a[i] > a[i+1])
{
int temp = a[i];
a[i] = a[i+1];
a[i+1] = temp;
}
}
--high;
for (int j = high; j != low; --j)//反向冒泡,确定最小值
{
if (a[j] < a[j-1])
{
int temp = a[j];
a[j] = a[j-1];
a[j-1] = temp;
}
}
++low;
}
}
1.2 快速排序
[注]:rand()%30将随机数限制在[0,30)
随机初始设定数组中的任意值,以他为标准,小于他的放在他左边,大于他的放他右边(能够确定这个值的位置)。在反复
eg : nums = [6,4,5,3,2]
设定l为这段数组的首位置,r为这段数组的尾位置。随机选择一个值cur(rand()%(r-l+1)+l
),作为标准
在 l < r的情况下进入循环,
- 1.若nums[r] >=cur,则 r–,循环结束再令nums[l] = nums[r]。即:若从数组末端开始若有小于cur的数,就将他移到前面,若大于cur,就先r–,直至出现<=cur的数或者l>=r。
- 2.同理接着若nums[l] <=cur,则l++,循环结束再令nums[r] = nums[l]。即:若从数组首端开始若有大于cur的数,就将他移到后面,若<=cur,就先l++,直至出现>=cur的数或者l>=r。
- l >= r后,再令nums[l] = cur,并返回l。
-令mid = 返回的l, 然后再以(l,mid-1)和(mid+1,r)重复1,2
vector<int> sortArray(vector<int>& nums) {
quickSort(nums, 0, nums.size() - 1);
return nums;
}
void quickSort(vector<int>& nums, int l, int r) {
if (l >= r) return;
int mid = dfs(nums, l, r);
quickSort(nums, l, mid - 1);
quickSort(nums, mid + 1, r);
}
int dfs(vector<int>& nums, int l, int r) {
int i = rand()%(r - l + 1) +l;
swap(nums[i],nums[l]);
int cur = nums[l];
while (l < r) {
while (l < r and nums[r] >= cur) r--;
nums[l] = nums[r];
while (l < r and nums[l] <= cur) l++;
nums[r] = nums[l];
}
nums[l] = cur;
return l;
}
1.3归并排序
稳定排序,平均 O(nlogn),最好 O(nlogn), 最差 O(nlogn),辅助空间 O(n)
归并排序的主要思想是分治法。主要过程是:
- 将n个元素从中间切开,分成两部分。(左边可能比右边多1个数)
- 将步骤1分成的两部分,再分别进行递归分解。直到所有部分的元素个数都为1。
- 从最底层开始逐步合并两个排好序的数列。
思路:
- 确定数组的大小,以及输入数组中的元素值;将输入的数组进行分组归并;
- 将整个数组分成左右两个数组,左右两个数组再向下分,直至子数组的元素少于2个时(first >= last,),子数组将停止分割;
**[注]: **找到其中中点,不断划分成mergeSort(nums, first, mid, temp)
;mergeSort(nums, mid + 1, last, temp);
- 当左右子数组不能再分割,也是都是一个元素时,比较他们的大小,进行排序合并;
- (针对nums)让
l(左序列指针)=first
和r(右序列指针)=mid+1
,再设置一个temp数组的指针 i=0; - 在l<=mid且r<=last的条件下 比较左右序列元素的大小,小的那个等于nums[i++] ,然后同时l++或r++
- 若左右序列还有剩(l<=mid或r<=last),在将左边或右边的剩余元素填充进temp中
- 最后将temp里面的元素拷贝给nums
- 再排序合并上一级子数组为两个元素的数组,接着再排序合并上一级子数组为四个元素的数组;直至到排序合并刚开始的两个子数组,最后成为排好序的数组;
class Solution {
void MergeSort(vector<int>& nums, int first, int last, vector<int>& temp) {
if (first >= last) return;
int mid = (first + last) / 2;
MergeSort(nums, first, mid, temp);
MergeSort(nums, mid + 1, last, temp);
int i = 0, l = first, r = mid + 1;
while (l <= mid and r <= last) {
if (nums[l] >= nums[r]) temp[i++] = nums[r++];
else {
temp[i++] = nums[l++];
}
}
while (l <= mid) temp[i++] = nums[l++];
while (r <= last) temp[i++] = nums[r++];
i = 0;
for (int j = first; j <= last; ++j) {
nums[j] = temp[i++];
}
}
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
if (n < 2) return nums;
vector<int> temp(n, 0);
MergeSort(nums, 0, n - 1, temp);
return nums;
}
};
1.4 堆排序
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
- 先构造一个大顶堆:
- 从数组长度len的len/2-1位置开始调整位置,一直到0
for (int i = len / 2 - 1; i >= 0; --i )
- 调整:将位置k为根的子树进行调整,先保存最开始需要比较的结点的值nums[k],它的左孩子位置为
2*k+1
,看它的右孩子是否存在并且比较与左孩子的关系,拿大的值和nums[k]进行比较。若nums[k]大,则说明不用交换,直接break。若它的孩子大,则交换值,并令k=i,继续循环.最后再令nums[k]=temp;
- 然后交换堆顶元素(最大值)和堆底元素,再根据新的堆顶重新调整堆
class Solution {
void Bulid(vector<int>& nums, int len) { //构造一个大顶堆
for (int i = len / 2 - 1; i >= 0; --i ) {
Adjust(nums, i, len);
}
}
void Adjust(vector<int>& nums, int k, int len) {
int temp = nums[k];
for (int i = 2 * k + 1; i < len; i = 2 * i + 1) {
if (i < len - 1 and nums[i] < nums[i + 1]) ++i;
if (temp > nums[i]) break;
else {
nums[k] = nums[i];
k = i;
}
}
nums[k] = temp;
}
void HeapSort(vector<int>& nums, int len) { //堆排序函数
Bulid(nums, len);
for (int i = len - 1; i >= 0; --i) {
swap(nums[i], nums[0]);
Adjust(nums, 0, i);
}
}
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
if (n < 2) return nums;
HeapSort(nums, n);
return nums;
}
};
1.5 插入排序
稳定排序,平均 O(n2),最好 O(n), 最差 O(n2),辅助空间 O(1)
将一个数插入一个已经排好序的数据中:
直接插入排序基本思想是每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果被扫描的元素(已排序)大于新元素,将该元素后移一位
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5
// 稳定排序,平均 O(n**2),最好 O(n), 最差 O(n**2),辅助空间 O(1)
void InsertSort(vector<int> &nums)
{
int n = nums.size();
if (n==0) return;
// 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
for (int i=1;i<n;i++)
{
// 记录要插入的数据
int temp = nums[i];
int j = i- 1;
//与已排序的数逐一比较,大于temp时,该数移后
while (j>=0) && (temp < nums[j])
{
nums[j+1] = nums[j];
j--;
}
nums[j+1] = temp;
}
}
2 最小K个数
- 思路一: 直接排序
- 思路二:用大顶堆(从大到小)
priority_queue<int> q
当q的size小于K,就填充进去,否则比较top和arr[i]的大小,若top大,则pop,再将arr[i]放进去,始终保持里面是最小的K个数。
vector<int> GetLeastNumbers_Solution(vector<int>& input, int k) {
if (input.size() == 0 or k == 0) return {};
priority_queue<int> q;
for (auto c : input) {
if (q.size() < k) q.emplace(c);
else {
if (q.top() > c) {
q.pop();
q.emplace(c);
}
}
}
vector<int> res;
while (!q.empty()) {
res.emplace_back(q.top());
q.pop();
}
return res;
}
- 思路三:快排优化:
第一次快排后,会返回哨兵的位置l,若l >k,则对左边序列继续快排,若l<k则对右边序列继续快排,l=k时,新建一个数组并v.assign(arr.begin(), arr.begin() + k);
vector<int> GetLeastNumbers_Solution(vector<int>& nums, int k) {
quickSort(nums, 0, nums.size() - 1, k);
vector<int> res(nums.begin(), nums.begin() + k);
return res;
}
void quickSort(vector<int>& nums, int l, int r, int k) {
if (l >= r) return;
int first = l;
int last = r;
int i = rand() % (r - l + 1) + l;
swap(nums[i], nums[l]);
int cur = nums[l];
while (l < r) {
while (l < r and nums[r] >= cur) r--;
nums[l] = nums[r];
while (l < r and nums[l] <= cur) l++;
nums[r] = nums[l];
}
nums[l] = cur;
if (l > k) quickSort(nums, first, l - 1, k);
if (l < k) quickSort(nums, l + 1, last, k);
return;
}
附:手撕大小顶堆??
3 数组中的第K个最大元素
- 思路一: 直接排序
- 思路二:用小顶堆(从小到大)
priority_queue<int, vector<int>, greater<int>>q;
当q的size小于K,就填充进去,否则比较top和arr[i]的大小,若top小,则pop,再将arr[i]放进去,始终保持堆顶是第K个最大数。
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> q;
for (auto c : nums) {
q.emplace(c);
if (q.size() > k) q.pop();
}
return q.top();
}
- 思路三:快排优化
排序好后,数组中的第K个最大元素位置是index = nums.size() - k
第一次快排后,会返回哨兵的位置l,若l >index,则对左边序列继续快排,若l<index则对右边序列继续快排,l=k时 返回nums[l]
int findKthLargest(vector<int>& nums, int k) {
return quickSort(nums, 0, nums.size() - 1, k);
}
int quickSort(vector<int> nums, int l, int r, int k) {
if (l >= r) return nums[l];
int index = nums.size() - k;
int first = l;
int last = r;
int i = rand() % (r - l + 1) + l;
swap(nums[i], nums[l]);
int cur = nums[l];
while (l < r) {
while (l < r and nums[r] >= cur) r--;
nums[l] = nums[r];
while (l < r and nums[l] <= cur) l++;
nums[r] = nums[l];
}
nums[l] = cur;
if (l < index) return quickSort(nums, l + 1, last, k);
if (l > index) return quickSort(nums, first, l - 1, k);
return nums[l];
}
4 合并区间
区间:Interval a
= [A,B] a.start = A
, a.end = B
思路1:双指针 + 排序
- 首先排序,将二维数组按首个数字进行排序
sort(nums.begin(), nums.end());
,第i区间的起始值≤ 第i-1区间的起始值 - 设置两个指针u=0,v=1,排序后所有
nums[u][0] <= nums[v][0]
- 排序后,有三种情况:
- [1,2], [3,4]
nums[u][1] < nums[v][0]
即:u区间与v区间没有交集,直接将u添加进res,并令u=v,v++ - [1,4],[2,3] 或[1,5],[2,5]
nums[u][1] > nums[v][1]
即:u区间完全包含v区间,直接v++ - [1,5] ,[2, 6]
nums[u][1] < nums[v][1]
即u区间与v区间有交集,令nums[u][1] = nums[v][1];
再v++;
- 最后v = nums.size()时退出循环,还需要将此时的nums[u]放进res;
【注】:第三种情况应该最后讨论,(只要在第一种情况不满足时,才考虑他,不然会导致只有一个区间,因为第三种情况实际的条件是nums[u][1] < nums[v][1]
andnums[u][1] < nums[v][0]
)
vector<vector<int>> merge(vector<vector<int>>& nums) {
if (nums.size() == 0 or nums.size() == 1) return nums;
int u = 0;
int v = 1;
vector<vector<int>> res;
sort(nums.begin(), nums.end());
while (v < nums.size()) {
if (nums[u][1] < nums[v][0]) { //前者最大小于后者最小
res.emplace_back(nums[u]);
u = v;
}
else if (nums[u][1] <= nums[v][1]) {
nums[u][1] = nums[v][1];
}
v++;
}
res.emplace_back(nums[u]);
return res;
区间版:
vector<Interval> merge(vector<Interval> &intervals) {
if (intervals.size() == 0 or intervals.size() == 1) return intervals;
sort(intervals.begin(),intervals.end(),cmp);//排序
vector<Interval>ans;//返回的结果数组
int p1 = 0, p2 = 1;
while (p2 < intervals.size()) {
if (intervals[p1].end < intervals[p2].start) {
ans.emplace_back(intervals[p1]);
p1 = p2;
}
else if (intervals[p1].end < intervals[p2].end) intervals[p1].end = intervals[p2].end;
p2++;
}
ans.emplace_back(intervals[p1]);
return ans;
}
static bool cmp(Interval &a, Interval &b) {
if (a.start == b.start) return a.end < b.end;
return a.start < b.start;
}
5 最大数
定义一个新的比较函数。必须是static bool型的,因为std::sort是属于全局的,无法调用非静态成员函数;或者在该类的上方定义
static静态成员函数不用加对象名,就能直接访问函数
若排序后str第一个是‘0’
则返回“0”
string largestNumber(vector<int>& nums) {
if (nums.size() == 1) return to_string(nums[0]);
sort(nums.begin(), nums.end(), cmp);
string str;
for (auto c : nums) str += to_string(c);
if(str[0] == '0') return "0";
return str;
}
static bool cmp(const int& a, const int& b) {
return to_string(a) + to_string(b) > to_string(b) + to_string(a);
}
6 单链表排序
思路一:归并+切
- 快慢指针找中点,这里需要设置一个pre,在只需要切一次的时候不会用到,但是在这里,若链表长度为2,没有pre会导致栈溢出。返回slow。
- 合并两个有序链表
- 归并,注意归并时若链表长度为1,即head->next = nullprt,则返回head
ListNode* sortList(ListNode* head) {
if (!head or !head->next) return head;
return mergeSort(head);
}
ListNode* cut(ListNode* head) {
ListNode* pre = nullptr;
ListNode* fast = head;
ListNode* slow = head;
while (fast and fast->next) {
pre = slow;
slow = slow->next;
fast = fast->next->next;
}
pre->next = nullptr;
return slow;
}
ListNode* merge(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(0);
ListNode* p = dummy;
while(l1 and l2) {
if (l1->val > l2->val) {
p->next = l2;
l2 = l2->next;
}
else {
p->next = l1;
l1 = l1->next;
}
p = p->next;
}
p->next = l1 ? l1 : l2;
return dummy->next;
}
ListNode* mergeSort(ListNode* head) {
if (!head->next) return head;
ListNode* mid = cut(head);
ListNode* l1 = mergeSort(head);
ListNode* l2 = mergeSort(mid);
return merge(l1, l2);
}
7 奇偶链表
奇数节点串联 oddnode->next = oddnode->next->next;
偶数节点串联 evennode->next = evennode->next->next;
ListNode* oddEvenList(ListNode* head) {
if (!head or !head->next) return head;
ListNode* evenhead = head->next;
ListNode* odd = head;
ListNode* even =evenhead;
while (even and even->next) {
odd->next = odd->next->next;
even->next = even->next->next;
odd = odd->next;
even = even->next;
}
odd->next = evenhead;
return head;
}
8 数据流中的中位数
思路:
- 中位数< 它右边的数,大于它左边的数,因此根据左半边数的最大值及右半边数的最小值即可得到中位数。因此构建一个大顶堆放左边的数,小顶堆放右边的数。
- 若输入的数据个数是奇数,比如 1、2、3、4、5。我们可以把左边的 1、2 存入一个大顶堆中,把右边的 3、4、5 存入一个小顶堆中。那么中位数就是小顶堆的 top()。
- 若输入的数据个数是偶数,比如 1、2、3、4。我们可以把左边的 1、2 存入一个大顶堆中,把右边的 3、4 存入一个小顶堆中。那么中位数就是两个堆的 top() 的和再乘 0.5。
- 整个过程我们需要维护两个地方:两个堆的 size() 最大只能相差 1;大顶堆的 top() 必须小于等于小顶堆的 top()。
实现:保持了两个堆的size最大只相差1(要么相等,要么小顶堆size大1)
- 若大顶堆的size = 小顶堆的size,数据先添加进大顶堆,再将大顶堆的top到小顶堆。
- 否则数据先进入小顶堆,数据先添加进小顶堆,再将小顶堆的top到大顶堆。
- 输出的时候,只需要观察两个堆size,若相同,则两个堆顶求和 * 0.5(不能/2因为/以后强制化整),若不同返回小顶堆top()
class MedianFinder {
priority_queue<int> maxheap;
priority_queue<int, vector<int>, greater<int>> minheap;
public:
/** initialize your data structure here. */
MedianFinder() {
}
void addNum(int num) {
if (maxheap.size() == minheap.size()) {
maxheap.emplace(num);
minheap.emplace(maxheap.top());
maxheap.pop();
}
else {
minheap.emplace(num);
maxheap.emplace(minheap.top());
minheap.pop();
}
}
double findMedian() {
return maxheap.size() == minheap.size() ? (maxheap.top() + minheap.top()) * 0.5 : minheap.top();
}
};
9 前 K 个高频元素
TOPK问题,想到priority_queue,前K大,用小顶堆。
哈希表记录元素和出现的频率
因为比较的是频率,输出的是对应的元素,因此需要将他们作为一对pair一起放入堆里,且必须得是q.emplace(make_pair(it.second, it.first));
频率在前,元素在后,(因为比较的是频率)
对于里面元素是pair<int,int>的堆,创建时应该
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> ma;
vector<int> res;
for (auto& c : nums) ma[c]++;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
for (auto& it : ma) {
if (q.size() < k) q.emplace(make_pair(it.second, it.first));
else {
if (it.second > q.top().first) {
q.pop();
q.emplace(make_pair(it.second, it.first));
}
}
}
while (!q.empty()) {
res.emplace_back(q.top().second);
q.pop();
}
return res;
}
10 和为 K 的子数组
前缀和:
eg:
10.1 返回个数
题目就可以由:有多少个[i,j]使得从第 i 到 j 项的子数组和等于 k。
转变为有几种 i、j 的组合,满足 prefixSum[j] - prefixSum[i - 1] == k
找出前缀和之差=k的次数
-
unordered_map<int, int> ma;key表示前缀和,value表示出现的次数
-
因为i-1是在j之前的,所以去找prefixsum[j]-k 在哈希表里出现的次数
-
ma[0] = 1,表示最开始时,前缀和为0的情况出现了一次
需要初始化的原因是,为了防止漏掉prefixsum[j]恰好等于k的情况
例如[1,1,1] k = 2
i = 0, prefixsum= 1; hash={1,1}
i = 1, prefixsum= 2; hash={1,1}{2,1} 此时出现了prefixsum =k的情况,但是此时prefixsum - k = 0,在哈希表里没有出现,所以漏掉了
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> ma;
ma[0] = 1;
int prefixSum = 0;
int res = 0;
for (auto c : nums) {
prefixSum += c;
if (ma.count(prefixSum - k)) res += ma[prefixSum - k];
ma[prefixSum]++;
}
return res;
}
10.2 返回最长长度
要返回最长长度,需要记录下前缀和和位置
- unordered_map<int, int> ma;key表示前缀和,value表示位置i
if (ma.count(prefixSum - k))
,此时更新最大长度max(i - ma[prefixSum - k], maxL);
- 同时为避免后面也出现相同的prefixSum,因此若哈希表中没有prefixSum时,才将它放入哈希表
eg[1,-1,1,2,3] i=0和i=3时,perfixsum均为1,若不限制的添加perfixsum,会使最大长度减小。 - ma[0] = -1。表示前缀和为0的位置为-1。
int maxlenEqualK(vector<int>& arr, int k) {
// write code here
int prefixSum = 0;
int maxL = 0;
unordered_map<int, int> ma;
ma[0] = -1;
for (int i = 0; i < arr.size(); ++i) {
prefixSum += arr[i];
if (ma.count(prefixSum - k)) {
maxL =max(i - ma[prefixSum - k], maxL);
}
if (!ma.count(prefixSum))ma[prefixSum] = i;
}
return maxL;
}
11 前K个高频单词(字符串出现次数的TopK问题)
本来想用和上面一样的方法,但是由于要求当不同的字符串有相同出现频率,按字典序排序。行不通
,因为当q.size()<k时,他是直接填充,排序也是根据int来的,无法在频率相同时,根据字符来排序[[“2”,“174”],[“9”,“166”],[“6”,“148”],[“1”,“148”]]
所以想到用仿函数+小顶堆
小堆序实现从大到小排序,大堆序实现从小到大排序
频率从大到小,字符从小到大
因为频率相同时,需要比较字符,所以所有的都需要先进队列,size>k时再弹出
因为小顶堆堆顶是最小的,所以可以用res[i] = q.top().first; i--
struct cmp {
bool operator() (pair<string, int> a, pair<string, int> b) {
return a.second== b.second ? a.first < b.first ? a.second > b.second;
}
};
例如ma={‘a’,1}{‘c’,2}{‘b’, 2}
第一次比较 cab, 第二次比较cba,第三次比较bca
返回出现次数前k名的字符串
class Solution {
struct cmp {
bool operator() (pair<string, int> a, pair<string, int> b){
return a.second == b.second ? a.first < b.first : a.second > b.second;
}
};
public:
vector<string> topKFrequent(vector<string>& words, int k) {
unordered_map<string, int> ma;
for (auto& c : words) ma[c]++;
priority_queue<pair<string, int>, vector<pair<string, int>>, cmp> q;
for (auto& it : ma) {
q.emplace(it);
if (q.size() > k) q.pop();
}
vector<string> res(k);
for (int i = k - 1; i >= 0; --i) {
res[i] = q.top().first;
q.pop();
}
return res;
}
};
返回出现次数前k名的字符串和对应的次数。
class Solution {
struct cmp {
bool operator() (pair<string, int> a, pair<string, int> b) {
return a.second == b.second ? a.first < b.first : a.second > b.second;
}
};
public:
/**
* return topK string
* @param strings string字符串vector strings
* @param k int整型 the k
* @return string字符串vector<vector<>>
*/
vector<vector<string> > topKstrings(vector<string>& strings, int k) {
// write code here
unordered_map<string, int> ma;
for (auto& c : strings) ma[c]++;
priority_queue<pair<string, int>, vector<pair<string, int>>, cmp> q;
for (auto& it : ma) {
q.emplace(it);
if (q.size() > k) q.pop();
}
vector<vector<string> > res(k, vector<string>(2));
for (int i = k - 1; i >= 0; --i) {
res[i] = {q.top().first, to_string(q.top().second)};
q.pop();
}
return res;
}
};