215. Kth Largest Element in an Array
题目
Given an integer array
nums
and an integerk
, return thekth
largest element in the array.
Note that it is the
kth
largest element in the sorted order, not thekth
distinct element.
传入数字阵列 nums
及排名 k
,找到阵列中第 k
大的数字
Example 1
Input: nums = [3,2,1,5,6,4], k = 2
Output: 5
Example 2
Input: nums = [3,2,3,1,2,4,5,5,6], k = 4
Output: 4
答案
JavaScript
1. 数字阵列先排序
/**
* @param {number[]} numsList
* @param {number} kth
* @return {number}
*/
var findKthLargest = function(numsList, kth) {
// 排序数字列(小到大排序)
numsList.sort(function(a, b) {
return a - b;
});
// 越后面数字越大,从后面找第 k 排名的数字
let kthNumber = numsList[numsList.length - kth];
return kthNumber;
};
2. 使用 QuickSelect 的 Partition 切割,将数字阵列切割排序
一般的排序需要将所有的数字都排序过,所以必须比对所有的数字,所以时间複杂度至少需要 nlog(n)
,当排序的输字阵列越多,时间複杂度就会越多
所以如果要能够在数字越多的状况下,也能够有平均速度以上的排序速度,可以选择 QuickSelect
排序选择方式,时间複杂度最快可以是 O(n)
,但最慢可能会到 O(n^2)
在数字阵列比较小的状况,直接排序
是比较快的,但在数字阵列可大可小的状况下,QuickSelect
平均速度是都能够在水准以上的
QuickSelect 流程
Input: nums = [3,2,1,5,6,4], k = 2
Output: 5
A. 选择一用来比较的 pivotCheckNumber 当作检查数字
- 确保「检查数字」左边的数字,都是比「检查数字」小
- 确保「检查数字」右边的数字,都是比「检查数字」大
一开始随机选择 最右边
的数字当作检查数字
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
数字 | 3 | 2 | 1 | 5 | 6 | 4 |
检查指标 | 左指标 | 右指标 | ||||
其他指标 | 最小数字指标 | 检查数字指标 |
目标要将比「检查数字」
小的数字放在左边,比较大的就放在「检查数字」
右边
所以就拿「最小数字指标」
当作储存较小数字的指标位置,预设是最左边
B. 从左指标检查到右指标
当「左指标」数字
比 「检查数字」
小,就将「左指标」数字
放到 「最小数字指标」
,而 「最小数字指标」
就往右移动,等待放入下一个比 「检查数字」
小的数字,然后持续检查到右指标
Step 1
左指标数字 3
比检查数字指标数字 4
小,将左指标数字 3
与最小数字指标
数值交换「最小数字指标」
往右移动左指标
往右移动
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
数字 | 3 | 2 | 1 | 5 | 6 | 4 |
检查指标 | 左指标 | 右指标 | ||||
其他指标 | 最小数字指标 | 检查数字指标 |
Step 2
左指标数字 2
比检查数字指标数字 4
小,将左指标数字 2
与最小数字指标
数值交换「最小数字指标」
往右移动左指标
往右移动
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
数字 | 3 | 2 | 1 | 5 | 6 | 4 |
检查指标 | 左指标 | 右指标 | ||||
其他指标 | 最小数字指标 | 检查数字指标 |
Step 3
左指标数字 1
比检查数字指标数字 4
小,将左指标数字 1
与最小数字指标
数值交换「最小数字指标」
往右移动左指标
往右移动
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
数字 | 3 | 2 | 1 | 5 | 6 | 4 |
检查指标 | 左指标 | 右指标 | ||||
其他指标 | 最小数字指标 | 检查数字指标 |
Step 4
左指标数字 5
比检查数字指标数字 4
大,不做任何动作左指标
往右移动
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
数字 | 3 | 2 | 1 | 5 | 6 | 4 |
检查指标 | 左指标 | 右指标 | ||||
其他指标 | 最小数字指标 | 检查数字指标 |
Step 5
左指标数字 6
比检查数字指标数字 4
大,不做任何动作左指标
往右移动
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
数字 | 3 | 2 | 1 | 5 | 6 | 4 |
检查指标 | 左指标 & 右指标 | |||||
其他指标 | 最小数字指标 | 检查数字指标 |
Step 6
左指标
已经检查到右指标
位置了,不再继续检查- 将
检查数字指标数字 4
与最小数字指标数字 5
交换 - 所以目前切割的位置在
最小数字指标索引 3
的位置,数值是4
最小数字指标索引 3
的位置右边都是比4
大的数字最小数字指标索引 3
的位置左边都是比4
小的数字
- 所以目前
最小数字指标索引
的切割位置及数字都是确定的
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
数字 | 3 | 2 | 1 | 4 | 6 | 5 |
检查指标 | 左指标 & 右指标 | |||||
其他指标 | 最小数字指标 |
检查数字指标 |
Step 7
因为要选择第 k 大
的数字,所以在排序的数字阵列中,选择 (length - k) 阵列索引
的数字
- 如果要选
第 1 大
的数字,等于是要选择索引阵列中(6 - 1) = 5 第 5 个索引
位置的数字 - 如果要选
第 2 大
的数字,等于是要选择索引阵列中(6 - 2) = 4 第 4 个索引
位置的数字 - 如果要选
第 3 大
的数字,等于是要选择索引阵列中(6 - 3) = 3 第 3 个索引
位置的数字 - 如果要选
第 4 大
的数字,等于是要选择索引阵列中(6 - 4) = 2 第 2 个索引
位置的数字 - …以此类推
状况 1: 要找的数字是刚好是切割位置
确定的 切割位置
是 最小数字指标
,所以索引位置确定会是 3
,数字确定会是 4
若刚好要找 第 3 大
的数字,等于是要选择索引阵列中 (6 - 3) = 3 第 3 个索引
位置的数字
等于 目前的切割点
就是要找的 第 k 大数字
,可以直接回传 4
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
数字 | 3 | 2 | 1 | 4 | 6 | 5 |
检查指标 | 左指标 & 右指标 | |||||
其他指标 | 最小数字指标 |
检查数字指标 |
状况 2: 要找的数字是大小是大于
切割位置的数字
假如要找 第 2 大
的数字,等于是要选择索引阵列中 (6 - 2) = 4 第 4 个索引
位置的数字
但目前的切割位置
是在 最小数字指标,第 3 个索引
,而刚刚切割流程中,已经确定
最小数字指标索引 3
的位置右边都是比4
大的数字最小数字指标索引 3
的位置左边都是比4
小的数字
表示现在要往 切割位置右方
去找,才能找到 第 2 大
的数字,所以就重複刚刚的步骤,重新对右半边
的数字阵列做切割比较即可
- 可以将
左指标
设定在切割位置
右边4
- 右边区块的
最小数字指标
设定与左指标
相同 - 然后重複刚刚的步骤,直到找到
切割位置索引位置
与第 k 大数字
相同,符合状况 1: 要找的数字是刚好是切割位置
即可
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
数字 | 3 | 2 | 1 | 4 | 6 | 5 |
检查指标 | 左指标 | 右指标 | ||||
其他指标 | X | X | X | X | 最小数字指标 | 检查数字指标 |
状况 3: 要找的数字是大小是小于
切割位置的数字
假如要找 第 4 大
的数字,等于是要选择索引阵列中 (6 - 4) = 2 第 2 个索引
位置的数字
与 状况 2
雷同,只是要反过来往 切割位置左方
去找,才能找到 第 4 大
的数字,所以就重複刚刚的步骤,重新对左半边
的数字阵列做切割比较即可
- 可以将
右指标
设定在切割位置
左边2
- 左边区块的
最小数字指标
设定与左指标
相同 - 然后重複刚刚的步骤,直到找到
切割位置索引位置
与第 k 大数字
相同,符合状况 1: 要找的数字是刚好是切割位置
即可
索引 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
数字 | 3 | 2 | 1 | 4 | 6 | 5 |
检查指标 | 左指标 | 右指标 | ||||
其他指标 | 最小数字指标 | 检查数字指标 | X | X | X |
/**
* @param {number[]} numsList
* @param {number} kth
* @return {number}
*/
var findKthLargest = function(numsList, kth) {
// 选择第 k 大的数字 : 在排序的数字阵列中,选择 (length - k) 阵列索引的数字
let kIndexNumber= numsList.length - kth;
// 左指标:从阵列头开始
let leftPointer = 0;
// 右指标:从阵列尾开始
let rightPointer = numsList.length - 1;
while (leftPointer < rightPointer) {
// 当左右指标没有交叉,继续寻找
let partitionPointer = partitionNumberList(numsList, leftPointer, rightPointer);
if (partitionPointer == kIndexNumber) {
// 目前切割的点,就是要寻找的第 k 个索引的数字
break;
} else if (partitionPointer < kIndexNumber) {
// 目前切割的点,比要找的 k 个索引数字还小,切割点左边确认数字更小不需要找,往切割点右边部分继续找
// 将「左指标」移动到「切割点」右方
leftPointer = partitionPointer + 1;
} else {
// 目前切割的点,比要找的 k 个索引数字还大,切割点右边确认数字更大不需要找,往切割点左边部分继续找
// 将「右指标」移动到「切割点」左方
rightPointer = partitionPointer - 1;
}
}
return numsList[kIndexNumber];
};
// 切割数字阵列
var partitionNumberList = function(numsList, leftPointer, rightPointer) {
// 选择右指标当作「检查数字」
let pivotCheckNumber = numsList[rightPointer];
// 较小数字纪录指标设定为左指标
let smallerNumberPointer = leftPointer;
for(let i = leftPointer; i < rightPointer; i++) {
// 从「左指标」检查到「右指标」
if (numsList[i] <= pivotCheckNumber) {
// 若「目前检查的数字」小于等于「检查的数字」
// 将目前数字放到「较小数字记录指标」位置
[numsList[smallerNumberPointer], numsList[i]] = [numsList[i], numsList[smallerNumberPointer]];
// 「较小数字记录指标」位置往后移动
smallerNumberPointer++;
}
}
// 检查完成,确认「较小数字记录指标」数字都比「检查数字」小
// 「较小数字记录指标」与「检查数字」交换
// - 「较小数字记录指标」左方都是比「检查数字」小的数字
// - 「较小数字记录指标」右方都是比「检查数字」大的数字
[numsList[smallerNumberPointer], numsList[rightPointer]] = [numsList[rightPointer], numsList[smallerNumberPointer]];
return smallerNumberPointer;
};