DIVIDE AND CONQUER PROBLEM-SOLVING TRICKS

Essential Divide and Conquer Techniques

# Technique Description
1 Recursive decomposition Break problems into smaller subproblems until base case is reached
2 Merge Sort approach Divide array into halves, sort each half, then merge results
3 Quick Sort partitioning Select pivot, partition array, then recursively sort partitions
4 Binary Search Divide sorted array in halves to efficiently locate elements
5 Finding peak elements Compare middle element with neighbors to determine search direction
6 Closest pair of points Divide plane into regions and recursively find closest pairs
7 Strassen's matrix multiplication Break matrix multiplication into smaller subproblems
8 Karatsuba multiplication Fast multiplication algorithm for large numbers
9 Majority element finding Divide array and count occurrences in each half
10 Maximum subarray problem Find maximum contiguous subarray by dividing array
11 Skyline problem Merge skylines from divided subproblems
12 Fast Fourier Transform Divide polynomial evaluation into even/odd terms
13 Convex hull algorithms Divide points into subsets and merge hulls
14 Counting inversions Modify merge sort to count inversions during merge
15 Median of medians Divide array to find approximate median for pivot selection

Implementation Examples

Merge Sort Implementation

// Merge Sort in C
void merge(int arr[], int l, int m, int r) {
    int n1 = m - l + 1;
    int n2 = r - m;
    int L[n1], R[n2];
    
    for (int i = 0; i < n1; i++)
        L[i] = arr[l + i];
    for (int j = 0; j < n2; j++)
        R[j] = arr[m + 1 + j];
        
    int i = 0, j = 0, k = l;
    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 l, int r) {
    if (l < r) {
        int m = l + (r - l) / 2;
        mergeSort(arr, l, m);
        mergeSort(arr, m + 1, r);
        merge(arr, l, m, r);
    }
}

Binary Search

// Binary Search in C
int binarySearch(int arr[], int l, int r, int x) {
    while (l <= r) {
        int m = l + (r - l) / 2;
        if (arr[m] == x) return m;
        if (arr[m] < x) l = m + 1;
        else r = m - 1;
    }
    return -1;
}

// Recursive version
int binarySearchRec(int arr[], int l, int r, int x) {
    if (r >= l) {
        int mid = l + (r - l) / 2;
        if (arr[mid] == x) return mid;
        if (arr[mid] > x)
            return binarySearchRec(arr, l, mid - 1, x);
        return binarySearchRec(arr, mid + 1, r, x);
    }
    return -1;
}

Quick Sort

// Quick Sort in C
int partition(int arr[], int low, int high) {
    int pivot = arr[high];
    int i = (low - 1);
    
    for (int j = low; j <= high - 1; j++) {
        if (arr[j] < pivot) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return (i + 1);
}

void quickSort(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);
    }
}

Closest Pair of Points

// Closest pair of points (simplified)
typedef struct { double x, y; } Point;

double bruteForce(Point P[], int n) {
    double min = DBL_MAX;
    for (int i = 0; i < n; ++i)
        for (int j = i+1; j < n; ++j)
            if (dist(P[i], P[j]) < min)
                min = dist(P[i], P[j]);
    return min;
}

double closestUtil(Point P[], int n) {
    if (n <= 3) return bruteForce(P, n);
    
    int mid = n/2;
    Point midPoint = P[mid];
    
    double dl = closestUtil(P, mid);
    double dr = closestUtil(P + mid, n - mid);
    double d = fmin(dl, dr);
    
    // Check strip of points within d distance of middle line
    Point strip[n];
    int j = 0;
    for (int i = 0; i < n; i++)
        if (fabs(P[i].x - midPoint.x) < d)
            strip[j++] = P[i];
            
    return fmin(d, stripClosest(strip, j, d));
}

Pro Tips for Divide and Conquer

Remember: The base case is crucial in recursive Divide and Conquer algorithms to prevent infinite recursion.

Best Practices

  • Clearly define your base case(s) first
  • Ensure each recursive call makes progress toward the base case
  • Combine solutions efficiently in the conquer step
  • Analyze time complexity using recurrence relations

Performance Considerations

  • Divide and Conquer often has O(n log n) complexity
  • Recursion has overhead - consider iterative versions for small n
  • Memoization can optimize overlapping subproblems
  • Tail recursion optimization can help with stack usage