java算法之尋找最小的k個數

2021-01-10 計算機java編程

題目描述

輸入n個整數,輸出其中最小的k個。

分析與解法

解法一

要求一個序列中最小的k個數,按照慣有的思維方式,則是先對這個序列從小到大排序,然後輸出前面的最小的k個數。

至於選取什麼的排序方法,我想你可能會第一時間想到快速排序(我們知道,快速排序平均所費時間為n*logn),然後再遍歷序列中前k個元素輸出即可。因此,總的時間複雜度:O(n * log n)+O(k)=O(n * log n)。

解法二

咱們再進一步想想,題目沒有要求最小的k個數有序,也沒要求最後n-k個數有序。既然如此,就沒有必要對所有元素進行排序。這時,咱們想到了用選擇或交換排序,即:

1、遍歷n個數,把最先遍歷到的k個數存入到大小為k的數組中,假設它們即是最小的k個數;2、對這k個數,利用選擇或交換排序找到這k個元素中的最大值kmax(找最大值需要遍歷這k個數,時間複雜度為O(k));

3、繼續遍歷剩餘n-k個數。假設每一次遍歷到的新的元素的值為x,把x與kmax比較:如果

x < kmax ,用x替換kmax,並回到第二步重新找出k個元素的數組中最大元素kmax『;如果x >= kmax,則繼續遍歷不更新數組。每次遍歷,更新或不更新數組的所用的時間為O(k)或O(0)。故整趟下來,時間複雜度為n*O(k)=O(n*k)。

解法三

更好的辦法是維護容量為k的最大堆,原理跟解法二的方法相似:

1、用容量為k的最大堆存儲最先遍歷到的k個數,同樣假設它們即是最小的k個數;2、堆中元素是有序的,令k1<k2<...<kmax(kmax設為最大堆中的最大元素)3、遍歷剩餘n-k個數。假設每一次遍歷到的新的元素的值為x,把x與堆頂元素kmax比較:如果x < kmax,用x替換kmax,然後更新堆(用時logk);否則不更新堆。這樣下來,總的時間複雜度:O(k+(n-k)*logk)=O(n*logk)。此方法得益於堆中進行查找和更新的時間複雜度均為:O(logk)(若使用解法二:在數組中找出最大元素,時間複雜度:O(k))。解法四

在《數據結構與算法分析--c語言描述》一書,第7章第7.7.6節中,闡述了一種在平均情況下,時間複雜度為O(N)的快速選擇算法。如下述文字:

選取S中一個元素作為樞紐元v,將集合S-{v}分割成S1和S2,就像快速排序那樣如果k <= |S1|,那麼第k個最小元素必然在S1中。在這種情況下,返回QuickSelect(S1, k)。如果k = 1 + |S1|,那麼樞紐元素就是第k個最小元素,即找到,直接返回它。否則,這第k個最小元素就在S2中,即S2中的第(k - |S1| - 1)個最小元素,我們遞歸調用並返回QuickSelect(S2, k - |S1| - 1)。此算法的平均運行時間為O(n)。

示例代碼如下:

//QuickSelect 將第k小的元素放在 a[k-1]

void QuickSelect( int a[], int k, int left, int right )

{

int i, j;

int pivot;

if( left + cutoff <= right )

{

pivot = median3( a, left, right );

//取三數中值作為樞紐元,可以很大程度上避免最壞情況

i = left; j = right - 1;

for( ; ; )

{

while( a[ ++i ] < pivot ){ }

while( a[ --j ] > pivot ){ }

if( i < j )

swap( &a[ i ], &a[ j ] );

else

break;

}

//重置樞紐元

swap( &a[ i ], &a[ right - 1 ] );

if( k <= i )

QuickSelect( a, k, left, i - 1 );

else if( k > i + 1 )

QuickSelect( a, k, i + 1, right );

}

else

InsertSort( a + left, right - left + 1 );

}

這個快速選擇SELECT算法,類似快速排序的劃分方法。N個數存儲在數組S中,再從數組中選取「中位數的中位數」作為樞紐元X,把數組劃分為Sa和Sb倆部分,Sa<=X<=Sb,如果要查找的k個元素小於Sa的元素個數,則返回Sa中較小的k個元素,否則返回Sa中所有元素+Sb中小的k-|Sa|個元素,這種解法在平均情況下能做到O(n)的複雜度。

更進一步,《算法導論》第9章第9.3節介紹了一個最壞情況下亦為O(n)時間的SELECT算法,有興趣的讀者可以參看。

舉一反三

1、谷歌面試題:輸入是兩個整數數組,他們任意兩個數的和又可以組成一個數組,求這個和中前k個數怎麼做?

分析:

「假設兩個整數數組為A和B,各有N個元素,任意兩個數的和組成的數組C有N^2個元素。

那麼可以把這些和看成N個有序數列:

A[1]+B[1] <= A[1]+B[2] <= A[1]+B[3] <=…

A[2]+B[1] <= A[2]+B[2] <= A[2]+B[3] <=…

A[N]+B[1] <= A[N]+B[2] <= A[N]+B[3] <=…

問題轉變成,在這N^2個有序數列裡,找到前k小的元素」

2、有兩個序列A和B,A=(a1,a2,...,ak),B=(b1,b2,...,bk),A和B都按升序排列。對於1<=i,j<=k,求k個最小的(ai+bj)。要求算法儘量高效。

3、給定一個數列a1,a2,a3,...,an和m個三元組表示的查詢,對於每個查詢(i,j,k),輸出ai,ai+1,...,aj的升序排列中第k個數。

相關焦點

  • 機器學習算法之K-means算法
    K-means舉例shi'li1 K-means算法簡介k-means算法是一種聚類算法,所謂聚類,即根據相似性原則,將具有較高相似度的數據對象劃分至同一類簇,將具有較高相異度的數據對象劃分至不同類簇。
  • 五大常用算法:一文搞懂分治算法
    分治算法的描述從字面上也很容易理解,分、治其實還有個合併的過程:分(Divide):遞歸解決較小的問題(到終止層或者可以解決的時候停下)治(Conquer):遞歸求解,如果問題夠小直接求解。if(low>high)//作為判斷是否截止條件 return; int k=a[low];//額外空間k,取最左側的一個作為衡量,最後要求左側都比它小,右側都比它大。 while(low<high)//這一輪要求把左側小於a[low],右側大於a[low]。
  • 圖的最短路徑算法-Floyd算法-弗洛伊德算法
    Floyd算法又稱為插點法,是一種利用動態規劃的思想尋找給定的加權圖中多源點之間最短路徑的算法 在計算機科學中,Floyd-Warshall算法是一種在具有正或負邊緣權重(但沒有負周期)的加權圖中找到最短路徑的算法。算法的單個執行將找到所有頂點對之間的最短路徑的長度(加權)。
  • 機器學習之分類算法K-Means介紹與代碼分析(篇四)
    k-平均聚類的目的是:把n個點(可以是樣本的一次觀察或一個實例)劃分到k個聚類中,使得每個點都屬於離他最近的均值(此即聚類中心)對應的聚類,以之作為聚類的標準。這個問題將歸結為一個把數據空間劃分為Voronoi cells的問題。這個問題在計算上是NP困難的,不過存在高效的啟發式算法。
  • java生成隨機數的五種方法
    當第一次調用 Math.random() 方法時,自動創建了一個偽隨機數生成器,實際上用的是 new java.util.Random()。當接下來繼續調用 Math.random() 方法時,就會使用這個新的偽隨機數生成器。
  • JAVA必須掌握的數據結構和算法
    在這個過程中,數字會像泡泡一樣,慢慢從右往左「浮」到序列的頂端,所以這個算法才被稱為「冒泡排序」冒泡排序的時間複雜度為O(n2)選擇排序選擇排序就是重複「從待排序的數據中尋找最小值,將其與序列最左邊的數字進行交換」這一操作的算法。
  • 基礎不牢地動山搖記住 Java 面試中常用的八種排序算法與代碼實現
    將第一個數和第二個數排序,然後構成一個有序序列將第三個數插入進去,構成一個新的有序序列。對第四個數、第五個數……直到最後一個數,重複第二步。將數的個數設為n,取奇數k=n/2,將下標差值為k的數分為一組,構成有序序列。再取k=k/2 ,將下標差值為k的書分為一組,構成有序序列。重複第二步,直到k=1執行簡單插入排序。
  • 聚類算法入門:k-means
    比如垃圾分類就是分類算法,你知道豬能吃的是溼垃圾,不能吃的是幹垃圾……;打掃房間時你把雜物都分分類,這是聚類,你事先不知道每個類別的標準。二、劃分聚類方法: K-means:對於給定的樣本集,按照樣本之間的距離(也就是相似程度)大小,將樣本集劃分為K個簇(即類別)。讓簇內的點儘量緊密的連在一起,而讓簇間的距離儘量的大。
  • Java數據結構與算法之,二分查找算法
    學習過數據結構與算法的同學都知道,在 java 中,常用的查找有四種:二分查找、線性查找、插值查找、斐波那契查找。今天我們來聊一聊其中一種「二分查找算法」。概念二分查找算法又叫折半查找,是適用於有序的列表中的。思路:將列表中間位置元素和目標元素進行比較,如果相等,則查找成功;如果不相等,則查找的元素一定在表的前半部分或後半部分。
  • 2020年的Java程式設計師面試三件套:多線程+算法+微服務
    小編這裡針對多線程+算法+微服務這三個知識點推薦下面三本學習手冊,這三本書籍是小編用禿頭為代價,精心研究挑選出來的,讓大家對這三個知識框架有個基本輪廓,應對個面試還是沒什麼問題的;多線程提起多線程編程,恐怕許多開發人員都會搖頭表示不懂。
  • Java實現冒泡排序算法
    >2.考考你在上一篇:數據結構與算法系列十(排序概述)中,我們列舉了常用的排序算法,以及分析了如何綜合衡量排序算法的優劣。2.你能用java實現冒泡排序嗎?3.你能寫出更優秀的冒泡排序代碼嗎?3.案例3.1.冒泡排序思想假設有一個待排序序列:[4, 5, 6, 3, 2, 1]。
  • K-Means聚類講解:算法和Sklearn的實現(附代碼)
    K-Means聚類是機器學習領域中最強大的聚類算法之一。他的原因比較簡單,但得出的結果也非常準確。聚類是理解數據集的非常重要的方式,因此在本文中,我們將討論什麼是聚類,為什麼需要聚類以及什麼是k-means聚類。
  • 算法面試題:N個數中第K大的數
    首先,該問題存在一個鏡像問題,N個數中第(N-k)小的數,選擇k和N-K之中較為小的那個進行該問題的解答方法一:對所有元素進行排序,然後取出第K個元素時間複雜度:O(nlogn) 特點:對全部元素進行排序
  • 常見的機器學習算法,你知道幾個?
    各種算法以及對應的任務類型  接下來就簡單介紹幾種常用的機器學習算法及其應用場景,通過本篇文章大家可以對機器學習的常用算法有個常識性的認識。  (4)k-近鄰算法(K-Nearest Neighbor,KNN):是一種基於實例的學習,採用測量不同特徵值之間的距離方法進行分類。其基本思路是:給定一個訓練樣本集,然後輸入沒有標籤的新數據,將新數據的每個特徵與樣本集中數據對應的特徵進行比較,找到最鄰近的k個(通常是不大於20的整數)實例,這k個實例的多數屬於某個類,就把該輸入實例分類到這個類中。
  • 如何用聚類模型(k-means)做數據分析?
    k-means屬於無監督學習算法,無監督算法的內涵是觀察無標籤數據集自動發現隱藏結構和層次,在無標籤數據中尋找隱藏規律。聚類模型在數據分析當中的應用:既可以作為一個單獨過程,用於尋找數據內在規律,也可以作為分類等其他分析任務的前置探索。
  • 面試常問的數據結構十大經典算法
    它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。算法描述n個記錄的直接選擇排序可經過n-1趟直接選擇排序得到有序結果。
  • 程序算法設計題,N個骰子的點數問題
    程序算法的類型有很多。當然生活中的很多場景我們都可以用算法來實現。今天分享的這個N個骰子的點數問題,就是我們打麻將擲篩子時,經常遇到的場景。下面我們來看下具體的題目吧。題目:把N個骰子扔在地上,所有篩子朝上一面的點數之和為s.我們輸入篩子數N,列印出s的所有可能的值出現的概率。
  • 怎麼樣更好地理解排序算法
    假設數組有 N 個元素,冒泡排序過程如下:從當前元素起,向後依次比較每一對相鄰元素(a,b)如果 a>b 則交換這兩個數重複步驟1和2,直到比較最後一對元素(第 N-2 和 N-1 元素)假設數組有 N 個元素且 L=0,選擇排序過程如下:從 [L...N-1] 範圍中找到最小元素的下標 X交換第 X 與第 L 位置的元素值L 加 1,重複以上步驟,直到 L=N-2
  • Java動態規划算法策略
    舉個最簡單的例子去先淺顯的理解它,有個大概的雛形,找一個數組中的最大元素,如果只有一個元素,那就是它,再往數組裡面加元素,遞推關係就是,當你知道當前最大的元素,只需要拿當前最大元素和新加入的進行比較,較大的就是數組中最大的,這就是典型的DP策略,將小問題的解保存起來,解決大問題的時候就可以直接使用。
  • 構建GNN 的「統一場」:從與 WL 算法、組合優化算法的聯繫看 GNN...
    需要注意的是,集合 k-WL 算法一定弱於 k-WL 算法。例如,3-WL 可以區分圖 9 中的 a 和 b(因為 3-WL 可以檢測出 4 聯通分量的數目),而集合 3-WL 算法無法做到這一點。Morris 等人於 2019 年基於集合 k-WL 算法提出了 k-維 GNN(k-GNN)。k-GNN 為每 k 個節點組成的集合賦予一種嵌入。