算法-TopK相關的問題

TopK問題算是面試中常考的,而且有實際價值的算法中比較有代表性的一個了,主要解決方法有堆、快速選擇、排序三種方案。下一次專門講一下快速選擇類的算法。

1、給定一個數組,求其最大(小)的K個元素

解題思路有多種,比如可以對數組排序,使用Arrays.sort();然後取前面K個數,就是最小或者最大的K個數,sort可以指定按升序還是降序排列。當然,這麼做的時間複雜度是Nlog(N),雖然的確很快,但是還是會被面試官拿來鞭屍。

其實他們想考的就是堆罷了,雖然都是Nlog(N)。使用堆可以得到topK問題的通用解法。就拿本題來講,以最大的topK個數爲例,
1、我們可以建一個小頂堆,當堆元素數量小於K個時,直接入堆,
2、當大於等於K個時,與堆頂部比較,如果大於堆頂,則堆頂出堆,然後執行入堆操作
3、將堆裏面的元素全部彈出,即爲最大的K個數

我們知道,堆一般都是用優先隊列實現的,在Java裏面,PriorityQueue默認就是小頂堆。

public int[] getTopK(int[] nums,int k){
	PriorityQueue<Integer> heap=new PriorityQueue<>();
	for(int i:nums){
		if(heap.size()<k){
			heap.offer(i);
		}else if(heap.top()<i){
			heap.poll();
			heap.offer(i);
		}
	}
	int [] result=new int[heap.size()];
	for(int i=0;i<result.length;i++){
		result[i]=heap.poll();
	}
	return result;
}

2、給定一個數組,求其第K大的數

這個問題和上個問題不同之處就在於本題只需要一個數,直接套上題代碼,彈出的第一個數就是第K大的數。
不過這時候面試官大概率會要求你寫出一個複雜度爲O(N)的算法,而上題複雜度Nlog(N),空間複雜度爲k,就不好用了。
這時候考察的其實是一個快排的思想,快排還有個兄弟,叫快速選擇,也就是quickSelect算法,時間複雜度可以降到O(N)。
還記得快排怎麼做的嗎?選取一個樞紐元,在一輪過後,左邊元素全部小於等於樞紐元,右邊元素全部大於等於樞紐元。然而我們找的是最小或者最大的第K個數,完全不用care另一部分的值,我們只對樞紐元左半部分或者右半部分遞歸的實現快速選擇就行了。
經過快速選擇後,所需要的數據就是數組中第k-1個元素。

//不考慮k不合法的情況了
public int getKthNumber(int nums[],int k){
	quickSelect(nums,0,nums.length-1,k);
	return nums[k-1];
}

private void quickSelect(int []nums,int left,int right,int k){
	if(left<right){
		int i=left,j=right;
		int privot=nums[left];//選取樞紐元
		while(i<j){
			while(i<j&&nums[j]>=privot){j--;}//找到第一個小於樞紐元的值
			while(i<j&&nums[i]<=privot){i++;}//找到第一個大於樞紐元的值
			if(i<j){
				swap(nums[i],i,j);
			}
		}
		swap(nums,left,i);//恢復數紐元
		if(k<=i){
			quickSelect(nums,left,i-1,k);
		}else{
			quickSelect(nums,i+1,right,k);
		}
	}
}

private void swap(int[] nums,int i,int j){
	int temp=nums[i];
	nums[i]=nums[j];
	nums[j]=temp;
}

3、給定一個數組求中位數

這個問題用堆實現也比較簡單,如果數組大小爲奇數,我們設置一個(數組長度+1)/2的堆,然後按照題目1來彈出堆頂就是中位數。如果是偶數,我們設置一個大小爲 數組長度/2 +1的一個堆,然後彈出兩個元素取平均值即可。
然而這樣時間複雜度是Nlog(N),面試官無法忍受。。。
想到題目2中快速選擇的思想,我們可以這樣做:
1、數組長度len爲奇數,我們使用2中代碼選擇第(len+1)/2小的數,即爲中位數
2、數組長度len爲偶數,我們選擇第len/2小的數和第len/2+1小的數,取均值即爲中位數

話不多說,直接放碼:

    public void test(){
        int[] nums={3,5,1,6,7};
        if(nums.length%2==0){
            int k1=(nums.length+1)>>1;
            quickSelect(nums,k1);
            int a=nums[k1-1];
            int k2=(nums.length+1)>>1+1;
            quickSelect(nums,k2);
            int b=nums[k2-1];
            System.out.println((a+b)>>1);
        }else {
            int k=(nums.length+1)>>1;
            quickSelect(nums,k);
            System.out.println(nums[k-1]);
        }
    }

    public void quickSelect(int[] nums,int k){
        quickSelect(nums,0,nums.length-1,k);
    }


    public void quickSelect(int nums[],int left,int right,int k){
        if(left<right){
            int i=left,j=right;
            int pivot=nums[left];
            while(i<j){
                while(i<j&&nums[j]>=pivot){j--;}
                while(i<j&&nums[i]<=pivot){i++;}
                if(i<j){
                    swap(nums,i,j);
                }
            }
            swap(nums,left,i);
            if(k<=i){
                quickSelect(nums,left,i-1,k);
            }else {
                quickSelect(nums,i+1,right,k);
            }
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章