【譯】Swift算法俱樂部-第k大元素問題

本文是對 Swift Algorithm Club 翻譯的一篇文章霜定。
Swift Algorithm Clubraywenderlich.com網(wǎng)站出品的用Swift實現(xiàn)算法和數(shù)據(jù)結(jié)構(gòu)的開源項目塔鳍,目前在GitHub上有18000+??,我初略統(tǒng)計了一下,大概有一百左右個的算法和數(shù)據(jù)結(jié)構(gòu)般此,基本上常見的都包含了盖文,是iOSer學(xué)習(xí)算法和數(shù)據(jù)結(jié)構(gòu)不錯的資源砍濒。
??andyRon/swift-algorithm-club-cn是我對Swift Algorithm Club,邊學(xué)習(xí)邊翻譯的項目叫潦。由于能力有限蝇完,如發(fā)現(xiàn)錯誤或翻譯不妥,請指正,歡迎pull request短蜕。也歡迎有興趣氢架、有時間的小伙伴一起參與翻譯和學(xué)習(xí)??。當然也歡迎加??朋魔,??????????岖研。
本文的翻譯原文和代碼可以查看??swift-algorithm-club-cn/Kth Largest Element


第k大元素問題(k-th Largest Element Problem)

你有一個整數(shù)數(shù)組a。 編寫一個算法警检,在數(shù)組中找到第k大的元素孙援。

例如,第1個最大元素是數(shù)組中出現(xiàn)的最大值扇雕。 如果數(shù)組具有n個元素拓售,則第n最大元素是最小值。 中位數(shù)是第n/2最大元素洼裤。

樸素的解決方案

以下是半樸素的解決方案邻辉。 它的時間復(fù)雜度是 O(nlogn),因為它首先對數(shù)組進行排序腮鞍,因此也使用額外的 O(n) 空間值骇。

func kthLargest(a: [Int], k: Int) -> Int? {
  let len = a.count
  if k > 0 && k <= len {
    let sorted = a.sorted()
    return sorted[len - k]
  } else {
    return nil
  }
}

kthLargest() 函數(shù)有兩個參數(shù):由整數(shù)組成的數(shù)組a,已經(jīng)整數(shù)k移国。 它返回第k大元素吱瘩。

讓我們看一個例子并運行算法來看看它是如何工作的。 給定k = 4和數(shù)組:

[ 7, 92, 23, 9, -1, 0, 11, 6 ]

最初沒有找到第k大元素的直接方法迹缀,但在對數(shù)組進行排序之后使碾,它非常簡單。 這是排完序的數(shù)組:

[ -1, 0, 6, 7, 9, 11, 23, 92 ]

現(xiàn)在祝懂,我們所要做的就是獲取索引a.count - k的值:

a[a.count - k] = a[8 - 4] = a[4] = 9

當然票摇,如果你正在尋找第k個最小的元素,你會使用a [k-1]砚蓬。

更快的解決方案

有一種聰明的算法結(jié)合了二分搜索快速排序的思想來達到O(n)解決方案矢门。

回想一下,二分搜索會一次又一次地將數(shù)組分成兩半,以便快速縮小您要搜索的值。 這也是我們在這里所做的沾谓。

快速排序還會拆分數(shù)組矢腻。它使用分區(qū)將所有較小的值移動到數(shù)組的左側(cè),將所有較大的值移動到右側(cè)。在圍繞某個基準進行分區(qū)之后,該基準值將已經(jīng)處于其最終的排序位置。 我們可以在這里利用它叛薯。

以下是它的工作原理:我們選擇一個隨機基準浑吟,圍繞該基準對數(shù)組進行分區(qū),然后像二分搜索一樣運行案训,只在左側(cè)或右側(cè)分區(qū)中繼續(xù)买置。這一過程重復(fù)進行,直到我們找到一個恰好位于第k位置的基準强霎。

讓我們再看看初始的例子忿项。 我們正在尋找這個數(shù)組中的第4大元素:

[ 7, 92, 23, 9, -1, 0, 11, 6 ]

如果我們尋找第k個最小項,那么算法會更容易理解城舞,所以讓我們采用k = 4并尋找第4個最小元素轩触。

請注意,我們不必先對數(shù)組進行排序家夺。 我們隨機選擇其中一個元素作為基準脱柱,假設(shè)是11,并圍繞它分割數(shù)組拉馋。 我們最終會得到這樣的結(jié)論:

[ 7, 9, -1, 0, 6, 11, 92, 23 ]
 <------ smaller    larger -->

如您所見榨为,所有小于11的值都在左側(cè); 所有更大的值都在右邊』蛙睿基準值11現(xiàn)在處于最終排完序的位置随闺。基準的索引是5蔓腐,因此第4個最小元素肯定位于左側(cè)分區(qū)中的某個位置矩乐。從現(xiàn)在開始我們可以忽略數(shù)組的其余部分:

[ 7, 9, -1, 0, 6, x, x, x ]

再次讓我們選擇一個隨機的樞軸,讓我們說6回论,然后圍繞它劃分數(shù)組散罕。 我們最終會得到這樣的結(jié)論:

[ -1, 0, 6, 9, 7, x, x, x ]

基準值6在索引2處結(jié)束,所以顯然第4個最小的項必須在右側(cè)分區(qū)中傀蓉。 我們可以忽略左側(cè)分區(qū):

[ x, x, x, 9, 7, x, x, x ]

我們再次隨機選擇一個基準值欧漱,假設(shè)是9,并對數(shù)組進行分區(qū):

[ x, x, x, 7, 9, x, x, x ]

基準值9的索引是4葬燎,這正是我們正在尋找的 k误甚。 我們完成了! 注意這只需要幾個步驟萨蚕,我們不必先對數(shù)組進行排序靶草。

以下函數(shù)實現(xiàn)了這些想法:

public func randomizedSelect<T: Comparable>(_ array: [T], order k: Int) -> T {
  var a = array

  func randomPivot<T: Comparable>(_ a: inout [T], _ low: Int, _ high: Int) -> T {
    let pivotIndex = random(min: low, max: high)
    a.swapAt(pivotIndex, high)
    return a[high]
  }

  func randomizedPartition<T: Comparable>(_ a: inout [T], _ low: Int, _ high: Int) -> Int {
    let pivot = randomPivot(&a, low, high)
    var i = low
    for j in low..<high {
      if a[j] <= pivot {
        a.swapAt(i, j)
        i += 1
      }
    }
    a.swapAt(i, high)
    return i
  }

  func randomizedSelect<T: Comparable>(_ a: inout [T], _ low: Int, _ high: Int, _ k: Int) -> T {
    if low < high {
      let p = randomizedPartition(&a, low, high)
      if k == p {
        return a[p]
      } else if k < p {
        return randomizedSelect(&a, low, p - 1, k)
      } else {
        return randomizedSelect(&a, p + 1, high, k)
      }
    } else {
      return a[low]
    }
  }

  precondition(a.count > 0)
  return randomizedSelect(&a, 0, a.count - 1, k)
}

為了保持可讀性蹄胰,功能分為三個內(nèi)部函數(shù):

  • randomPivot()選擇一個隨機數(shù)并將其放在當前分區(qū)的末尾(這是Lomuto分區(qū)方案的要求岳遥,有關(guān)詳細信息,請參閱快速排序上的討論)裕寨。

  • randomizedPartition()是Lomuto的快速排序分區(qū)方案浩蓉。 完成后派继,隨機選擇的基準位于數(shù)組中的最終排序位置。它返回基準值的數(shù)組索引捻艳。

  • randomizedSelect()做了所有困難的工作驾窟。 它首先調(diào)用分區(qū)函數(shù),然后決定下一步做什么认轨。 如果基準的索引等于我們正在尋找的k元素绅络,我們就完成了。 如果k小于基準索引嘁字,它必須回到左分區(qū)中恩急,我們將在那里遞歸再次嘗試。 當?shù)?em>k數(shù)在右分區(qū)中時纪蜒,同樣如此衷恭。

很酷,對吧纯续? 通常随珠,快速排序是一種 O(nlogn) 算法,但由于我們只對數(shù)組中較小的部分進行分區(qū)猬错,因此randomizedSelect()的運行時間為 O(n)窗看。

注意: 此函數(shù)計算數(shù)組中第k最小項,其中k從0開始兔魂。如果你想要第k最大項烤芦,請用a.count - k

作者:Daniel Speiser析校,Matthijs Hollemans
翻譯:Andy Ron
校對:Andy Ron

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末构罗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子智玻,更是在濱河造成了極大的恐慌遂唧,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吊奢,死亡現(xiàn)場離奇詭異盖彭,居然都是意外死亡,警方通過查閱死者的電腦和手機页滚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門召边,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裹驰,你說我怎么就攤上這事隧熙。” “怎么了幻林?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵贞盯,是天一觀的道長音念。 經(jīng)常有香客問我,道長躏敢,這世上最難降的妖魔是什么闷愤? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮件余,結(jié)果婚禮上讥脐,老公的妹妹穿的比我還像新娘。我一直安慰自己啼器,他們只是感情好攘烛,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著镀首,像睡著了一般坟漱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上更哄,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天芋齿,我揣著相機與錄音,去河邊找鬼成翩。 笑死觅捆,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的麻敌。 我是一名探鬼主播栅炒,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼术羔!你這毒婦竟也來了赢赊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤级历,失蹤者是張志新(化名)和其女友劉穎释移,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寥殖,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡玩讳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嚼贡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熏纯。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖粤策,靈堂內(nèi)的尸體忽然破棺而出樟澜,到底是詐尸還是另有隱情,我是刑警寧澤掐场,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布往扔,位于F島的核電站,受9級特大地震影響熊户,放射性物質(zhì)發(fā)生泄漏萍膛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一嚷堡、第九天 我趴在偏房一處隱蔽的房頂上張望蝗罗。 院中可真熱鬧,春花似錦蝌戒、人聲如沸串塑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桩匪。三九已至,卻和暖如春友鼻,著一層夾襖步出監(jiān)牢的瞬間傻昙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工彩扔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妆档,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓虫碉,卻偏偏與公主長得像贾惦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子敦捧,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容