其他类似题目:
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;
else
left = 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(); // 维护堆大小为 k
q.push({arr[i], arr[j]}); // 入堆当前分数对
}
}
}
return q.top(); // 返回堆顶元素,即第 k 小的质数分数
}
};