ROSALIND|3-Way Partition (PAR3)
重新排列一個長度為 n 的數列為三個部分,使得各部分的數字分別小於、等於、大於原數列的首位數字。
Given: A positive integer $n \leq 10^5$ and an array $A[1..n]$ of integers from $-10^5$ to $10^5$.
Return: An array $B[1..n]$ such that it is a permutation of $A$ and there are indices $1 \leq q \leq r \leq n$ such that $B[i] < A[1]$ for all $1 \leq i \leq q-1$, $B[i] = A[1]$ for all $q \leq i \leq r$, and $B[i] > A[1]$ for all $r+1 \leq i \leq n$.
(https://rosalind.info/problems/par3/)
這題是 2-way partition 的延伸,我覺得最直觀的做法還是由左至右檢查輸入數列,再準備三個空數列分別儲存小於、等於、大於基準值的數字。檢查完所有數字後,依序合併這三個數列即完成排列。
1 | def par3_build(l: list[int]) -> list[int]: |
若不在意原數列的順序,可採用指標法重新排列數字。此方法延伸 2-way partition 的概念,使用兩組指標紀錄與基準值相當的數字範圍。實際執行時會進行兩次劃分,第一次劃分會移動right
指標,區分大於、小於等於基準值的數字;第二次則會移動 left
指標,進一步區分小於、等於基準值的數字。
以 python 實作時只是添加一個步驟,我覺得非常容易聯想跟記憶。
1 | def par3_nested(l: list[int]) -> list[int]: |
最後一種則是稱為 dutch national flag (DNF) algorithm 的作法,名稱源自於由三等份色塊組成的荷蘭國旗。同樣使用 left
和 right
兩個指標,遇到小於基準值的數字時,left
向右移動,表示數字進入小於區;遇到大於基準值的數字時,right
向右移動,表示數字進入大於區,最終自然區分出三個區段。
1 | def par3_dnf(l): |
後兩種算法會打亂數字原本的順序,不太容易憑空想像測試資料,所以測試時可以從定義著手。假設數列已分成小於、等於和大於基準值的三個區塊,那麼每個區塊內的數字與基準值的關係都是相同的。換句話說,不會出現這個數字小於基準值,但下個數字大於基準值的狀況。
基於這項觀察,可以記錄數值與基準值關係的狀態。假設數值為 x
,基準值為 p
,則數值位於哪個區塊可以表示為 (x > p) - (x < p)
,
-1
:該數值理應要位於左側0
:該數值理應要位於中央1
:該數值理應要位於右側
在劃分好的數列裡, 左側數字的狀態值永遠小於右側數字的狀態值,所以出現倒序的時候就表示劃分出錯了。
1 | def check_partition(arr: list[int], p: int) -> bool: |