1 题目
给定整数数组 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. 为什么用「小顶堆」?
堆有两种:
- 大顶堆:堆顶是最大的
- 小顶堆:堆顶是最小的
我们用 小顶堆,理由超简单:
- 维护一个大小固定为 k 的小顶堆
- 堆里只存当前最大的 k 个数
- 堆顶 = 这 k 个数里最小的 → 就是答案!
完整思路(一步一步走)
- 创建一个小顶堆(大小最多 k)
- 遍历数组里每一个数字:
- 堆没满 → 直接放进去
- 堆满了:
- 如果当前数 比堆顶大 → 删掉堆顶,把新数加进去
- 如果比堆顶小 → 跳过
- 遍历结束,堆顶就是第 k 大元素
举个例子秒懂
数组:[3,2,1,5,6,4],k=2
- 3 → 堆空,加入 → 堆:
[3] - 2 → 堆没满,加入 → 堆:
[2,3](堆顶是 2) - 1 → 比 2 小 → 跳过
- 5 → 比 2 大 → 删 2,加 5 → 堆:
[3,5](堆顶 3) - 6 → 比 3 大 → 删 3,加 6 → 堆:
[5,6](堆顶 5) - 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 的堆
你必须记住的总结(超简单)
- 找第 k 大 → 用小顶堆
- 堆大小固定为 k
- 比堆顶大就替换,小就跳过
- 最后堆顶就是答案
3 题目
给定两个以 非递减顺序排列 的整数数组 nums1 和nums2, 以及一个整数 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] <= 109nums1和nums2均为 升序排列1 <= k <= 104k <= 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;
};
思考
好难。。。。不会做。。。
题解
一、核心思路(和刚才完全一样,不变)
- 两个升序数组,找和最小的 k 个数对
- 用小顶堆(每次弹出最小和)
- 堆里存:
{两数之和, 数组1下标, 数组2下标} - 用set / 二维数组记录已经加入堆的下标对,防止重复
- 取出 k 次堆顶,就是答案
二、C++ 重点知识(必须知道)
-
C++ 默认优先队列是 大顶堆
-
想变成 小顶堆 必须这么写:
cpppriority_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;
}
};
四、代码逐行解释(超级简单)
-
小顶堆定义
cpppriority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>> pq;存储格式:
{两数之和, nums1下标i, nums2下标j} -
去重 set
cppset<pair<int, int>> visited;防止同一个
(i,j)被多次加入堆 -
初始化 最小和一定是第一个数对:
(0,0) -
循环 k 次
- 每次弹出堆顶(最小和)
- 加入答案
- 把下面 (i+1,j) 和 右边 (i,j+1) 加入堆
- 标记已访问
-
返回结果 存的就是前 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 句话
- 找最小 k 个 → 小顶堆
- 堆存:
{sum, i, j} - 每次取堆顶加入答案
- 向下、向右扩展,用 set 去重
5 小结
不知道怎么搞,我觉得非常难,先混过去了,非常难,第一题在hot 100 要会,后面都随意写一些。