一、合并 K 个升序链表
题目描述
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
思路分析
先思考如果两个升序链表该如何排序成一个链表?
可以用双指针把两个链表归并排序。
多个链表的话可以循环两两合并,最终合并成一个链表。但是这样效率太低。
可以使用优先级队列合并
维护当前每个链表没有被合并的元素的最前面一个放入小堆中,自定义比较排序,每次取出堆顶的链表,把该链表的头节点链接到排序链表中,然后把下一个节点再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 小的元素
题目描述
给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。
你必须找到一个内存复杂度优于 O(n2) 的解决方案。
示例 1:
输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13
示例 2:
输入:matrix = [[-5]], k = 1
输出:-5
思路分析
这道题其实根上面一道题一样,只不过把链表变成了数组
为了让数组模拟链表的形式来实现多路归并,我们可以定义一个结构体。
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):
那么怎么统计数量呢?
从左下角开始,i为行,j为列
如果matrix[i][j]≤target
,说明该列都要被统计,num += i + 1;然后j++进入下一列。
如果matrix[i][j]>target
,就向上移动。
代码如下:
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 对数字
题目描述
给定两个以 升序排列 的整数数组 nums1 和 nums2 , 以及一个整数 k 。
定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。
请找到和最小的 k 个数对 (u1,v1), (u2,v2) … (uk,vk) 。
示例 1:
输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
输出: [1,2],[1,4],[1,6]
解释: 返回序列中的前 3 对数:
[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]
示例 2:
输入: nums1 = [1,1,2], nums2 = [1,2,3], k = 2
输出: [1,1],[1,1]
解释: 返回序列中的前 2 对数:
[1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]
示例 3:
输入: nums1 = [1,2], nums2 = [3], k = 3
输出: [1,3],[2,3]
解释: 也可能序列中所有的数对都被返回:[1,3],[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;
}
};