题目描述
大名鼎鼎的TOP K,主要考察排序 快排 & 堆排
思路 & 代码
快排
没啥好说的,就是快排结束后,返回倒数第K个数字即可。 重点就在于快排的实现了,好久没敲了= =:
原理:使用标志位进行分治的排序,每趟能让标志位的左边都不大于标志位,右边都不小于标志位。 注意点:边界问题、起始位置等,详见代码注释 留个坑:随机化mark,防止退化到 O(
n
2
n^2
n 2 )
class Solution {
public int findKthLargest ( int [ ] nums, int k) {
myQuickSort ( nums, 0 , nums. length - 1 ) ;
return nums[ nums. length - k] ;
}
void myQuickSort ( int [ ] nums, int left, int right) {
if ( left >= right) {
return ;
}
int mark = nums[ left] ;
int i = left, j = right;
while ( i < j) {
while ( i < j && nums[ j] >= mark) {
j-- ;
}
while ( i < j && nums[ i] <= mark) {
i++ ;
}
if ( i < j) {
int temp = nums[ i] ;
nums[ i] = nums[ j] ;
nums[ j] = temp;
}
}
nums[ left] = nums[ i] ;
nums[ i] = mark;
myQuickSort ( nums, left, i - 1 ) ;
myQuickSort ( nums, i + 1 , right) ;
}
}
基于 Fork / Join 的并发快排
效率直接从 34s 提高到 8s,太狠了 fork / join简直是快排的指定好兄弟,又好用效率又高。
class Solution {
public int findKthLargest ( int [ ] nums, int k) {
ForkJoinPool forkJoinPool = new ForkJoinPool ( Runtime . getRuntime ( ) . availableProcessors ( ) ) ;
forkJoinPool. invoke ( new SortTask ( nums, 0 , nums. length - 1 ) ) ;
return nums[ nums. length - k] ;
}
class SortTask extends RecursiveAction {
int [ ] arr;
int left;
int right;
public SortTask ( int [ ] arr, int left, int right) {
this . arr = arr;
this . left = left;
this . right = right;
}
@Override
protected void compute ( ) {
if ( left >= right) return ;
int mark = arr[ left] ;
int i = left, j = right;
while ( i < j) {
while ( i < j && arr[ j] >= mark) j-- ;
while ( i < j && arr[ i] <= mark) i++ ;
if ( i < j) {
int temp = arr[ i] ;
arr[ i] = arr[ j] ;
arr[ j] = temp;
}
}
arr[ left] = arr[ i] ;
arr[ i] = mark;
SortTask leftTask = new SortTask ( arr, left, i - 1 ) ;
SortTask rightTask = new SortTask ( arr, i + 1 , right) ;
leftTask. fork ( ) ;
rightTask. fork ( ) ;
leftTask. join ( ) ;
rightTask. join ( ) ;
}
}
}
针对 topK 的快排优化
能达到 Fork / Join 的效率,并且不需要额外线程资源 关键在于局部有序 ,对于 topK 问题来说,我们只关心第K个最大元素。 因此实际上并不需要做到全局有序,可以在每次递归时都只选择一个区间进行递归 时间复杂度:等待有缘人补充
class Solution {
public int findKthLargest ( int [ ] nums, int k) {
sort ( nums, 0 , nums. length - 1 , k) ;
return nums[ nums. length - k] ;
}
void sort ( int [ ] nums, int left, int right, int k) {
if ( left >= right) return ;
int mark = nums[ left] ;
int i = left, j = right;
while ( i < j) {
while ( i < j && nums[ j] >= mark) j-- ;
while ( i < j && nums[ i] <= mark) i++ ;
if ( i < j) {
int temp = nums[ i] ;
nums[ i] = nums[ j] ;
nums[ j] = temp;
}
}
nums[ left] = nums[ i] ;
nums[ i] = mark;
int rightNums = right - i + 1 ;
if ( rightNums < k) sort ( nums, left, i - 1 , k - rightNums) ;
else sort ( nums, i + 1 , right, k) ;
}
}
堆排
可以直接用堆排,也可以针对题目变形一下(会快很多)。这里都贴一下
基本堆排
三个主要函数:heapify、buildTree 与 myHeapSort 逻辑上是完全二叉树,通过数组实现 Parent = (son - 1) / 2 Son1 = Parent * 2 + 1。 Son2 = Son1 + 1。 heapify:递归向下 buildTree:从下(第一个Parent)往上遍历进行heapify myHeapSort:每次都取最值,与结尾交换。然后缩小范围继续。
class Solution {
public int findKthLargest ( int [ ] nums, int k) {
myHeapSort ( nums) ;
return nums[ nums. length - k] ;
}
void swap ( int [ ] nums, int one, int two) {
int temp = nums[ one] ;
nums[ one] = nums[ two] ;
nums[ two] = temp;
}
void heapify ( int [ ] nums, int n, int now) {
if ( now >= n) {
return ;
}
int maxIndex = now;
int son1 = 2 * now + 1 ;
int son2 = son1 + 1 ;
if ( son1 < n && nums[ maxIndex] < nums[ son1] ) {
maxIndex = son1;
}
if ( son2 < n && nums[ maxIndex] < nums[ son2] ) {
maxIndex = son2;
}
if ( maxIndex != now) {
swap ( nums, now, maxIndex) ;
heapify ( nums, n, maxIndex) ;
}
}
void buildTree ( int [ ] nums) {
int lastNode = nums. length - 1 ;
int lastParent = ( lastNode - 1 ) / 2 ;
for ( ; lastNode >= 0 ; lastNode-- ) {
heapify ( nums, nums. length, lastNode) ;
}
}
void myHeapSort ( int [ ] nums) {
buildTree ( nums) ;
int lastNode = nums. length - 1 ;
for ( int i = lastNode; i >= 0 ; i-- ) {
swap ( nums, 0 , i) ;
heapify ( nums, i, 0 ) ;
}
}
}
结合题目的堆排
class Solution {
public int findKthLargest ( int [ ] nums, int k) {
return myHeapSort ( nums, k) ;
}
void swap ( int [ ] nums, int one, int two) {
int temp = nums[ one] ;
nums[ one] = nums[ two] ;
nums[ two] = temp;
}
void heapify ( int [ ] nums, int n, int now) {
if ( now >= n) {
return ;
}
int maxIndex = now;
int son1 = 2 * now + 1 ;
int son2 = son1 + 1 ;
if ( son1 < n && nums[ maxIndex] < nums[ son1] ) {
maxIndex = son1;
}
if ( son2 < n && nums[ maxIndex] < nums[ son2] ) {
maxIndex = son2;
}
if ( maxIndex != now) {
swap ( nums, now, maxIndex) ;
heapify ( nums, n, maxIndex) ;
}
}
void buildTree ( int [ ] nums) {
int lastNode = nums. length - 1 ;
int lastParent = ( lastNode - 1 ) / 2 ;
for ( ; lastNode >= 0 ; lastNode-- ) {
heapify ( nums, nums. length, lastNode) ;
}
}
int myHeapSort ( int [ ] nums, int k) {
buildTree ( nums) ;
int lastNode = nums. length - 1 ;
for ( int i = lastNode; i >= 0 ; i-- , k-- ) {
if ( k == 1 ) {
return nums[ 0 ] ;
}
swap ( nums, 0 , i) ;
heapify ( nums, i, 0 ) ;
}
return - 1 ;
}
}
二刷
while ( i < j) {
while ( i < j && nums[ j] >= mark) j-- ;
while ( i < j && nums[ i] <= mark) i++ ;
class Solution {
public int findKthLargest ( int [ ] nums, int k) {
heapSort ( nums) ;
return nums[ nums. length - k] ;
}
void heapSort ( int [ ] nums) {
buildTree ( nums) ;
for ( int i = nums. length - 1 ; i >= 0 ; i-- ) {
swap ( nums, i, 0 ) ;
heapify ( nums, i, 0 ) ;
}
}
void buildTree ( int [ ] nums) {
for ( int son = nums. length - 1 ; son >= 0 ; son-- ) {
heapify ( nums, nums. length, son) ;
}
}
void heapify ( int [ ] nums, int length, int nowIndex) {
if ( nowIndex >= length) {
return ;
}
int son1 = nowIndex * 2 + 1 ;
int son2 = son1 + 1 ;
int maxIndex = nowIndex;
if ( son1 < length && nums[ son1] > nums[ maxIndex] ) {
maxIndex = son1;
}
if ( son2 < length && nums[ son2] > nums[ maxIndex] ) {
maxIndex = son2;
}
if ( maxIndex != nowIndex) {
swap ( nums, maxIndex, nowIndex) ;
heapify ( nums, length, maxIndex) ;
}
}
void swap ( int [ ] nums, int left, int right) {
int temp = nums[ left] ;
nums[ left] = nums[ right] ;
nums[ right] = temp;
}
}