Sorting Exercises: Answer Sheet & Worked Solutions
This is the answer sheet for the sorting exercises. Each solution gives the key idea, the optimal complexity, the code in all four languages (use the switcher), and common pitfalls. Try the problems first — then check your work here.
Q1. Implement insertion sort
Idea: Grow a sorted prefix. For each element, shift larger prefix elements one slot right and drop the element into the gap. The strict > test keeps it stable.
Complexity: O(n) best (already sorted), O(n²) average and worst; O(1) space.
void insertionSort(std::vector<int>& a) {
for (int i = 1; i < (int)a.size(); i++) {
int key = a[i], j = i - 1;
while (j >= 0 && a[j] > key) { a[j + 1] = a[j]; j--; }
a[j + 1] = key;
}
}
void insertionSort(int[] a) {
for (int i = 1; i < a.length; i++) {
int key = a[i], j = i - 1;
while (j >= 0 && a[j] > key) { a[j + 1] = a[j]; j--; }
a[j + 1] = key;
}
}
function insertionSort(a) {
for (let i = 1; i < a.length; i++) {
const key = a[i];
let j = i - 1;
while (j >= 0 && a[j] > key) { a[j + 1] = a[j]; j--; }
a[j + 1] = key;
}
}
def insertion_sort(a):
for i in range(1, len(a)):
key = a[i]
j = i - 1
while j >= 0 and a[j] > key:
a[j + 1] = a[j]
j -= 1
a[j + 1] = key
Pitfall: Using
>=instead of>shifts equal elements too, breaking stability and wasting work. Stop the shift as soon as you reach an element that is not strictly greater.
Q2. Sort numbers descending (mind the comparator)
Idea: Pass a descending comparator. In JavaScript the default sort() is the trap: with no comparator it sorts elements as strings, so [10, 2, 1].sort() yields [1, 10, 2]. Always pass (a, b) => b - a for numeric descending order.
Complexity: O(n log n) time; space depends on the library (O(log n)–O(n)).
#include <algorithm>
#include <functional>
#include <vector>
void sortDesc(std::vector<int>& a) {
std::sort(a.begin(), a.end(), std::greater<int>());
}
import java.util.Arrays;
import java.util.Collections;
Integer[] sortDesc(Integer[] a) { // boxed: comparators need objects
Arrays.sort(a, Collections.reverseOrder());
return a;
}
function sortDesc(a) {
// CORRECT: numeric comparator. a.sort() alone would sort as strings!
return a.sort((x, y) => y - x);
}
def sort_desc(a):
a.sort(reverse=True)
return a
Pitfall:
arr.sort()in JavaScript and the Java primitiveArrays.sort(int[])have no notion of “descending.” For JS you must supply a comparator; for Java primitives there is no descending overload — box toInteger[]and useCollections.reverseOrder(), or sort ascending then reverse.
Q3. Sort pairs by score, ties by name
Idea: Use a multi-key comparator: compare by score descending; if equal, by name ascending. A single composite comparator handles both keys in one pass.
Complexity: O(n log n) time, O(1)–O(n) space depending on the sort.
struct Rec { std::string name; int score; };
void sortRecs(std::vector<Rec>& v) {
std::sort(v.begin(), v.end(), [](const Rec& a, const Rec& b) {
if (a.score != b.score) return a.score > b.score; // score desc
return a.name < b.name; // name asc
});
}
record Rec(String name, int score) {}
void sortRecs(java.util.List<Rec> v) {
v.sort(java.util.Comparator
.comparingInt(Rec::score).reversed() // score desc
.thenComparing(Rec::name)); // name asc
}
function sortRecs(v) {
return v.sort((a, b) =>
b.score - a.score || // score desc
a.name.localeCompare(b.name)); // tie-break: name asc
}
def sort_recs(v):
# negate score for descending, keep name ascending, in one tuple key
v.sort(key=lambda r: (-r["score"], r["name"]))
return v
Pitfall: Doing two separate sorts in the wrong order, or relying on stability without it being guaranteed. C++
std::sortis not stable, so encode all tie-breakers in the comparator rather than assuming equal elements keep their order.
Q4. Merge two sorted arrays
Idea: Two pointers, one per array. Repeatedly append the smaller front element; when one array is exhausted, append the rest of the other. Single linear pass.
Complexity: O(n + m) time, O(n + m) space for the output.
std::vector<int> merge(const std::vector<int>& a, const std::vector<int>& b) {
std::vector<int> out;
out.reserve(a.size() + b.size());
size_t i = 0, j = 0;
while (i < a.size() && j < b.size())
out.push_back(a[i] <= b[j] ? a[i++] : b[j++]);
while (i < a.size()) out.push_back(a[i++]);
while (j < b.size()) out.push_back(b[j++]);
return out;
}
int[] merge(int[] a, int[] b) {
int[] out = new int[a.length + b.length];
int i = 0, j = 0, k = 0;
while (i < a.length && j < b.length)
out[k++] = (a[i] <= b[j]) ? a[i++] : b[j++];
while (i < a.length) out[k++] = a[i++];
while (j < b.length) out[k++] = b[j++];
return out;
}
function merge(a, b) {
const out = [];
let i = 0, j = 0;
while (i < a.length && j < b.length)
out.push(a[i] <= b[j] ? a[i++] : b[j++]);
while (i < a.length) out.push(a[i++]);
while (j < b.length) out.push(b[j++]);
return out;
}
def merge(a, b):
out = []
i = j = 0
while i < len(a) and j < len(b):
if a[i] <= b[j]:
out.append(a[i]); i += 1
else:
out.append(b[j]); j += 1
out.extend(a[i:])
out.extend(b[j:])
return out
Pitfall: Forgetting to drain the remaining elements after one array runs out — the two trailing
whileloops are essential. Use<=(not<) when picking to keep the merge stable.
Q5. Counting sort for a bounded range
Idea: Tally each value into a count array of size k + 1, then walk the counts in order, emitting each value the number of times it appeared. No comparisons.
Complexity: O(n + k) time, O(n + k) space.
std::vector<int> countingSort(const std::vector<int>& a, int k) {
std::vector<int> count(k + 1, 0), out;
out.reserve(a.size());
for (int x : a) count[x]++;
for (int v = 0; v <= k; v++)
while (count[v]-- > 0) out.push_back(v);
return out;
}
int[] countingSort(int[] a, int k) {
int[] count = new int[k + 1], out = new int[a.length];
for (int x : a) count[x]++;
int idx = 0;
for (int v = 0; v <= k; v++)
while (count[v]-- > 0) out[idx++] = v;
return out;
}
function countingSort(a, k) {
const count = new Array(k + 1).fill(0);
const out = [];
for (const x of a) count[x]++;
for (let v = 0; v <= k; v++)
while (count[v]-- > 0) out.push(v);
return out;
}
def counting_sort(a, k):
count = [0] * (k + 1)
for x in a:
count[x] += 1
out = []
for v in range(k + 1):
out.extend([v] * count[v])
return out
Pitfall: Counting sort is only efficient when
kis on the order ofn. If values span a huge range (or aren’t small integers), use radix sort or a comparison sort instead — acountarray sized to a billion is not viable.
Q6. Kth largest element
Idea: Sorting gives O(n log n). For average O(n), use quickselect: partition around a random pivot like quick sort, but recurse into only the side that contains the target index n - k (the position of the k-th largest in ascending order).
Complexity: O(n) average, O(n²) worst (mitigated by a random pivot); O(1) extra space.
int kthLargest(std::vector<int> a, int k) {
int target = (int)a.size() - k, lo = 0, hi = (int)a.size() - 1;
while (lo <= hi) {
std::swap(a[lo + rand() % (hi - lo + 1)], a[hi]); // random pivot
int pivot = a[hi], i = lo;
for (int j = lo; j < hi; j++)
if (a[j] < pivot) std::swap(a[i++], a[j]);
std::swap(a[i], a[hi]);
if (i == target) return a[i];
if (i < target) lo = i + 1; else hi = i - 1;
}
return -1; // unreachable for valid k
}
int kthLargest(int[] a, int k) {
int target = a.length - k, lo = 0, hi = a.length - 1;
java.util.Random rnd = new java.util.Random();
while (lo <= hi) {
int r = lo + rnd.nextInt(hi - lo + 1);
int t0 = a[r]; a[r] = a[hi]; a[hi] = t0; // random pivot
int pivot = a[hi], i = lo;
for (int j = lo; j < hi; j++)
if (a[j] < pivot) { int t = a[i]; a[i] = a[j]; a[j] = t; i++; }
int t = a[i]; a[i] = a[hi]; a[hi] = t;
if (i == target) return a[i];
if (i < target) lo = i + 1; else hi = i - 1;
}
return -1; // unreachable for valid k
}
function kthLargest(a, k) {
let target = a.length - k, lo = 0, hi = a.length - 1;
while (lo <= hi) {
const r = lo + Math.floor(Math.random() * (hi - lo + 1));
[a[r], a[hi]] = [a[hi], a[r]]; // random pivot
const pivot = a[hi];
let i = lo;
for (let j = lo; j < hi; j++)
if (a[j] < pivot) { [a[i], a[j]] = [a[j], a[i]]; i++; }
[a[i], a[hi]] = [a[hi], a[i]];
if (i === target) return a[i];
if (i < target) lo = i + 1; else hi = i - 1;
}
return -1; // unreachable for valid k
}
import random
def kth_largest(a, k):
a = a[:] # don't mutate caller's list
target, lo, hi = len(a) - k, 0, len(a) - 1
while lo <= hi:
r = random.randint(lo, hi)
a[r], a[hi] = a[hi], a[r] # random pivot
pivot, i = a[hi], lo
for j in range(lo, hi):
if a[j] < pivot:
a[i], a[j] = a[j], a[i]
i += 1
a[i], a[hi] = a[hi], a[i]
if i == target:
return a[i]
if i < target:
lo = i + 1
else:
hi = i - 1
return -1 # unreachable for valid k
Pitfall: Mixing up “k-th largest” with index
k. The k-th largest sits at ascending indexn - k(so k = 1 maps to indexn - 1, the maximum). A fixed (non-random) pivot makes sorted input degrade toO(n²)— randomize it.
Q7. Sort colors (Dutch national flag)
Idea: Three pointers in one pass: low (next 0 slot), high (next 2 slot), and mid (scanner). Push 0s to the front and 2s to the back; 1s fall into place in the middle. When you swap a 2 in from high, don’t advance mid — the swapped-in value is unexamined.
Complexity: O(n) time, O(1) space, single pass.
void sortColors(std::vector<int>& a) {
int low = 0, mid = 0, high = (int)a.size() - 1;
while (mid <= high) {
if (a[mid] == 0) std::swap(a[low++], a[mid++]);
else if (a[mid] == 1) mid++;
else std::swap(a[mid], a[high--]); // a[mid]==2; don't advance mid
}
}
void sortColors(int[] a) {
int low = 0, mid = 0, high = a.length - 1;
while (mid <= high) {
if (a[mid] == 0) { int t = a[low]; a[low] = a[mid]; a[mid] = t; low++; mid++; }
else if (a[mid] == 1) mid++;
else { int t = a[mid]; a[mid] = a[high]; a[high] = t; high--; } // 2
}
}
function sortColors(a) {
let low = 0, mid = 0, high = a.length - 1;
while (mid <= high) {
if (a[mid] === 0) { [a[low], a[mid]] = [a[mid], a[low]]; low++; mid++; }
else if (a[mid] === 1) mid++;
else { [a[mid], a[high]] = [a[high], a[mid]]; high--; } // 2
}
}
def sort_colors(a):
low = mid = 0
high = len(a) - 1
while mid <= high:
if a[mid] == 0:
a[low], a[mid] = a[mid], a[low]
low += 1
mid += 1
elif a[mid] == 1:
mid += 1
else: # a[mid] == 2
a[mid], a[high] = a[high], a[mid]
high -= 1
Pitfall: Advancing
midafter swapping withhigh. The element pulled in fromhighhasn’t been classified yet, so you must re-examine it on the next iteration — only advancemidfor the 0 and 1 cases.
That’s the full answer sheet. Back to the sorting exercises or continue to Searching.