文章目录
前言
一、概念
什么是 K 大数?就是给定一些数据,要求求其中第 K 大的数是什么(当然也可以变形,求第 K 小数)。
二、排序
前置算法:夜深人静写算法(四十)- 八大排序
1、题目描述
输入整数数组
arr
,找出其中最小的k
个数。例如,输入 4、5、1、6、2、7、3、8 这8个数字,则最小的4个数字是 1、2、3、4。
2、算法思路
( 1 ) (1) (1) 直接排序,然后取前 k k k 个元素就是答案了。
3、时间复杂度
时间复杂度就是排序的时间复杂度,即 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
4、源码分析
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 个,则从尾部剔除;
三、哈希表
前置算法:夜深人静写算法(九)- 哈希表
1、题目描述
给你一个整数数组
nums
和一个整数k
,请你返回其中出现频率前k
高的元素。你可以按 任意顺序 返回答案。
2、算法思路
(
1
)
(1)
(1) 首先,将所有数字都映射到哈希表中;
(
2
)
(2)
(2) 然后,进行排序,排序关键字有两个:当哈希表中的计数器相等的时候,按照数字本身大小进行递增排序;当计时器不相等的时候,按照每个数字计数的大小进行递减排序;
(
3
)
(3)
(3) 最后,从排好序的数组中找出最小的
k
k
k 个不相等的数据;
3、时间复杂度
哈希表过程的时间复杂度为 O ( n ) O(n) O(n),排序过程的时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
4、源码分析
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)- 堆 与 优先队列
1、题目描述
设计一个找到数据流中第 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
大的元素。
2、算法思路
(
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 大的元素辣!
3、时间复杂度
每次插入删除都是 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)。
4、源码分析
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) 树状数组
1、题目描述
设计一个找到数据流中第 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
大的元。
2、算法思路
(
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)。
3、时间复杂度
4、源码分析
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小数字 * | ★★★★☆ |