LeetCode: 215.数组中的第 K 个最大元素|刷题打卡

396 阅读3分钟

一、题目描述

在未排序的数组中找到第 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 ≤ 数组的长度。

二、思路分析

简单来说就是获取降序排序数组中下标为 k - 1 的值。
它这个第 k 个最大的元素不需要考虑数值重复的情况,直接排序后获取第 k 个值就可以了。

三、AC 代码

排序

var findKthLargest = function(nums, k) {
    return nums.sort((a, b) => b - a)[k - 1]
    
    /**
     * const _nums = nums.sort((a, b) => b - a) // 降序排序
     * return _nums[k - 1] // 返回第 k 个最大的元素。
     * 因为数组是从 0 开始,所以要拿 k - 1 的那个元素
    */
}

最小堆

/**
 * 最小堆
 * 把值插入堆的底部,即数组的尾部。
 * 然后上移,把这个值和它的父节点进行交换,直到父节点小于等于这个插入的值
 * 大小为 k 的堆中,插入的元素的时间复杂度是 O(logk)
 */ 
class MinHeap {
    constructor() {
        this.heap = [] // 堆
    }
	
    /**
     * 插入值
     * @param value 要插入的值
     */ 
    insert(value) {
        this.heap.push(value) // 把值推入堆的尾部
        this.shiftUp(this.heap.length - 1) // 执行上移方法
    }
	
    /**
     * 上移
     * @param index 要上移的值的下标
     */ 
    shiftUp(index) {
        if(index === 0) return // 边界判断
        const heap = this.heap
        const parentIndex = this.getParentIndex(index) // 获取上移值的父节点的下标
        
        // 当父节点的值大于当前值,则进行上移,否则不做任何操作
        if(heap[parentIndex] > this.heap[index]) {
            this.swap(parentIndex, index) // 父子节点交换
            this.shiftUp(parentIndex) // 继续向上移操作
        }
    }
	
    /**
     * 删除堆顶
     * 由于 堆 的数据结构,所以我们不能直接删除堆顶的数据,需要把堆底的数据和堆顶的进行替换,然后进行下移操作
     */ 
    pop() {
        this.heap[0] = this.heap.pop() // 替换堆顶数据为堆底的
        this.shiftDown(0) // 下移
    }
	
    /**
     * 下移
     * @param index 要下移的值的下标
     */ 
    shiftDown(index) {
        if(index >= this.length) return // 边界判断
        const heap = this.heap
        const leftIndex = this.getLeftIndex(index) // 获取左子节点
        const rightIndex = this.getRightIndex(index) // 获取右子节点
        
        // 当前节点值 大于 左子节点的值的时候
        if(heap[index] > heap[leftIndex]) {
            this.swap(index, leftIndex) // 交换
            this.shiftDown(leftIndex) // 继续下移
        } 
        
        // 当前节点值 大于 右子节点的值的时候
        // 这里值得注意的是,当上述左子节点交换后,左子节点的值不一定就比右子节点小
        // 所以哪怕上述方法通过且进行交换了,下述方法也要进行判断
        if(heap[index] > heap[rightIndex]) {
            this.swap(index, rightIndex) // 交换
            this.shiftDown(rightIndex) // 继续下移
        }
    }
	
    /**
     * 交换值
     * @param parentIndex 父节点下标
     * @param index 子节点下标
     */ 
    swap(parentIndex, index) {
        const heap = this.heap
        const temp = heap[parentIndex]
        heap[parentIndex] = heap[index]
        heap[index] = temp
    }
	
    /**
     * 获取父元素的下标
     * @param index 需要获取父元素下标元素的下标
     */ 
    getParentIndex(index) {
    	// >> 右移操作符 就是把二进制数右移一位,等于除以二取商
        return (index - 1) >> 1 
        // return Math.floor( (index - 1) / 2 ) 改代码和上述代码效果一致
    }
	
    /**
     * 获取左子节点
     * @param index 当前节点下标
     */ 
    getLeftIndex(index) {
    	// 左移操作符 二进制位左移一位 相当于乘 2 | index * 2 + 1
        return (index << 1) + 1
    }
	
    /**
     * 获取右子节点
     * @param index 当前节点下标
     */ 
    getRightIndex(index) {
    	// 左移操作符 二进制位左移一位 相当于乘 2 | index * 2 + 2
        return (index << 1) + 2
    }
	
    /**
     * 获取堆顶
     */ 
    peek() {
        return this.heap[0]
    }
	
    /**
     * 获取堆的长度
     */ 
    size() {
        return this.heap.length
    }
}

// 获取第 k 个最大元素
// 该算法的思路是通过最小堆来设置堆顶为最小的值
// 然后限制当前堆的长度为 k 如果超出 k 个数,就把堆顶给删除掉
// 那么到最后堆顶自然就是第 k 大的元素了
var findKthLargest = function(nums, k) {
    const h = new MinHeap()
    nums.forEach(v => {
        h.insert(v)
        if(h.size() > k) {
            h.pop()
        }
    })
    return h.peek()
}

四、总结

其实主要问题就是排序,排序过后这个问题就迎刃而解了。

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情