其他类似题目:
373. 查找和最小的 K 对数字378. 有序矩阵中第 K 小的元素719. 找出第 K 小的数对距离786. 第 K 个最小的素数分数
2040. 两个有序数组的第 K 小乘积
2386. 找出数组的第 K 大和
215. 数组中的第K个最大元素
不纠结直接sort排序解决。
class Solution {
public:int findKthLargest(vector<int>& nums, int k) {int n=nums.size();sort(nums.begin(), nums.end());//从小到大排列return nums[n-k];}
};
//sort(nums,nums+n);//sort(s, s + 3);的形式应该只能用在数组,不能用在vector
719. 找出第 K 小的数对距离
思路:
一开始想的是直接便利,求差值,然后找到第K个。
代码:但超出时间限制 时间复杂度
class Solution {
public:int smallestDistancePair(vector<int>& nums, int k) {vector<int> distance;int dis;for(int i=0;i<nums.size();i++){for(int j=i+1;j<nums.size();j++){dis=abs(nums[i]-nums[j]);distance.push_back(dis);}}sort(distance.begin(),distance.end());return distance[k-1];}
};
超出时间限制 16 / 19 个通过的测试用例
改进一下:
方法一:双指针+二分法
这个方法是将距离划分成一个有序序列:距离在(0,max(nums)-min(nums))之间。
这样我们找找出第 K 小的数对距离---》转换为找 距离 序列 里面第k小的元素。--》思考二分法
不同的是,我们这个距离序列需要动态计算,而不是事先求出来的。
为了将计算量减少,我们不防直接计算元素个数与k(个数)相比;而不是原先的 k值与 中间位置的数相比较。
元素个数计算方法:
class Solution {
public:int countPairs(const vector<int>& nums, int mid) {int count = 0;int n = nums.size();int j = 0;for (int i = 0; i < n; ++i) {while (j < n && nums[j] - nums[i] <= mid) {++j;}count += j - i - 1;}return count;}int smallestDistancePair(vector<int>& nums, int k) {sort(nums.begin(), nums.end());int n = nums.size(), left = 0, right = nums.back() - nums.front();while (left <= right) {int mid = left + (right - left) / 2;int cnt = countPairs(nums, mid);if (cnt >= k)right = mid - 1;elseleft = mid + 1;}return left;}
};
378. 有序矩阵中第 K 小的元素
方法一:暴力法
将二维数组存储到一维数组中,然后排序。
class Solution {
public:int kthSmallest(vector<vector<int>>& matrix, int k) {int n=matrix.size();vector<int> ans;for(int i=0;i<n;i++){for(int j=0;j<n;j++)ans.push_back(matrix[i][j]);}sort(ans.begin(),ans.end());return ans[k-1];}
};
时间复杂度:空间复杂度:
方法二:归并排序--》堆
由题目给出的性质可知,这个矩阵的每一行均为一个有序数组。问题即转化为从这 n 个有序数组中找第 k大的数,可以想到利用归并排序的做法,归并到第 k个数即可停止。
大体思路:
- 首先,定义了一个结构体
Element
,用于表示矩阵中的元素及其在矩阵中的位置。- 使用一个最小堆(由
priority_queue
实现),其中元素按照它们的值(val
)从小到大排序。这个最小堆用于存储和排序所有可能成为第k小元素的候选。- 初始化时,将矩阵的第一列所有元素加入到最小堆中。这是因为矩阵是按行和按列都排序的,所以每行的第一个元素是该行的最小值,有可能是全局第k小的值。
- 接下来,重复执行k-1次从最小堆中取出元素的操作。每次取出堆顶元素后,检查这个元素是否有右侧的相邻元素(即检查是否到达了其所在行的末尾),如果有,则将这个右侧的相邻元素加入到最小堆中。这保证了堆中始终保存着所有可能是第k小元素的候选。
- 经过k-1次取出操作后,最小堆的堆顶元素就是第k小的元素,返回其值。
class Solution {
public:int kthSmallest(vector<vector<int>>& matrix, int k) {// 定义一个结构体,用来表示矩阵中的元素及其位置struct Element {int val; // 元素的值int x, y; // 元素在矩阵中的位置(行x,列y)Element(int val, int x, int y) : val(val), x(x), y(y) {}};// 优先队列的比较函数,用于构建最小堆auto cmp = [](const Element& a, const Element& b) {return a.val > b.val;};// 定义一个最小堆,用于存储Element结构体,其中元素按照val值从小到大排序priority_queue<Element, vector<Element>, decltype(cmp)> minHeap(cmp);int n = matrix.size(); // 矩阵的维度// 初始化堆,将矩阵的第一列元素全部加入堆中for (int i = 0; i < n; i++) {minHeap.emplace(matrix[i][0], i, 0);}// 循环k-1次,每次从堆中取出最小的元素,并将该元素所在行的下一个元素加入堆中for (int i = 0; i < k - 1; i++) {Element cur = minHeap.top(); // 取出当前堆顶元素,即最小元素minHeap.pop(); // 从堆中移除该元素if (cur.y != n - 1) { // 如果当前元素不是所在行的最后一个元素// 将当前元素所在行的下一个元素加入堆中minHeap.emplace(matrix[cur.x][cur.y + 1], cur.x, cur.y + 1);}}// 循环结束后,堆顶元素即为第k小的元素return minHeap.top().val;}
};
方法三:二分法
类似上一道题目:
- 初始化左边界
left
为矩阵中的最小元素,右边界right
为矩阵中的最大元素。 - 在
while
循环中,通过二分法不断调整left
和right
,直到它们相等。 - 在每次循环中,计算中间值
mid
,然后统计矩阵中不大于mid
的元素个数count
。 - 如果
count
小于k
,说明第k
小的元素在右半部分,将left
更新为mid + 1
; - 否则,第
k
小的元素在左半部分,将right
更新为mid
。 - 当
left
和right
收敛时,返回left
或right
即可,它们的值相等且为第k
小的元素。
这种方法的时间复杂度为O(nlog(max-min)),其中n为矩阵的维度,max和min分别为矩阵中的最大值和最小值。
class Solution {
public:int Count(vector<vector<int>>& matrix, int mid) {int count = 0;int n=matrix.size();int j = n - 1;for (int i = 0; i < n; ++i) {while (j >= 0 && matrix[i][j] > mid) {j--;}count += (j + 1);}return count;}int kthSmallest(vector<vector<int>>& matrix, int k) {int n = matrix.size();int left = matrix[0][0]; // 左边界为矩阵中最小的元素int right = matrix[n - 1][n - 1]; // 右边界为矩阵中最大的元素while (left < right) {int mid = left + (right - left) / 2;int count = Count(matrix, mid); // 统计不大于mid的元素个数// 如果count小于k,说明第k小的元素在右半部分if (count < k) {left = mid + 1;} else { // 否则在左半部分right = mid;}}// left和right收敛时,即为第k小的元素return left;}
};
786. 第 K 个最小的质数分数
题目:
给你一个按递增顺序排序的数组 arr
和一个整数 k
。数组 arr
由 1
和若干 质数 组成,且其中所有整数互不相同。
对于每对满足 0 <= i < j < arr.length
的 i
和 j
,可以得到分数 arr[i] / arr[j]
。
那么第 k
个最小的分数是多少呢? 以长度为 2
的整数数组返回你的答案, 这里 answer[0] == arr[i]
且 answer[1] == arr[j]
。
思路:
方法一:二分法
class Solution {
public:vector<int> kthSmallestPrimeFraction(vector<int>& arr, int k) {int n=arr.size();double left=0;double right=1;while(1){double mid=left+(right-left)/2;int i=-1,count=0;int x=arr[0],y=arr[n-1];for(int j=1;j<n;j++){while((double)arr[i+1]/arr[j]<mid){++i;if(arr[i]*y>arr[j]*x){x=arr[i];y=arr[j];}}count+=i+1;}if(count==k) return{x,y};if(count<k) left=mid;else right=mid;}}
};
方法三:优先队列:
使用「扫描点对」+「优先队列(堆)」的做法,使用二元组 (arr[i],arr[j]) 进行存储,构建大小为 k的大根堆。
根据「堆内元素多少」和「当前计算值与堆顶元素的大小关系」决定入堆行为:
- 若堆内元素不足 k个,直接将当前二元组进行入堆;
- 若堆内元素已达 k个,根据「当前计算值 arr[i]/arr[j] 与堆顶元素 peek[0]\peek[1]的大小关系」进行分情况讨论:
- 如果当前计算值比堆顶元素大,那么当前元素不可能是第 k 小的值,直接丢弃;
- 如果当前计算值比堆顶元素小,那么堆顶元素不可能是第 k小的值,使用当前计算值置换掉堆顶元素。
构建比较关系函数:
// 自定义比较函数,用于维护最小堆auto compare = [](const vector<int>& a, const vector<int>& b) {// 计算分数值,转换为 double 类型进行比较double fracA = static_cast<double>(a[0]) / a[1];double fracB = static_cast<double>(b[0]) / b[1];return fracA < fracB;};
class Solution {
public:vector<int> kthSmallestPrimeFraction(vector<int>& arr, int k) {int n = arr.size();// 自定义比较函数,用于维护最小堆auto compare = [](const vector<int>& a, const vector<int>& b) {// 计算分数值,转换为 double 类型进行比较double fracA = static_cast<double>(a[0]) / a[1];double fracB = static_cast<double>(b[0]) / b[1];return fracA < fracB;};// 声明优先队列,使用自定义比较函数构造最小堆priority_queue<vector<int>, vector<vector<int>>, decltype(compare)> q(compare);// 遍历数组 arr,找到前 k 个最小的质数分数for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {// 计算当前分数值double t = static_cast<double>(arr[i]) / arr[j];// 如果队列大小小于 k 或者当前分数比堆顶元素小,则入堆if (q.size() < k || static_cast<double>(q.top()[0]) / q.top()[1] > t) {if (q.size() == k) q.pop(); // 维护堆大小为 kq.push({arr[i], arr[j]}); // 入堆当前分数对}}}return q.top(); // 返回堆顶元素,即第 k 小的质数分数}
};