前言
吐槽一下,用c语言实现堆好麻烦呀,而且效率还没有库函数高哈哈哈。
74.数组中的第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 <= 10^5
-10^4 <= nums[i] <= 10^4
思路一:快速排序
将原数组由大到小排序,再返回第k个数
code
// 比较函数,用于排序
int compare(const void* a, const void* b) {
return (*(int*)b - *(int*)a); // 降序排序
}
int findKthLargest(int* nums, int numsSize, int k) {
// 对数组进行降序排序
qsort(nums, numsSize, sizeof(int), compare);
// 返回第 k 个最大的元素
return nums[k - 1];
}
思路二:堆
维护大小为k的最小堆:
- 使用一个最小堆(优先队列)来维护数组中的前
k
个最大元素。 - 遍历数组,将元素加入堆中,如果堆的大小超过
k
,则弹出堆顶元素。 - 最终堆顶元素就是第
k
个最大的元素。
code
#include <stdio.h>
#include <stdlib.h>
// 最小堆的实现
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
void heapify(int* heap, int size, int i) {
int smallest = i; // 假设当前节点是最小值
int left = 2 * i + 1; // 左子节点
int right = 2 * i + 2; // 右子节点
// 找到最小值
if (left < size && heap[left] < heap[smallest]) {
smallest = left;
}
if (right < size && heap[right] < heap[smallest]) {
smallest = right;
}
// 如果最小值不是当前节点,交换并递归调整
if (smallest != i) {
swap(&heap[i], &heap[smallest]);
heapify(heap, size, smallest);
}
}
void buildHeap(int* heap, int size) {
for (int i = size / 2 - 1; i >= 0; i--) {
heapify(heap, size, i);
}
}
int findKthLargest(int* nums, int numsSize, int k) {
int* heap = malloc(sizeof(int) * k); // 最小堆
for (int i = 0; i < k; i++) {
heap[i] = nums[i];
}
// 构建最小堆
buildHeap(heap, k);
// 遍历剩余元素
for (int i = k; i < numsSize; i++) {
if (nums[i] > heap[0]) {
heap[0] = nums[i]; // 替换堆顶元素
heapify(heap, k, 0); // 调整堆
}
}
int result = heap[0]; // 堆顶元素就是第 k 个最大的元素
free(heap);
return result;
}
75.前K个高频元素
题目描述
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
1 <= nums.length <= 10^5
k
的取值范围是[1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前
k
个高频元素的集合是唯一的
**进阶:**你所设计算法的时间复杂度 必须 优于 O(n log n)
,其中 n
是数组大小。
思路:堆
- 统计频率:
- 使用哈希表(
uthash
库)来统计每个元素在数组中出现的次数。 - 遍历数组,对于每个元素,如果它已经在哈希表中,则将其频率加 1;如果不在哈希表中,则将其插入哈希表并初始化频率为 1。
- 使用哈希表(
- 构建最小堆:
- 使用一个最小堆来维护当前频率最高的
k
个元素。最小堆的特点是堆顶元素是堆中最小的元素。 - 遍历哈希表中的每个元素,将其插入堆中。如果堆的大小超过
k
,则弹出堆顶元素(即频率最小的元素),以保持堆的大小为k
。
- 使用一个最小堆来维护当前频率最高的
- 提取结果:
- 最后,堆中的元素就是频率最高的
k
个元素。将这些元素从堆中依次弹出并存储到结果数组中。
- 最后,堆中的元素就是频率最高的
code
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
// 哈希表结构体(使用uthash库)
struct hash_stable{
int key; // 存储元素值
int val; // 存储出现次数
UT_hash_handle hh; // uthash必备句柄
};
typedef struct hash_stable* hash_ptr;
// 堆元素结构体(存储元素值和频率)
struct pair{
int first; // 元素值
int second; // 出现次数
};
struct pair* heap; // 堆数组(下标从1开始)
int heapSize; // 当前堆大小
// 交换两个堆元素
void swap(struct pair* a, struct pair* b){
struct pair t = *a;
*a = *b;
*b = t;
}
// 比较函数(小根堆比较逻辑)
bool cmp(struct pair* a, struct pair* b){
return a->second < b->second; // 当a的频率更小时返回true
}
// 获取堆顶元素
struct pair top(){
return heap[1];
}
// 插入元素到堆中
void push(hash_ptr x){
heap[++heapSize].first = x->key; // 放入堆尾
heap[heapSize].second = x->val;
// 上浮操作
int p = heapSize;
while(p > 1){
int parent = p >> 1; // 父节点下标
if(cmp(&heap[parent], &heap[p])) break; // 父节点更小则停止
swap(&heap[parent], &heap[p]);
p = parent;
}
}
// 弹出堆顶元素
void pop(){
heap[1] = heap[heapSize--]; // 用堆尾元素替换堆顶
// 下沉操作
int p = 1;
while((p << 1) <= heapSize){
int child = p << 1; // 左孩子
if(child < heapSize && cmp(&heap[child+1], &heap[child]))
child++; // 选择更小的子节点
if(cmp(&heap[p], &heap[child])) break;
swap(&heap[p], &heap[child]);
p = child;
}
}
int* topKFrequent(int* nums, int numsSize, int k, int* returnSize) {
hash_ptr head = NULL, p = NULL, tmp = NULL;
// Step1: 统计频率(时间复杂度O(n))
for(int i = 0; i < numsSize; i++){
HASH_FIND_INT(head, &nums[i], p); // 查找元素
if(!p){
p = malloc(sizeof(struct hash_stable));
p->key = nums[i];
p->val = 1;
HASH_ADD_INT(head, key, p); // 插入新元素
} else {
p->val++; // 频率+1
}
}
// Step2: 创建最小堆(容量k)
heap = malloc(sizeof(struct pair) * (k + 1)); // 下标从1开始
heapSize = 0;
// Step3: 遍历哈希表构建堆(时间复杂度O(nlogk))
HASH_ITER(hh, head, p, tmp){
if(heapSize == k){ // 堆已满时动态维护
struct pair topElement = top();
if(topElement.second < p->val){ // 当前元素频率更高
pop(); // 弹出堆顶
push(p); // 压入新元素
}
} else {
push(p); // 堆未满直接插入
}
}
// Step4: 提取结果(逆序弹出)
*returnSize = k;
int* res = malloc(sizeof(int) * k);
for(int i = 0; i < k; i++){
struct pair topElement = top();
res[i] = topElement.first; // 存储元素值
pop();
}
return res;
}
76.数据流的中位数
题目描述
中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
- 例如
arr = [2,3,4]
的中位数是3
。 - 例如
arr = [2,3]
的中位数是(2 + 3) / 2 = 2.5
。
实现 MedianFinder 类:
MedianFinder()
初始化MedianFinder
对象。void addNum(int num)
将数据流中的整数num
添加到数据结构中。double findMedian()
返回到目前为止所有元素的中位数。与实际答案相差10-5
以内的答案将被接受。
示例 1:
输入
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]
解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0
提示:
-10^5 <= num <= 10^5
- 在调用
findMedian
之前,数据结构中至少有一个元素 - 最多
5 * 10^4
次调用addNum
和findMedian
思路:堆
大小堆维护中位数:
- 最大堆(Max Heap):存储数据流中较小的一半元素,堆顶是这一半的最大值。
- 最小堆(Min Heap):存储数据流中较大的一半元素,堆顶是这一半的最小值。
通过这种方式,我们可以保证:
- 最大堆和最小堆的大小相等(偶数个元素时),或者最大堆比最小堆多一个元素(奇数个元素时)。
- 最大堆的堆顶和最小堆的堆顶分别表示中位数的候选值。
(C++可以用优先队列)
code
#include <stdio.h>
#include <stdlib.h>
// 定义最大堆和最小堆的结构
typedef struct {
int* data;
int size;
int capacity;
} Heap;
// 创建堆
Heap* createHeap(int capacity) {
Heap* heap = (Heap*)malloc(sizeof(Heap));
heap->data = (int*)malloc(sizeof(int) * capacity);
heap->size = 0;
heap->capacity = capacity;
return heap;
}
// 交换两个元素
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 最大堆的上浮操作
void maxHeapifyUp(Heap* heap, int index) {
while (index > 0) {
int parent = (index - 1) / 2;
if (heap->data[parent] >= heap->data[index]) break;
swap(&heap->data[parent], &heap->data[index]);
index = parent;
}
}
// 最小堆的上浮操作
void minHeapifyUp(Heap* heap, int index) {
while (index > 0) {
int parent = (index - 1) / 2;
if (heap->data[parent] <= heap->data[index]) break;
swap(&heap->data[parent], &heap->data[index]);
index = parent;
}
}
// 最大堆的下沉操作
void maxHeapifyDown(Heap* heap, int index) {
while (1) {
int left = 2 * index + 1;
int right = 2 * index + 2;
int largest = index;
if (left < heap->size && heap->data[left] > heap->data[largest])
largest = left;
if (right < heap->size && heap->data[right] > heap->data[largest])
largest = right;
if (largest == index) break;
swap(&heap->data[index], &heap->data[largest]);
index = largest;
}
}
// 最小堆的下沉操作
void minHeapifyDown(Heap* heap, int index) {
while (1) {
int left = 2 * index + 1;
int right = 2 * index + 2;
int smallest = index;
if (left < heap->size && heap->data[left] < heap->data[smallest])
smallest = left;
if (right < heap->size && heap->data[right] < heap->data[smallest])
smallest = right;
if (smallest == index) break;
swap(&heap->data[index], &heap->data[smallest]);
index = smallest;
}
}
// 插入元素到最大堆
void maxHeapPush(Heap* heap, int num) {
if (heap->size == heap->capacity) return; // 堆已满
heap->data[heap->size++] = num;
maxHeapifyUp(heap, heap->size - 1);
}
// 插入元素到最小堆
void minHeapPush(Heap* heap, int num) {
if (heap->size == heap->capacity) return; // 堆已满
heap->data[heap->size++] = num;
minHeapifyUp(heap, heap->size - 1);
}
// 弹出最大堆的堆顶元素
int maxHeapPop(Heap* heap) {
if (heap->size == 0) return -1; // 堆为空
int top = heap->data[0];
heap->data[0] = heap->data[--heap->size];
maxHeapifyDown(heap, 0);
return top;
}
// 弹出最小堆的堆顶元素
int minHeapPop(Heap* heap) {
if (heap->size == 0) return -1; // 堆为空
int top = heap->data[0];
heap->data[0] = heap->data[--heap->size];
minHeapifyDown(heap, 0);
return top;
}
// 数据结构定义
typedef struct {
Heap* maxHeap; // 存储较小的一半
Heap* minHeap; // 存储较大的一半
} MedianFinder;
// 初始化数据结构
MedianFinder* medianFinderCreate() {
MedianFinder* obj = (MedianFinder*)malloc(sizeof(MedianFinder));
obj->maxHeap = createHeap(100000); // 假设最大容量为 100000
obj->minHeap = createHeap(100000);
return obj;
}
// 插入元素
void medianFinderAddNum(MedianFinder* obj, int num) {
// 先将元素插入最大堆
maxHeapPush(obj->maxHeap, num);
// 将最大堆的堆顶元素移动到最小堆
minHeapPush(obj->minHeap, maxHeapPop(obj->maxHeap));
// 如果最小堆的大小大于最大堆,则移动一个元素回最大堆
if (obj->minHeap->size > obj->maxHeap->size) {
maxHeapPush(obj->maxHeap, minHeapPop(obj->minHeap));
}
}
// 查找中位数
double medianFinderFindMedian(MedianFinder* obj) {
if (obj->maxHeap->size > obj->minHeap->size) {
return (double)obj->maxHeap->data[0];
} else {
return (double)(obj->maxHeap->data[0] + obj->minHeap->data[0]) / 2;
}
}
// 释放内存
void medianFinderFree(MedianFinder* obj) {
free(obj->maxHeap->data);
free(obj->maxHeap);
free(obj->minHeap->data);
free(obj->minHeap);
free(obj);
}
/**
* Your MedianFinder struct will be instantiated and called as such:
* MedianFinder* obj = medianFinderCreate();
* medianFinderAddNum(obj, num);
* double param_2 = medianFinderFindMedian(obj);
* medianFinderFree(obj);
*/