题目
给定整数数组 nums 和整数 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
题解一 堆排序
var findKthLargest = function(nums, k) {
let heapSize=nums.length
buildMaxHeap(nums,heapSize) // 构建好了一个大顶堆
// 进行下沉 大顶堆是最大元素下沉到末尾
for(let i=nums.length-1;i>=nums.length-k+1;i--){
swap(nums,0,i)
--heapSize // 下沉后的元素不参与到大顶堆的调整
// 重新调整大顶堆
maxHeapify(nums, 0, heapSize);
}
return nums[0]
// 自下而上构建一颗大顶堆
function buildMaxHeap(nums,heapSize){
for(let i=Math.floor(heapSize/2)-1;i>=0;i--){
maxHeapify(nums,i,heapSize)
}
}
// 从左向右,自上而下的调整节点
function maxHeapify(nums,i,heapSize){
let l=i*2+1
let r=i*2+2
let largest=i
if(l < heapSize && nums[l] > nums[largest]){
largest=l
}
if(r < heapSize && nums[r] > nums[largest]){
largest=r
}
if(largest!==i){
swap(nums,i,largest) // 进行节点调整
// 继续调整下面的非叶子节点
maxHeapify(nums,largest,heapSize)//调整不了就不调了
}
}
function swap(a, i, j){
let temp = a[i];
a[i] = a[j];
a[j] = temp;
}
};
笔记:
-
堆排序:
基本思想
将待排序序列构造成一个大顶堆注意:这里使用的是数组,而不是一颗二叉树
此时:整个序列的 最大值就是堆顶的根节点
将其 与末尾元素进行交换,此时末尾就是最大值
然后将剩余 n-1 个元素重新构造成一个堆,这样 就会得到 n 个元素的次小值。如此反复,便能的得到一个有序序列。
- 顺序存储二叉树
特点
第 n 个元素的 左子节点 为 2n+1
第 n 个元素的 右子节点 为 2n+2
第 n 个元素的 父节点 为 (n-1)/2
最后一个非叶子节点为 Math.floor(arr.length/2)-1
- 顺序存储二叉树
-
最后一个非叶子节点为 Math.floor(arr.length/2)-1,先得到一个数组,构造成一棵树,从最后一个非叶子节点开始,跟两个子节点比较大小,将大的那个放在父节点上,然后按顺序将前面的父节点的位置调整好,最后根节点就是最大的。取第二大的节点,将根节点与最后一个子节点交换,然后将最后一个子节点去除,再将树重排一遍,这时新的根节点就是第二大的节点。
题解二 快排
let findKthLargest = function(nums, k) {
return quickSelect(nums, nums.length - k)
};
let quickSelect = (arr, k) => {
return quick(arr, 0 , arr.length - 1, k)
}
let quick = (arr, left, right, k) => {
let index
if(left < right) {
// 划分数组
index = partition(arr, left, right)
// Top k
if(k === index) {
return arr[index]
} else if(k < index) {//都要有return!!!
// Top k 在左边
return quick(arr, left, index-1, k)
} else {
// Top k 在右边
return quick(arr, index+1, right, k)
}
}
return arr[left]
}
let partition = (arr, left, right) => {
// 取中间项为基准
var datum = arr[Math.floor(Math.random() * (right - left + 1)) + left],
i = left,
j = right
// 开始调整
while(i < j) {
// 左指针右移
while(arr[i] < datum) {
i++
}
// 右指针左移
while(arr[j] > datum) {
j--
}
// 交换 如果提前停下说明arr[i]>arr[j] 需要交换
if(i < j) swap(arr, i, j)
// 当数组中存在重复数据时,即都为datum,但位置不同
// 继续递增i,防止死循环
if(arr[i] === arr[j] && i !== j) {
i++
}
}
return i
}
// 交换
let swap = (arr, i , j) => {
let temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
var findKthLargest = function(nums, k) {
return quickSort(nums,0,nums.length-1,nums.length-k)
};
let quickSort = (arr,left,right,k)=>{
let index;
if(left<right){
index=partition(arr,left,right)
if(index === k){
return arr[index]
}else if(k<index){
quickSort(arr,left,index-1,k)
}else{
quickSort(arr,index+1,right,k)
}
}
return arr[left];
}
let partition = (arr,left,right)=>{
let dataRad=arr[Math.floor(Math.random()*(right-left+1))+left],i=left,j=right
while(i<j){
while(arr[i]<dataRad){i++}
while(arr[j]>dataRad){j--}
if(i<j){swap(arr,i,j)}
if(arr[i]===arr[j]&&i!==j){
i++
}
}
return i
}
let swap = (arr, i , j) => {
let temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
笔记:
-
快排,分区,partition
其实没有必要全部排序,可以利用快速排序的 partition 操作,找到第 K 个最大元素。
每进行一次快速排序的 partition 操作,就能找到这次我们选中的基准值排序之后的正确位置。
如果它的位置刚好是排序之后第 K 个最大元素的位置,即 len - k,我们直接得到了答案;
因为进行 partition 操作之后,位于基准值之前的元素都要小于基准值,位于基准值之后的元素都要大于等于基准值。如果它的位置小于排序之后第 K 个最大元素的位置,我们就去它之后寻找第 K 个最大元素;
如果它的位置大于排序之后第 K 个最大元素的位置,我们就去它之前寻找第 K 个最大元素; -
我们仅仅需要在每执行一次的时候,比较基准值位置是否在 n-k 位置上,如果小于 n-k ,则第 k 个最大值在基准值的右边,我们只需递归基准值右边的子序列即可;如果大于 n-k ,则第 k 个最大值在基准值的做边,我们只需递归***基准值左边的子序列即可;如果等于 n-k ,则第 k 个最大值就是基准值