文章目录
一、合并 K 个升序链表
题目链接
题目描述
示例 1:
示例 2:
示例 3:
思路分析
先思考如果两个升序链表该如何排序成一个链表?
可以用双指针把两个链表归并排序。
多个链表的话可以循环两两合并,最终合并成一个链表。但是这样效率太低。
可以使用优先级队列合并
维护当前每个链表没有被合并的元素的最前面一个放入小堆中,自定义比较排序,每次取出堆顶的链表,把该链表的头节点链接到排序链表中,然后把下一个节点再push进优先级队列中,如果是空节点就跳过。
priority_queue自定义比较函数
1️⃣ 使用仿函数
struct cmp
{
bool operator()(ListNode* h1, ListNode* h2)
{
return h1->val > h2->val;
}
};
priority_queue<ListNode*, vector<ListNode*>, cmp> q;
2️⃣ 使用函数指针
static bool cmp(ListNode* h1, ListNode* h2)
{
return h1->val > h2->val;
}
priority_queue<ListNode*, vector<ListNode*>, decltype(&cmp)> q(&cmp);
这里要注意如果是在类里面定义优先级队列要定义在函数体内。
3️⃣ 使用lambda表达式
auto cmp = [](const ListNode* h1, const ListNode* h2){
return h1->val > h2->val;
};
priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> q(cmp);
代码如下:
class Solution {
public:
struct cmp
{
bool operator()(ListNode* h1, ListNode* h2)
{
return h1->val > h2->val;
}
};
priority_queue<ListNode*, vector<ListNode*>, cmp> q;
ListNode* mergeKLists(vector<ListNode*>& lists) {
int n = lists.size();
for(int i = 0; i < n; i++)
{
if(lists[i])
q.push(lists[i]);
}
ListNode* head = new ListNode;
ListNode* tail = head;
while(q.size())
{
tail->next = q.top();
ListNode* cur = nullptr;
if(q.top())
{
cur = q.top()->next;
tail = tail->next;
}
q.pop();
if(cur) q.push(cur);
}
return head->next;
}
};
二、有序矩阵中第 K 小的元素
题目链接
题目描述
示例 1:
示例 2:
思路分析
这道题其实根上面一道题一样,只不过把链表变成了数组
为了让数组模拟链表的形式来实现多路归并,我们可以定义一个结构体。
struct op
{
int _val;
int _row;
int _col;
op(int val, int row, int col)
: _val(val)
, _row(row)
, _col(col)
{}
};
优先级队列中维护的是结构体,结构体包含下标和数值,这样就可以做到模拟链表形式。
代码如下:
class Solution {
public:
struct op
{
int _val;
int _row;
int _col;
op(int val, int row, int col)
: _val(val)
, _row(row)
, _col(col)
{}
};
struct cmp
{
bool operator()(const op& p1, const op& p2)
{
return p1._val > p2._val;
}
};
priority_queue<op, vector<op>, cmp> q;
int kthSmallest(vector<vector<int>>& matrix, int k) {
int n = matrix.size();
for(int i = 0; i < n; i++)
{
q.push({matrix[i][0], i, 0});
}
for(int i = 0; i < k - 1; i++)
{
auto op = q.top();
q.pop();
if(op._col < n - 1)
{
q.push({matrix[op._row][op._col + 1], op._row, op._col + 1});
}
}
return q.top()._val;
}
};
拓展思路:二分法
观察矩阵找到规律:
矩阵任一点的数值一定比左边和上边的围成的矩阵的值要大。
根据这个性质,假如我们找到了一个值,让矩阵的左上角的值 <= 该值,然后统计左上角有多少个值,如果小于k,说明这个值取小了,如果大于等于k,说明答案不大于该值。
例如如图(取8):
那么怎么统计数量呢?
代码如下:
class Solution {
public:
bool search(vector<vector<int>>& vv, int n, int target, int k)
{
int num = 0;
int i = n - 1, j = 0;
while(i >= 0 && j < n)
{
if(vv[i][j] <= target)
{
num += i + 1;
j++;
}
else
{
i--;
}
}
return num >= k;
}
int kthSmallest(vector<vector<int>>& matrix, int k) {
int n = matrix.size();
int l = matrix[0][0], r = matrix[n - 1][n - 1];
while(l < r)
{
int mid = l + (r - l) / 2;
if(search(matrix, n, mid, k))
{
r = mid;
}
else
{
l = mid + 1;
}
}
return l;
}
};
这里要注意二分的是值域。
三、查找和最小的 K 对数字
题目链接
题目描述
示例 1:
示例 2:
示例 3:
思路分析
优先级队列维护两个下标,i表示nums1中的下标,j表示nums2中的下标。
正常思路是假设枚举到(ai, bi),那么接下来就把(ai+1, bi), (ai, bi+1)push进优先级队列,但这样会出现重复的情况,所以可以先把nums1的每个元素和nums2的首元素组成的二元组push进去,这样每次只用让nums2的下标变化就行。
小优化:始终确保 nums1 为两数组中长度较少的那个,然后通过标识位来记录是否发生过交换,确保答案的点顺序的正确性。
举个例子:
首次取出的二元组为 (0,0),即点对 (nums1[0],nums2[0]),取完后将序列的下一位点对 (nums1[0],nums2[1])以二元组 (0,1)(0, 1)(0,1) 形式放入优先队列。
代码如下:
class Solution {
public:
typedef pair<int, int> PII;
vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {
vector<vector<int>> vv;
int n = nums1.size(), m = nums2.size();
bool flag = true;
if(n > m)
{
swap(n, m);
swap(nums1, nums2);
flag = false;
}
auto cmp = [&](const auto& e1, const auto& e2){
return nums1[e1.first] + nums2[e1.second] > nums1[e2.first] + nums2[e2.second];
};
priority_queue<PII, vector<PII>, decltype(&cmp)> q(cmp);
for(int i = 0; i < min(n, k); i++)
{
q.push({i, 0});
}
while(vv.size() < k && q.size())
{
auto [a, b] = q.top();
q.pop();
flag ? vv.push_back({nums1[a], nums2[b]}) : vv.push_back({nums2[b], nums1[a]});
if(b < m - 1) q.push({a, b + 1});
}
return vv;
}
};