3种方法实现数组第K大元素的查找

229 阅读3分钟

这是我参与更文挑战的第8天,活动详情查看: 更文挑战

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

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2 输出: 5 示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 输出: 4 说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/kt… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方法一:快排

用性能较好的排序方法对整个数组排序,找到第K大的元素。

代码参考

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function(nums, k) {
    const sortedArr = nums.sort((a,b)=>a-b)
    return sortedArr[nums.length-k]
};

image.png

方法二:快排优化

快排里的分而治之,每次拿到一个基准,可以确定基准的最终位置,即最终在有序数组里的顺序。 基准和我们要查找的K大元素的位置对比,不断缩小查找范围,知道最终的基准就是要找到的位置,即可实现对部分元素排序找到K大元素。

-_-||不知道为什么在LeetCode上执行超时,而同样的case我本地没问题。。。

代码

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function(nums, k) {
    const len = nums.length
    const target = len - k
    let left = 0
    let right = len-1
    while (true) {
        //分而治之,每次确定了数组里某个pivort的位置。这个位置和目标位置的关系做比较,决定后续要不要继续查找
        const resultIndex = partition(nums,left,right)
        if(resultIndex < target){
            left = resultIndex+1
        }else if(resultIndex > target){
            right = resultIndex - 1
        }else{
            return nums[resultIndex]
        }
    }
};
function partition(nums,left,right) {
    const len = right - left + 1
    const pivort = Math.floor(Math.random() * len )+left
    const lessThanPivortValue = nums.slice(left,right+1).filter((item,index) =>{
        const flag = (item <= nums[pivort]) && ((index+left) !== pivort)
        return flag
    })
    const bigThanPivortValue = nums.slice(left,right+1).filter((item,index) =>{
        const flag = item > nums[pivort]&& (index+left) !== pivort
        return flag
    })
    const newArr = lessThanPivortValue.concat([nums[pivort]],bigThanPivortValue)
    nums.splice(left,len,...newArr)
    return lessThanPivortValue.length
}

方法三:堆实现

用堆来存储K大的元素,遍历完数组后返回第K大元素。因此可以用最小堆,这样堆顶就是第K大元素。

最小堆我们采用完全二叉树来存储,这样有一个特定,从树根从1开始数,它的左子树的序号是最近父节点i的2i倍,右子树是最近父节点i的2i+1倍。因此我们从1开始存储堆里的有效数据。

最小堆的特性是最小的放在上面,最大的放在下面。

当我们往堆里插入一个元素,放到末尾后。这个元素如果是比它的父节点小,那么就应该和父节点交换位置,这样一直比下去,找到它的最终的大小合适的位置。

同理,当我们删除堆顶后,把末尾元素放到堆顶,这样最小保持了堆的结构。堆顶应该小于它的子节点,这样一直比较下去,找到堆顶元素合适的位置。

代码参考

class MinHeap{
    constructor(){
        //第0个元素不存储,从1开始存储
        this.heap = [0]
    }
    
    insert(data){
        //插入新的数据,并对数据排序使得最小的元素在堆顶
        this.heap.push(data)
        this.shiftUp(this.heap.length-1)
    }

    shiftUp(index){
        //元素上浮,和父元素比较大小,
        const parentIndex = this.getParentIndex(index)
        if(index === 0 || parentIndex === 0) return
        if (this.heap[parentIndex] > this.heap[index]) {
            this.swap(parentIndex,index)
            this.shiftUp(parentIndex)
        }
    }

    getParentIndex(i){
        return parseInt(i/2)
    }

    getLeftIndex(i){
        return 2 * i
    }

    getRightIndex(i){
        return 2 * i +1
    }

    swap(i1, i2){
        const temp = this.heap[i1]
        this.heap[i1]= this.heap[i2]
        this.heap[i2] = temp
    }

    delete(){
        this.heap[1] = this.heap.pop()
        this.shiftDown(1)
    }

    shiftDown(index){
        //元素下沉,和子元素比较大小
        const leftIndex = this.getLeftIndex(index)
        const rightIndex = this.getRightIndex(index)
        if (this.heap[leftIndex] <this.heap[index]) {
            this.swap(leftIndex,index)
            this.shiftDown(leftIndex)
        }
        if (this.heap[rightIndex] <this.heap[index]) {
            this.swap(rightIndex,index)
            this.shiftDown(rightIndex)
        }
    }

    getSize(){
        return this.heap.length - 1
    }

    getPeek(){
        return this.heap[1]
    }
}
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function(nums, k) {
    const heap = new MinHeap()
    for (let index = 0; index < nums.length; index++) {
        heap.insert(nums[index])
        const size = heap.getSize()
        if(size > k){
            heap.delete()
        }
    }
    return heap.getPeek()
};

image.png