Leetcode Algorithm 215. Kth Largest Element in an Array

Leetcode Algorithm 215. Kth Largest Element in an Array

Kth Largest Element in an Array
给定一个乱序的非空整型数数组,返回数组中第k大的数

解题思路

解法1

先对数组 a 利用排序算法从大到小排好序,最后直接取下标k1的值 a[k1] 即为答案。若采用快排或归并排序等算法,时间复杂度将是 O(nlogn)

解法2

在快排的过程中,其实我们是不是以及久可以定位到第 k 大的元素了呢?答案是肯定的。下面结合题目给出的样例说明。

有一个数组a,有 n 个元素:

a=[3,2,1,5,6,4]

回顾一下快排的划分过程(时间复杂度为 O(n) ):
1. 找主元(pivot),一般取开头元素,即 pivot=a[0]=3
2. 若对数组从大到小排序,就要从头遍历数组,把大于pivot的数字与小于pivot的数字分成两块;
3. 把pivot换回去两块中间。

假设划分完成之后,pivot在下标为 p 的位置,它之前的p个数一定比pivot大,pivot之后的 (np1) 个数一定比pivot小,那么pivot就是在数组中的第 (p+1) 大的数了。

p+1==k p==k1 ,正好是我们想要的答案。但是事情不一定会按照这个剧情展开,有可能 p<k1 ,也有可能 p>k1

假如 p<k1 ,说明第 k 大的数在比pivot小的那堆数里面,即a[0:p];假如 p>k1 ,说明第 k 大的数在比pivot大的那堆数里面,即a[p+1:n]

这样,我们将原本的问题就按照两种不同的情况各自分成了一个子问题,然后在指定的数组下标范围内继续用快排划分的过程找出答案。对于平均时间复杂度,有递归方程:

T(n)=T(n/2)+O(n)

用主定理可求得平均时间复杂度为 O(n)

代码与测试样例

#include <iostream>
#include <vector>

using namespace std;

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int start = 0;
        int end = nums.size();

        int p = partition(nums, start, end);

        while (p != k - 1) {
            if (p > k - 1)
                end = p;
            else
                start = p + 1;

            p = partition(nums, start, end);
        }

        return nums[p];
    }

    int partition(vector<int>& nums, int start, int end) {
        int pivot = nums[start];
        int last_small = start;

        for (int i = start + 1; i < end; i++) {
            if (nums[i] > pivot) {
                last_small++;

                if (i != last_small) {
                    int temp = nums[i];
                    nums[i] = nums[last_small];
                    nums[last_small] = temp;
                }
            }
        }

        nums[start] = nums[last_small];
        nums[last_small] = pivot;

        return last_small;
    }
};

int main() {
    int nums[] = { 3, 2, 1, 5, 6, 4 };
    int n = 6;

    vector<int> v_num(nums, nums + n);

    Solution s;

    for (int i = 0; i < v_num.size(); i++) {
        cout << s.findKthLargest(v_num, i+1) << endl;
    }

    return 0;
}

输出

6
5
4
3
2
1

知识点

快排

平均时间复杂度为 O(nlogn) ,最坏情况是 O(n2) ,原因是划分出现不平衡,每次找到的pivot恰好是数组的最小元素或最大元素,导致子问题规模只减少到 T(n1)

void quick_sort(vector<int>& nums) {
    int start = 0;
    int end = nums.size();

    int p = partition(nums, start, end);

    partition(nums, start, p);
    partition(nums, p, end);
}

int partition(vector<int>& nums, int start, int end) {
    int pivot = nums[start];
    int last_small = start;

    for (int i = start + 1; i < end; i++) {
        if (nums[i] > pivot) {               // 如果是从小到大排就改成 '<'
            last_small++;

            if (i != last_small) {
                int temp = nums[i];
                nums[i] = nums[last_small];
                nums[last_small] = temp;
            }
        }
    }

    nums[start] = nums[last_small];
    nums[last_small] = pivot;

    return last_small;
}

找乱序数组的中位数

n 为奇数,中位数实则为求第(n/2+1)大的数;若 n 为偶数,中位数为第(n/2)大和第 (n/2+1) 大的数的平均值。可以用两次找第k大的数,但是也可以在找第 (n/2+1) 大的数的途中找第 (n/2) 大的数,若在途中没找到,则继续在比第 (n/2+1) 大的数大的那边去找。

double findMedian(vector<int>& nums) {
    int start = 0;
    int end = nums.size();
    int k1, k2;
    int k2_flag = false;

    k1 = end / 2 + 1;
    if (end % 2 == 0)
        k2 = k1 - 1;
    else
        k2 = -1;

    int p = partition(nums, start, end);

    while (p != k1 - 1) {
        if (k2 == -1 && p == k2 - 1) {
            k2 = p;
            k2_flag = true;
            cout << "K2 FOUND!" << endl;
        }

        if (p > k1 - 1)
            end = p;
        else
            start = p + 1;

        p = partition(nums, start, end);
    }

    if (end % 2 != 0)
        return nums[k1 - 1];
    else {
        if (!k2_flag) {
            k2 = k1 - 1;
            start = 0;
            end = p;

            p = partition(nums, start, end);

            while (p != k2 - 1) {
                if (p > k2 - 1)
                    end = p;
                else
                    start = p + 1;

                p = partition(nums, start, end);
            }
        }
        return (nums[k1 - 1] + nums[k2 - 1]) / 2.0f;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值