Quick Sort as Divide and Conquer in DSA Go - Time & Space Complexity
We want to understand how the time to sort a list with Quick Sort changes as the list grows.
How does the number of steps grow when the list gets bigger?
Analyze the time complexity of the following code snippet.
func quickSort(arr []int) []int {
if len(arr) <= 1 {
return arr
}
pivot := arr[len(arr)/2]
left := []int{}
right := []int{}
middle := []int{}
for _, v := range arr {
if v < pivot {
left = append(left, v)
} else if v > pivot {
right = append(right, v)
} else {
middle = append(middle, v)
}
}
left = quickSort(left)
right = quickSort(right)
return append(append(left, middle...), right...)
}
This code sorts an array by picking a pivot, splitting the array into parts, and sorting each part recursively.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Looping through the array to split it around the pivot.
- How many times: This splitting happens at every recursive call, which divides the array into smaller parts.
Each time we split, we do work proportional to the size of the current part. Then we sort smaller parts recursively.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 30 to 40 steps |
| 100 | About 700 to 900 steps |
| 1000 | About 10,000 to 15,000 steps |
Pattern observation: The work grows faster than just doubling but slower than squaring the input size.
Time Complexity: O(n log n)
This means the time grows a bit more than linearly, multiplying by the number of times we split the list.
[X] Wrong: "Quick Sort always takes the same time no matter the input order."
[OK] Correct: If the pivot splits are uneven, Quick Sort can take much longer, up to O(n²) in the worst case.
Understanding Quick Sort's time complexity shows you know how divide and conquer works and how algorithms behave with different inputs.
"What if we always picked the first element as pivot instead of the middle? How would the time complexity change?"
Practice
Quick Sort algorithm in the divide and conquer approach?Solution
Step 1: Understand divide and conquer in Quick Sort
Quick Sort divides the array into parts around a pivot, sorts parts recursively.Step 2: Compare with other sorting methods
Unlike bubble or selection sort, Quick Sort uses division and conquering subproblems.Final Answer:
Divide the array into smaller parts, sort them, and combine the results -> Option AQuick Check:
Divide and conquer = divide, sort, combine [OK]
- Confusing Quick Sort with bubble or selection sort
- Thinking Quick Sort merges arrays like merge sort
- Ignoring the pivot selection step
Solution
Step 1: Check Go syntax for function parameters
In Go, slices are declared as []int, and multiple parameters can be declared with types.Step 2: Verify return type for partition
Partition returns an int index, so return type must be int.Final Answer:
func partition(arr []int, low int, high int) int -> Option AQuick Check:
Go func syntax with slice and int return = func partition(arr []int, low int, high int) int [OK]
- Using incorrect array syntax like int[]
- Wrong return type for partition function
- Missing return type in function declaration
quickSort(arr, 0, len(arr)-1)? func partition(arr []int, low, high int) int {
pivot := arr[high]
i := low - 1
for j := low; j < high; j++ {
if arr[j] < pivot {
i++
arr[i], arr[j] = arr[j], arr[i]
}
}
arr[i+1], arr[high] = arr[high], arr[i+1]
return i + 1
}
func quickSort(arr []int, low, high int) {
if low < high {
pi := partition(arr, low, high)
quickSort(arr, low, pi-1)
quickSort(arr, pi+1, high)
}
}
func main() {
arr := []int{4, 2, 7, 1}
quickSort(arr, 0, len(arr)-1)
fmt.Println(arr)
}Solution
Step 1: Trace partition and quickSort on array [4, 2, 7, 1]
Pivot is last element; after partition, smaller elements move left; recursive calls sort subarrays.Step 2: Final sorted array after all recursive calls
Array becomes [1, 2, 4, 7] after full quick sort completes.Final Answer:
[1 2 4 7] -> Option CQuick Check:
Quick Sort sorts array ascending = [1 2 4 7] [OK]
- Assuming array stays unchanged
- Confusing pivot placement
- Ignoring recursive calls
func partition(arr []int, low, high int) int {
pivot := arr[high]
i := low
for j := low; j < high; j++ {
if arr[j] < pivot {
i++
arr[i], arr[j] = arr[j], arr[i]
}
}
arr[i+1], arr[high] = arr[high], arr[i+1]
return i + 1
}Solution
Step 1: Analyze initialization of i in partition
In standard Lomuto partition with pivot at arr[high], i should start at low - 1 to track smaller elements correctly.Step 2: Check loop and swap logic
Loop and swaps are correct if i starts at low - 1; current i = low causes incorrect partitioning.Final Answer:
Variable i should be initialized to low - 1 -> Option DQuick Check:
Correct i start fixes partition logic [OK]
- Starting i at low causes wrong partition index
- Confusing pivot position with swap indices
- Changing pivot without adjusting i initialization
Solution
Step 1: Understand problem with duplicates in Quick Sort
Duplicates cause many equal elements, leading to unbalanced partitions and slow sorting.Step 2: Apply three-way partitioning solution
Three-way partitioning divides array into less than, equal to, and greater than pivot, improving efficiency.Final Answer:
Use a three-way partitioning to separate less, equal, and greater elements -> Option BQuick Check:
Three-way partitioning handles duplicates better [OK]
- Choosing fixed pivot causes worst-case on duplicates
- Trying to avoid recursion breaks divide and conquer
- Sorting only half misses elements
Ready to prove it?
Pass the test and this pattern moves unknown
