leetcode-215. 数组中的第K个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
大顶堆
class Solution {
public:
void heapSortHelper(vector<int>& nums, int start, int len) {
int child = 2*start + 1;
int tmp = nums[start];
while(child < len) {
if (child < len - 1 && nums[child] < nums[child + 1])
child++;
if(tmp >= nums[child])
break;
nums[start] = nums[child];
start = child;
child = 2*child+1;
}
nums[start] = tmp;
}
int findKthLargest(vector<int>& nums, int k) {
int len = nums.size();
if(len < k) return 0;
int non_leaf_index = (len - 1)/2;
int tmp;
for(int i = non_leaf_index; i >= 0; i--) {
heapSortHelper(nums, i, len);
}
// 执行 k - 1 次
for(int i = len - 1; i >= len + 1 - k; i--) {
tmp = nums[i];
nums[i] = nums[0];
nums[0] = tmp;
heapSortHelper(nums, 0, i);
}
// 此时堆顶元素就是第k大元素
return nums[0];
}
};
时间复杂度:O(nlogn),建堆的时间代价是 O(n),删除的总代价是 O(klogn)
空间复杂度:O(1)
堆排序的源码
#include <iostream>
#include <vector>
using namespace std;
void printVector(vector<int> &v) {
for(int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << endl;
}
void heapSortHelper(vector<int>& nums, int start, int len) {
int child = 2*start + 1;
int tmp = nums[start];
while(child < len) {
if (child < len - 1 && nums[child] < nums[child + 1])
child++;
if(tmp >= nums[child])
break;
nums[start] = nums[child];
start = child;
child = 2*child+1;
}
nums[start] = tmp;
}
void heapSort(vector<int>& nums) {
int len = nums.size();
int non_leaf_index = (len - 1)/2;
int tmp;
for(int i = non_leaf_index; i >= 0; i--) {
heapSortHelper(nums, i, len);
}
printVector(nums);
for(int i = len - 1; i > 0; i--) {
tmp = nums[i];
nums[i] = nums[0];
nums[0] = tmp;
heapSortHelper(nums, 0, i);
}
printVector(nums);
}
int main ()
{
vector<int> nums = {5,4,2,7,1,9,0,3,4};
heapSort(nums);
return 0;
}
引入随机化的快排做法(参考别人)
class Solution {
public:
int quickSelect(vector<int>& a, int l, int r, int index) {
int q;
// l = 3, r = 5 的时候
// i 的取值范围是 1,2,3
int i = rand() % (r - l + 1) + l;
swap(a[i], a[r]);
q = partition(a, l, r);
if (q == index) {
return a[q];
} else {
return q < index ? quickSelect(a, q + 1, r, index) : quickSelect(a, l, q - 1, index);
}
}
inline int partition(vector<int>& a, int l, int r) {
int x = a[r], i = l;
for (int j = l; j < r; j++) {
if (a[j] <= x) {
swap(a[i], a[j]);
i++;
}
}
swap(a[i + 1], a[r]);
return i + 1;
}
int findKthLargest(vector<int>& nums, int k) {
// srand,随机数发生器的初始化函数
srand(time(0));
// 传入 第一个位置和最后一个位置
// 传入 期望的位置,对于长度为 len 的升序数组的倒数第k个位置存储的就是数组第k大元素
// 倒数第 1 个 len-1
// 倒数第 k 个 len-k
return quickSelect(nums, 0, nums.size() - 1, nums.size() - k);
}
};
时间复杂度:O(n)
空间复杂度:O(logn)
只要某次划分的 q 为倒数第 k 个下标时,就已经找到了答案
只需要确保 a[start, end]中,a[start, q-1] 所有元素比 a[q]小,a[q+1, end] 所有元素比 a[q] 大
在分解的过程当中,对子数组进行划分,如果划分得到的 q 正好就是需要的下标,直接返回 a[q];
否则,如果 q 比目标下标小,就递归右子区间,否则递归左子区间。
这样就可以把原来递归两个区间变成只递归一个区间,提高了时间效率。
这就是「快速选择」算法。
快速排序的性能和「划分」出的子数组的长度密切相关。
直观地理解如果每次规模为 n 的问题都划分成 1 和 n - 1,
每次递归的时候又向 n - 1 的集合中递归,这种情况是最坏的,时间代价是 O(n^2)。
引入随机化来加速这个过程,它的时间代价的期望是 O(n)
上文的若干接口
srand(time(0)); //先设置种子
rand(); //产生随机数
srand是种下随机种子数,每回种下的种子不一样,用rand得到的随机数就不一样。
为了每回种下一个不一样的种子,选用time(0),time(0)是得到当前时时间值(每时每刻时间不一样)。
解析
inline int partition(vector<int>& a, int l, int r) {
int x = a[r], i = l;
// 这不就是个快慢指针嘛
for (int j = l; j < r; ++j) {
if (a[j] <= x) {
swap(a[i], a[j]);
i++;
}
}
swap(a[i], a[r]);
return i;
}
l r
2 9 4 7 3 6 5
0 1 2 3 4 5 6
i
j
j = 0
2 9 4 7 3 6 5
0 1 2 3 4 5 6
i
j = 1
2 9 4 7 3 6 5
0 1 2 3 4 5 6
i
j = 2
2 4 9 7 3 6 5
0 1 2 3 4 5 6
i
j = 3
2 4 9 7 3 6 5
0 1 2 3 4 5 6
i
j = 4
2 4 3 7 9 6 5
0 1 2 3 4 5 6
i
j = 5
2 4 3 7 9 6 5
0 1 2 3 4 5 6
i
最后
j = 4
2 4 3 5 9 6 7
0 1 2 3 4 5 6
i
返回 i = 3
i 左边的数比 i 的小
i 右边的数比 i 的大
本人重写
3 2 1 5 6 4
l r
3 2 1 5 6 4
l r
3 2 1 5 6 4
l r
3 2 1 5 6 4
l r
3 2 1 6 5 4
lr
3 2 1 6 5 4
r l
1 2
lr
1 2
r l
2 1
lr
2 1
l
1 2
l
2 4 3 5 9 6 7
l r
2 4 3 5 9 6 7
l r
2 4 3 5 9 6 7
l r
2 4 3 5 9 6 7
l r
2 4 3 5 9 6 7
l r
2 4 3 5 6 9 7
lr
2 4 3 5 6 9 7
r l
2 4 3 5 6 7 9
r l
inline int partition(vector<int>& a, int l, int r) {
int index = r;
r--;
while(l <= r) {
if (a[l] > a[index]) {
swap(a[l],a[r]);
r--;
} else {
l++;
}
}
swap(a[l],a[index]);
return l;
}
最终
class Solution {
public:
// 《算法导论》9.2:期望为线性的选择算法
int quickSelect(vector<int>& a, int l, int r, int index) {
int i = rand() % (r - l + 1) + l;
swap(a[i], a[r]);
int q = partition(a, l, r);
if (q == index) {
return a[q];
} else {
return q < index ? quickSelect(a, q + 1, r, index) : quickSelect(a, l, q - 1, index);
}
}
inline int partition(vector<int>& a, int l, int r) {
int index = r;
r--;
while(l <= r) {
if (a[l] > a[index]) {
swap(a[l],a[r]);
r--;
} else {
l++;
}
}
swap(a[l],a[index]);
return l;
}
int findKthLargest(vector<int>& nums, int k) {
srand(time(0));
// 传入 第一个位置和最后一个位置
// 传入 期望的位置,对于长度为 len 的升序数组的倒数第k个位置存储的就是数组第k大元素
// 倒数第 1 个 len-1
// 倒数第 k 个 len-k
return quickSelect(nums, 0, nums.size() - 1, nums.size() - k);
}
};
又手敲了一遍降序
这个更好理解
class Solution {
public:
int randomSortHelper(vector<int>& nums, int start, int end) {
int new_end = end - 1;
while(start <= new_end) {
if(nums[start] >= nums[end])
start++;
else {
swap(nums[start], nums[new_end]);
new_end--;
}
}
swap(nums[start], nums[end]);
return start;
}
// 降序查找,找index位置的值
int randomSort(vector<int>& nums, int start, int end, int index) {
int tmp = rand() % (end - start + 1) + start;
swap(nums[tmp], nums[end]);
int new_index = randomSortHelper(nums, start, end);
// 这里是if不是while
if(new_index != index) {
if(new_index > index) {
new_index = randomSort(nums, start, new_index - 1, index);
} else {
new_index = randomSort(nums, new_index + 1, end, index);
}
}
return nums[index];
}
int findKthLargest(vector<int>& nums, int k) {
srand(time(0));
int len = nums.size();
if(len < k)
return 0;
// 第k大的位置位于降序全排序数组的k-1位置
// 因为0位置是最大元素
return randomSort(nums, 0, len-1, k - 1);
}
};