分析
条件:
- 数组长度为
numsSize 1<=k<=numsSize, 数组元素取值在[-10000, 10000]区间.
数组中第 k 大的数
- 即
降序时下标为k-1的数,升序时下标为numsSize-k的数
我们首先想到的就是对这个数组进行降序排序, 下标为 k-1 的元素就是我们要找的第 k 大的数.
但是仔细想想, 数据量小了还可以, 当数据量较大时, 可能会有效率问题. 因此我们只要把第 k 大的数放到正确的位置就可以用下标方法取出我们要换的数.
因此我们可以结合选择排序 和 快速排序的思路, 去把这个第 k 大的数找出来即可, 而不需要对整个数组进行排序, 只需要进行部分排序.
我们暂且称为 选择排序法 和 快速排序法, 下面我们首先看看 选择排序法
选择排序法
以降序为例进行说明:
- 每次找到一个剩余元素中最大的数, 当找到下标为
k-1的元素时, 就是我们要找的第k大的数, 就可以结束循环了.
int findKthLargest(int *nums, int numsSize, int k) {
if (k < 1 || k > numsSize) { return -10001; }
// true 用降序, false 升序
bool isDesc = k <= numsSize / 2;
for (int i = 0; i < numsSize; i++) {
if (nums[i] < -10000 || nums[i] > 10000) {
return -10001;
}
int idx = i;
for int j = i + 1; j < numsSize; j++) {
if (isDesc) {
// 降序
if (nums[j] > nums[idx]) {
idx = j;
}
} else {
// 升序
if (nums[j] < nums[idx]) {
idx = j;
}
}
}
swap(nums, i, idx);
if (isDesc) {
if (k == i + 1) {
return nums[i];
}
} else {
if (k == numsSize - i) {
return nums[i];
}
}
}
return -10001;
}
注意:
bool isDesc = k <= numsSize / 2;我们用这个标记去记录这个元素大小在数组中整体的排序的分布情况,如果
k < numsSize/2, 则使用按降序, 最多遍历numsSize/2次,如果
k > numsSize/2, 如果使用降序, 遍历次数就要超过numsSize/2次, 此时, 如果用升序, 找下标为numsSize-k的数, 最多遍历numsSize/2次, 也就是我们要找的 第 k 大的数, 更高效;
快速排序法
同样以降序为例进行说明:
- 我们要找的下标为
k-1, 所以在最后递归时, 就要判断我们要找的下标为k-1的这个元素在哪个范围, 只需要递归他所在的范围即可, 不在这个范围的不需要管, 他不会影响确认他的位置. 如果正好k-1就在分界位置, 那就不需要继续了, 因为分界位置的顺序是确定且是正确的.
void quickSortForK(int *list, int left, int right, int k) {
if (left >= right) { return; }
int i = left, j = right;
int key = list[i];
while (i < j) {
while (i < j && list[j] <= key) {
j--;
}
swap(list, i, j);
while (i < j && list[i] >= key) {
i++;
}
swap(list, i, j);
}
list[i] = key;
if (k-1 < i) {
quickSortForK(list, left, i-1, k);
} else if (k-1 > i) {
quickSortForK(list, i+1, right, k);
} else {
return;
}
}
int findKthLargest2(int *nums, int numsSize, int k){
if (k < 1 || k > numsSize) { **return** -10001; }
quickSortForK(nums, 0, numsSize-1, k);
return nums[k-1];
}
注意:
swap函数参照 交换数组元素的位置 这篇文章