文章目錄
- 題目描述
- 思路 & 代碼
- 快排
- 基于 Fork / Join 的并發(fā)快排
- 針對 topK 的快排優(yōu)化
- 堆排
- 二刷
題目描述
思路 & 代碼
快排
- 沒啥好說的,就是快排結(jié)束后,返回倒數(shù)第K個數(shù)字即可。
- 重點就在于快排的實現(xiàn)了,好久沒敲了= =:
原理:使用標(biāo)志位進(jìn)行分治的排序,每趟能讓標(biāo)志位的左邊都不大于標(biāo)志位,右邊都不小于標(biāo)志位。注意點:邊界問題、起始位置等,詳見代碼注釋留個坑:隨機(jī)化mark,防止退化到 O(n2n^2n2)
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 的并發(fā)快排
- 效率直接從 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
;}@Overrideprotected 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 的快排優(yōu)化
- 能達(dá)到 Fork / Join 的效率,并且不需要額外線程資源
- 關(guān)鍵在于局部有序,對于 topK 問題來說,我們只關(guān)心第K個最大元素。
- 因此實際上并不需要做到全局有序,可以在每次遞歸時都只選擇一個區(qū)間進(jìn)行遞歸
- 時間復(fù)雜度:等待有緣人補(bǔ)充
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
); }
}
堆排
- 可以直接用堆排,也可以針對題目變形一下(會快很多)。這里都貼一下
基本堆排
- 三個主要函數(shù):heapify、buildTree 與 myHeapSort
- 邏輯上是完全二叉樹,通過數(shù)組實現(xiàn)
- Parent = (son - 1) / 2
- Son1 = Parent * 2 + 1。 Son2 = Son1 + 1。
- heapify:遞歸向下
- buildTree:從下(第一個Parent)往上遍歷進(jìn)行heapify
- myHeapSort:每次都取最值,與結(jié)尾交換。然后縮小范圍繼續(xù)。
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);}}
}
結(jié)合題目的堆排
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
++;
- 堆排:三個主要函數(shù)。一個自頂向上,一個自底向上。
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
;}
}
總結(jié)
以上是生活随笔為你收集整理的【LeetCode笔记】215. 数组中的第K个最大元素(Java、快排、堆排、并发快排)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。