面試 演算法 Top K

2021-07-25 11:37:11 字數 3415 閱讀 6355

top k問題是面試時手寫**的常考題,某些場景下的解法與堆排和快排的關係緊密,所以把它放在堆排後面講。

下面先來還原一下top k考試常見的套路。

你正緊張地坐在小隔間裡,聽著越來越近的腳步聲,內心忐忑,猶如兔脫。

推門聲呷然而起,你扭頭一看,身體已不由自主起立,打量著眼前來人,心裡一陣竊喜:還好,面善。

面試官點頭致意,你配合坐下,滿心滿眼一片赤誠,恨不得把公司的茶水阿姨都誇一遍來表明你面試的誠意。

面試官拿著你的簡歷目不斜視,有意無意間想起了經年彼時自己面試的緊張場景,自己走過的彎路不能讓他再重複走,於是決定先暖暖場活躍活躍氣氛,也多少祭奠一下那夕陽晚鐘,那迎風少年,那狗日的青春。

面試官說今天的天氣真好,你說藍天白雲不多見,哈哈哈哈。

你說貴公司的辦公環境真心不錯,面試官看看四周表面克制內心放浪——那是自…不對,不能這麼說,年輕人面前還是不要喜形於色。

面試官說還好了,哈哈哈哈。

真的,環境很不錯,哈哈哈哈。你說著,手心大汗淋漓,嗓子裡幹得冒煙。

面試官眯著眼看了看你,說公司大了人就多,人多了資料就多了,現在有一組千萬級別的數,你能不能幫我找出最大的5個?盡量少用空間和時間。

你聽完風中凌亂一臉懵逼,電光火石之間抖一抖眼皮,一陣狂喜,還好看過丑旦的這篇筆記。

offer,穩了。

…嘿嘿,以上扯的這個淡,希望能加深你對top k問題的印象^_^。

言歸正傳,筆者見過關於top k問題最全的分類總結是在這裡(包括海量資料的處理),個人將這些題分成了兩類:一類是容易寫**實現的;另一類側重考察思路的。毫無疑問,後一種比較簡單,你只要記住它的應用場景、解決思路,並能在面試的過程中將它順利地表達出來,便能以不變應萬變。前一種,需要手寫**,就必須要掌握一定的技巧,常見的解法有兩種,就是前面說過的堆排和快排的變形。

本文主要來看看方便用**解決的問題。

用堆排來解決top k的思路很直接。

前面已經說過,堆排利用的大(小)頂堆所有子節點元素都比父節點小(大)的性質來實現的,這裡故技重施:既然乙個大頂堆的頂是最大的元素,那我們要找最小的k個元素,是不是可以先建立乙個包含k個元素的堆,然後遍歷集合,如果集合的元素比堆頂元素小(說明它目前應該在k個最小之列),那就用該元素來替換堆頂元素,同時維護該堆的性質,那在遍歷結束的時候,堆中包含的k個元素是不是就是我們要找的最小的k個元素?

實現:

在堆排的基礎上,稍作了修改,buildheap和heapify函式都是一樣的實現,不難理解。

速記口訣:最小的k個用最大堆,最大的k個用最小堆。

public

class topk ;

int b = topk(a, 4);

for (int i = 0; i < b.length; i++)

}public

static

void heapify(int array, int index, int length)

if (right < length && array[right] > array[largest])

if (index != largest)

}public

static

void swap(int array, int a, int b)

public

static

void buildheap(int array)

}public

static

void settop(int array, int top)

public

static

int topk(int array, int k)

//先建堆,然後依次比較剩餘元素與堆頂元素的大小,比堆頂小的, 說明它應該在堆中出現,則用它來替換掉堆頂元素,然後沉降。

buildheap(top);

for (int j = k; j < array.length; j++)

}return top;

}}

時間複雜度

n*logk

速記:堆排的時間複雜度是n*logn,這裡相當於只對前top k個元素建堆排序,想法不一定對,但一定有助於記憶。

適用場景

實現的過程中,我們先用前k個數建立了乙個堆,然後遍歷陣列來維護這個堆。這種做法帶來了三個好處:(1)不會改變資料的輸入順序(按順序讀的);(2)不會占用太多的記憶體空間(事實上,一次唯讀入乙個數,記憶體只要求能容納前k個數即可);(3)由於(2),決定了它特別適合處理海量資料。

這三點,也決定了它最優的適用場景。

用快排的思想來解top k問題,必然要運用到」分治」。

與快排相比,兩者唯一的不同是在對」分治」結果的使用上。我們知道,分治函式會返回乙個position,在position左邊的數都比第position個數小,在position右邊的數都比第position大。我們不妨不斷呼叫分治函式,直到它輸出的position = k-1,此時position前面的k個數(0到k-1)就是要找的前k個數。

實現:

「分治」還是原來的那個分治,關鍵是gettopk的邏輯,務必要結合注釋理解透徹,自動動手寫寫。

public

class topk ;

gettopk(array, 4);

for (int i = 0; i < array.length; i++)

}// 分治

public

static

int partition(int array, int low, int high)

array[low] = array[high];

while (low < high && array[low] <= flag)

array[high] = array[low];

}array[low] = flag;

return low;

}return

0; }

public

static

void gettopk(int array, int k)

//小了,往後調整

if (index < k - 1) }}

}}

時間複雜度

n速記:記住就行,基於partition函式的時間複雜度比較難證明,從來沒考過。

適用場景

對照著堆排的解法來看,partition函式會不斷地交換元素的位置,所以它肯定會改變資料輸入的順序;既然要交換元素的位置,那麼所有元素必須要讀到記憶體空間中,所以它會占用比較大的空間,至少能容納整個陣列;資料越多,占用的空間必然越大,海量資料處理起來相對吃力。

但是,它的時間複雜度很低,意味著資料量不大時,效率極高。

好了,兩種解法寫完了,趕緊實現一下吧。

面試 TopK演算法解析

最簡單且最容易想到的演算法是對陣列進行排序 快速排序 然後取最大或最小的k個元素。總的時間複雜度為o n logn o k o n logn 該演算法存在以下問題 快速排序的平均複雜度為o n logn 但最壞時間複雜度為o n2 不能始終保證較好的複雜度 只需要前k大或k小的數,實際對其餘不需要的...

面試演算法題

前幾天,一好友去筆試,有一題 現在有1000個蘋果,和10個箱子,如何把這1000個蘋果裝在這10個箱子裡,才能使不管任何數量 1 1000 的蘋果,都能一次給出?當時,我們都想,出題這人。今天,在想移位的時候,突然想到了,這絕對是二進位制數的變種。分析 1000個蘋果,最接近1024,轉化為2進製...

面試演算法記錄

1.親和數問題 求500萬以內的所有親和數 如果兩個數a和b,a的所有真因數之和等於b,b的所有真因數之和等於a,則稱a,b是一對親和數。例如220和284,1184和1210,2620和2924。思路 220 1 2 4 71 142 sum 284 284 1 2 4 5 10 11 20 22...