Given an integer array where each element is at most k positions away from its sorted position, sort the array in a non-decreasing order.
Input: nums = [5, 1, 9, 4, 7, 10], k = 2
Output: [1, 4, 5, 7, 9, 10]
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
Runners cross a finish line almost in the right order — a few jostle and swap places, but nobody ends up more than k spots from where their time says they should be. To post the true ranking you do not need to re-sort the whole field; you only ever look at a small window of k + 1 runners at a time and pick the fastest among them. That small window is a heap.
In a k-sorted array, each element is at most k indexes away from where it would be in a fully sorted array. We can visualize this with the following example where k = 2, and no number is more than k indexes away from its sorted position:
A trivial solution to this problem is to sort the array using a standard sorting algorithm. However, since the input is partially sorted (k-sorted), we should assume there's a faster way to sort the array.
We can think about this problem backward. For any index i, the element that belongs at index i in the sorted array is located within the range [i - k, i + k]. Below, we visualize how number 7, which is meant to be at index 3 when sorted, correctly falls within the range [3 - k, 3 + k] in the k-sorted array:
This is a good start, but we can reduce this range even further. Consider index 0 from the above array. We know the number which belongs at index 0 when sorted is somewhere in the range [0, 0 + k]:
Note that the sorted array in the diagrams is purely provided as a reference point. We don't yet know which number in the range [0, 0 + k] belongs at index 0. However, one fact remains consistent: in a sorted array, index 0 always holds the smallest number. This means the value needed at index 0 is also the smallest number within the range [0, 0 + k] of the k-sorted array, which is 1 in this example.
So, let's swap 1 with the number at index 0 to position 1 as the first value in the sorted array:
Now let's find the number that belongs at index 1: the second smallest number. Since index 0 currently contains the smallest value in the array, we won't need to consider index 0 in our search. Therefore, we can find the value that belongs at index 1 in the range [1, 1 + k]. The smallest value in this range will be the second smallest value overall, which is 4 in this case.
So, let's swap 4 with the number at index 1 to position 4 as the second value in the sorted array:
If we continue this process for the rest of the array, we'll successfully sort the k-sorted array.
The main inefficiency with this approach is finding the minimum number in the range [i, i + k] at each index i. Linearly searching for it will take time at each index.
To improve this approach, we'd need a way to efficiently access the minimum value at each of these ranges. A min-heap would be perfect for this.
Min-heap
For a min-heap to determine the minimum value within each range [i, i + k], it will always need to be populated with the values in these ranges as we iterate through the array. Let's see how this works over the same example.
Before we can determine which value belongs at index 0, we'll need to populate the heap with all the values in the range [0, k], which are the first k + 1 values (where k = 2 in this example):
An alternative way to create a heap of the first k + 1 elements is to heapify a list of the first k + 1 elements.
Now, let's begin inserting the smallest elements from the heap into the array, using the insert_index pointer. The value that belongs at index 0 in sorted order is the value currently at the top of the heap, which is 1:
Once we insert 1 at index 0, push the value at index i to the heap before incrementing both pointers:
Let's continue this process for the remaining numbers:
Once there are no more elements to push into the heap, the rest of the array can be sorted by inserting the remaining values from the heap:
Once the heap is empty, the array is sorted.
from typing import List
import heapq
def sort_a_k_sorted_array(nums: List[int], k: int) -> List[int]:
# Populate a min-heap with the first k + 1 values in 'nums'.
min_heap = nums[:k+1]
heapq.heapify(min_heap)
# Replace elements in the array with the minimum from the heap at each
# iteration.
insert_index = 0
for i in range(k + 1, len(nums)):
nums[insert_index] = heapq.heappop(min_heap)
insert_index += 1
heapq.heappush(min_heap, nums[i])
# Pop the remaining elements from the heap to finish sorting the array.
while min_heap:
nums[insert_index] = heapq.heappop(min_heap)
insert_index += 1
return numsvector<int> sortAKSortedArray(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> minHeap; // min-heap
for (int i = 0; i <= k && i < (int)nums.size(); i++) {
minHeap.push(nums[i]); // prime with the first k+1
}
int insertIndex = 0;
for (int i = k + 1; i < (int)nums.size(); i++) {
nums[insertIndex++] = minHeap.top(); // smallest in window
minHeap.pop();
minHeap.push(nums[i]); // slide forward
}
while (!minHeap.empty()) { // drain leftovers
nums[insertIndex++] = minHeap.top();
minHeap.pop();
}
return nums;
}int[] sortAKSortedArray(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>(); // min-heap
for (int i = 0; i <= k && i < nums.length; i++) minHeap.offer(nums[i]);
int insertIndex = 0;
for (int i = k + 1; i < nums.length; i++) {
nums[insertIndex++] = minHeap.poll(); // smallest in window
minHeap.offer(nums[i]); // slide forward
}
while (!minHeap.isEmpty()) { // drain leftovers
nums[insertIndex++] = minHeap.poll();
}
return nums;
}Time complexity: The time complexity of sort_a_k_sorted_array is , where denotes the length of the array. Here's why:
We perform heapify on a min_heap of size which takes time. Note that is upper-bounded by in this operation since the heap won't have more than values.
Then, we perform push and pop operations on approximately values using the heap. Since the heap can grow up to a size of , each push and pop operations takes time. Therefore, this loop takes time in the worst case.
The final while-loop runs in time since we pop values from the heap. Note that here is also upper-bounded by .
Therefore, the overall time complexity is .
Space complexity: The space complexity is since the heap can grow up to in size.
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
[5, 1, 9, 4, 7, 10], k = 2 (window size 3).heap (current window) popped smallest written to output
Start by heapifying the first 3 values [5, 1, 9]. Then pop-smallest / push-next until the input ends, then drain.
{5, 1, 9}; smallest is 1.
{5, 9, 4}.
1
{5, 9, 7}.
1 4
{9, 7, 10}.
1 4 5
1 4 5 7 9 10 ✓
| step | pop (smallest) | push next | window after | output |
|---|---|---|---|---|
| build | — | — | {5,1,9} | |
| 1 | 1 | 4 | {5,9,4} | 1 |
| 2 | 4 | 7 | {5,9,7} | 1 4 |
| 3 | 5 | 10 | {9,7,10} | 1 4 5 |
| drain | 7, 9, 10 | — | {} | 1 4 5 7 9 10 |
[4, 3, 2, 1], k = 3 (window size 4 = whole array).heap (holds everything) written to output
When k + 1 ≥ n, the first window swallows the entire array, the sliding loop never runs, and we simply drain the heap — i.e. a plain heap-sort.
{4, 3, 2, 1}. The sliding loop range(4, 4) is empty.
1 2 3 4 ✓
| step | pop | window after | output |
|---|---|---|---|
| build | — | {4,3,2,1} | |
| drain | 1 | {4,3,2} | 1 |
| drain | 2 | {4,3} | 1 2 |
| drain | 3 | {4} | 1 2 3 |
| drain | 4 | {} | 1 2 3 4 |
Take-away: the heap trick shines when k is much smaller than n. As k approaches n, it gracefully degrades to O(n log n) — never worse than sorting.
nums[:k+1] grabs the first window and heapq.heapify builds it in O(k). We write popped values back into nums at insert_index, reusing the input array as output.
priority_queue<int, vector<int>, greater<int>> is the min-heap. We push the first k + 1 values, then pop-smallest / push-next. (You can also heapify in O(k) by constructing from a range.)
The default PriorityQueue<Integer> is already a min-heap. Same window loop; poll gives the smallest, offer slides the window forward.