Merge Sort Algorithm in DSA Go - Time & Space Complexity
We want to understand how the time taken by merge sort changes as the list size grows.
How does splitting and merging affect the total work done?
Analyze the time complexity of the following code snippet.
func mergeSort(arr []int) []int {
if len(arr) <= 1 {
return arr
}
mid := len(arr) / 2
left := mergeSort(arr[:mid])
right := mergeSort(arr[mid:])
return merge(left, right)
}
func merge(left, right []int) []int {
result := []int{}
i, j := 0, 0
for i < len(left) && j < len(right) {
if left[i] <= right[j] {
result = append(result, left[i])
i++
} else {
result = append(result, right[j])
j++
}
}
result = append(result, left[i:]...)
result = append(result, right[j:]...)
return result
}
This code splits the array into halves recursively and merges sorted halves back.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive splitting and merging of arrays.
- How many times: The array is split about log₂(n) times, and merging happens at each level over all elements.
Each split divides the array in half, creating about log₂(n) levels. At each level, merging all parts touches all n elements.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 * 4 = 40 |
| 100 | About 100 * 7 = 700 |
| 1000 | About 1000 * 10 = 10,000 |
Pattern observation: Operations grow roughly as n times log n, which grows faster than n but much slower than n squared.
Time Complexity: O(n log n)
This means the time grows a bit faster than the size of the input but much better than simple sorting methods that check every pair.
[X] Wrong: "Merge sort is just a simple loop over the array, so it must be O(n)."
[OK] Correct: Merge sort uses recursion and splits the array multiple times, so it does more work than a single loop.
Understanding merge sort's time complexity helps you explain efficient sorting and recursion, a key skill in many coding interviews.
"What if we changed merge sort to split the array into three parts instead of two? How would the time complexity change?"
Practice
Merge Sort algorithm?Solution
Step 1: Understand the divide and conquer approach
Merge Sort splits the list into two halves recursively until single elements remain.Step 2: Merge sorted halves
It then merges these halves back together in sorted order.Final Answer:
Divide the list into halves, sort each half, then merge them -> Option CQuick Check:
Merge Sort = Divide and Conquer [OK]
- Confusing Merge Sort with Bubble Sort
- Thinking Merge Sort uses a pivot like Quick Sort
- Believing Merge Sort sorts in place without extra space
Solution
Step 1: Check Go function syntax for slices
In Go, slices are declared as[]intfor integer slices.Step 2: Verify return type matches input type
Merge sort returns a sorted slice, so return type is also[]int.Final Answer:
func mergeSort(arr []int) []int {} -> Option AQuick Check:
Go slice syntax = []int [OK]
- Using array syntax like int[] which is invalid in Go
- Returning int instead of []int for sorted slice
- Using incorrect parameter types like 'array of int'
package main
import "fmt"
func mergeSort(arr []int) []int {
if len(arr) <= 1 {
return arr
}
mid := len(arr) / 2
left := mergeSort(arr[:mid])
right := mergeSort(arr[mid:])
return merge(left, right)
}
func merge(left, right []int) []int {
result := []int{}
i, j := 0, 0
for i < len(left) && j < len(right) {
if left[i] < right[j] {
result = append(result, left[i])
i++
} else {
result = append(result, right[j])
j++
}
}
result = append(result, left[i:]...)
result = append(result, right[j:]...)
return result
}
func main() {
arr := []int{5, 3, 8, 4, 2}
sorted := mergeSort(arr)
fmt.Println(sorted)
}Solution
Step 1: Trace the recursive splitting
The array splits into halves until single elements: [5,3,8,4,2] -> [5,3] and [8,4,2], then further splits.Step 2: Merge sorted halves step-by-step
Merge [5] and [3] -> [3,5]; merge [8] and [4,2] -> [2,4,8]; finally merge [3,5] and [2,4,8] -> [2,3,4,5,8].Final Answer:
[2 3 4 5 8] -> Option BQuick Check:
Sorted array = [2 3 4 5 8] [OK]
- Assuming original array prints without sorting
- Mixing up merge order causing wrong output
- Forgetting to append remaining elements after loop
func merge(left, right []int) []int {
result := []int{}
i, j := 0, 0
for i < len(left) && j < len(right) {
if left[i] <= right[j] {
result = append(result, left[i])
i++
} else {
result = append(result, right[j])
j++
}
}
result = append(result, left[i:]...)
result = append(result, right[i:]...)
return result
}Solution
Step 1: Analyze the append of remaining elements
After merging common parts, remaining elements from left[i:] and right[j:] must be appended.Step 2: Spot the incorrect slice used
The code incorrectly appends right[i:] instead of right[j:], which can cause wrong elements or panic.Final Answer:
Change last append from right[i:] to right[j:] -> Option DQuick Check:
Append remaining right elements using j index [OK]
- Using wrong index variable for right slice leftover
- Removing leftover appends causing incomplete merge
- Changing comparison operator unnecessarily
Solution
Step 1: Understand recursion overhead in large data
Recursive merge sort can cause deep call stacks and many slice copies, increasing memory use.Step 2: Bottom-up iterative merge sort reduces recursion
Bottom-up merge sort merges pairs iteratively, reducing recursion and memory overhead.Final Answer:
Use a bottom-up iterative merge sort to avoid deep recursion -> Option AQuick Check:
Iterative merge sort = better memory for large data [OK]
- Ignoring recursion limits on large inputs
- Copying slices unnecessarily increasing memory
- Choosing inefficient sorts like bubble sort for big data
Ready to prove it?
Pass the test and this pattern moves unknown
