例1. 215 返回数组中第k大的数
练习1-1. 703 数据流中第k大元素
练习1-2. 414 返回数组中第3大的数
一、返回数组中第k大的数
【题目】
【方法一】
排序,返回第k个数即可。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length-k];
}
}
【方法二】
【方法三】
【方法四】
class Solution {
public int findKthLargest(int[] nums, int k) {
buildMinHeap(nums, k); //对前k个元素建立小根堆
for(int i = k;i<nums.length;i++){
if(nums[i]> nums[0]){
nums[0] = nums[i]; //堆顶换成nums[i]
adjustDown(nums, 0 , k); //重新调整堆
}
}
return nums[0];
}
public void buildMinHeap(int[] nums, int k){
//对数组nums的前k个元素建立小根堆
for(int i=(k-2)/2; i>=0; i--){
adjustDown(nums,i, k);
}
}
public void adjustDown(int[] nums, int i, int len){
//调整第i个元素,数组长度为len
int tmp = nums[i];
for(int j=2*i+1; j<len; j = 2*j +1){
if(j+1 < len && nums[j+1] < nums[j]){
j = j+1;
}
if(nums[j] >= tmp){
break;
}
nums[i] = nums[j];
i = j;
}
nums[i] = tmp;
}
}
如果使用PriorityQueue来做:
class Solution {
public int findKthLargest(int[] nums, int k) {
Queue<Integer> minHeap = new PriorityQueue<Integer>();
int i=0;
for(;i<k;i++){
minHeap.offer(nums[i]);
}
for(;i<nums.length;i++){
if(nums[i]>minHeap.peek()){
minHeap.poll();
minHeap.offer(nums[i]);
}
}
return minHeap.peek();
}
}
结果:
【方法五】
分治法。借用快排的思想。
代码:
class Solution {
public int findKthLargest(int[] nums, int k) {
return findKth(nums, 0, nums.length-1 , k);
}
public int findKth(int[] nums, int low, int high, int k){
int pos = partition(nums, low, high);
if(pos == low+k-1){
return nums[pos];
}else if(pos < low+k-1){
return findKth(nums, pos+1, high, k-(pos+1-low));
}else{
return findKth(nums, low, pos-1, k);
}
}
public int partition(int[] nums, int low, int high){
int pivot = nums[low];
while(low < high){
while(low< high && nums[high] <= pivot){
high--;
}
nums[low] = nums[high];
while(low< high && nums[low] >= pivot){
low++;
}
nums[high] = nums[low];
}
nums[low] = pivot;
return low;
}
}
结果:
效果不是太满意!
为什么呢?
可能的原因是,数组不够乱,导致快排的效果变差。
改进:
在partition的时候,不是以nums[low]作为pivot,而是随机找一个数作为pivot。再进行划分。
class Solution {
Random ran = new Random(); //全局变量
public int findKthLargest(int[] nums, int k) {
return findKth(nums, 0, nums.length-1 , k);
}
public int findKth(int[] nums, int low, int high, int k){
int pos = partition(nums, low, high);
if(pos == low+k-1){
return nums[pos];
}else if(pos < low+k-1){
return findKth(nums, pos+1, high, k-(pos+1-low));
}else{
return findKth(nums, low, pos-1, k);
}
}
public int partition(int[] nums, int low, int high){
//随机找一个点,交换到最前面,作为pivot
int x = ran.nextInt(high-low+1);
int pivot = nums[low+x];
nums[low+x] = nums[low];
while(low < high){
while(low< high && nums[high] <= pivot){
high--;
}
nums[low] = nums[high];
while(low< high && nums[low] >= pivot){
low++;
}
nums[high] = nums[low];
}
nums[low] = pivot;
return low;
}
}
分析:
二、703 数据流中第k大元素
【题目】
【分析】
跟据上一个题,只有最小堆的方法可以处理流数据。
思路完全相同。
【代码】
class KthLargest {
PriorityQueue<Integer> minHeap;
int k;
public KthLargest(int k, int[] nums) {
this.k = k;
minHeap = new PriorityQueue<Integer>(k);
for(int num : nums){
add(num); //调用的是下面的add()函数
}
}
public int add(int val) {
if(minHeap.size() < k){
minHeap.offer(val);
}else if(val > minHeap.peek()){
minHeap.poll();
minHeap.offer(val);
}
return minHeap.peek();
}
}
这个代码写的比较简洁。因为nums[]的元素个数可能是k-1个,也可能是k个,也可能是>k个。所以不好KthLargest()函数中建立一个k大小的堆。因此通过add()来做统一处理。
结果:
我之前跑出过20ms的结果,击败90%的用户,但忘记截图了。另外,我看别人18ms的写法跟我完全一样。
这应该就是最优解了,除非自己实现堆可能更快点。
三、414 返回数组中第3大的数
【题目】
【分析】
要求时间复杂度为o(n)。
从大到小排序,然后找第三个大的数,不行,超时。
3趟冒泡或选择排序,不大行,因为这里第三大的数指不重复的第三大的数。
维护一个大小为k的堆,不大行,同上。
分治。不太行。因为经过partition之后,得到的pos是第几大的数还是不好确定。
方法一:三趟遍历。第一趟找最大的;第二趟找次大的;第三趟找第三大的。
方法二:一趟遍历,设置三个变量,来回倒腾。
方法三:红黑树。
方法一代码如下。方法二、三以后再补充。
【代码】
class Solution {
public int thirdMax(int[] nums) {
int len = nums.length;
if(len == 1) return nums[0];
if(len == 2) return Math.max(nums[0], nums[1]);
int max = 0;
int max2 = 0;
int max3 = 0;
//第一遍,找最大的。
max = nums[0];
for(int num :nums){
if(num > max){
max = num;
}
}
//第二遍,找第二大的。
boolean mark = false;
int i=0;
for(;i<len;i++){
if(nums[i]<max){
max2 = nums[i];
mark = true;
break;
}
}
if(mark == false){ //如果没有第二大的,肯定就没有第三大的了。
return max;
}
for(;i<len;i++){ //找到第二大的。
if(nums[i]<max && nums[i]>max2){
max2 = nums[i];
}
}
//第三遍,找第三大的。
mark = false;
i=0;
for(;i<len;i++){
if(nums[i]<max2){
max3 = nums[i];
mark = true;
break;
}
}
if(mark == false){ //如果没有第三大的。
return max;
}
for(;i<len;i++){ //找到第三大的。
if(nums[i]<max2 && nums[i]>max3){
max3 = nums[i];
}
}
return max3;
}
}
结果: