λΆ„ν•  정볡 (Divide and Conquer)

λΆ„ν•  정볡 (Divide and Conquer)

κ°œμš”

λΆ„ν•  정볡은 큰 문제λ₯Ό μž‘μ€ λΆ€λΆ„ 문제둜 λ‚˜λˆ„κ³ , 각각을 ν•΄κ²°ν•œ ν›„ κ²°ν•©ν•˜λŠ” μ•Œκ³ λ¦¬μ¦˜ 섀계 κΈ°λ²•μž…λ‹ˆλ‹€.


λͺ©μ°¨

  1. λΆ„ν•  정볡 κ°œλ…
  2. 병합 μ •λ ¬
  3. 퀡 μ •λ ¬
  4. 이진 탐색
  5. κ±°λ“­μ œκ³±
  6. μ—°μŠ΅ 문제

1. λΆ„ν•  정볡 κ°œλ…

κΈ°λ³Έ 단계

λΆ„ν•  정볡 3단계:

1. λΆ„ν•  (Divide)
   - 문제λ₯Ό 더 μž‘μ€ λΆ€λΆ„ 문제둜 λ‚˜λˆ”

2. 정볡 (Conquer)
   - λΆ€λΆ„ 문제λ₯Ό μž¬κ·€μ μœΌλ‘œ ν•΄κ²°
   - μΆ©λΆ„νžˆ μž‘μœΌλ©΄ 직접 ν•΄κ²°

3. κ²°ν•© (Combine)
   - λΆ€λΆ„ 문제의 ν•΄λ₯Ό 합쳐 μ›λž˜ 문제의 ν•΄ ꡬ성

μ‹œκ°ν™”

        [문제]
       /      \
   [λΆ€λΆ„1]  [λΆ€λΆ„2]
   /    \    /    \
 [a]   [b] [c]   [d]
   \    /    \    /
   [λΆ€λΆ„1]  [λΆ€λΆ„2]
       \      /
        [ν•΄λ‹΅]

λΆ„ν•  정볡 vs DP

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                β”‚ λΆ„ν•  정볡       β”‚ DP              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ λΆ€λΆ„ 문제      β”‚ μ„œλ‘œ 독립       β”‚ 쀑볡 쑴재       β”‚
β”‚ μ €μž₯           β”‚ λΆˆν•„μš”          β”‚ λ©”λͺ¨μ΄μ œμ΄μ…˜    β”‚
β”‚ μ˜ˆμ‹œ           β”‚ 병합정렬        β”‚ ν”Όλ³΄λ‚˜μΉ˜        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. 병합 μ •λ ¬ (Merge Sort)

원리

1. 배열을 반으둜 λ‚˜λˆ”
2. 각 뢀뢄을 μž¬κ·€μ μœΌλ‘œ μ •λ ¬
3. μ •λ ¬λœ 두 뢀뢄을 병합

[38, 27, 43, 3, 9, 82, 10]
           ↓ λΆ„ν• 
[38, 27, 43, 3]  [9, 82, 10]
      ↓              ↓
[38, 27] [43, 3] [9, 82] [10]
    ↓       ↓       ↓      ↓
[38][27] [43][3] [9][82]  [10]
    ↓       ↓       ↓      ↓
[27, 38] [3, 43] [9, 82] [10]
      ↓              ↓
[3, 27, 38, 43]  [9, 10, 82]
           ↓ κ²°ν•©
[3, 9, 10, 27, 38, 43, 82]

κ΅¬ν˜„

// C
void merge(int arr[], int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;

    int L[n1], R[n2];

    for (int i = 0; i < n1; i++) L[i] = arr[left + i];
    for (int i = 0; i < n2; i++) R[i] = arr[mid + 1 + i];

    int i = 0, j = 0, k = left;

    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) arr[k++] = L[i++];
        else arr[k++] = R[j++];
    }

    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];
}

void mergeSort(int arr[], int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;

        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }
}
def merge_sort(arr):
    if len(arr) <= 1:
        return arr

    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])

    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 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

    result.extend(left[i:])
    result.extend(right[j:])
    return result

λ³΅μž‘λ„ 뢄석

T(n) = 2T(n/2) + O(n)

λΆ„ν• : O(1)
정볡: 2 Γ— T(n/2)
κ²°ν•©: O(n)

λ§ˆμŠ€ν„° 정리:
a = 2, b = 2, f(n) = n
n^(log_b(a)) = n^1 = n
f(n) = Θ(n)

β†’ T(n) = Θ(n log n)

곡간: O(n) - μž„μ‹œ λ°°μ—΄

3. 퀡 μ •λ ¬ (Quick Sort)

원리

1. ν”Όλ²— 선택
2. 피벗보닀 μž‘μ€ μ›μ†ŒλŠ” μ™Όμͺ½, 큰 μ›μ†ŒλŠ” 였λ₯Έμͺ½
3. 각 뢀뢄을 μž¬κ·€μ μœΌλ‘œ μ •λ ¬

[5, 3, 8, 4, 2, 7, 1, 6], ν”Όλ²—=5
           ↓ νŒŒν‹°μ…˜
[3, 4, 2, 1] [5] [8, 7, 6]
      ↓              ↓
[1, 2, 3, 4]    [6, 7, 8]
           ↓
[1, 2, 3, 4, 5, 6, 7, 8]

κ΅¬ν˜„

// C++
int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;

    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            swap(arr[++i], arr[j]);
        }
    }

    swap(arr[i + 1], arr[high]);
    return i + 1;
}

void quickSort(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);

        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}
def quick_sort(arr, low, high):
    if low < high:
        pi = partition(arr, low, high)
        quick_sort(arr, low, pi - 1)
        quick_sort(arr, pi + 1, high)

def partition(arr, low, high):
    pivot = arr[high]
    i = low - 1

    for j in range(low, high):
        if arr[j] < pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]

    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1

λ³΅μž‘λ„ 뢄석

평균: T(n) = 2T(n/2) + O(n) = O(n log n)
μ΅œμ•…: T(n) = T(n-1) + O(n) = O(nΒ²)
      (이미 μ •λ ¬λœ λ°°μ—΄ + 첫/λ§ˆμ§€λ§‰ ν”Όλ²—)

곡간: O(log n) - μž¬κ·€ μŠ€νƒ

λΆ„ν•  정볡 관점

문제: μ •λ ¬λœ λ°°μ—΄μ—μ„œ target μ°ΎκΈ°

λΆ„ν• : 쀑간값 κΈ°μ€€μœΌλ‘œ μ™Όμͺ½/였λ₯Έμͺ½ λ‚˜λˆ”
정볡: target이 μžˆλŠ” μͺ½λ§Œ 탐색
κ²°ν•©: ν•„μš” μ—†μŒ (찾으면 λ°˜ν™˜)

[1, 3, 5, 7, 9, 11, 13], target=9
           ↓
       [7] 쀑간값
      7 < 9
           ↓
    였λ₯Έμͺ½ 탐색: [9, 11, 13]
           ↓
       [11] 쀑간값
      11 > 9
           ↓
    μ™Όμͺ½ 탐색: [9]
           ↓
       찾음!

μž¬κ·€ κ΅¬ν˜„

// C++
int binarySearchRecursive(const vector<int>& arr, int left, int right, int target) {
    if (left > right) return -1;

    int mid = left + (right - left) / 2;

    if (arr[mid] == target) return mid;
    if (arr[mid] > target) return binarySearchRecursive(arr, left, mid - 1, target);
    return binarySearchRecursive(arr, mid + 1, right, target);
}
def binary_search_recursive(arr, left, right, target):
    if left > right:
        return -1

    mid = (left + right) // 2

    if arr[mid] == target:
        return mid
    if arr[mid] > target:
        return binary_search_recursive(arr, left, mid - 1, target)
    return binary_search_recursive(arr, mid + 1, right, target)

5. κ±°λ“­μ œκ³± (Exponentiation)

λ‹¨μˆœ 방법 vs λΆ„ν•  정볡

a^n 계산

λ‹¨μˆœ: a Γ— a Γ— a Γ— ... Γ— a (n번) β†’ O(n)

λΆ„ν•  정볡:
a^n = a^(n/2) Γ— a^(n/2)        (n이 짝수)
a^n = a^(n/2) Γ— a^(n/2) Γ— a    (n이 ν™€μˆ˜)

β†’ O(log n)

예: 2^10
= 2^5 Γ— 2^5
= (2^2 Γ— 2^2 Γ— 2) Γ— (2^2 Γ— 2^2 Γ— 2)
= ((2 Γ— 2)^2 Γ— 2)^2

κ΅¬ν˜„

// C - μž¬κ·€
long long power(long long a, int n) {
    if (n == 0) return 1;
    if (n == 1) return a;

    long long half = power(a, n / 2);

    if (n % 2 == 0) {
        return half * half;
    } else {
        return half * half * a;
    }
}

// C - 반볡 (λΉ„νŠΈ μ—°μ‚°)
long long powerIterative(long long a, int n) {
    long long result = 1;

    while (n > 0) {
        if (n & 1) {  // n이 ν™€μˆ˜
            result *= a;
        }
        a *= a;
        n >>= 1;
    }

    return result;
}
def power(a, n):
    if n == 0:
        return 1
    if n == 1:
        return a

    half = power(a, n // 2)

    if n % 2 == 0:
        return half * half
    else:
        return half * half * a

# λͺ¨λ“ˆλŸ¬ κ±°λ“­μ œκ³± (큰 수)
def power_mod(a, n, mod):
    result = 1
    a %= mod

    while n > 0:
        if n & 1:
            result = (result * a) % mod
        a = (a * a) % mod
        n >>= 1

    return result

ν–‰λ ¬ κ±°λ“­μ œκ³±

ν”Όλ³΄λ‚˜μΉ˜λ₯Ό O(log n)에 계산

[F(n+1)]   [1 1]^n   [F(1)]
[F(n)  ] = [1 0]   Γ— [F(0)]

ν–‰λ ¬ κ±°λ“­μ œκ³±μ„ λΆ„ν•  μ •λ³΅μœΌλ‘œ!
def matrix_mult(A, B, mod=10**9+7):
    return [
        [(A[0][0]*B[0][0] + A[0][1]*B[1][0]) % mod,
         (A[0][0]*B[0][1] + A[0][1]*B[1][1]) % mod],
        [(A[1][0]*B[0][0] + A[1][1]*B[1][0]) % mod,
         (A[1][0]*B[0][1] + A[1][1]*B[1][1]) % mod]
    ]

def matrix_power(M, n, mod=10**9+7):
    if n == 1:
        return M

    if n % 2 == 0:
        half = matrix_power(M, n // 2, mod)
        return matrix_mult(half, half, mod)
    else:
        return matrix_mult(M, matrix_power(M, n - 1, mod), mod)

def fibonacci(n):
    if n <= 1:
        return n

    M = [[1, 1], [1, 0]]
    result = matrix_power(M, n)
    return result[1][0]

6. μ—°μŠ΅ 문제

문제 1: λ°°μ—΄μ˜ μ—­μˆœ 쌍 개수

i < j이고 arr[i] > arr[j]인 쌍의 개수

힌트 병합 μ •λ ¬ κ³Όμ •μ—μ„œ 카운트
μ •λ‹΅ μ½”λ“œ
def count_inversions(arr):
    def merge_count(arr, temp, left, mid, right):
        i = left
        j = mid + 1
        k = left
        inv_count = 0

        while i <= mid and j <= right:
            if arr[i] <= arr[j]:
                temp[k] = arr[i]
                i += 1
            else:
                temp[k] = arr[j]
                inv_count += (mid - i + 1)  # 핡심!
                j += 1
            k += 1

        while i <= mid:
            temp[k] = arr[i]
            i += 1
            k += 1

        while j <= right:
            temp[k] = arr[j]
            j += 1
            k += 1

        for i in range(left, right + 1):
            arr[i] = temp[i]

        return inv_count

    def merge_sort_count(arr, temp, left, right):
        inv_count = 0
        if left < right:
            mid = (left + right) // 2
            inv_count += merge_sort_count(arr, temp, left, mid)
            inv_count += merge_sort_count(arr, temp, mid + 1, right)
            inv_count += merge_count(arr, temp, left, mid, right)
        return inv_count

    n = len(arr)
    temp = [0] * n
    return merge_sort_count(arr, temp, 0, n - 1)

μΆ”μ²œ 문제

λ‚œμ΄λ„ 문제 ν”Œλž«νΌ μœ ν˜•
⭐⭐ ν–‰λ ¬ 제곱 λ°±μ€€ ν–‰λ ¬ κ±°λ“­μ œκ³±
⭐⭐ ν”Όλ³΄λ‚˜μΉ˜ 수 6 λ°±μ€€ ν–‰λ ¬ κ±°λ“­μ œκ³±
⭐⭐ K번째 수 λ°±μ€€ Quick Select
⭐⭐⭐ 버블 μ†ŒνŠΈ λ°±μ€€ μ—­μˆœ 쌍
⭐⭐⭐ κ°€μž₯ κ°€κΉŒμš΄ 두 점 λ°±μ€€ λΆ„ν•  정볡

λ§ˆμŠ€ν„° 정리 (Master Theorem)

T(n) = aT(n/b) + f(n) ν˜•νƒœμ˜ 점화식 ν•΄κ²°

Case 1: f(n) = O(n^(log_b(a) - Ξ΅))
        β†’ T(n) = Θ(n^(log_b(a)))

Case 2: f(n) = Θ(n^(log_b(a)))
        β†’ T(n) = Θ(n^(log_b(a)) log n)

Case 3: f(n) = Ξ©(n^(log_b(a) + Ξ΅))
        β†’ T(n) = Θ(f(n))

예:
- 병합 μ •λ ¬: T(n) = 2T(n/2) + n β†’ O(n log n) [Case 2]
- 이진 탐색: T(n) = T(n/2) + 1 β†’ O(log n) [Case 2]
- κ±°λ“­μ œκ³±: T(n) = T(n/2) + 1 β†’ O(log n) [Case 2]

λ‹€μŒ 단계


참고 자료

to navigate between lessons