力扣hot100——堆

前言

吐槽一下,用c语言实现堆好麻烦呀,而且效率还没有库函数高哈哈哈。

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

题目描述

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 <= 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个高频元素

题目描述

347. 前 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 是数组大小。

思路:堆

  1. 统计频率
    • 使用哈希表(uthash 库)来统计每个元素在数组中出现的次数。
    • 遍历数组,对于每个元素,如果它已经在哈希表中,则将其频率加 1;如果不在哈希表中,则将其插入哈希表并初始化频率为 1。
  2. 构建最小堆
    • 使用一个最小堆来维护当前频率最高的 k 个元素。最小堆的特点是堆顶元素是堆中最小的元素。
    • 遍历哈希表中的每个元素,将其插入堆中。如果堆的大小超过 k,则弹出堆顶元素(即频率最小的元素),以保持堆的大小为 k
  3. 提取结果
    • 最后,堆中的元素就是频率最高的 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.数据流的中位数

题目描述

295. 数据流的中位数

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

  • 例如 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 次调用 addNumfindMedian

思路:堆

大小堆维护中位数:

  1. 最大堆(Max Heap):存储数据流中较小的一半元素,堆顶是这一半的最大值。
  2. 最小堆(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);
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值