有序矩阵中的第 k 个最小数组和
给你一个 m * n 的矩阵 mat,以及一个整数 k ,矩阵中的每一行都以非递减的顺序排列
你可以从每一行中选出1个元素形成一个数组,返回所有可能数组中的第k个最小数组和
1. 暴力
逐行遍历
贪心记录加上当前行的值后,前k个最小数组和
class Solution {
public:
int kthSmallest(vector<vector<int>> &mat, int k) {
vector<int> pre = {0};//便于统一操作,初始为0
for (auto &row: mat) {//遍历每一行
vector<int> cur;//记录所有数组和
for (int x: pre) //将之前的数组和
for (int y: row)//加上当前行的值
cur.push_back(x + y);
sort(cur.begin(), cur.end());//排序
if (cur.size() > k) // 保留最小的 k 个
cur.resize(k);
pre = cur;
}
return pre.back();
}
};
2. 小根堆(算法一优化)
利用小根堆优化了二维组合的贪心寻找
而不需要全部暴力列举再排序
class Solution {
vector<int> kSmallestPairs(vector<int> &nums1, vector<int> &nums2, int k) {
vector<int> ans;
priority_queue<tuple<int, int, int>> pq;
int n = nums1.size(), m = nums2.size();
pq.emplace(-nums1[0] - nums2[0], 0, 0); // 取相反数变成小顶堆
while (!pq.empty() && ans.size() < k) {//利用小根堆只存k个数,不用去暴力再排序,相当于广度优先搜索,但先搜索小的
auto [_, i, j] = pq.top(); pq.pop();//两个数组对应下标
ans.push_back(nums1[i] + nums2[j]); // 数对和,放入结果,该结果最小
//关键语句,贪心思想,实际上类似在横向单调增,竖向单调增的二维数组上贪心移动指针
if (j == 0 && i + 1 < n) //pre处于第一个数时
pq.emplace(-nums1[i + 1] - nums2[0], i + 1, 0);//移动row 的 i下标,竖向扩张(不会横向拓展后再竖向,因为每行第一个元素一定更小)
if (j + 1 < m)//无条件移动
pq.emplace(-nums1[i] - nums2[j + 1], i, j + 1);//移动pre 的 j下标,横向扩张
}
return ans;
}
public:
int kthSmallest(vector<vector<int>> &mat, int k) {
vector<int> pre = {0};
for (auto &row: mat)
pre = kSmallestPairs(row, pre, k);
return pre.back();
}
};
这样写扩张方式也可以
实际上利用组合递增的性质,就是限制一端的搜索,从而减少时间复杂度
当然也可以事先将一端全部入堆,然后再横向(纵向)扩展即可
if (i + 1 < n)//无条件移动
pq.emplace(-nums1[i + 1] - nums2[j], i + 1, j);//移动row 的 i下标
if (i == 0 &&j + 1 < m)
pq.emplace(-nums1[0] - nums2[j + 1], 0, j + 1);//移动pre 的 j下标