owned this note
owned this note
Published
Linked with GitHub
{%hackmd @lumynou5/dark-theme %}
# leetcode 215 Kth Largest Element in Array
[原題](https://leetcode.com/problems/kth-largest-element-in-an-array/description/) 要找出未排序數組中 第K大的元素
**[先推一下這篇 排序跟搜索都講很細](https://web.ntnu.edu.tw/~algo/Sort.html)**
### 1.排序
簡單來說就是懶
#### qsort版 直接排序大到小
```c
int cmp(const void*a,const void*b){return *(int*)b-*(int*)a;}
int findKthLargest(int*a,int n,int k){
qsort(a,n,4,cmp);//126ms,13.2mb
return a[k-1];
}
```
時間複雜度平均$O(nlogn)$最差$O(n^2)$ 空間$O(1)$
還能不能更快呢
#### std::sort(introsort)版
```cpp
int findKthLargest(vector<int>&v,int k){
//sort(v.rbegin(),v.rend());//168ms,45.3mb
sort(v.begin(),v.end(),greater<>());//114ms,45.5mb
//兩種都行
return v[k-1];
}
```
時間複雜度平均$O(nlogn)$最差$O(nlogn)$ 空間$O(1)$
解決了worse case
但是還能快
### 2.QuickSelect
用跟quick sort一樣的想法去做 但是不用把排序排完
一樣可以用STL
```cpp
int findKthLargest(vector<int>&v,int k){
nth_element(v.begin(),v.begin()+k-1,v.end(),greater<>());//91ms,45.6mb
return v[k-1];
}
```
時間複雜度平均$O(n)$最差$O(n^2)$ 空間$O(1)$
也可以跟quick sort變intro sort一樣 讓quick select變intro select
時間複雜度平均$O(n)$最差$O(n)$ 空間$O(1)$
### 3.priority_queue
#### STL pq
用pq好寫的方法當然是全部push進去再pop k次
```cpp
int findKthLargest(vector<int>&v,int k){
priority_queue<int> pq;
for(int e:v)pq.push(e);
for(;--k;)pq.pop();
return pq.top();
}
//130ms,49.9mb
```
時間複雜度平均$O(nlogn)$最差$O(n^2)$(數列遞減時) 空間$O(n)$
既然速度跟qsort一樣慢 空間花最多
而且會pq的人還比會std::sort的人少 那為甚麼我還要提呢
#### linear pq
**[SA 流 C++ 競程修練心法](https://hackmd.io/@sa072686/cp/%2FGTdCSUuiR56t0K7TxtlF7A)**
~~雖然我不是中一中的同學但是~~我在網路上剛好找到
正常的pq是逐次push進去 然後逐次shift down($O(logn)$) 所以要$O(nlogn)$
但是先把n個值全部放進去 再開始做shift down 每做完一次就有一個值的位置固定下來
所以做到後面要動的值會越來越少 複雜度是$O(n)$ 連結原文有詳細證明
(注意 這個數組下標要從1開始 因為我們假設$i$是根節點 $2i-1$是左節點 $2i$是右節點)
```cpp
void shift_down(vector<int>&v,int n,int now){
int next;
while(now<<1<=n){//當左節點還在heap內
next=now<<1;//先預設下個是左邊
if(now<<1|1<=n&&v[now<<1|1]>v[now<<1])next|=1;
//如果有右節點 而且右節點大 就把下一格設成右邊
if(v[now]>=v[next])break;//根節點大 不用繼續推
v[now]^=v[next]^=v[now]^=v[next];//往下交換
now=next;//下一個循環
}
}
void pop(vector<int>&v){
v[1]=v[v.size()-1];//刪掉第1項 把最後一項拿過來
v.erase(v.begin()+v.size()-1);//刪掉最後一項
shift_down(v,v.size()-1,1);//重新排好
}
int findKthLargest(vector<int>&v,int k){
v.insert(v.begin(),0);//需要下標從1開始 隨便插個數 不過這個動作也要O(n)
for(int n=v.size()-1,i=n;i;--i)shift_down(v,n,i);//這段只要O(n)
for(;--k;)pop(v);//跟建heap的時候一樣 數量會越來越少 最差一樣是O(n)
return v[1];
}
//171ms,45.4mb
```
時間複雜度平均$O(n)$最差$O(n)$ 空間$O(1)$
常數偏大 尤其vector第一個insert跟每次的erase
可以開個a[n+1]降常數
#### linear pq但是陣列降常數
```cpp
void shift_down(int*a,int n,int now){
int next;
while(now<<1<n){//當左節點還在heap內
next=now<<1;//先預設下個是左邊
if(now<<1|1<n&&a[now<<1|1]>a[now<<1])next|=1;
//如果有右節點 而且右節點大 就把下一格設成右邊
if(a[now]>=a[next])break;//根節點大 不用繼續推
a[now]^=a[next]^=a[now]^=a[next];//往下交換
now=next;//下一個循環
}
}
void pop(int*a,int&n){
a[1]=a[--n];//刪掉第1項 把最後一項拿過來 順便刪掉最後一項(不用真刪)
shift_down(a,n,1);//重新排好
}
int findKthLargest(vector<int>&v,int k){
int n=v.size()+1,i=n,a[100005]={0};
for(;--i;)a[i]=v[i-1];//需要下標從1開始 還是要O(n)
for(i=n;--i;)shift_down(a,n,i);//這段只要O(n)
for(;--k;)pop(a,n);///跟建heap的時候一樣 數量會越來越少 最差一樣是O(n)
return a[1];
}
//130ms,45.8mb
```
時間複雜度不變 空間$O(n)$
可惜常數還是有點大 最後還是比quick select跟兩個排序慢 甚至跟STL pq差不多
不過可能是測資不夠強
但是是一個很炫的作法 用了兩次的均攤 最後居然可以最差O(n)