前k个高频元素

摘要:欢迎又来到leetcode每日一题系列,今天我们为大家讲解的是有关于leetcode347.前k个高频元素的讲解(又名topk),大家在看完我的讲解之后也可以点开链接自己做一下。

一、题目简介

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

二、思路讲解

这道题主要有两个思路:

  1. 利用qsort进行排序,在直接输出topk(暴力排序)
  2. 利用最小堆的概念

暴力排序法

解决这个问题的关键在于两个步骤:

  • 统计元素出现次数:首先,我们需要统计数组中每个元素的出现次数。
  • 根据出现次数排序:然后,根据元素的出现次数对元素进行排序,取出前k个元素。
实现方法
  1. 统计元素出现次数
    我们可以使用哈希表(在C语言中,我们可以使用结构体数组模拟哈希表)来统计每个元素的出现次数。为了简化问题,我们采用以下策略:
    首先对数组进行排序。
    然后遍历排序后的数组,统计每个元素的出现次数。
  2. 根据出现次数排序
    统计完元素的出现次数后,我们可以使用结构体数组来存储元素及其出现次数。然后,根据出现次数对结构体数组进行排序。
代码实现

以下是实现上述思路的C语言代码:

#include 
#include 

typedef struct {
    int value;
    int cnt;
} Vat;

int compFunc(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

int compVatFunc(const void *a, const void *b) {
    Vat *node1 = (Vat *)a;
    Vat *node2 = (Vat *)b;
    return node2->cnt - node1->cnt;
}

int* topKFrequent(int* nums, int numsSize, int k, int* returnSize) {
    if (k <= 0 || numsSize <= 0) return NULL;

    qsort(nums, numsSize, sizeof(int), compFunc);

    Vat *nums_info = (Vat *)malloc(numsSize * sizeof(Vat));
    int index = 0;
    for (int i = 0; i < numsSize; ++i) {
        if (i == 0 || nums[i] != nums[i - 1]) {
            nums_info[index].value = nums[i];
            nums_info[index].cnt = 1;
        } else {
            nums_info[index].cnt++;
        }
        index++;
    }

    qsort(nums_info, index, sizeof(Vat), compVatFunc);

    *returnSize = k;
    int *result = (int *)malloc(k * sizeof(int));
    for (int i = 0; i < k; ++i) {
        result[i] = nums_info[i].value;
    }

    free(nums_info);
    return result;
}

int main() {
    int nums[] = {1,1,1,2,2,3};
    int k = 2;
    int returnSize;
    int* result = topKFrequent(nums, sizeof(nums)/sizeof(nums[0]), k, &returnSize);
    for (int i = 0; i < returnSize; ++i) {
        printf("%d ", result[i]);
    }
    free(result);
    return 0;
}

大家如果不熟悉qsort的逻辑,我曾经写过一篇有关于qsort的模拟实现,链接放在这里大家可以点击查看。库函数的模拟实现。

最小堆法

解决思路

解决这个问题的关键在于两个步骤:统计元素出现次数和根据出现次数排序。这里,我们采用哈希表来统计次数,使用堆来找出前K大的值。

  1. 统计元素出现次数
    首先,我们遍历整个数组,并使用哈希表记录每个数字出现的次数。这样,我们可以得到一个「出现次数数组」,其中每个元素是一个键值对,键是数组中的数字,值是该数字出现的次数。
  2. 利用堆找出前K大的值
    由于直接对「出现次数数组」进行排序的时间复杂度可能达到O(NlogN),其中N是数组的长度,这在大数据集上可能效率不高。因此,我们采用堆这一数据结构来优化这个过程。
    具体来说,我们可以使用一个最小堆来存储出现次数。最小堆的特点是堆顶元素是所有元素中最小的。在本问题中,我们将出现次数作为堆的元素,这样堆顶就是出现次数最小的元素。
    遍历「出现次数数组」,对于每个元素,我们执行以下操作:
    如果堆的元素个数小于K,直接将其插入堆中。
    如果堆的元素个数等于K,比较堆顶元素(出现次数最小的元素)与当前元素的出现次数。如果堆顶元素的出现次数大于当前元素的出现次数,说明至少有K个数字的出现次数比当前值大,因此可以舍弃当前值;否则,弹出堆顶元素,并将当前元素插入堆中。
    遍历完成后,堆中的元素就代表了「出现次数数组」中前K大的值。
#include 
#include 

// 定义最小堆中的元素节点
typedef struct {
    int key;   // 数字
    int count; // 出现次数
} HeapNode;

// 定义最小堆结构
typedef struct {
    HeapNode *nodes; // 堆中元素数组
    int capacity;   // 堆的容量
    int size;       // 堆中当前元素数量
} MinHeap;

// 创建最小堆
MinHeap* createMinHeap(int capacity) {
    MinHeap *hp = (MinHeap *)malloc(sizeof(MinHeap));
    hp->nodes = (HeapNode *)malloc(sizeof(HeapNode) * capacity);
    hp->capacity = capacity;
    hp->size = 0;
    return hp;
}

// 交换两个堆节点
void swapNodes(HeapNode *a, HeapNode *b) {
    HeapNode temp = *a;
    *a = *b;
    *b = temp;
}

// 维护最小堆性质,从索引idx开始向下调整
void minHeapify(MinHeap *hp, int idx) {
    int smallest = idx;
    int left = 2 * idx + 1;
    int right = 2 * idx + 2;
    if (left < hp->size && hp->nodes[left].count < hp->nodes[smallest].count) {
        smallest = left;
    }
    if (right < hp->size && hp->nodes[right].count < hp->nodes[smallest].count) {
        smallest = right;
    }
    if (smallest != idx) {
        swapNodes(&hp->nodes[smallest], &hp->nodes[idx]);
        minHeapify(hp, smallest);
    }
}

// 向最小堆中插入新节点
void insertMinHeap(MinHeap *hp, HeapNode node) {
    if (hp->size == hp->capacity) {
        // 如果堆已满,且新节点的count大于堆顶元素的count,则替换堆顶
        if (hp->nodes[0].count < node.count) {
            hp->nodes[0] = node;
            minHeapify(hp, 0);
        }
        return;
    }
    hp->size++;
    hp->nodes[hp->size - 1] = node;
    // 从底向上调整堆,维护最小堆性质
    int i = hp->size - 1;
    while (i && hp->nodes[(i - 1) / 2].count > hp->nodes[i].count) {
        swapNodes(&hp->nodes[(i - 1) / 2], &hp->nodes[i]);
        i = (i - 1) / 2;
    }
}

// 从最小堆中提取最小元素
void extractMin(MinHeap *hp) {
    if (hp->size > 0) {
        hp->size--;
        hp->nodes[0] = hp->nodes[hp->size];
        minHeapify(hp, 0);
    }
}

// 定义哈希表节点
typedef struct Node {
    int key;
    int value;
    struct Node *next;
} Node;

// 定义哈希表结构
typedef struct {
    Node **buckets;
    int capacity;
} HashMap;

// 创建哈希表
HashMap* createHashMap(int capacity) {
    HashMap *hm = (HashMap *)malloc(sizeof(HashMap));
    hm->buckets = (Node **)malloc(sizeof(Node *) * capacity);
    for (int i = 0; i < capacity; i++) {
        hm->buckets[i] = NULL;
    }
    hm->capacity = capacity;
    return hm;
}

// 哈希表插入或更新操作
void put(HashMap *hm, int key, int value) {
    int index = abs(key) % hm->capacity;
    Node *node = hm->buckets[index];
    if (node == NULL) {
        hm->buckets[index] = (Node *)malloc(sizeof(Node));
        hm->buckets[index]->key = key;
        hm->buckets[index]->value = value;
        hm->buckets[index]->next = NULL;
    } else {
        while (node->next != NULL && node->key != key) {
            node = node->next;
        }
        if (node->key == key) {
            node->value = value;
        } else {
            node->next = (Node *)malloc(sizeof(Node));
            node->next->key = key;
            node->next->value = value;
            node->next->next = NULL;
        }
    }
}

// 哈希表查询操作
int get(HashMap *hm, int key) {
    int index = abs(key) % hm->capacity;
    Node *node = hm->buckets[index];
    while (node != NULL) {
        if (node->key == key) {
            return node->value;
        }
        node = node->next;
    }
    return -1;
}

// 查找数组中前K个最频繁出现的元素
int* topKFrequent(int* nums, int numsSize, int k, int* returnSize) {
    // 创建哈希表,统计每个数字出现的次数
    HashMap *hm = createHashMap(769); // 使用质数作为哈希表的大小
    for (int i = 0; i < numsSize; i++) {
        int count = get(hm, nums[i]);
        if (count == -1) {
            put(hm, nums[i], 1);
        } else {
            put(hm, nums[i], count + 1);
        }
    }

    // 创建最小堆,用于存储出现次数最多的K个元素
    MinHeap *hp = createMinHeap(k);
    // 遍历哈希表,构建最小堆
    for (int i = 0; i < hm->capacity; i++) {
        Node *node = hm->buckets[i];
        while (node != NULL) {
            HeapNode heapNode = {node->key, node->value};
            insertMinHeap(hp, heapNode);
            node = node->next;
        }
    }

    // 从最小堆中提取前K个最频繁出现的元素
    *returnSize = hp->size;
    int *result = (int *)malloc(sizeof(int) * hp->size);
    for (int i = 0; i < hp->size; i++) {
        result[i] = hp->nodes[i].key;
        extractMin(hp); // 提取最小元素后,堆大小减1
    }

    // 释放哈希表和最小堆占用的内存
    for (int i = 0; i < hm->capacity; i++) {
        Node *node = hm->buckets[i];
        while (node != NULL) {
            Node *temp = node;
            node = node->next;
            free(temp);
        }
    }
    free(hm->buckets);
    free(hm);
    free(hp->nodes);
    free(hp);

    return result;
}

int main() {
    int nums[] = {1,1,1,2,2,3};
    int k = 2;
    int returnSize;
    int* result = topKFrequent(nums, sizeof(nums)/sizeof(nums[0]), k, &returnSize);
    for (int i = 0; i < returnSize; ++i) {
        printf("%d ", result[i]);
    }
    free(result);
    return 0;
}

关于代码中的堆的内容,在堆的实现的这篇文中有详细的讲解。大家对于题目还有什么不理解的可以评论留言。

你可能感兴趣的:(leetcode每日一题,算法)