春招百题--堆--扩展篇--找出最小

文章介绍了如何使用双指针、二分法、优先队列和堆数据结构解决一系列与查找第K小元素、质数分数相关的问题,包括数组中的第K大元素、有序矩阵中的第K小元素以及第K个最小的质数分数。
摘要由CSDN通过智能技术生成

其他类似题目:

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个。

代码:但超出时间限制  时间复杂度0\left (n ^{2} \right )

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];
    }
};

时间复杂度:0\left ( n^{2} logn\right )空间复杂度:0\left ( n^{2} \right )

方法二:归并排序--》堆

由题目给出的性质可知,这个矩阵的每一行均为一个有序数组。问题即转化为从这 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 小的质数分数
    }
};

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值