数组中的第K个最大元素

173 阅读2分钟

题目描述

构造堆

看到这个题目,找第k个大的元素,第一想法是堆排序!把数组按照堆排序的方式排列,然后进行k次移除最大的元素操作,就能得到结果。

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var arr;
var size;
var findKthLargest = function(nums, k) {
    //buildHeap
    var result;
    arr = nums;
    size = arr.length;
    for(let i=Math.floor(size/2)- 1;i>=0;i--){
        siftdown(i);
    }
    for(let i=0;i<k;i++){
        result = removeMax();
    }
    return result;
};
function siftdown(i){
    if(i > Math.floor(size/2)- 1) return;
    let child = 2 * i + 1;
    if(child < size-1){
        if(arr[child] < arr[child+1])
            child++;
    }
    if(arr[child] > arr[i]){
        swap(child,i);
        siftdown(child);
    }
}

function swap(i,j){
    let temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}v
function removeMax(){
    swap(0,size-1);
    size--;
    siftdown(0);
    return arr[size];
}

确实,这个思路能通过。 时间复杂度应该是O(n + klogn)=O(nlogn),空间复杂度是O(logn)。堆排序也确实是个不错的方法,但用堆排序有没有更优雅的方式呢? 当然是有的! 我们可以建一个大小为k的最小堆,来记录数组最大的k个元素。通过扫描数组一遍,不断更新调整这个最小堆,最后堆顶的元素,就是我们要求的结果:

var findKthLargest = function (nums, k) {
    var heap = nums.slice(0,k);
    var siftdown = (i) => {
        if(i > Math.floor(k/2)) return;
        let child = 2 * i + 1;
        if(child < heap.length-1 && heap[child+1] < heap[child]){
            child++;
        }
        if(heap[child] < heap[i]){
            swap(heap,i,child);
            siftdown(child);
        }
    }
    for(let i=Math.floor(k/2);i>=0;i--){
        siftdown(i);
    }
    for(let i=k;i<nums.length;i++){
        if(nums[i] > heap[0]){
            heap[0] = nums[i];
            siftdown(0);
        }
    }
    return heap[0];
};
function swap(nums, i, j){
    let temp = nums[j];
    nums[j] = nums[i];
    nums[i] = temp;
}

这个解法对比起对整个数组进行堆排序,用的堆空间更小了,空间复杂度是O(k),时间复杂度是O(nlogk)。

基于快速排序的思路

除了利用堆的思路,我们还可以基于快速排序来减治。 随机选取一个轴值,通过一轮快速排序的交换,获取到这个轴值的位置,如果刚好是nums.length - k,我们就找到了第k大的元素,这是最好的情况。当然,如果是一般情况,我们也减小了问题的规模。 我们令目标值target = nums.length - k,若轴值的位置p<target,我们进一步在p的右边寻找第k大的元素;否则,我们在p的左边寻找。

var findKthLargest = function (nums, k) {
   let target = nums.length - k;
   let left = 0;
   let right = nums.length - 1;
   while(true){
       let p = partition(nums,left,right);
       if(p === target) return nums[p];
       else if(p < target) left = p + 1;
       else right = p - 1;
   }
};

function partition(nums, left, right){
    let p = nums[left];
    let j = left;
    for(let i = left+1;i<=right;i++){
        if(nums[i] < p){
            j++;
            swap(nums,j,i);
        }
    }
    swap(nums,left,j);
    return j;
}

function swap(nums,i,j){
    let temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

时间复杂度:O(n),空间复杂度:O(1)