文章目录 题目描述 思路 & 代码 快排 基于 Fork / Join 的并发快排 针对 topK 的快排优化 堆排 二刷
题目描述
大名鼎鼎的TOP K,主要考察排序 快排 & 堆排
思路 & 代码
快排
没啥好说的,就是快排结束后,返回倒数第K个数字即可。 重点就在于快排的实现了,好久没敲了= =:
原理:使用标志位进行分治的排序,每趟能让标志位的左边都不大于标志位,右边都不小于标志位。 注意点:边界问题、起始位置等,详见代码注释 留个坑:随机化mark,防止退化到 O(n2n^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; }
}