Leetcode 215. Kth Largest Element in an Array

Leetcode 215. Kth Largest Element in an Array


题目

Find the **k**th largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.

Example 1:

Input: [3,2,1,5,6,4] and k = 2
Output: 5

Example 2:

Input: [3,2,3,1,2,4,5,5,6] and k = 4
Output: 4

Note:
You may assume k is always valid, 1 ≤ k ≤ array’s length.


Solution1

第一种解法是最直接的,就是sort然后取倒数第k个元素就好了

int findKthLargest(vector<int>& nums, int k) {
    sort(nums.begin(), nums.end());
    return nums[nums.size() - k];
}

分析

看起来是不是so simple。它的性能也很不错,sort是用快速排序来实现的,因此排序的时间复杂度是O(nlogn)

Solution2

第二种解法采用了快速选择算法(Quick Select Algorithm)。

快速选择算法和快速排序很像,但是由于无需做的像快排那么多(我们不关心整体是如何排序的,我们只需要得到第k大的元素就好了),因此我们可以做的比快速排序更快一些。

class Solution {
public:
    /*
    Choose an element from the array as pivot, exchange the position of pivot and number at the end of the array. 
    The pivot can either be the end element or a random chosen element. 
    A random chosen pivot can make the algorithm much possibly run in average case time.

    Partition the array into 2 parts in which the numbers in left subarray is less than (or equal to) the pivot and the numbers in right subarray is greater than (or equal to) the pivot.

    Exchange pivot (at the end of the array now) with the first element in the right part.

    Compare k with length of the left subarray, say, len. 

    if k == len + 1, then pivot is the target.
    if k <= len, repeat from step 1 on the left subarray.
    if k > len, k = k - len, repeat from step 1 on the right subarray.
    */
    int randomProducer(int min, int max) {
        default_random_engine e;
        uniform_int_distribution<int> u(min, max);
        return u(e);
    }

    int findKthLargest(vector<int>& nums, int k) {
        std::shuffle(nums.begin(), nums.end(), std::default_random_engine (2));
        k = nums.size() - k;
        int low = 0, high = nums.size() - 1;
        while (low < high) {
            int p = partition(nums, low, high);
            if (p == k) break;
            else if (p < k) low = p + 1;
            else high = p - 1;
        }
        return nums[k];
    }

    int partition(vector<int>& nums, int low, int high) {
        int i = low, j = high + 1;
        while (true) {
            while (nums[++i] < nums[low] && i < high);
            while (nums[--j] > nums[low] && j > low);
            if (i >= j) break;
            swap(nums[i], nums[j]);
        }
        swap(nums[low], nums[j]);
        return j;
    }
};

思路

和快速排序很像,要一个partition函数,用来划分数组。

首先我们将原问题转换为寻找第nums.size()-k小的元素,这是由于排序是左边小右边大,这样考虑比较符合习惯。因此k = nums.size() - k;

现在我们使用partition()函数对整个数组进行划分,每次使用一个锚点(pivot),将数组划分为两半。其中左边的一半小于锚点,而右边的一半大于锚点,函数返回这个锚点所在的位置(下标)。

假设这个下标是p。我们很容易想象,在进行这样的划分之后,锚点的值就是第p大的元素(因为左边的所有数都比它小而右边所有数又比它大)。

现在我们用p和新的k进行比较:
- 如果p < k,那么说明所寻找的元素在后边一部分,因此改low = p + 1
- 如果p > k,那么说明所寻找的元素在左边一部分,因此改high = p - 1
- 如果p == k,明显我们已经找到了这个元素,因此可以直接返回

分析

时间复杂度方面,最差的情况应该是需要O(n2),也就是说在划分的时候,每次只能把一个元素归到左边或者右边,而不是达到理想的二分的情况。

平均情况应该是O(n)的复杂度,为了避免最坏情况的出现,我们在进行运算之前最好进行一步shuffle的处理,将nums随机打乱,这样可以保证达到较好的复杂度。

我们假设每次都能达到二分(运气不是太差的话,理论上平均是可以达到二分的情况的),那么我们每次都会扫描一半的数组,也就是说

【第0次】数组长度为n,扫描n个元素
【第1次】数组长度为n/2,扫描n/2个元素
【第2次】数组长度为n/4,扫描n/4个元素
...
【第logn次】...

我们把这个总结起来,也就说一共需要O(n)的复杂度

(亲测方法1的时间是8ms,不打乱的方法2是20ms,打乱之后是4ms,如果加上取消cin特性的代码的话应该能达到0ms,不过好像也没什么追求的必要hhh)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值