站在巨人的肩膀上
思路
直接用原生方法倒叙排序后取值,取巧的办法,哈哈。
代码
function findKthLargest(nums: number[], k: number): number {
return nums.sort((a, b) => b - a)?.[k - 1];
};
快排
利用快排的思想找出第k大的值
思路
快排基本思路:
- 随便找数组
[i, j]里的一个基准值,假设q是该基准值的索引; - 然后将比
q大的值移动到p的右边,将比q小的值移动到p的左边,这样数组[i, j]就会被q分成左右两个区间[i, q - 1]和[i, q + 1]; - 然后递归两个区间做同样的操作,最后能完成排序。
这里的q每次调整完就是最终排好序的位置,因为目标就是找到第k大的值,只要刚好是第k大的数,直接返回就行了,后面就不用判断,不需要将整个数组都排好序。
这里有个优化点,判断一下q和k的位置,如果q在k左边,只需要递归左区间就行,反正,递归右区间
代码
function findKthLargest(nums: number[], k: number): number {
return quickSelect(nums, 0, nums.length - 1, nums.length - k);
};
function quickSelect(nums: number[], l: number, r: number, index: number): number {
const q = partition(nums, l, r);
if (q === index) {
// 找到目标值,返回
return nums[q];
} else {
return q < index ?
quickSelect(nums, q + 1, r, index) : // 递归右区间
quickSelect(nums, l, q - 1, index); // 递归左区间
}
}
function partition(nums: number[], l: number, r: number): number {
let x = nums[r]; // 以最后一项为基准点,大于它放它右边,小于它的放它左边
let i = l - 1; // 基准点最后的位置
for (let j = l; j < r; j++) {
// 当前值小于基准点,则交换
if (nums[j] <= x) {
swap(nums, ++i, j); // i加1,保证i是最后一个小于基准值的值的位置
}
}
swap(nums, ++i, r); // i加1变成第一个大于基准值的位置,再交换第一个大于基准值和基准值的位置
return i; // 最后的i就是基准值的位置
}
/**
* 交换两数
*/
function swap(nums: number[], i: number, j: number): void {
[nums[i], nums[j]] = [nums[j], nums[i]];
}
代码优化
问题:
如果每次都取同一个位置的值作为基准值(这里是区间的最后一个值),有时候会出现一种极端情况,就是一直在左区间或一直在右区间进行递归,这种情况是最坏的,时间复杂度是 O(n^2)
解决办法:
我们可以引入随机化来加速这个过程,基准值每次都是随机取的,它的时间复杂度会降为 O(n)。
function findKthLargest(nums: number[], k: number): number {
return quickSelect(nums, 0, nums.length - 1, nums.length - k);
};
function quickSelect(nums: number[], l: number, r: number, index: number): number {
- const q = partition(nums, l, r);
+ const q = randomPartition(nums, l, r);
if (q === index) {
// 找到目标值,返回
return nums[q];
} else {
return q < index ?
quickSelect(nums, q + 1, r, index) : // 递归右区间
quickSelect(nums, l, q - 1, index); // 递归左区间
}
}
function partition(nums: number[], l: number, r: number): number {
let x = nums[r]; // 以最后一项为基准点,大于它放它右边,小于它的放它左边
let i = l - 1; // 基准点最后的位置
for (let j = l; j < r; j++) {
// 当前值小于基准点,则交换
if (nums[j] <= x) {
swap(nums, ++i, j); // i加1,保证i是最后一个小于基准值的值的位置
}
}
swap(nums, ++i, r); // i加1变成第一个大于基准值的位置,再交换第一个大于基准值和基准值的位置
return i; // 最后的i就是基准值的位置
}
/**
* 交换两数
*/
function swap(nums: number[], i: number, j: number): void {
[nums[i], nums[j]] = [nums[j], nums[i]];
}
+ function randomPartition(nums: number[], l: number, r: number): number {
+ const i = getRandom(l, r); // 获取范围内的随机值
+ swap(nums, i, r); // 和最后一项交换
+ return partition(nums, l, r);
+ }
+ /**
+ * 获取指定范围内的随机数
+ */
+ function getRandom(n: number, m: number): number {
+ return Math.floor(Math.random() * (m - n + 1) + n)
+ }
堆排序
思路
我们需要的是第k大的元素,只需要对部分元素进行排序,无需对整个数组进行排序。
堆是一个完全二叉树,所以可以用数组进行映射,堆的特点:
- 第
n个元素的左子节点为2 * n + 1 - 第
n个元素的右子节点为2 * n + 2 - 第
n个元素的父节点为Math.floor((n - 1) / 2) - 最后一个非叶子节点为
Math.floor(arr.length / 2) - 1

堆排序是基于堆这种数据结构而设计的一种算法。
堆的种类有两种:
- 大顶堆:每个节点的值都 大于或等于 其左右孩子节点的值
- 小顶堆:每个节点的值都 小于或等于 其左右孩子节点的值
结点的两个孩子节点大小没有要求
这里查找第k大的元素,显然用大顶堆比较合适。
堆排序就是对部分元素进行排序,利用堆排序可以将无序数组遍历具有一定规律的大顶堆数组。大顶堆的根元素,也就是数组的第一项一定是最大的,删除最大的重新排序,将第二大的移动到堆顶,重复删除k-1次,即可获取第k大的元素。
如何将无序数组变成大顶堆参考文章:www.cnblogs.com/chengxiao/p…
代码
function findKthLargest(nums: number[], k: number): number {
let { length } = nums;
buildMaxHeap(nums, length); // 将数组构建为一个大顶堆
// 找第k大的元素
for (let i = nums.length - 1; i >= nums.length - k + 1; i--) {
swap(nums, 0, i); // 将堆顶元素(最大的那个)放到数组最后面
length--; // 堆顶元素不再参数后续的上浮
// 将后续的最大值浮上来
bubble(nums, 0, length);
}
return nums[0];
};
function buildMaxHeap(nums: number[], length: number) {
for (let i = Math.floor(length / 2); i >= 0; i--) {
bubble(nums, i, length); // 进行上浮操作
}
}
/**
* 上浮
*/
function bubble(nums: number[], i: number, length: number) {
const l = i * 2 + 1; // 左子节点
const r = i * 2 + 2; // 右子节点
let largest = i; // 最大值下标
// 比较当前节点和其两个子节点的大小,报错最大值的下标
if (l < length && nums[l] > nums[largest]) {
largest = l;
}
if (r < length && nums[r] > nums[largest]) {
largest = r;
}
// 如果当前值不是最大值
if (largest !== i) {
// 上浮
swap(nums, i, largest);
// 继续调整下面的子节点
bubble(nums, largest, length);
}
}
/**
* 交换两数
*/
function swap(nums: number[], i: number, j: number) {
[nums[i], nums[j]] = [nums[j], nums[i]];
}