leetcode 215. 数组中的第K个最大元素
大顶堆算法
- 步骤1:将数组中所有的数据按照大顶堆排序
- 步骤2:然后将堆顶元素删除,将堆尾数据放到堆顶,然后调整堆满足大顶堆的属性(调整的时候使用递归的方式,减少代码量)
- 重复步骤2 k - 1次,堆顶数据就是想要的结果
时间复杂度分析
- 在建堆的开始,针对n个节点,每一个节点的时间复杂度为log(k), k为二叉树的层数,总的就是nlog(k)
- 后面删除k-1次,时间消耗为 (k-1)log(k)
- 所以整个运行的时间复杂度为O(n)
void Swap(int *arr, int i, int j)
{
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
void AdjustHeap(int *arr, int i, int arrSize)
{
int left = 2 * i + 1;
int right = 2 * i + 2;
int largestIndex = i;
/*********************大顶堆关键代码***************************/
if (left < arrSize && arr[left] > arr[i]) {
largestIndex = left;
}
if (right < arrSize && arr[right] > arr[largestIndex]) {
largestIndex = right;
}
/**********************大顶堆关键代码**************************/
if (i != largestIndex) {
Swap(arr, i, largestIndex);
AdjustHeap(arr, largestIndex, arrSize);
}
}
int findKthLargest(int* nums, int numsSize, int k){
// 使用堆排序(最大堆),堆顶就是最大的数,然后将堆顶的数删除,将堆尾的数放到堆顶,进行堆调整
// 删除k - 1次后,堆顶数据就是结果
// 首先构造大顶堆
for (int i = numsSize / 2 - 1; i >= 0; i--) {
AdjustHeap(nums, i, numsSize);
}
// 删除k - 1次堆顶元素(其实是将堆末尾和堆顶交换)
int arrSize = numsSize;
for (int i = 0; i < k - 1; i++) {
Swap(nums, 0, numsSize - 1 - i);
arrSize--;
AdjustHeap(nums, 0, arrSize);
}
return nums[0];
}
小顶堆算法
- 使用小顶堆算法。则要保持堆的个数为k个,将前k个大的数据保存到堆里。堆顶保存较小的,使用小顶堆就可以获取到第k大的数
void Swap(int *arr, int i, int j)
{
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
void SiftUp(int *arr, int i)
{
if (i == 0) return;
int parent = (i + 1) / 2 - 1;
if (i != parent) {
if (arr[i] < arr[parent]) {
Swap(arr, i, parent);
SiftUp(arr, parent);
}
}
}
void AdjustHeap(int *arr, int i, int arrSize)
{
int left = 2 * i + 1;
int right = 2 * i + 2;
int min = i;
if (left < arrSize && arr[left] < arr[i]) {
min = left;
}
if (right < arrSize && arr[right] < arr[min]) {
min = right;
}
if (i != min) {
Swap(arr, i, min);
AdjustHeap(arr, min, arrSize);
}
}
int findKthLargest(int* arr, int numsSize, int k)
{
// 首先将数组总的前k个数按照最小堆排序
for (int i = 0; i < k; i++) {
SiftUp(arr, i);
}
// 然后将剩下的元素,添加到最小堆里
// 要求堆的个数保持k个不变,如果新的数值比堆顶还小,则直接跳过,反正,将堆顶替换之,然后调整堆的状态。
for (int i = k; i < numsSize; i++) {
if (arr[0] >= arr[i]) {
continue;
}
arr[0] = arr[i];
AdjustHeap(arr, 0, k);
}
return arr[0];
}
有必要总结一个大小顶堆的通用模版
void Swap(int *arr, int i, int j)
{
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
typedef int (MyCmp)(const void *a, const void *b);
// 小顶堆
int HeapCmp(const void *a, const void *b)
{
int left = *(int *)a;
int right = *(int *)b;
return left < right ? -1 : 1;
}
void SiftUp(int *arr, int i, MyCmp cmp)
{
if (i == 0) return;
int parent = (i + 1) / 2 - 1;
if (i != parent) {
if (cmp(&arr[parent], &arr[i]) > 0) { // if (arr[parent] > arr[i]) {
Swap(arr, i, parent);
SiftUp(arr, parent, cmp);
}
}
}
void AdjustHeap(int *arr, int i, int arrSize, MyCmp cmp)
{
int left = 2 * i + 1;
int right = 2 * i + 2;
int min = i;
// if (left < arrSize && arr[i] > arr[left]) {
if (left < arrSize && cmp(&arr[i], &arr[left]) > 0) {
min = left;
}
if (right < arrSize && cmp(&arr[min], &arr[right]) > 0) {
min = right;
}
if (i != min) {
Swap(arr, i, min);
AdjustHeap(arr, min, arrSize, cmp);
}
}
int findKthLargest(int* arr, int numsSize, int k)
{
// 首先将数组总的前k个数按照最小堆排序
for (int i = 0; i < k; i++) {
SiftUp(arr, i, HeapCmp);
}
// 然后将剩下的元素,添加到最小堆里
// 要求堆的个数保持k个不变,如果新的数值比堆顶还小,则直接跳过,反正,将堆顶替换之,然后调整堆的状态。
for (int i = k; i < numsSize; i++) {
if (arr[0] >= arr[i]) {
continue;
}
arr[0] = arr[i];
AdjustHeap(arr, 0, k, HeapCmp);
}
return arr[0];
}