Divide and conquer
Divide and conquer solves a problem by splitting it into independent subproblems, solving each, and combining the answers. The split is only the start: the cost of combining and how evenly you split decide the running time.
Learning goals
- Name the divide, conquer and combine steps of an algorithm
- Explain why balanced splits matter
- Read a recursion tree to estimate total work
- Describe merge sort and quick sort as divide-and-conquer algorithms
Prerequisites
To count the votes in a large election, split the ballots among many teams, let each team count its pile, and add the totals. Counting a smaller pile is the same job as counting the whole thing, and combining is cheap.
Divide
Divide splits the input into smaller instances of the same problem. For an array, that usually means the two halves around mid; for a number, it means n / 2; for a list, the head and the rest.
The subproblems should be independent (they do not share work) and of the same kind as the original. If they overlap, you are in the territory of dynamic programming (Chapter 23).
Conquer
Conquer solves each subproblem recursively. When a subproblem is small enough, solve it directly: that is the base case. Libraries often switch to a simple method (such as insertion sort) at a small size because recursion has overhead.
In the animation, max(lo, hi) calls itself on both halves. Each box in the tree is one conquered subproblem, with its answer once it returns.
Worst
O(n)
Space
O(log n) stack
Divide and conquer: maximum of an array
Interactive · edit the inputint[] arr · length 8
Recursion tree · 1 call
Call max(a, 0, 7) on 8 elements.
1static int max(int[] a, int lo, int hi) {2if (lo == hi) return a[lo]; // one element: it is the max3int mid = lo + (hi - lo) / 2; // divide4int left = max(a, lo, mid); // conquer left half5int right = max(a, mid + 1, hi); // conquer right half6return Math.max(left, right); // combine7}
Live state
lo
0
hi
7
Call stack (top first)
- max(0, 7)
Execution trace (dry run table)Show 60 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | lo=0 hi=7 | Call max(a, 0, 7) on 8 elements. |
| 2 | L2 | lo=0 hi=7 | More than one element: divide. |
| 3 | L3 | lo=0 mid=3 hi=7 | mid = 3. Left half [0..3], right half [4..7]. |
| 4 | L1 | lo=0 hi=3 | Call max(a, 0, 3) on 4 elements. |
| 5 | L2 | lo=0 hi=3 | More than one element: divide. |
| 6 | L3 | lo=0 mid=1 hi=3 | mid = 1. Left half [0..1], right half [2..3]. |
| 7 | L1 | lo=0 hi=1 | Call max(a, 0, 1) on 2 elements. |
| 8 | L2 | lo=0 hi=1 | More than one element: divide. |
| 9 | L3 | lo=0 mid=0 hi=1 | mid = 0. Left half [0..0], right half [1..1]. |
| 10 | L1 | lo=0 hi=0 | Call max(a, 0, 0) on 1 element. |
| 11 | L2 | lo=0 hi=0 | One element: the maximum is a[0] = 7. |
| 12 | L2 | return=7 | return 7. |
| 13 | L4 | left=7 | Left half returned 7. Now the right half. |
| 14 | L1 | lo=1 hi=1 | Call max(a, 1, 1) on 1 element. |
| 15 | L2 | lo=1 hi=1 | One element: the maximum is a[1] = 2. |
| 16 | L2 | return=2 | return 2. |
| 17 | L6 | left=7 right=2 return=7 | Combine: the larger of 7 and 2 is 7. One comparison joins two answers. |
| 18 | L4 | left=7 | Left half returned 7. Now the right half. |
| 19 | L1 | lo=2 hi=3 | Call max(a, 2, 3) on 2 elements. |
| 20 | L2 | lo=2 hi=3 | More than one element: divide. |
| 21 | L3 | lo=2 mid=2 hi=3 | mid = 2. Left half [2..2], right half [3..3]. |
| 22 | L1 | lo=2 hi=2 | Call max(a, 2, 2) on 1 element. |
| 23 | L2 | lo=2 hi=2 | One element: the maximum is a[2] = 9. |
| 24 | L2 | return=9 | return 9. |
| 25 | L4 | left=9 | Left half returned 9. Now the right half. |
| 26 | L1 | lo=3 hi=3 | Call max(a, 3, 3) on 1 element. |
| 27 | L2 | lo=3 hi=3 | One element: the maximum is a[3] = 4. |
| 28 | L2 | return=4 | return 4. |
| 29 | L6 | left=9 right=4 return=9 | Combine: the larger of 9 and 4 is 9. One comparison joins two answers. |
| 30 | L6 | left=7 right=9 return=9 | Combine: the larger of 7 and 9 is 9. One comparison joins two answers. |
| 31 | L4 | left=9 | Left half returned 9. Now the right half. |
| 32 | L1 | lo=4 hi=7 | Call max(a, 4, 7) on 4 elements. |
| 33 | L2 | lo=4 hi=7 | More than one element: divide. |
| 34 | L3 | lo=4 mid=5 hi=7 | mid = 5. Left half [4..5], right half [6..7]. |
| 35 | L1 | lo=4 hi=5 | Call max(a, 4, 5) on 2 elements. |
| 36 | L2 | lo=4 hi=5 | More than one element: divide. |
| 37 | L3 | lo=4 mid=4 hi=5 | mid = 4. Left half [4..4], right half [5..5]. |
| 38 | L1 | lo=4 hi=4 | Call max(a, 4, 4) on 1 element. |
| 39 | L2 | lo=4 hi=4 | One element: the maximum is a[4] = 11. |
| 40 | L2 | return=11 | return 11. |
| 41 | L4 | left=11 | Left half returned 11. Now the right half. |
| 42 | L1 | lo=5 hi=5 | Call max(a, 5, 5) on 1 element. |
| 43 | L2 | lo=5 hi=5 | One element: the maximum is a[5] = 3. |
| 44 | L2 | return=3 | return 3. |
| 45 | L6 | left=11 right=3 return=11 | Combine: the larger of 11 and 3 is 11. One comparison joins two answers. |
| 46 | L4 | left=11 | Left half returned 11. Now the right half. |
| 47 | L1 | lo=6 hi=7 | Call max(a, 6, 7) on 2 elements. |
| 48 | L2 | lo=6 hi=7 | More than one element: divide. |
| 49 | L3 | lo=6 mid=6 hi=7 | mid = 6. Left half [6..6], right half [7..7]. |
| 50 | L1 | lo=6 hi=6 | Call max(a, 6, 6) on 1 element. |
| 51 | L2 | lo=6 hi=6 | One element: the maximum is a[6] = 8. |
| 52 | L2 | return=8 | return 8. |
| 53 | L4 | left=8 | Left half returned 8. Now the right half. |
| 54 | L1 | lo=7 hi=7 | Call max(a, 7, 7) on 1 element. |
| 55 | L2 | lo=7 hi=7 | One element: the maximum is a[7] = 5. |
| 56 | L2 | return=5 | return 5. |
| 57 | L6 | left=8 right=5 return=8 | Combine: the larger of 8 and 5 is 8. One comparison joins two answers. |
| 58 | L6 | left=11 right=8 return=11 | Combine: the larger of 11 and 8 is 11. One comparison joins two answers. |
| 59 | L6 | left=9 right=11 return=11 | Combine: the larger of 9 and 11 is 11. One comparison joins two answers. |
| 60 | L6 | return=11 | The maximum is 11, found with 7 comparisons (n - 1, the same as a plain loop) and 15 calls. The point is the pattern: divide, conquer, combine. |
Combine
Combine builds the answer to the whole problem from the answers to its parts. For the maximum it is one comparison; for merge sort it is an O(n) merge; for counting inversions it is a merge plus a count. For quick sort the combine step is empty, because all the work is in the divide (partition).
The cost of combining at each level is what the recurrence T(n) = a·T(n/b) + f(n) calls f(n). Cheap combines give linear algorithms; expensive ones may not help at all.
Balanced vs unbalanced split
If each split is balanced (halves), the tree has about log₂ n levels. If the split is lopsided (sizes n - 1 and 1), it has n levels and the algorithm behaves like a linear chain.
Merge sort always splits in the middle, so it is always O(n log n). Quick sort splits at the pivot, so a bad pivot gives O(n²). That is the whole difference between a worst case that is guaranteed good and one that depends on the data.
| Split | Depth for n = 1,000,000 | Example |
|---|---|---|
| Halves | about 20 | Merge sort, binary search |
| 90% / 10% | about 132 | Quick sort with a mediocre pivot |
| n - 1 / 1 | 1,000,000 | Quick sort on sorted input, last pivot |
Recursion tree
Draw the tree of subproblems and add up the work per level. For merge sort: level 0 has 1 problem of size n, level 1 has 2 of size n / 2, level k has 2^k of size n / 2^k, so every level does n work in total over log₂ n levels.
The calculator in the next lesson computes these level totals for any a, b and d. Whether the top, the bottom or all levels dominate decides the answer.
| Level | Subproblems | Size each | Work per level |
|---|---|---|---|
| 0 | 1 | n | n |
| 1 | 2 | n / 2 | n |
| 2 | 4 | n / 4 | n |
| k | 2^k | n / 2^k | n |
| log₂ n | n | 1 | n |
Merge sort intuition
Merge sort divides at the middle, conquers by sorting each half, and combines by merging two sorted halves in linear time. The recurrence is T(n) = 2·T(n/2) + O(n), which solves to O(n log n).
Its easy combine step is the key: merging two sorted lists is a single pass. Step through the animation and notice that all the sorting happens inside the merges, on the way back up.
Worst
O(n log n)
Space
O(n)
Merge sort (top-down)
Interactive · edit the inputint[] arr · length 8
Divide the 8 elements until single elements remain, then merge sorted runs back together.
1public static void mergeSort(int[] arr, int lo, int hi) {2if (hi - lo < 1) return;3int mid = lo + (hi - lo) / 2;4mergeSort(arr, lo, mid);5mergeSort(arr, mid + 1, hi);6merge(arr, lo, mid, hi);7}89static void merge(int[] arr, int lo, int mid, int hi) {10int[] tmp = new int[hi - lo + 1];11int i = lo, j = mid + 1, k = 0;12while (i <= mid && j <= hi) {13if (arr[i] <= arr[j]) tmp[k++] = arr[i++];14else tmp[k++] = arr[j++];15}16while (i <= mid) tmp[k++] = arr[i++];17while (j <= hi) tmp[k++] = arr[j++];18System.arraycopy(tmp, 0, arr, lo, tmp.length);19}
Live state
n
8
Execution trace (dry run table)Show 94 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=8 | Divide the 8 elements until single elements remain, then merge sorted runs back together. |
| 2 | L1 | lo=0 hi=7 size=8 | Enter mergeSort(arr, 0, 7) on 8 elements. |
| 3 | L2 | lo=0 hi=7 | More than one element, so split in half. |
| 4 | L3 | lo=0 mid=3 hi=7 | mid = 3. Left half [0..3], right half [4..7]. |
| 5 | L4 | lo=0 hi=3 size=4 | Enter mergeSort(arr, 0, 3) on 4 elements. |
| 6 | L2 | lo=0 hi=3 | More than one element, so split in half. |
| 7 | L3 | lo=0 mid=1 hi=3 | mid = 1. Left half [0..1], right half [2..3]. |
| 8 | L4 | lo=0 hi=1 size=2 | Enter mergeSort(arr, 0, 1) on 2 elements. |
| 9 | L2 | lo=0 hi=1 | More than one element, so split in half. |
| 10 | L3 | lo=0 mid=0 hi=1 | mid = 0. Left half [0..0], right half [1..1]. |
| 11 | L4 | lo=0 hi=0 size=1 | Enter mergeSort(arr, 0, 0) on 1 element. |
| 12 | L2 | lo=0 hi=0 | Base case: one element is already sorted. |
| 13 | L5 | lo=1 hi=1 size=1 | Enter mergeSort(arr, 1, 1) on 1 element. |
| 14 | L2 | lo=1 hi=1 | Base case: one element is already sorted. |
| 15 | L10 | lo=0 mid=0 hi=1 i=0 j=1 k=0 | Allocate tmp for 2 elements. Left run [0..0] and right run [1..1] are each already sorted. |
| 16 | L13 | lo=0 mid=0 hi=1 i=0 j=1 k=0 | The right head is strictly smaller, so take it. |
| 17 | L14 | lo=0 mid=0 hi=1 i=0 j=2 k=1 | tmp[0] = 27. |
| 18 | L16 | lo=0 mid=0 hi=1 i=1 j=2 k=2 | The left run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 19 | L18 | lo=0 mid=0 hi=1 i=1 j=2 k=2 | Copy tmp back into arr[0..1]. That range is now sorted. |
| 20 | L5 | lo=2 hi=3 size=2 | Enter mergeSort(arr, 2, 3) on 2 elements. |
| 21 | L2 | lo=2 hi=3 | More than one element, so split in half. |
| 22 | L3 | lo=2 mid=2 hi=3 | mid = 2. Left half [2..2], right half [3..3]. |
| 23 | L4 | lo=2 hi=2 size=1 | Enter mergeSort(arr, 2, 2) on 1 element. |
| 24 | L2 | lo=2 hi=2 | Base case: one element is already sorted. |
| 25 | L5 | lo=3 hi=3 size=1 | Enter mergeSort(arr, 3, 3) on 1 element. |
| 26 | L2 | lo=3 hi=3 | Base case: one element is already sorted. |
| 27 | L10 | lo=2 mid=2 hi=3 i=2 j=3 k=0 | Allocate tmp for 2 elements. Left run [2..2] and right run [3..3] are each already sorted. |
| 28 | L13 | lo=2 mid=2 hi=3 i=2 j=3 k=0 | The right head is strictly smaller, so take it. |
| 29 | L14 | lo=2 mid=2 hi=3 i=2 j=4 k=1 | tmp[0] = 3. |
| 30 | L16 | lo=2 mid=2 hi=3 i=3 j=4 k=2 | The left run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 31 | L18 | lo=2 mid=2 hi=3 i=3 j=4 k=2 | Copy tmp back into arr[2..3]. That range is now sorted. |
| 32 | L10 | lo=0 mid=1 hi=3 i=0 j=2 k=0 | Allocate tmp for 4 elements. Left run [0..1] and right run [2..3] are each already sorted. |
| 33 | L13 | lo=0 mid=1 hi=3 i=0 j=2 k=0 | The right head is strictly smaller, so take it. |
| 34 | L14 | lo=0 mid=1 hi=3 i=0 j=3 k=1 | tmp[0] = 3. |
| 35 | L13 | lo=0 mid=1 hi=3 i=0 j=3 k=1 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 36 | L13 | lo=0 mid=1 hi=3 i=1 j=3 k=2 | tmp[1] = 27. |
| 37 | L13 | lo=0 mid=1 hi=3 i=1 j=3 k=2 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 38 | L13 | lo=0 mid=1 hi=3 i=2 j=3 k=3 | tmp[2] = 38. |
| 39 | L17 | lo=0 mid=1 hi=3 i=2 j=4 k=4 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 40 | L18 | lo=0 mid=1 hi=3 i=2 j=4 k=4 | Copy tmp back into arr[0..3]. That range is now sorted. |
| 41 | L5 | lo=4 hi=7 size=4 | Enter mergeSort(arr, 4, 7) on 4 elements. |
| 42 | L2 | lo=4 hi=7 | More than one element, so split in half. |
| 43 | L3 | lo=4 mid=5 hi=7 | mid = 5. Left half [4..5], right half [6..7]. |
| 44 | L4 | lo=4 hi=5 size=2 | Enter mergeSort(arr, 4, 5) on 2 elements. |
| 45 | L2 | lo=4 hi=5 | More than one element, so split in half. |
| 46 | L3 | lo=4 mid=4 hi=5 | mid = 4. Left half [4..4], right half [5..5]. |
| 47 | L4 | lo=4 hi=4 size=1 | Enter mergeSort(arr, 4, 4) on 1 element. |
| 48 | L2 | lo=4 hi=4 | Base case: one element is already sorted. |
| 49 | L5 | lo=5 hi=5 size=1 | Enter mergeSort(arr, 5, 5) on 1 element. |
| 50 | L2 | lo=5 hi=5 | Base case: one element is already sorted. |
| 51 | L10 | lo=4 mid=4 hi=5 i=4 j=5 k=0 | Allocate tmp for 2 elements. Left run [4..4] and right run [5..5] are each already sorted. |
| 52 | L13 | lo=4 mid=4 hi=5 i=4 j=5 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 53 | L13 | lo=4 mid=4 hi=5 i=5 j=5 k=1 | tmp[0] = 9. |
| 54 | L17 | lo=4 mid=4 hi=5 i=5 j=6 k=2 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 55 | L18 | lo=4 mid=4 hi=5 i=5 j=6 k=2 | Copy tmp back into arr[4..5]. That range is now sorted. |
| 56 | L5 | lo=6 hi=7 size=2 | Enter mergeSort(arr, 6, 7) on 2 elements. |
| 57 | L2 | lo=6 hi=7 | More than one element, so split in half. |
| 58 | L3 | lo=6 mid=6 hi=7 | mid = 6. Left half [6..6], right half [7..7]. |
| 59 | L4 | lo=6 hi=6 size=1 | Enter mergeSort(arr, 6, 6) on 1 element. |
| 60 | L2 | lo=6 hi=6 | Base case: one element is already sorted. |
| 61 | L5 | lo=7 hi=7 size=1 | Enter mergeSort(arr, 7, 7) on 1 element. |
| 62 | L2 | lo=7 hi=7 | Base case: one element is already sorted. |
| 63 | L10 | lo=6 mid=6 hi=7 i=6 j=7 k=0 | Allocate tmp for 2 elements. Left run [6..6] and right run [7..7] are each already sorted. |
| 64 | L13 | lo=6 mid=6 hi=7 i=6 j=7 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 65 | L13 | lo=6 mid=6 hi=7 i=7 j=7 k=1 | tmp[0] = 10. |
| 66 | L17 | lo=6 mid=6 hi=7 i=7 j=8 k=2 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 67 | L18 | lo=6 mid=6 hi=7 i=7 j=8 k=2 | Copy tmp back into arr[6..7]. That range is now sorted. |
| 68 | L10 | lo=4 mid=5 hi=7 i=4 j=6 k=0 | Allocate tmp for 4 elements. Left run [4..5] and right run [6..7] are each already sorted. |
| 69 | L13 | lo=4 mid=5 hi=7 i=4 j=6 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 70 | L13 | lo=4 mid=5 hi=7 i=5 j=6 k=1 | tmp[0] = 9. |
| 71 | L13 | lo=4 mid=5 hi=7 i=5 j=6 k=1 | The right head is strictly smaller, so take it. |
| 72 | L14 | lo=4 mid=5 hi=7 i=5 j=7 k=2 | tmp[1] = 10. |
| 73 | L13 | lo=4 mid=5 hi=7 i=5 j=7 k=2 | The right head is strictly smaller, so take it. |
| 74 | L14 | lo=4 mid=5 hi=7 i=5 j=8 k=3 | tmp[2] = 15. |
| 75 | L16 | lo=4 mid=5 hi=7 i=6 j=8 k=4 | The left run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 76 | L18 | lo=4 mid=5 hi=7 i=6 j=8 k=4 | Copy tmp back into arr[4..7]. That range is now sorted. |
| 77 | L10 | lo=0 mid=3 hi=7 i=0 j=4 k=0 | Allocate tmp for 8 elements. Left run [0..3] and right run [4..7] are each already sorted. |
| 78 | L13 | lo=0 mid=3 hi=7 i=0 j=4 k=0 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 79 | L13 | lo=0 mid=3 hi=7 i=1 j=4 k=1 | tmp[0] = 3. |
| 80 | L13 | lo=0 mid=3 hi=7 i=1 j=4 k=1 | The right head is strictly smaller, so take it. |
| 81 | L14 | lo=0 mid=3 hi=7 i=1 j=5 k=2 | tmp[1] = 9. |
| 82 | L13 | lo=0 mid=3 hi=7 i=1 j=5 k=2 | The right head is strictly smaller, so take it. |
| 83 | L14 | lo=0 mid=3 hi=7 i=1 j=6 k=3 | tmp[2] = 10. |
| 84 | L13 | lo=0 mid=3 hi=7 i=1 j=6 k=3 | The right head is strictly smaller, so take it. |
| 85 | L14 | lo=0 mid=3 hi=7 i=1 j=7 k=4 | tmp[3] = 15. |
| 86 | L13 | lo=0 mid=3 hi=7 i=1 j=7 k=4 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 87 | L13 | lo=0 mid=3 hi=7 i=2 j=7 k=5 | tmp[4] = 27. |
| 88 | L13 | lo=0 mid=3 hi=7 i=2 j=7 k=5 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 89 | L13 | lo=0 mid=3 hi=7 i=3 j=7 k=6 | tmp[5] = 38. |
| 90 | L13 | lo=0 mid=3 hi=7 i=3 j=7 k=6 | The left head is not larger, so take it. Using <= (left wins ties) is what makes merge sort stable. |
| 91 | L13 | lo=0 mid=3 hi=7 i=4 j=7 k=7 | tmp[6] = 43. |
| 92 | L17 | lo=0 mid=3 hi=7 i=4 j=8 k=8 | The right run still has 1 element. They are already sorted and no larger run remains, so copy them all. |
| 93 | L18 | lo=0 mid=3 hi=7 i=4 j=8 k=8 | Copy tmp back into arr[0..7]. That range is now sorted. |
| 94 | L6 | Sorted with 17 comparisons. About n log n comparisons, whatever the input order. |
Quick sort intuition
Quick sort puts the work in the divide: partition around a pivot so that everything smaller is on the left and everything larger on the right. The conquer step sorts both sides, and the combine step is empty, because the pieces are already in the right order.
Its running time depends entirely on how balanced the partitions are. Change the Pivot option and run the sorted-input preset to watch the tree become a chain.
Average
O(n log n)
Worst
O(n²)
Space
O(log n)
Quick sort (Lomuto partition)
Interactive · edit the inputint[] arr · length 8
Pick a pivot, partition around it, then sort each side. Pivot strategy: last element.
1public static void quickSort(int[] arr, int lo, int hi) {2if (lo >= hi) return;3int p = partition(arr, lo, hi);4quickSort(arr, lo, p - 1);5quickSort(arr, p + 1, hi);6}78static int partition(int[] arr, int lo, int hi) {9movePivotToEnd(arr, lo, hi); // strategy: last, first, middle, median of 310int pivot = arr[hi];11int i = lo; // arr[lo..i-1] < pivot12for (int j = lo; j < hi; j++) {13if (arr[j] < pivot) {14swap(arr, i, j);15i++;16}17}18swap(arr, i, hi); // pivot to its final place19return i;20}
Live state
n
8
Execution trace (dry run table)Show 61 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=8 | Pick a pivot, partition around it, then sort each side. Pivot strategy: last element. |
| 2 | L1 | lo=0 hi=7 | Enter quickSort(arr, 0, 7). |
| 3 | L2 | lo=0 hi=7 | Two or more elements, so partition. |
| 4 | L9 | lo=0 hi=7 | Pivot strategy: last element. The pivot is already at arr[7]. |
| 5 | L11 | pivot=15 i=0 | pivot = 15. i marks where the next value smaller than the pivot will go. |
| 6 | L13 | pivot=15 i=0 j=0 | Not smaller: leave it for the right region. |
| 7 | L13 | pivot=15 i=0 j=1 | Not smaller: leave it for the right region. |
| 8 | L13 | pivot=15 i=0 j=2 | Not smaller: leave it for the right region. |
| 9 | L13 | pivot=15 i=0 j=3 | Smaller than the pivot: it belongs in the left region. |
| 10 | L14 | pivot=15 i=1 j=3 | Swap arr[0] and arr[3], then i = 1. |
| 11 | L13 | pivot=15 i=1 j=4 | Smaller than the pivot: it belongs in the left region. |
| 12 | L14 | pivot=15 i=2 j=4 | Swap arr[1] and arr[4], then i = 2. |
| 13 | L13 | pivot=15 i=2 j=5 | Not smaller: leave it for the right region. |
| 14 | L13 | pivot=15 i=2 j=6 | Smaller than the pivot: it belongs in the left region. |
| 15 | L14 | pivot=15 i=3 j=6 | Swap arr[2] and arr[6], then i = 3. |
| 16 | L18 | pivot=15 p=3 | Swap the pivot into index 3. Everything left of it is < 15, everything right is >= 15, and 15 will never move again. |
| 17 | L4 | lo=0 hi=2 | Enter quickSort(arr, 0, 2). |
| 18 | L2 | lo=0 hi=2 | Two or more elements, so partition. |
| 19 | L9 | lo=0 hi=2 | Pivot strategy: last element. The pivot is already at arr[2]. |
| 20 | L11 | pivot=10 i=0 | pivot = 10. i marks where the next value smaller than the pivot will go. |
| 21 | L13 | pivot=10 i=0 j=0 | Smaller than the pivot: it belongs in the left region. |
| 22 | L14 | pivot=10 i=1 j=0 | arr[0] is already in place; i advances to 1. |
| 23 | L13 | pivot=10 i=1 j=1 | Smaller than the pivot: it belongs in the left region. |
| 24 | L14 | pivot=10 i=2 j=1 | arr[1] is already in place; i advances to 2. |
| 25 | L18 | pivot=10 p=2 | Swap the pivot into index 2. Everything left of it is < 10, everything right is >= 10, and 10 will never move again. |
| 26 | L4 | lo=0 hi=1 | Enter quickSort(arr, 0, 1). |
| 27 | L2 | lo=0 hi=1 | Two or more elements, so partition. |
| 28 | L9 | lo=0 hi=1 | Pivot strategy: last element. The pivot is already at arr[1]. |
| 29 | L11 | pivot=9 i=0 | pivot = 9. i marks where the next value smaller than the pivot will go. |
| 30 | L13 | pivot=9 i=0 j=0 | Smaller than the pivot: it belongs in the left region. |
| 31 | L14 | pivot=9 i=1 j=0 | arr[0] is already in place; i advances to 1. |
| 32 | L18 | pivot=9 p=1 | Swap the pivot into index 1. Everything left of it is < 9, everything right is >= 9, and 9 will never move again. |
| 33 | L4 | lo=0 hi=0 | Enter quickSort(arr, 0, 0). |
| 34 | L2 | lo=0 hi=0 | Zero or one element: already sorted. |
| 35 | L5 | lo=2 hi=1 | Enter quickSort(arr, 2, 1). |
| 36 | L2 | lo=2 hi=1 | Zero or one element: already sorted. |
| 37 | L5 | lo=3 hi=2 | Enter quickSort(arr, 3, 2). |
| 38 | L2 | lo=3 hi=2 | Zero or one element: already sorted. |
| 39 | L5 | lo=4 hi=7 | Enter quickSort(arr, 4, 7). |
| 40 | L2 | lo=4 hi=7 | Two or more elements, so partition. |
| 41 | L9 | lo=4 hi=7 | Pivot strategy: last element. The pivot is already at arr[7]. |
| 42 | L11 | pivot=38 i=4 | pivot = 38. i marks where the next value smaller than the pivot will go. |
| 43 | L13 | pivot=38 i=4 j=4 | Smaller than the pivot: it belongs in the left region. |
| 44 | L14 | pivot=38 i=5 j=4 | arr[4] is already in place; i advances to 5. |
| 45 | L13 | pivot=38 i=5 j=5 | Not smaller: leave it for the right region. |
| 46 | L13 | pivot=38 i=5 j=6 | Not smaller: leave it for the right region. |
| 47 | L18 | pivot=38 p=5 | Swap the pivot into index 5. Everything left of it is < 38, everything right is >= 38, and 38 will never move again. |
| 48 | L4 | lo=4 hi=4 | Enter quickSort(arr, 4, 4). |
| 49 | L2 | lo=4 hi=4 | Zero or one element: already sorted. |
| 50 | L5 | lo=6 hi=7 | Enter quickSort(arr, 6, 7). |
| 51 | L2 | lo=6 hi=7 | Two or more elements, so partition. |
| 52 | L9 | lo=6 hi=7 | Pivot strategy: last element. The pivot is already at arr[7]. |
| 53 | L11 | pivot=82 i=6 | pivot = 82. i marks where the next value smaller than the pivot will go. |
| 54 | L13 | pivot=82 i=6 j=6 | Smaller than the pivot: it belongs in the left region. |
| 55 | L14 | pivot=82 i=7 j=6 | arr[6] is already in place; i advances to 7. |
| 56 | L18 | pivot=82 p=7 | Swap the pivot into index 7. Everything left of it is < 82, everything right is >= 82, and 82 will never move again. |
| 57 | L4 | lo=6 hi=6 | Enter quickSort(arr, 6, 6). |
| 58 | L2 | lo=6 hi=6 | Zero or one element: already sorted. |
| 59 | L5 | lo=8 hi=7 | Enter quickSort(arr, 8, 7). |
| 60 | L2 | lo=8 hi=7 | Zero or one element: already sorted. |
| 61 | L6 | Sorted with 14 comparisons and 5 swaps. |
Practice ladder
Now try it yourself
Easy
- Find the minimum of an array by divide and conquer
- Count the elements equal to x by halving
- Compute the sum of an array recursively by halves
Medium
- Maximum subarray sum by divide and conquer (crossing sum)
- Count inversions with merge sort
- Closest pair of points (outline)
Advanced
- Karatsuba multiplication
- Strassen matrix multiplication recurrence
- Find the median of two sorted arrays
Key takeaways
- Divide and conquer = split, solve independently, combine.
- Balanced splits give logarithmic depth; lopsided splits give linear depth.
- Add up the work per level of the recursion tree to find the running time.