Partition 搜寻第 k 个数字
找出第 k 小的数字,使用 partition 将阵列切割
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 |
搜寻第 K 大的数字
索引是从 0 开始计算,第 k 大等于是索引第 length - k
个索引数字
TypeScript
/**
* @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;
};
搜寻第 K 小的数字
索引是从 0 开始计算,第 k 小等于是索引第 k
个索引数字
TypeScript
function findKthSmallestNumber(numsList: number[], kth: number): any {
// 设定指标
let leftPointer = 0;
let rightPointer = numsList.length - 1;
while (leftPointer < rightPointer) {
// 切割点
let partitionPointer = partitionNumberList(numsList, leftPointer, rightPointer);
if (partitionPointer == kth) {
// 如果「切割点」刚好是第 k 个数字,不需要再找了,直接回传切割点指标数字
break;
}
if (partitionPointer < kth) {
// 如果「切割点」比第 k 个数字还要小,表示目前切割点是偏左,在 k 的左边
// 所以左边的数字一定是比较小不用找,可能的答案在右边
// 往右方部分找,找下一个切割点
leftPointer = partitionPointer + 1;
} else {
// 如果「切割点」比第 k 个数字还要大,表示目前切割点是偏右,在 k 的右边
// 所以右边的数字一定是比较大不用找,可能的答案在左边
// 往左方部分找,找下一个切割点
rightPointer = partitionPointer - 1;
}
}
return numsList[kth];
}
// 切割数字阵列
var partitionNumberList = function(numsList: number[], leftPointer: number, rightPointer: number): number {
// 选择右指标当作「检查数字」
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;
};