algorithm divide and conquer merge sort quick sort convex hull closest pair FFT

[Algorithm] 分而治之法

演算法概念

分而治之(Divide and Conquer)的三步驟:

  1. Divide:把大問題切成幾個規模較小、結構相同的子問題。
  2. Conquer:對每個子問題遞迴求解,小到不能再切時直接解。
  3. Combine:把子問題的解合起來變成原問題的解。

關鍵是「合起來」——沒有合起來就不是分治,是淘汰。淘汰切完只進其中一塊、其他丟掉;分治切完每塊都要解,最後再合回去。

典型遞迴式 T(n)=aT(n/b)+f(n)T(n) = a \, T(n/b) + f(n)aa 是子問題個數、bb 是子問題規模相對原問題縮幾倍、f(n)f(n) 是合併成本。Combine 決定整體效率——合併做到 O(n)O(n) 就能配上 T(n)=2T(n/2)+O(n)=O(nlogn)T(n) = 2T(n/2) + O(n) = O(n \log n),合併若變成 O(nlogn)O(n \log n) 整體就退化到 O(nlog2n)O(n \log^2 n)

適合分治的條件:子問題之間獨立(不獨立就要動態規劃)、合併成本合理(通常要 O(n)O(n) 或更低)。

各適用情境

合併排序(Merge Sort)

演算法策略

Divide:從中間切成左右兩半。Conquer:各自遞迴排序。Combine:兩個已排序的小陣列用雙指標合併——各指一個最左元素,誰小先寫進結果,相等時優先取左邊(保穩定性)。

複雜度

每層合併 O(n)O(n)、共 logn\log n 層 → T(n)=2T(n/2)+O(n)=O(nlogn)T(n) = 2T(n/2) + O(n) = O(n \log n)。不論輸入如何都這麼快。代價是要 O(n)O(n) 額外空間。

範例

題目[38, 27, 43, 3, 9, 82, 10] 從小到大排序。

圖解

步驟

Divide 階段:一路切半到單元素。

陣列狀態
0[38, 27, 43, 3, 9, 82, 10]
1[38, 27, 43, 3] | [9, 82, 10]
2[38, 27] | [43, 3] | [9] | [82, 10]
3[38] | [27] | [43] | [3] | [9] | [82] | [10]

Combine 階段:由下往上兩兩合併。

第 3 → 2 層:

合併結果
[38] + [27][27, 38]
[43] + [3][3, 43]
[9] 落單[9]
[82] + [10][10, 82]

第 2 → 1 層:

合併結果
[27, 38] + [3, 43][3, 27, 38, 43]
[9] + [10, 82][9, 10, 82]

第 1 → 0 層:合併 [3, 27, 38, 43] + [9, 10, 82],雙指標細節:

iijjL[i]L[i]R[j]R[j]MM
0039LL[3]
10279RR[3, 9]
112710RR[3, 9, 10]
122782LL[3, 9, 10, 27]
223882LL[3, 9, 10, 27, 38]
324382LL[3, 9, 10, 27, 38, 43]
4282LL 用完、接 RR[3, 9, 10, 27, 38, 43, 82]
Python 實作
def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    return merge(merge_sort(arr[:mid]), merge_sort(arr[mid:]))


def merge(left, right):
    result, i, j = [], 0, 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    return result + left[i:] + right[j:]

快速排序(Quick Sort)

演算法策略

跟合併排序是分治的鏡像:Divide 昂貴(挑 pivot 後原地用雙指標重排、左邊都 \leq pivot、右邊都 \geq pivot),Combine 便宜(什麼都不做——原地分好就排好了)。

Hoare partition 的流程(pivot = a[left]):

  • 左指標 ii:從 left+1 起往右,停在 a[i] >= pivot 的位置。
  • 右指標 jj:從 right 起往左,停在 a[j] <= pivot 的位置。
  • 如果 i < j 就 swap(兩個站錯邊);如果 i >= j 就跳出外層、做 swap(a[left], a[j]),pivot 就定位。

複雜度

平均 pivot 切出接近均等的兩半 → T(n)=2T(n/2)+O(n)=O(nlogn)T(n) = 2T(n/2) + O(n) = O(n \log n)。最壞 pivot 每次都極值、一邊空一邊 n1n - 1T(n)=T(n1)+O(n)=O(n2)T(n) = T(n - 1) + O(n) = O(n^2)。實務用隨機 pivot 或三數取中(median-of-three)避免 worst case。

範例

題目[25, 10, 35, 5, 30, 15](1-indexed)排序。

步驟

第一層 Quick_Sort(1, 6):pivot = a[1] = 25,i=1i = 1j=7j = 7

i 往右掃停在j 往左掃停在i < j?動作陣列
1i=3i = 3a[3]=3525a[3] = 35 \geq 25j=6j = 6a[6]=1525a[6] = 15 \leq 25swap a[3],a[6]a[3], a[6][25, 10, 15, 5, 30, 35]
2i=5i = 5a[5]=3025a[5] = 30 \geq 25j=4j = 4a[4]=525a[4] = 5 \leq 25跳出外層[25, 10, 15, 5, 30, 35]

跳出後 swap(a[1], a[4])[5, 10, 15, 25, 30, 35]。pivot 25 落在位置 4。左半 (1, 3)、右半 (5, 6)

第二層左 Quick_Sort(1, 3)[5, 10, 15],pivot = 5。ii 停在 2、jj 走到 1,swap(a[1], a[1]) 不動。

第三層 Quick_Sort(2, 3)[10, 15],pivot = 10。ii 停在 3、jj 走到 2,swap(a[2], a[2]) 不動。

第二層右 Quick_Sort(5, 6)[30, 35],pivot = 30,同理不動。

最終 [5, 10, 15, 25, 30, 35]。整個過程沒有 Combine——partition 原地分、遞迴回來就排好。

另一種看法:每次 partition 完成的快照

上面那張表追的是 partition 內部的 i/j 小輪。換個粒度——只記每次 partition 完成、pivot 落定的瞬間:

粒度是什麼
小輪i/j 各停一次、要嘛 swap 要嘛跳出(對應上面「輪」表的每一列)
一次 partition從進入 Quick_Sort 到 pivot 落定(對應下面全程表的每一列)

下表【 】框住待處理子陣列、left/right下一次 partition 的範圍:

a[1]a[1]a[2]a[2]a[3]a[3]a[4]a[4]a[5]a[5]a[6]a[6]leftright
【25103553015】16
【51015】25【3035】13
5【1015】25【3035】23
5101525【3035】56
51015253035

每一列到下一列就是一次完整 partition(內部小輪都被吞進去)。pivot 落定後就不再動,剩下的子陣列各自遞迴;單元素子陣列直接回傳、不畫列。

Python 實作(Hoare partition)
def quick_sort(a, left, right):
    if left >= right:
        return
    pivot = a[left]
    i, j = left, right + 1
    while True:
        while True:
            i += 1
            if i > right or a[i] >= pivot:
                break
        while True:
            j -= 1
            if a[j] <= pivot:
                break
        if i < j:
            a[i], a[j] = a[j], a[i]
        else:
            break
    a[left], a[j] = a[j], a[left]
    quick_sort(a, left, j - 1)
    quick_sort(a, j + 1, right)
例題:Quick Sort worst case

已排序 [1, 2, 3, 4, 5]、固定選 a[left]:每次 pivot 都是最小值,左半空、右半 n1n - 1 個。T(n)=T(n1)+O(n)=O(n2)T(n) = T(n - 1) + O(n) = O(n^2)。實務用隨機 pivot 或 median-of-three 避免。


二維平面上的極點

演算法策略

極點(maximal point):若 x1>x2x_1 > x_2y1>y2y_1 > y_2,則 P1P_1 宰制 P2P_2。沒被任何人宰制的點就是極點。所有極點排起來是一條「往右下的階梯」。

分治招式:

  1. Divide:按 x 中位數切成左半 LL(小 x)、右半 RR(大 x)。
  2. Conquer:各自遞迴求 LmaxL_{max}RmaxR_{max}
  3. Combine
    • RmaxR_{max} 的每點 x 都大於 LL 全部 → 不可能被 LL 宰制 → 全保留
    • LmaxL_{max} 裡的點要跟 RR 比 y——令 y=max{y:(x,y)Rmax}y^* = \max\{y : (x, y) \in R_{max}\}(就是 RmaxR_{max} 的最大 y),LmaxL_{max}y>yy > y^* 保留、yyy \leq y^* 丟掉。

為什麼只比 RmaxR_{max} 的最大 y 就夠?因為 RmaxR_{max} 裡最大 y 的那個點本身一定是極點,它能宰制的範圍就是 RR 裡最廣的——它都打不贏的 LL 點,其他 RR 點也打不贏。

極點跟凸包不是同一回事:凸包關心「最外圍輪廓」(上下左右都算)、極點只關心「右上角」。

複雜度

每層合併掃一遍 O(n)O(n)、共 logn\log n 層 → T(n)=2T(n/2)+O(n)=O(nlogn)T(n) = 2T(n/2) + O(n) = O(n \log n)。若輸入未排序,初始按 x 排序也是 O(nlogn)O(n \log n)

範例

題目:8 個點(已按 x 排序)P1=(1,6),P2=(2,8),P3=(3,4),P4=(4,7),P5=(5,3),P6=(6,5),P7=(7,1),P8=(8,2)P_1 = (1, 6), P_2 = (2, 8), P_3 = (3, 4), P_4 = (4, 7), P_5 = (5, 3), P_6 = (6, 5), P_7 = (7, 1), P_8 = (8, 2)

圖解

步驟:中位 x 切半,L={P1,P2,P3,P4}L = \{P_1, P_2, P_3, P_4\}R={P5,P6,P7,P8}R = \{P_5, P_6, P_7, P_8\}

遞迴 LL:切成 {P1,P2}\{P_1, P_2\}{P3,P4}\{P_3, P_4\}

子問題左邊極點右邊 yy^*合併
{P1,P2}\{P_1, P_2\}{P1}\{P_1\}8(P2P_2P1.y=68P_1.y = 6 \leq 8 丟 → {P2}\{P_2\}
{P3,P4}\{P_3, P_4\}{P3}\{P_3\}7(P4P_4P3.y=47P_3.y = 4 \leq 7 丟 → {P4}\{P_4\}

合併:右半 {P4}\{P_4\}y=7y^* = 7,左半 {P2}\{P_2\}P2.y=8>7P_2.y = 8 > 7 保留 → Lmax={P2,P4}L_{max} = \{P_2, P_4\}

遞迴 RR:同理求得 Rmax={P6,P8}R_{max} = \{P_6, P_8\}

最後合併RmaxR_{max} 最大 y = 5(P6P_6)。LmaxL_{max}P2.y=8>5P_2.y = 8 > 5P4.y=7>5P_4.y = 7 > 5 都保留。最終極點 {P2,P4,P6,P8}\{P_2, P_4, P_6, P_8\}。座標 (2,8)(4,7)(6,5)(8,2)(2, 8) \to (4, 7) \to (6, 5) \to (8, 2),y 遞減階梯。

Python 實作
def maximal_points(points):
    pts = sorted(points, key=lambda p: p[0])

    def solve(a):
        if len(a) == 1:
            return a
        mid = len(a) // 2
        L = solve(a[:mid])
        R = solve(a[mid:])
        y_star = max(p[1] for p in R)
        L_keep = [p for p in L if p[1] > y_star]
        return L_keep + R

    return solve(pts)
例題:極點數量的極端情況

(a) 所有點都是極點:x 遞增時 y 嚴格遞減(左上到右下的階梯)。任兩點 PiP_iPjP_jxi<xjx_i < x_jyi>yjy_i > y_j,無人能同時在 x、y 兩維都贏 → 無人被宰制。極點數達上限 nn

(b) 極點最少:x 遞增時 y 嚴格遞增(左下到右上的線)。除最右上那個,任一點都被下一個宰制。極點只有最右上那個。

(c) 下界 = 1:x 最大的那個點永遠無人能在 x 上贏過它 → 必為極點 → 極點數 1\geq 1。結合 (b),最少恰為 1。

整理:1Mn1 \leq |M| \leq n。隨機分佈下期望極點數為 O(logn)O(\log n)


最接近的兩點(Closest Pair)

演算法策略

平面 nn 點,找距離最近的一對。Naive 兩兩配對 O(n2)O(n^2)。分治能壓到 O(nlogn)O(n \log n)——關鍵在 Combine 的巧思。

  1. 預先按 x 排序。
  2. Divide:中位 x 切左右半 PLP_LPRP_R
  3. ConquerdL=closest(PL)d_L = \text{closest}(P_L)dR=closest(PR)d_R = \text{closest}(P_R)d=min(dL,dR)d = \min(d_L, d_R)
  4. Combine:檢查跨越中垂線的點對。

Combine 若兩兩配對會爆成 O(n2)O(n^2),得想辦法剪枝。

Strip(中垂線帶):以中垂線 x=xmx = x_m 為中心、左右各 dd 寬的直立帶子,寬度 2d2d;形式定義 strip={P:P.xxm<d}\text{strip} = \{P : |P.x - x_m| < d\}

關鍵直覺:離中垂線超過 dd 的點,光 x 軸距離就 d\geq d,不可能和任何另一點配出 <d< d 的距離——整個跳過。所以只用 strip 內的點繼續配對。

strip 裡的點按 y 排序後,每個點只需往下檢查最多 7 個就夠——strip 寬 2d2d、左右兩側內部最近距離都 d\geq d,幾何上 d×2dd \times 2d 的矩形至多容 8 個點。下方範例的 ClosestPairDiagram 從第 5 步起會把 strip(藍色帶狀區)疊在中垂線兩側。

複雜度

每層 Combine O(n)O(n)(strip 篩 + y 排序 + 每點掃 ≤ 7)、共 logn\log n 層 → T(n)=2T(n/2)+O(n)=O(nlogn)T(n) = 2T(n/2) + O(n) = O(n \log n)。實作技巧:遞迴過程中同時維護 x 和 y 兩份預排序索引,避免每層重排 strip 退化成 O(nlog2n)O(n \log^2 n)

範例

題目:6 個點 A=(1,3),B=(2,1),C=(3,4),D=(5,2),E=(6,5),F=(8,3)A = (1, 3), B = (2, 1), C = (3, 4), D = (5, 2), E = (6, 5), F = (8, 3)

圖解

步驟

Divide:中位 x ≈ 4,左半 [A, B, C]、右半 [D, E, F]

左半(3 點直接兩兩算):

距離
ABAB12+22=52.24\sqrt{1^2 + 2^2} = \sqrt{5} \approx 2.24
ACAC22+12=52.24\sqrt{2^2 + 1^2} = \sqrt{5} \approx 2.24
BCBC103.16\sqrt{10} \approx 3.16

dL2.24d_L \approx 2.24

右半

距離
DEDE103.16\sqrt{10} \approx 3.16
DFDF103.16\sqrt{10} \approx 3.16
EFEF82.83\sqrt{8} \approx 2.83

dR2.83d_R \approx 2.83d=min(dL,dR)=2.24d = \min(d_L, d_R) = 2.24

Combine:中垂線 xm=4x_m = 4,strip 收 x4<2.24|x - 4| < 2.24,即 x(1.76,6.24)x \in (1.76, 6.24)B,C,D,EB, C, D, E。按 y 排序:B(1)D(2)C(4)E(5)B(1) \to D(2) \to C(4) \to E(5)

每個點往下檢查 y 差 < 2.24 的:

起點檢查Δy\Delta y距離更新?
B(2,1)B(2,1)D(5,2)D(5,2)1103.16\sqrt{10} \approx 3.16
BBC(3,4)C(3,4)3 > 2.24
D(5,2)D(5,2)C(3,4)C(3,4)282.83\sqrt{8} \approx 2.83
DDE(6,5)E(6,5)3 > 2.24
C(3,4)C(3,4)E(6,5)E(6,5)1103.16\sqrt{10} \approx 3.16

沒更短的 → 最近距離 2.24\approx 2.24ABABACAC)。

Python 實作
import math


def closest_pair(points):
    pts = sorted(points, key=lambda p: p[0])

    def dist(p, q):
        return math.hypot(p[0] - q[0], p[1] - q[1])

    def solve(a):
        if len(a) <= 3:
            return min((dist(a[i], a[j]) for i in range(len(a)) for j in range(i + 1, len(a))), default=float('inf'))
        mid = len(a) // 2
        xm = a[mid][0]
        d = min(solve(a[:mid]), solve(a[mid:]))
        strip = sorted([p for p in a if abs(p[0] - xm) < d], key=lambda p: p[1])
        for i in range(len(strip)):
            for j in range(i + 1, min(i + 8, len(strip))):
                if strip[j][1] - strip[i][1] >= d:
                    break
                d = min(d, dist(strip[i], strip[j]))
        return d

    return solve(pts)

快速傅立葉轉換(FFT)

演算法策略

DFT(Discrete Fourier Transform,離散傅立葉轉換):把長度 nn 的序列 [a0,a1,,an1][a_0, a_1, \ldots, a_{n-1}] 換成另一串 [A0,A1,,An1][A_0, A_1, \ldots, A_{n-1}]。沒新增、沒減少資訊,只是同一筆資料換個視角看(小寫 aa 是輸入、大寫 AA 是轉換後輸出,下同)。FFT(Fast Fourier Transform)是 DFT 的快速計算法,把時間從 O(n2)O(n^2) 壓到 O(nlogn)O(n \log n)

最經典的應用是多項式乘法:兩個 nn 次多項式直接相乘 O(n2)O(n^2);若先各自 FFT 變成「點值表示」,乘法就退化成點對點相乘 O(n)O(n),再用反向 FFT(IFFT)轉回係數,總共 O(nlogn)O(n \log n),比直接乘快很多。

兩個小道具:iiω\omega

ii 是虛數單位,定義 i2=1i^2 = -1;自乘四次轉一圈:

次方i0i^0i1i^1i2i^2i3i^3i4i^4
11ii1-1i-i11(轉回)

ω\omega 是「nn 次單位根」(nn-th root of unity),FFT/DFT 挑來代入計算的特殊複數,定義 ω=ei2π/n\omega = e^{-i \cdot 2\pi/n}n=4n = 4ω=i\omega = -iω\omega 表跟 ii 表幾乎一樣(差個負號):

次方ω0\omega^0ω1\omega^1ω2\omega^2ω3\omega^3ω4\omega^4
11i-i1-1ii11(轉回)

關鍵性質:ω\omega 自乘 nn 次就轉一圈回到 ω0=1\omega^0 = 1,所以指數對 nn 取餘數即可查表(ω4=ω0\omega^4 = \omega^0ω5=ω1\omega^5 = \omega^1⋯⋯)。

DFT 公式

Ak=j=0n1ajωjkA_k = \sum_{j=0}^{n-1} a_j \cdot \omega^{jk}

拆白話:

  • kk(output index):第幾個輸出,共 nnAkA_k 各跑一次。
  • jj(input index):輸入編號,把 a0,,an1a_0, \ldots, a_{n-1} 各乘一個 ωjk\omega^{jk} 再加起來。
  • ωjk\omega^{jk}jkjknn 取餘後查上表。

每個 AkA_k 就是「把所有 aja_j 各乘一個從表查到的數,全部加起來」。

FFT 的加速觀察:DFT 公式直接展開要 O(n2)O(n^2)。把奇偶下標拆兩組會出現重複計算,分治下去得 T(n)=2T(n/2)+O(n)=O(nlogn)T(n) = 2T(n/2) + O(n) = O(n \log n)。具體流程是 Cooley–Tukey butterfly 遞迴,手算考題很少要求展開到這層;考試多半要的是用 DFT 公式硬代,FFT 是程式實作時才寫的。

複雜度

算法時間
DFT(直接套公式)O(n2)O(n^2)
FFTT(n)=2T(n/2)+O(n)=O(nlogn)T(n) = 2T(n/2) + O(n) = O(n \log n)
多項式乘法(FFT + 點對點 + IFFT)O(nlogn)O(n \log n)

多項式乘法的整體流程:

階段動作成本
1. 係數 → 點值aabb 各做 FFT 得 A^\hat{A}B^\hat{B}O(nlogn)O(n \log n)
2. 點值乘法C^k=A^kB^k\hat{C}_k = \hat{A}_k \cdot \hat{B}_k 點對點相乘O(n)O(n)
3. 點值 → 係數C^\hat{C} 做 IFFT 得 ccO(nlogn)O(n \log n)

關鍵就是「換視角後乘法變便宜」——直接係數相乘 O(n2)O(n^2),繞點值一圈反而 O(nlogn)O(n \log n)

範例

題目:對 [a0,a1,a2,a3]=[0,1,2,3][a_0, a_1, a_2, a_3] = [0, 1, 2, 3] 做 DFT,n=4n = 4

步驟:先記好 ω\omegaω0=1, ω1=i, ω2=1, ω3=i\omega^0 = 1,\ \omega^1 = -i,\ \omega^2 = -1,\ \omega^3 = i,然後逐個 AkA_k 套公式。

A0A_0k=0k = 0:每項都乘 ω0=1\omega^0 = 1,全部加起來。

A0=0+1+2+3=6A_0 = 0 + 1 + 2 + 3 = 6

A1A_1k=1k = 1

jjaja_jjkjkωjk\omega^{jk}
00011
111i-i
2221-1
333ii
A1=0(1)+1(i)+2(1)+3(i)=2+2iA_1 = 0(1) + 1(-i) + 2(-1) + 3(i) = -2 + 2i

A2A_2k=2k = 2jk=0,2,4,6jk = 0, 2, 4, 6,對 44 取餘 → 0,2,0,20, 2, 0, 2

jjaja_jjkmod4jk \bmod 4ωjkmod4\omega^{jk \bmod 4}
00011
1121-1
22011
3321-1
A2=0(1)+1(1)+2(1)+3(1)=2A_2 = 0(1) + 1(-1) + 2(1) + 3(-1) = -2

A3A_3k=3k = 3jk=0,3,6,9jk = 0, 3, 6, 9,對 44 取餘 → 0,3,2,10, 3, 2, 1

jjaja_jjkmod4jk \bmod 4ωjkmod4\omega^{jk \bmod 4}
00011
113ii
2221-1
331i-i
A3=0(1)+1(i)+2(1)+3(i)=22iA_3 = 0(1) + 1(i) + 2(-1) + 3(-i) = -2 - 2i

結果

A=[6, 2+2i, 2, 22i]A = [6,\ -2 + 2i,\ -2,\ -2 - 2i]
Python 實作(用 numpy)
import numpy as np


def poly_multiply(A, B):
    n = 1
    while n < len(A) + len(B) - 1:
        n *= 2
    A_hat = np.fft.fft(A, n)
    B_hat = np.fft.fft(B, n)
    C = np.fft.ifft(A_hat * B_hat).real
    return [round(x) for x in C[:len(A) + len(B) - 1]]


print(poly_multiply([1, 2, 3], [4, 5]))  # [4, 13, 22, 15]
FFT 派得上用場的地方
問題不用 FFT用 FFT
多項式乘法O(n2)O(n^2)O(nlogn)O(n \log n)
大整數乘法O(n2)O(n^2)O(nlogn)O(n \log n)
訊號卷積 / 濾波O(n2)O(n^2)O(nlogn)O(n \log n)

只要看到「兩個序列配對累加」(卷積),FFT 幾乎都能套。Python 的 numpy.fftscipy.signal.fftconvolve、MP3/JPEG 壓縮都用它。

常見考法

題型必備技能
合併排序手跑畫遞迴樹、列每輪 merge 後陣列;雙指標合併
快速排序手跑Hoare partition(pivot = a[left]、i/j 雙指標掃到交錯、swap a[left] ↔ a[j]
Quick Sort worst case已排序 + 固定選首 pivot → T(n)=T(n1)+O(n)=O(n2)T(n) = T(n-1) + O(n) = O(n^2)
極點按 x 排序切半、RmaxR_{max} 最大 y 當門檻過濾 LmaxL_{max}
最接近兩點strip 寬 2d2d、每點只檢查 y 接近的 7 個 → 合併 O(n)O(n)
FFT記住 O(n2)O(nlogn)O(n^2) \to O(n \log n)、適用卷積 / 多項式乘法
主定理T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)c=logbac = \log_b a → 比 ffncn^c → Case 1/2/3
展開法推導展 2–3 層看規律、找觸底 k=logbnk = \log_b n、代回求和

常見雷

  • Hoare partition 要先 i++ 再比較(不是先比較再 i++);結束條件 i >= j 不是 i > j
  • 極點 \ne 凸包。凸包關心「最外圍輪廓」,極點只關心「右上角」。
  • Combine 若做到 O(nlogn)O(n \log n)、整體變 O(nlog2n)O(n \log^2 n)(主定理 Case 3 以外)。

下一篇動態規劃——當子問題不再獨立、彼此重疊時,分治會重複計算,DP 用「記下來不再重算」解掉這個低效。

Latest Updates

  • 2026.04.10 Content updated