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

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function (nums, k) {
    // 基于桶排序
    const sorted = bucketSort(nums)
    return sorted[k - 1]
};

/**
 * 桶排序,时间复杂度O(n)
 * @param {number[]} arr 
 */
function bucketSort(arr) {
    if (!arr.length) {
        return []
    }
    // 1.确定桶容量
    const bucketSize = arr.length

    // 2.确定最小值最大值
    const min = Math.min(...arr)
    const max = Math.max(...arr)

    // 3.确定桶数量
    const bucketCount = Math.floor((max - min) / bucketSize) + 1
    const buckets = new Array(bucketCount).fill(0).map(() => [])

    // 4.确定每个元素放到哪个桶
    for (let i = 0; i < arr.length; i++) {
        let pos = Math.floor((arr[i] - min) / bucketSize)
        buckets[pos].push(arr[i])
    }

    // 5.对每个桶进行排序,这里升序
    for (let i = 0; i < buckets.length; i++) {
        buckets[i].sort((a, b) => a - b)
    }

    // 6.合并桶
    let res = []
    for (let i = 0; i < buckets.length; i++) {
        res = res.concat(buckets[i])
    }

    // 这里结果要降序
    return res.reverse()
}

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function (nums, k) {
    // 基于快速排序
    // 我们知道快速排序基准值q将数组a[l...r]划分成左右两个子数组,a[l...q-1]和a[q+1...r]
    // 每次划分都能确定基准值q的最终位置,所以我们只需要在某次划分的时候基准值q为倒数第k个下标的时候,我们就找到答案了。
    // q的下标小于nums.length-k时在基准值右边的数组找,大于nums.length-k时在左边的数组找

    // 定义区间[start, end],在区间找目标数
    function search(start, end) {
        if (start > end) { // 递归结束条件
            return
        }

        // 取第一个数作为基数,然后把区间划分为左右两部分
        let x = nums[start]
        // 定义两根指针,把比基数小的放到左边,把比基数大的放到右边
        let i = start
        let j = end
        while (i < j) {
            // 一定是j先移动,遇到比基数小的元素就停止
            while (i < j && nums[j] >= x) {
                j--
            }
            // 然后把元素值赋给i
            nums[i] = nums[j]

            // 然后到i移动,遇到比基数大的元素就停止
            while (i < j && nums[i] <= x) {
                i++
            }
            // 然后把元素值赋给j
            nums[j] = nums[i]
        }
        // 最后i==j,这个位置就是基数的下标
        let index = i // 基数的下标
        nums[index] = x

        // 第k大数就是倒数第k个元素
        if (index === nums.length - k) {
            return nums[index]
        } else if (index < nums.length - k) { // q小了,就往右边找
            return search(index + 1, end)
        } else if (index > nums.length - k) { // q大了,就往左边找
            return search(start, index - 1)
        }
    }

    return search(0, nums.length - 1)
};

// console.log(findKthLargest([3, 2, 1, 5, 6, 4], 2))
// console.log(findKthLargest([1], 1))
// console.log(findKthLargest([99, 99], 1))
// console.log(findKthLargest([2, 1], 2))
// console.log(findKthLargest([5, 2, 4, 1, 3, 6, 0], 4))
console.log(findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4))