Leetcode 158 数组中的第K个最大元素 | 查找和最小的 K 对数字

1 题目

215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

复制代码
输入: [3,2,1,5,6,4], k = 2
输出: 5

示例 2:

复制代码
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示:

  • 1 <= k <= nums.length <= 105
  • -104 <= nums[i] <= 104

2 代码实现

c++

cpp 复制代码
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int , vector<int> ,greater<int>> minHeap ;

        for (int num : nums){
            minHeap.push(num);

            if(minHeap.size() > k ){
                minHeap.pop();
            }
        }
        return minHeap.top();
    }
};

js

javascript 复制代码
var findKthLargest = function(nums, k) {
    // 最小堆
    class MinHeap {
        constructor() {
            this.heap = [];
        }
        push(val) {
            this.heap.push(val);
            this.bubbleUp(this.heap.length - 1);
        }
        pop() {
            const min = this.heap[0];
            const last = this.heap.pop();
            if (this.heap.length > 0) {
                this.heap[0] = last;
                this.bubbleDown(0);
            }
            return min;
        }
        top() {
            return this.heap[0];
        }
        size() {
            return this.heap.length;
        }
        bubbleUp(idx) {
            while (idx > 0) {
                const parent = (idx - 1) >> 1;
                if (this.heap[parent] <= this.heap[idx]) break;
                [this.heap[parent], this.heap[idx]] = [this.heap[idx], this.heap[parent]];
                idx = parent;
            }
        }
        bubbleDown(idx) {
            const len = this.heap.length;
            while (true) {
                let left = idx * 2 + 1;
                let right = idx * 2 + 2;
                let smallest = idx;
                if (left < len && this.heap[left] < this.heap[smallest]) smallest = left;
                if (right < len && this.heap[right] < this.heap[smallest]) smallest = right;
                if (smallest === idx) break;
                [this.heap[idx], this.heap[smallest]] = [this.heap[smallest], this.heap[idx]];
                idx = smallest;
            }
        }
    }

    const heap = new MinHeap();
    for (let num of nums) {
        heap.push(num);
        if (heap.size() > k) heap.pop();
    }
    return heap.top();
};

思考

完了,只会想到要用sort,各种排序算法其实我也都不会啊,完蛋了!!!!

题解

先搞懂 2 个核心问题

1. 我们要找什么?

第 k 个最大元素 比如数组 [3,2,1,5,6,4],k=2排序后:[1,2,3,4,5,6]最大的 2 个数是 6、5第 2 大就是 5

也就是说:

我们只要保留数组里最大的 k 个数,这里面最小的那个,就是第 k 大元素!

2. 为什么用「小顶堆」?

堆有两种:

  • 大顶堆:堆顶是最大的
  • 小顶堆:堆顶是最小的

我们用 小顶堆,理由超简单:

  1. 维护一个大小固定为 k 的小顶堆
  2. 堆里只存当前最大的 k 个数
  3. 堆顶 = 这 k 个数里最小的 → 就是答案!

完整思路(一步一步走)

  1. 创建一个小顶堆(大小最多 k)
  2. 遍历数组里每一个数字:
    • 堆没满 → 直接放进去
    • 堆满了:
      • 如果当前数 比堆顶大 → 删掉堆顶,把新数加进去
      • 如果比堆顶小 → 跳过
  3. 遍历结束,堆顶就是第 k 大元素

举个例子秒懂

数组:[3,2,1,5,6,4],k=2

  1. 3 → 堆空,加入 → 堆:[3]
  2. 2 → 堆没满,加入 → 堆:[2,3](堆顶是 2)
  3. 1 → 比 2 小 → 跳过
  4. 5 → 比 2 大 → 删 2,加 5 → 堆:[3,5](堆顶 3)
  5. 6 → 比 3 大 → 删 3,加 6 → 堆:[5,6](堆顶 5)
  6. 4 → 比 5 小 → 跳过

最终堆顶 = 5 → 正确答案!


C++ 代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <queue>  // 用优先队列实现堆
using namespace std;

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        // ====================== 关键行 ======================
        // 小顶堆:堆顶永远是堆内最小元素
        // priority_queue 默认是大顶堆,加上 greater<int> 变成小顶堆
        priority_queue<int, vector<int>, greater<int>> minHeap;

        // 遍历数组所有数字
        for (int num : nums) {
            // 先把当前数放进堆
            minHeap.push(num);

            // ====================== 核心规则 ======================
            // 如果堆的大小 超过 k,就把最小的堆顶删掉
            if (minHeap.size() > k) {
                minHeap.pop();
            }
        }

        // 最后堆顶 = 最大的 k 个数里最小的 = 第 k 大元素
        return minHeap.top();
    }
};

// 测试
int main() {
    vector<int> nums1 = {3,2,1,5,6,4};
    cout << Solution().findKthLargest(nums1, 2) << endl;  // 输出 5

    vector<int> nums2 = {3,2,3,1,2,4,5,5,6};
    cout << Solution().findKthLargest(nums2, 4) << endl;  // 输出 4
}

最关键的 3 行代码(背下来!)

cpp 复制代码
// 1. 创建小顶堆
priority_queue<int, vector<int>, greater<int>> minHeap;

// 2. 插入元素
minHeap.push(num);

// 3. 超过 k 大小就弹出最小的
if (minHeap.size() > k) minHeap.pop();

时间 & 空间复杂度

  • 时间复杂度 :O (n log k)每个元素入堆出堆是 log k,一共 n 个元素题目要求 O (n),但这个方法面试 100% 能过,代码极短、不容易写错
  • 空间复杂度:O (k)只维护大小为 k 的堆

你必须记住的总结(超简单)

  1. 找第 k 大 → 用小顶堆
  2. 堆大小固定为 k
  3. 比堆顶大就替换,小就跳过
  4. 最后堆顶就是答案

3 题目

373. 查找和最小的 K 对数字

给定两个以 非递减顺序排列 的整数数组 nums1nums2, 以及一个整数 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]

提示:

  • 1 <= nums1.length, nums2.length <= 105
  • -109 <= nums1[i], nums2[i] <= 109
  • nums1nums2 均为 升序排列
  • 1 <= k <= 104
  • k <= nums1.length * nums2.length

4 代码实现

c++

cpp 复制代码
#include <vector>
#include <queue>
#include <set>
using namespace std;

class Solution {
public:
    vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {
        // 小顶堆:存储 {sum, i, j}
        priority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>> pq;
        
        // 去重:记录已经加入堆的下标对 (i,j)
        set<pair<int, int>> visited;
        
        vector<vector<int>> res;
        
        // 初始化:最小和一定是 nums1[0]+nums2[0]
        pq.push({nums1[0] + nums2[0], 0, 0});
        visited.insert({0, 0});
        
        // 取 k 次
        while (k-- > 0 && !pq.empty()) {
            auto cur = pq.top();
            pq.pop();
            
            int sum = cur[0];
            int i = cur[1];
            int j = cur[2];
            
            // 加入答案
            res.push_back({nums1[i], nums2[j]});
            
            // 往下走:i+1, j
            if (i + 1 < nums1.size() && !visited.count({i+1, j})) {
                pq.push({nums1[i+1] + nums2[j], i+1, j});
                visited.insert({i+1, j});
            }
            
            // 往右走:i, j+1
            if (j + 1 < nums2.size() && !visited.count({i, j+1})) {
                pq.push({nums1[i] + nums2[j+1], i, j+1});
                visited.insert({i, j+1});
            }
        }
        
        return res;
    }
};

js

javascript 复制代码
var kSmallestPairs = function(nums1, nums2, k) {
    // ------------- 手写 最小堆(和上一题完全一样!)-------------
    class MinHeap {
        constructor() {
            this.heap = [];
        }
        push(val) { this.heap.push(val); this.bubbleUp(this.heap.length - 1); }
        pop() {
            const min = this.heap[0];
            const last = this.heap.pop();
            if (this.heap.length) { this.heap[0] = last; this.bubbleDown(0); }
            return min;
        }
        top() { return this.heap[0]; }
        size() { return this.heap.length; }
        bubbleUp(idx) {
            while (idx > 0) {
                const p = (idx - 1) >> 1;
                if (this.heap[p][0] <= this.heap[idx][0]) break;
                [this.heap[p], this.heap[idx]] = [this.heap[idx], this.heap[p]];
                idx = p;
            }
        }
        bubbleDown(idx) {
            const len = this.heap.length;
            while (true) {
                let l = idx * 2 + 1, r = idx * 2 + 2, s = idx;
                if (l < len && this.heap[l][0] < this.heap[s][0]) s = l;
                if (r < len && this.heap[r][0] < this.heap[s][0]) s = r;
                if (s === idx) break;
                [this.heap[idx], this.heap[s]] = [this.heap[s], this.heap[idx]];
                idx = s;
            }
        }
    }

    // ------------- 正式逻辑 -------------
    const heap = new MinHeap();
    const visited = new Set(); // 防止重复加入
    const res = [];

    // 初始:把第0行0列放进去
    heap.push([nums1[0] + nums2[0], 0, 0]);
    visited.add('0,0');

    // 取 k 次
    while (k-- > 0 && heap.size() > 0) {
        // 取出当前最小和
        const [sum, i, j] = heap.pop();
        res.push([nums1[i], nums2[j]]);

        // 往下走:i+1, j
        if (i + 1 < nums1.length && !visited.has(`${i+1},${j}`)) {
            heap.push([nums1[i+1] + nums2[j], i+1, j]);
            visited.add(`${i+1},${j}`);
        }
        // 往右走:i, j+1
        if (j + 1 < nums2.length && !visited.has(`${i},${j+1}`)) {
            heap.push([nums1[i] + nums2[j+1], i, j+1]);
            visited.add(`${i},${j+1}`);
        }
    }

    return res;
};

思考

好难。。。。不会做。。。

题解

一、核心思路(和刚才完全一样,不变)

  1. 两个升序数组,找和最小的 k 个数对
  2. 小顶堆(每次弹出最小和)
  3. 堆里存:{两数之和, 数组1下标, 数组2下标}
  4. set / 二维数组记录已经加入堆的下标对,防止重复
  5. 取出 k 次堆顶,就是答案

二、C++ 重点知识(必须知道)

  • C++ 默认优先队列是 大顶堆

  • 想变成 小顶堆 必须这么写:

    cpp 复制代码
    priority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>> pq;
  • 堆会自动按第一个元素(和)从小到大排序


三、C++ 完整代码

cpp 复制代码
#include <vector>
#include <queue>
#include <set>
using namespace std;

class Solution {
public:
    vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) {
        // 小顶堆:存储 {sum, i, j}
        priority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>> pq;
        
        // 去重:记录已经加入堆的下标对 (i,j)
        set<pair<int, int>> visited;
        
        vector<vector<int>> res;
        
        // 初始化:最小和一定是 nums1[0]+nums2[0]
        pq.push({nums1[0] + nums2[0], 0, 0});
        visited.insert({0, 0});
        
        // 取 k 次
        while (k-- > 0 && !pq.empty()) {
            auto cur = pq.top();
            pq.pop();
            
            int sum = cur[0];
            int i = cur[1];
            int j = cur[2];
            
            // 加入答案
            res.push_back({nums1[i], nums2[j]});
            
            // 往下走:i+1, j
            if (i + 1 < nums1.size() && !visited.count({i+1, j})) {
                pq.push({nums1[i+1] + nums2[j], i+1, j});
                visited.insert({i+1, j});
            }
            
            // 往右走:i, j+1
            if (j + 1 < nums2.size() && !visited.count({i, j+1})) {
                pq.push({nums1[i] + nums2[j+1], i, j+1});
                visited.insert({i, j+1});
            }
        }
        
        return res;
    }
};

四、代码逐行解释(超级简单)

  1. 小顶堆定义

    cpp 复制代码
    priority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>> pq;

    存储格式:{两数之和, nums1下标i, nums2下标j}

  2. 去重 set

    cpp 复制代码
    set<pair<int, int>> visited;

    防止同一个 (i,j) 被多次加入堆

  3. 初始化 最小和一定是第一个数对:(0,0)

  4. 循环 k 次

    • 每次弹出堆顶(最小和)
    • 加入答案
    • 下面 (i+1,j)右边 (i,j+1) 加入堆
    • 标记已访问
  5. 返回结果 存的就是前 k 小和数对


五、测试示例

cpp 复制代码
// 示例1
nums1 = [1,7,11], nums2 = [2,4,6], k=3
输出:[[1,2],[1,4],[1,6]]

// 示例2
nums1 = [1,1,2], nums2 = [1,2,3], k=2
输出:[[1,1],[1,1]]

六、你只需要记住 4 句话

  1. 找最小 k 个 → 小顶堆
  2. 堆存:{sum, i, j}
  3. 每次取堆顶加入答案
  4. 向下、向右扩展,用 set 去重

5 小结

不知道怎么搞,我觉得非常难,先混过去了,非常难,第一题在hot 100 要会,后面都随意写一些。

相关推荐
qq_12084093712 小时前
Three.js 场景性能优化实战:首屏、帧率与内存的工程化治理
开发语言·javascript·性能优化·three.js
脱氧核糖核酸__2 小时前
LeetCode热题100——48.旋转图像(题解+答案+要点)
c++·算法·leetcode
木井巳2 小时前
【递归算法】字母大小写全排列
java·算法·leetcode·决策树·深度优先
竹林8182 小时前
Solana前端开发:从连接钱包到发送交易,我如何用@solana/web3.js搞定第一个DApp
前端·javascript
宵时待雨2 小时前
优选算法专题2:滑动窗口
数据结构·c++·笔记·算法
Mr_pyx2 小时前
LeetCode HOT 100 —— 矩阵置零(多种解法详解)
算法·leetcode·矩阵
葫三生2 小时前
《论三生原理》系列:文化自信、知识范式重构与科技自主创新的思想运动源头?
大数据·人工智能·科技·深度学习·算法·重构·transformer
Q741_1472 小时前
每日一题 力扣 3761. 镜像对之间最小绝对距离 哈希表 数组 C++ 题解
c++·算法·leetcode·哈希算法·散列表
John.Lewis2 小时前
C++加餐课-哈希:扩展学习(2)布隆过滤器
c++·算法·哈希算法