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

一 快排堆排

刚开始直接用的快速排序,然后取出第k大的元素,虽然时间复杂度是O(nlogn),但还是算我通过了。

快排代码

void swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int median3(int* nums, int left, int right)
{
    int mid = (left + right) / 2;
    
    if(nums[left] > nums[mid])
        swap(&nums[left], &nums[mid]);
    if(nums[left] > nums[right])
        swap(&nums[left], &nums[right]);
    if(nums[mid] > nums[right])
        swap(&nums[mid], &nums[right]);

    swap(&nums[mid], &nums[right - 1]);
    return nums[right - 1];
}

void queck_sort(int* nums, int left, int right)
{
    if(left < right)
    {
        int pivot = median3(nums, left, right);
        int i = left;
        int j = right - 1;
        while(1)
        {
            while(i < right - 1 && nums[++i] < pivot);
            while(j > 0 && nums[--j] > pivot);
            if(i < j)
                swap(&nums[i], &nums[j]);
            else
                break;
        }
        swap(&nums[i], &nums[right - 1]);
        queck_sort(nums, left, i - 1);
        queck_sort(nums, i + 1, right);
    }
}

int findKthLargest(int* nums, int numsSize, int k) {
    queck_sort(nums, 0, numsSize - 1);
    return nums[numsSize - k];
}

后来有用了堆排,思路是用最大堆排序,不过思路较一般堆排序有了一点优化,不断把堆顶元素放到最后,直到弹出第k个元素就是第K个最大元素

堆排代码

void swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

void perdown(int* nums, int numsSize, int i)
{
    int parent, child;
    int temp = nums[i];
    for(parent = i; parent * 2 + 1 < numsSize; parent = child)
    {
        child = parent * 2 + 1;
        if(child != numsSize - 1 && nums[child + 1] > nums[child])
            child++;
        if(nums[child] < temp)
            break;
        nums[parent] = nums[child];
    }
    nums[parent] = temp;
}

int findKthLargest(int* nums, int numsSize, int k) {
    for(int i = numsSize / 2; i >= 0; i--)
        perdown(nums, numsSize, i);
    for(int i = 0; i < k; i++)
    {
        swap(&nums[0], &nums[numsSize - i - 1]);
        perdown(nums, numsSize - i - 1, 0);
    }
    return nums[numsSize - k];
}

二 快一点的快排

再快速排序中,划分出两个子集之后,pivot所处的位置就是最终有序数组的位置所以我们只要找到下标恰好是numsSize - k的pivot,pivot就是第k个最大元素,所以再快排中我们就要判断pivot下标与numsSize - k的关系,pivot下标大于numsSize - k,说明k位于pivot的小子集中,接下来只递归小子集即可,pivot下标小于numsSize - k同理当pivot = numsSize - k时,我们退出递归返回 nums[numsSize - k]即可。

void swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int median3(int* nums, int left, int right)
{
    int mid = (left + right) / 2;
    
    if(nums[left] > nums[mid])
        swap(&nums[left], &nums[mid]);
    if(nums[left] > nums[right])
        swap(&nums[left], &nums[right]);
    if(nums[mid] > nums[right])
        swap(&nums[mid], &nums[right]);

    swap(&nums[mid], &nums[right - 1]);
    return nums[right - 1];
}

void queck_sort(int* nums, int left, int right, int k)
{
    if(left < right)
    {
        int pivot = median3(nums, left, right);
        int i = left;
        int j = right - 1;
        while(1)
        {
            while(i < right - 1 && nums[++i] < pivot);
            while(j > 0 && nums[--j] > pivot);
            if(i < j)
                swap(&nums[i], &nums[j]);
            else
                break;
        }
        swap(&nums[i], &nums[right - 1]);
        if(i == k)
            return ;
        else if(i > k)
            queck_sort(nums, left, i - 1, k);
        else
            queck_sort(nums, i + 1, right, k);
    }
}

int findKthLargest(int* nums, int numsSize, int k) {
    queck_sort(nums, 0, numsSize - 1, numsSize - k);
    return nums[numsSize - k];
}

三 快一点的堆排

看评论的时候看到了还有一种堆排,思路很巧妙:在一个n个数的数组中,如果我们可以得到前k个最大数,那么用这k个数建一个最小堆,堆顶就是第k大的数,那么如何找到前k个最大数呢。

我们先将前k个数建一个最小堆,再遍历后面n - k个数,每碰到一个大于堆顶的数,就做交换,再建堆。这样每次都把k个数中的最小值弹出来,遍历到最后前k个数就会变成前k个最大的数。

见代码:

void swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

void perdown(int *nums, int n, int p)
{
    int parent, child;
    int temp = nums[p];
    for(parent = p; parent * 2 + 1 < n; parent = child)
    {
        child = parent * 2 + 1;
        if(child != n - 1 && nums[child + 1] < nums[child])
            child++;
        if(nums[child] > temp)
            break;
        nums[parent] = nums[child];
    }
    nums[parent] = temp;
}

int findKthLargest(int* nums, int numsSize, int k) {
    for(int i = k / 2 - 1; i >= 0; i--)
        perdown(nums, k, i);
    for(int i = k; i < numsSize; i++)
    {
        if(nums[i] > nums[0])
        {
            swap(&nums[i], &nums[0]);
            perdown(nums, k, 0);
        }
    }
    return nums[0];
}

四 递归实现堆排序

之前在慕课的浙大数据结构课程中,讲的是堆排序的迭代实现,在树的路径上不断将元素向上挪动,temp的位置不断下滤。在这道题中,官方题解给的时堆排序的递归实现。

对于最小堆来说,如果左右子树都是最小堆,那么在根节点,左子节点,右子节点三个节点中选出值最小的。如果根节点为最小值,则说明当前树为最小堆,否则交换最小值节点与根节点,在进入最小值节点的树中。

见代码:

void swap(int* a, int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void perdown(int* nums, int root, int numsSize)
{
    int mid = root;
    int left = root * 2 + 1;
    int right = root * 2 + 2;

    if(left < numsSize && nums[left] < nums[mid])
        mid = left;
    if(right < numsSize && nums[right] < nums[mid])
        mid = right;
    
    if(mid == root)
        return ;
    swap(&nums[mid], &nums[root]);
    perdown(nums, mid, numsSize);
}

int findKthLargest(int* nums, int numsSize, int k) {
    for(int i = k / 2; i >= 0; i--)
        perdown(nums, i, k);
    for(int i = k; i < numsSize; i++)
    {
        if(nums[i] > nums[0])
            swap(&nums[i], &nums[0]);
        perdown(nums, 0, k);
    }
    return nums[0];
}

其中left right分别表示左子节 点右子节点,mid存放root的值,接下来的两个if,用来判断root是不是三个树中的最小值,如果是的话mid的值不会改变,也就不用再进行堆的调整,直接return就行。如果不是的话,mid就会存放left,right中值较小的那一个,再与root中的值进行交换,然后进入该子树继续递归。

递归结束的条件就是该节点没有左右子树,那么mid最后还是root的值,也会直接return。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值