Count Inversions in Array in DSA Rust - Time & Space Complexity
We want to understand how long it takes to count inversions in an array as the array grows.
Specifically, how the number of steps changes when the array size increases.
Analyze the time complexity of the following code snippet.
fn merge_sort_count(arr: &mut [i32]) -> usize {
let n = arr.len();
if n <= 1 {
return 0;
}
let mid = n / 2;
let mut count = merge_sort_count(&mut arr[..mid]);
count += merge_sort_count(&mut arr[mid..]);
count += merge(arr, mid);
count
}
fn merge(arr: &mut [i32], mid: usize) -> usize {
let mut count = 0;
let left = arr[..mid].to_vec();
let right = arr[mid..].to_vec();
let (mut i, mut j, mut k) = (0, 0, 0);
while i < left.len() && j < right.len() {
if left[i] <= right[j] {
arr[k] = left[i];
i += 1;
} else {
arr[k] = right[j];
count += left.len() - i;
j += 1;
}
k += 1;
}
while i < left.len() {
arr[k] = left[i];
i += 1;
k += 1;
}
while j < right.len() {
arr[k] = right[j];
j += 1;
k += 1;
}
count
}
This code counts how many pairs of elements are out of order by using a merge sort approach.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive splitting of the array and merging while counting inversions.
- How many times: The array is split into halves repeatedly until size 1, then merged back, processing all elements each time.
Each time the array size doubles, the number of operations grows a bit more than double because of the merging steps.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 50 steps |
| 100 | About 700 steps |
| 1000 | About 10,000 steps |
Pattern observation: The operations grow a bit faster than linear but much slower than quadratic, roughly n times log n.
Time Complexity: O(n log n)
This means the time to count inversions grows proportionally to the size of the array times the number of times it can be split in half.
[X] Wrong: "Counting inversions must check every pair, so it always takes n squared time."
[OK] Correct: The merge sort method cleverly counts inversions while sorting, avoiding checking every pair directly, so it runs faster than n squared.
Understanding this method shows you can combine sorting and counting efficiently, a skill useful in many algorithm problems.
"What if we used a simple nested loop to count inversions instead? How would the time complexity change?"
Practice
inversion mean in the context of an array?Solution
Step 1: Understand the definition of inversion
An inversion is a pair of elements where the first element is greater than the second and appears before it in the array.Step 2: Compare options with definition
A pair of elements where the first is greater than the second and appears before it matches this definition exactly, while others describe different concepts.Final Answer:
A pair of elements where the first is greater than the second and appears before it -> Option AQuick Check:
Inversion = pair (i, j) with i < j and arr[i] > arr[j] [OK]
- Confusing inversion with sorted order
- Thinking inversion counts total elements
- Mixing up greater and smaller element order
Solution
Step 1: Identify correct function signature for counting inversions
The function should take an immutable slice of integers and return the count as usize (non-negative integer).Step 2: Compare options
fn count_inversions(arr: &[i32]) -> usize { 0 } uses &[i32] for input and usize for output, which is correct. fn count_inversions(arr: Vec i32 { 0 } takes ownership (Vec) and returns i32 (can be negative). fn count_inversions(arr: &mut [i32]) -> usize { 0 } uses mutable slice unnecessarily. fn count_inversions(arr: &[i32]) -> i32 { 0 } returns i32 which can be negative, not ideal for count.Final Answer:
fn count_inversions(arr: &[i32]) -> usize { 0 } -> Option CQuick Check:
Immutable slice input + usize output = correct signature [OK]
- Using Vec instead of slice for input
- Returning signed integer for count
- Using mutable slice when not needed
fn main() {
let arr = [2, 4, 1, 3, 5];
let mut count = 0;
for i in 0..arr.len() {
for j in i+1..arr.len() {
if arr[i] > arr[j] {
count += 1;
}
}
}
println!("{}", count);
}Solution
Step 1: Identify all inversion pairs in the array
Array: [2, 4, 1, 3, 5]. Inversions are (2,1), (4,1), (4,3). Total 3 inversions.Step 2: Confirm count from nested loops
Loops check all pairs i < j. Each inversion increments count by 1. Final count is 3.Final Answer:
3 -> Option BQuick Check:
Count inversions by pairs = 3 [OK]
- Counting non-inversion pairs
- Missing some inversion pairs
- Confusing indices order
fn count_inversions(arr: &[i32]) -> usize {
let mut count = 0;
for i in 0..arr.len() {
for j in 0..arr.len() {
if arr[i] > arr[j] {
count += 1;
}
}
}
count
}Solution
Step 1: Analyze loop ranges for counting inversions
Inversion counts pairs where i < j and arr[i] > arr[j]. Inner loop starting at 0 compares all pairs including i >= j, causing double counting and invalid pairs.Step 2: Correct inner loop start index
Inner loop should start at i+1 to ensure only pairs with i < j are counted once.Final Answer:
Inner loop should start from i+1, not 0 -> Option AQuick Check:
Loop indices must ensure i < j for correct inversion count [OK]
- Starting inner loop at 0 causing double counting
- Using wrong comparison operator
- Changing input slice to mutable unnecessarily
[8, 4, 2, 1], what is the number of inversions? Use the merge sort based approach to count inversions efficiently.Solution
Step 1: Understand merge sort inversion counting
Merge sort counts inversions by counting how many elements from right subarray are placed before left subarray elements during merge.Step 2: Count inversions in [8,4,2,1]
Pairs: (8,4), (8,2), (8,1), (4,2), (4,1), (2,1) total 6 inversions.Final Answer:
6 -> Option DQuick Check:
All pairs where left > right count to 6 [OK]
- Counting only adjacent pairs
- Missing some inversion pairs
- Confusing merge sort steps
Ready to prove it?
Pass the test and this pattern moves unknown
