什么是 K 大数?就是给定一些数据,要求求其中第 K 大的数是什么(当然也可以变形,求第 K 小数)。
前置算法:夜深人静写算法(四十)- 八大排序
输入整数数组
arr
,找出其中最小的k
个数。例如,输入 4、5、1、6、2、7、3、8 这8个数字,则最小的4个数字是 1、2、3、4。
( 1 ) (1) (1) 直接排序,然后取前 k k k 个元素就是答案了。
时间复杂度就是排序的时间复杂度,即 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
sort(arr.begin(), arr.end()); // (1)
while(arr.size() > k) arr.pop_back(); // (2)
return arr;
}
};
( 1 ) (1) (1) 将数组按照递增排序;
( 2 ) (2) (2) 如果元素大于 k k k 个,则从尾部剔除;
前置算法:夜深人静写算法(九)- 哈希表
给你一个整数数组
nums
和一个整数k
,请你返回其中出现频率前k
高的元素。你可以按 任意顺序 返回答案。
( 1 ) (1) (1) 首先,将所有数字都映射到哈希表中;
( 2 ) (2) (2) 然后,进行排序,排序关键字有两个:当哈希表中的计数器相等的时候,按照数字本身大小进行递增排序;当计时器不相等的时候,按照每个数字计数的大小进行递减排序;
( 3 ) (3) (3) 最后,从排好序的数组中找出最小的 k k k 个不相等的数据;
哈希表过程的时间复杂度为 O ( n ) O(n) O(n),排序过程的时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
class Solution {
unordered_map<int, int> hash;
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
hash.clear();
int i;
vector<int> ret;
for(i = 0; i < nums.size(); ++i) {
++hash[nums[i]]; // (1)
}
// (2)
sort(nums.begin(), nums.end(), [&](const auto& a, const auto&b) {
int cnta = hash[a];
int cntb = hash[b];
if(cnta == cntb) {
return a < b;
}
return cnta > cntb;
});
ret = { nums[0] };
--k;
for(i = 1; i < nums.size() && k; ++i) {
if(nums[i] == nums[i-1]) { // (3)
continue;
}
--k;
ret.push_back(nums[i]);
}
return ret;
}
};
( 1 ) (1) (1) 进行哈希计数;
( 2 ) (2) (2) 实现排序函数,仿函数采用匿名函数;
( 3 ) (3) (3) 每个数字保证只能取一次;
前置算法:《画解数据结构》(2 - 5)- 堆 与 优先队列
设计一个找到数据流中第 k k k 大元素的类。注意是排序后的第 k k k 大元素,不是第 k k k 个不同的元素。请实现
KthLargest
类:
KthLargest(int k, int[] nums)
使用整数 k k k 和整数流nums
初始化对象。
int add(int val)
将val
插入数据流nums
后,返回当前数据流中第k
大的元素。
( 1 ) (1) (1) 对于这个问题,数据一定是越来越多的,也就是如果本身数据排好序以后,第 k + 1 k+1 k+1 个元素永远是没有意义的,因为永远不会通过add
进行返回;
( 2 ) (2) (2) 基于以上这点,可以实现一个 小顶堆,并且不断的弹出元素,直到堆保持 k k k 个元素位置;
( 3 ) (3) (3) 每次add
操作,就往堆里插入一个元素,并且弹出一个元素,使得堆始终保持 k k k 个元素,这样,堆顶的元素就是第 k k k 大的元素辣!
每次插入删除都是 O ( l o g 2 n ) O(log_2n) O(log2n),查找是 O ( 1 ) O(1) O(1) 的,初始化的时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
class KthLargest {
priority_queue <int, vector<int>, greater<> > q;
int K;
public:
KthLargest(int k, vector<int>& nums) {
K = k;
while( !q.empty() ) q.pop(); // (1)
for(int i = 0; i < nums.size(); ++i) { // (2)
add(nums[i]);
}
}
int add(int val) {
q.push(val); // (3)
while(q.size() > K) { // (4)
q.pop();
}
return q.top();
}
};
( 1 ) (1) (1) 清空数据;
( 2 ) (2) (2) 调用封装好的插入接口;
( 3 ) (3) (3) 将数据插入堆中;
( 4 ) (4) (4) 如果元素个数大于 K K K 个,弹出对顶;
前置算法:
( 1 ) (1) (1) 二分枚举
( 2 ) (2) (2) 前缀和
( 3 ) (3) (3) 树状数组
设计一个找到数据流中第 k k k 大元素的类。注意是排序后的第 k k k 大元素(元素范围是 [ − 1 0 4 , 1 0 4 ] [-10^4, 10^4] [−104,104]),不是第 k k k 个不同的元素。请实现
KthLargest
类:
KthLargest(int k, int[] nums)
使用整数 k k k 和整数流nums
初始化对象。
int add(int val)
将val
插入数据流nums
后,返回当前数据流中第k
大的元。
( 1 ) (1) (1) 由于数据范围较小,且存在负数,可以将数据加上一个偏移量后,直接映射到数组中;
( 2 ) (2) (2) 然后每次从大到小统计数组中元素的个数,当出现某一次满足元素总数 ≥ k \ge k ≥k,说明这个数就是我们要求的第 k k k 大数;
( 3 ) (3) (3) 以上算法,插入时间复杂度 O ( 1 ) O(1) O(1),查询时间复杂度 O ( k ) O(k) O(k),所以需要用树状数组优化。
( 4 ) (4) (4) 由于数组统计肯定满足单调性,所以插入可以采用树状数组的插入操作,查找可以用二分查找配合树状数组的求和操作,时间复杂度都保证到 O ( l o g 2 n ) O(log_2n) O(log2n)。
class KthLargest {
#define maxn 20002
#define base 10001
int c[maxn];
int K;
void init() {
memset(c, 0, sizeof(c));
}
int lowbit(int x) {
return x & -x;
}
void treeArrayAdd(int x) {
while(x < maxn) {
++c[x];
x += lowbit(x);
}
}
int treeArraySum(int x) {
int s = 0;
while(x) {
s += c[x];
x -= lowbit(x);
}
return s;
}
int getKth() {
int total = treeArraySum(maxn-1);
int l = 1, r = maxn - 1;
int ans = 0;
while(l <= r) {
int mid = (l + r) >> 1;
int cnt = total - treeArraySum(mid - 1);
// >= mid 的数字有 cnt 个
if(cnt >= K) {
ans = mid;
l = mid + 1;
}else {
r = mid - 1;
}
}
return ans;
}
public:
KthLargest(int k, vector<int>& nums) {
memset(c, 0, sizeof(c));
K = k;
for(int i = 0; i < nums.size(); ++i) {
add(nums[i]);
}
}
int add(int val) {
treeArrayAdd(val + base);
return getKth() - base;
}
};
序号 | 题目链接 | 难度 |
---|---|---|
1 | 剑指 Offer 40. 最小的k个数 | ★☆☆☆☆ |
2 | 面试题 17.14. 最小K个数 | ★☆☆☆☆ |
3 | LeetCode 347. 前 K 个高频元素 | ★☆☆☆☆ |
4 | LeetCode 692. 前K个高频单词 | ★☆☆☆☆ |
5 | LeetCode 1985. 找出数组中的第 K 大整数 * | ★☆☆☆☆ |
6 | 面试题 17.09. 第 k 个数 | ★★☆☆☆ |
7 | 剑指 Offer II 059. 数据流的第 K 大数值 | ★★☆☆☆ |
8 | 703. 数据流中的第 K 大元素 | ★★☆☆☆ |
9 | LeetCode 440. 字典序的第K小数字 * | ★★★★☆ |