215. 数组中的第K个最大元素
一、topk问题
考察的核心是可以进行topk排序算法。即不一定要全部排序好再去拿,只针对部分元素进行排序,解决topk问题的排序算法有快速排序、堆排序。
二、代码实现
1、调库函数sort()
var findKthLargest = function(nums, k) {
nums.sort((a,b) => b-a);
return nums[k-1];
};
2、快速排序
a、思想
1、划分操作:选择一个基准(通常选择第一个或者是最后一个元素)。然后从右往左进行遍历,直到找到一个比基准小的元素,将它放到左边。从左往右遍历,直到找到一个比基准大的元素,将它放到右边。这样当控制左右遍历的指针相遇时,停止遍历。这个位置就是基准的最后位置。
2、递归操作:划分之后,基准左边是比它小的数字,基准右边是比它大的数字。所以分别在基准的左右两边进行划分操作。
var quicksort = function(nums,low,high){
if(low < high){
let index = partition(nums,low,high)
// 以第一轮得出的index为基准划分出左半区和右半区 对数组的左半区进行递归 将其全部变为有序
quicksort(nums,low,index-1);
quicksort(nums,index+1,high);
}
return nums;
};
var partition = function(nums,low,high){
// 选定第一个元素为基准值 把它拿出来 即为“挖坑”.low和high就是指向左右两边的指针
// let pivot = nums[low];
// 更好的基准选择,用random取low-high之间的随机整数
let pivot_idx = Math.floor(Math.random()*(high-low+1))+low;
// pivot放置到最左边,两个值是相互替换。
nums[low], nums[pivot_idx] = nums[pivot_idx], nums[low] ;
let pivot = nums[low];
// 开始遍历
while(low < high){
// 挖了坑就需要填坑~从high指针开始向左找
while (low < high && nums[high] >= pivot) high--;
// 一旦找到比坑对应值pivot小的 就扔到low那侧的坑里
nums[low] = nums[high];
while (low < high && nums[low] < pivot) low++;
nums[high] = nums[low];
}
// 经过上面的不断迭代 low===high 此时这个位置即为基准位置
nums[low] = pivot;
// 分区成功!返回定海神针~(此时low=high哦~)
return low;
};
b、本题的代码实现
直接利用快排划分之后的index,和k-1 进行比较。
- 检查一下,index是不是为 len(nums) - k,如果是,就直接返回。 如果index比len(nums)
- 如果index比len(nums) -k要小,说明我们要找的数在右半边,我们只对右半边进行快速排序,左半边就不管了。
- 如果index比len(nums) - k要大,说明我们要找的数在左半边,我们只对左半边进行快速排序,忽略右半边。
var findKthLargest = function(nums, k) {
const n = nums.length;
const target_index = n-k;
let left = 0,
right = n-1;
// 注意只有一个元素的用例
while(left <= right){
// 目的是反复的调整区间,得到目标中的index。所以是放在循环里面
const index = partition(nums,left,right);
// 将index和n-k相比
if(index == target_index){
return nums[target_index];
}
// n-k小了,就需要到左边去找
else if(index > target_index){
right = index-1;
}
else{
left = index+1;
}
}
};
// 进行划分
var partition = function(nums,low,high){
// 选定第一个元素为基准值 把它拿出来 即为“挖坑”.low和high就是指向左右两边的指针
// let pivot = nums[low];
// 更好的基准选择,用random取low-high之间的随机整数
let pivot_idx = Math.floor(Math.random()*(high-low+1))+low;
// pivot放置到最左边,两个值是相互替换。
nums[low], nums[pivot_idx] = nums[pivot_idx], nums[low] ;
let pivot = nums[low];
// 开始遍历
while(low < high){
// 挖了坑就需要填坑~从high指针开始向左找
while (low < high && nums[high] >= pivot) high--;
// 一旦找到比坑对应值pivot小的 就扔到low那侧的坑里
nums[low] = nums[high];
while (low < high && nums[low] < pivot) low++;
nums[high] = nums[low];
}
// 经过上面的不断迭代 low===high 此时这个位置即为基准位置
nums[low] = pivot;
// 分区成功!返回定海神针~(此时low=high哦~)
return low;
};
3、堆排序
a、思想
b、本题的代码实现
使用堆排序来解决这个问题——建立一个大顶堆,做k−1 次删除操作后,堆顶元素就是我们要找的答案(堆排序过程中,不全部下沉,下沉nums.length-k+1,然后堆顶可以拿到我们top k答案了)
// 整个流程就是上浮下沉
var findKthLargest = function(nums, k) {
let heapSize=nums.length
// 1、构建好了一个大顶堆
buildMaxHeap(nums,heapSize)
// 2、进行下沉 大顶堆是最大元素下沉到末尾
for(let i=nums.length-1;i>=nums.length-k+1;i--){
swap(nums,0,i)
--heapSize // 下沉后的元素不参与到大顶堆的调整
// 3、重新调整大顶堆
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)
}
}
// 从左向右,自上而下的调整节点。i就是要调整节点的序号
function maxHeapify(nums,i,heapSize){
// 左子节点,右子节点
let l=i*2+1, 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){
[a[i],a[j]] = [a[j],a[i]]
// let temp = a[i];
// a[i] = a[j];
// a[j] = temp;
}