Cyclic Sort Pattern
1. Place Elements in Correct Index
| Concept | Rule | Invariant | Application |
|---|---|---|---|
| Index Mapping | For range [1,n]: place value k at index k-1 | arr[i] = i+1 after sorting |
Numbers 1 to n in array of size n |
| Zero-based Range | For range [0,n-1]: place value k at index k | arr[i] = i after sorting |
Numbers 0 to n-1 in array of size n |
| Swap Until Correct | Keep swapping until element at correct position | Each swap places at least one element correctly | O(n) time - each element moved once |
| Detection Phase | After sorting, scan for missing/duplicate | Incorrect position indicates problem | Missing numbers, duplicates, corrupt pairs |
Example: Cyclic sort for range [1, n]
function cyclicSort(nums) {
let i = 0;
while (i < nums.length) {
const correctIndex = nums[i] - 1; // For range [1,n]
if (nums[i] !== nums[correctIndex]) {
// Swap to place element at correct position
[nums[i], nums[correctIndex]] = [nums[correctIndex], nums[i]];
} else {
i++; // Element already at correct position
}
}
return nums;
}
// [3, 1, 5, 4, 2] -> [1, 2, 3, 4, 5]
// Time: O(n), Space: O(1)
// Key insight: Each element swapped at most once
Example: Cyclic sort for range [0, n-1]
function cyclicSortZeroBased(nums) {
let i = 0;
while (i < nums.length) {
const correctIndex = nums[i]; // For range [0,n-1]
if (nums[i] < nums.length && nums[i] !== nums[correctIndex]) {
[nums[i], nums[correctIndex]] = [nums[correctIndex], nums[i]];
} else {
i++;
}
}
return nums;
}
// [2, 0, 3, 1] -> [0, 1, 2, 3]
2. Find Missing Numbers O(n) Time
| Problem Variant | Approach | Complexity | Key Insight |
|---|---|---|---|
| Single Missing | Cyclic sort then find index where arr[i] ≠ i+1 | O(n), O(1) |
First incorrect position has missing number |
| All Missing | Cyclic sort then collect all incorrect positions | O(n), O(k) |
k missing numbers at wrong indices |
| Missing in Range | Place elements, scan for gaps | O(n), O(1) |
Expected value vs actual value mismatch |
| Without Modification | XOR or sum formula approach | O(n), O(1) |
Alternative when can't modify array |
Example: Find missing number (single)
function findMissingNumber(nums) {
let i = 0;
const n = nums.length;
// Cyclic sort for range [0, n]
while (i < n) {
const correctIndex = nums[i];
if (nums[i] < n && nums[i] !== nums[correctIndex]) {
[nums[i], nums[correctIndex]] = [nums[correctIndex], nums[i]];
} else {
i++;
}
}
// Find missing number
for (let i = 0; i < n; i++) {
if (nums[i] !== i) {
return i; // Missing number
}
}
return n; // Missing number is n
}
// [3, 0, 1] -> 2
// [0, 1] -> 2
// [9,6,4,2,3,5,7,0,1] -> 8
Example: Find all missing numbers
function findDisappearedNumbers(nums) {
let i = 0;
// Cyclic sort for range [1, n]
while (i < nums.length) {
const correctIndex = nums[i] - 1;
if (nums[i] !== nums[correctIndex]) {
[nums[i], nums[correctIndex]] = [nums[correctIndex], nums[i]];
} else {
i++;
}
}
// Find all missing numbers
const missing = [];
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== i + 1) {
missing.push(i + 1);
}
}
return missing;
}
// [4,3,2,7,8,2,3,1] -> [5,6]
// After sort: [1,2,3,2,3,8,7,8], positions 4,5 have wrong values
3. Find Duplicates O(1) Space Complexity
| Detection Method | Strategy | Works For | Trade-off |
|---|---|---|---|
| Cyclic Sort Scan | After sorting, duplicate at wrong position | Range [1,n] with one duplicate | Modifies array |
| Swap Detection | During swap, if both same value = duplicate | Find during sort process | Early termination possible |
| Index Marking | Use sign to mark visited indices | Positive integers only | Reversible modification |
| Multiple Duplicates | Collect all positions with wrong values | Multiple duplicates present | Returns all duplicates |
Example: Find duplicate number
function findDuplicate(nums) {
let i = 0;
while (i < nums.length) {
const correctIndex = nums[i] - 1;
if (nums[i] !== nums[correctIndex]) {
[nums[i], nums[correctIndex]] =
[nums[correctIndex], nums[i]];
} else {
i++;
}
}
// Find duplicate
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== i + 1) {
return nums[i];
}
}
return -1;
}
// [1,3,4,2,2] -> 2
Example: Find all duplicates
function findDuplicates(nums) {
let i = 0;
while (i < nums.length) {
const correctIndex = nums[i] - 1;
if (nums[i] !== nums[correctIndex]) {
[nums[i], nums[correctIndex]] =
[nums[correctIndex], nums[i]];
} else {
i++;
}
}
const duplicates = [];
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== i + 1) {
duplicates.push(nums[i]);
}
}
return duplicates;
}
Example: Find duplicates without modification (index marking)
function findDuplicatesIndexMarking(nums) {
const duplicates = [];
for (let i = 0; i < nums.length; i++) {
const index = Math.abs(nums[i]) - 1;
if (nums[index] < 0) {
// Already negative - duplicate found
duplicates.push(Math.abs(nums[i]));
} else {
// Mark as visited by negating
nums[index] = -nums[index];
}
}
// Restore array (optional)
for (let i = 0; i < nums.length; i++) {
nums[i] = Math.abs(nums[i]);
}
return duplicates;
}
// [4,3,2,7,8,2,3,1] -> [2,3]
4. Corrupt Pairs Detection Pattern
| Problem Type | Definition | Detection | Solution |
|---|---|---|---|
| Set Mismatch | One number duplicated, one missing | Find both using cyclic sort | [duplicate, missing] |
| Corrupt Pair | Two numbers swapped incorrectly | Both positions have wrong values | Return the swapped pair |
| XOR Technique | Use XOR to find different elements | XOR all elements with expected range | Result is duplicate XOR missing |
| Sum Difference | Compare actual sum vs expected sum | Difference reveals relationship | Combined with product or sum of squares |
Example: Find corrupt pair (set mismatch)
function findErrorNums(nums) {
let i = 0;
// Cyclic sort
while (i < nums.length) {
const correctIndex = nums[i] - 1;
if (nums[i] !== nums[correctIndex]) {
[nums[i], nums[correctIndex]] = [nums[correctIndex], nums[i]];
} else {
i++;
}
}
// Find duplicate and missing
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== i + 1) {
return [nums[i], i + 1]; // [duplicate, missing]
}
}
return [-1, -1];
}
// [1,2,2,4] -> [2,3] (2 is duplicate, 3 is missing)
// [1,1] -> [1,2]
5. First Missing Positive Integer Problem
| Challenge | Solution | Key Insight | Complexity |
|---|---|---|---|
| Range Constraint | Only care about positives in [1, n] | Answer must be in [1, n+1] | Ignore negatives and > n |
| Cyclic Sort Modified | Sort only valid range values | Skip out-of-range during swap | O(n), O(1) |
| First Gap | First index where arr[i] ≠ i+1 | Missing positive at that index | Return index + 1 |
| All Present Case | If [1..n] all present, answer is n+1 | Perfect sequence means next is missing | Edge case handling |
Example: First missing positive integer
function firstMissingPositive(nums) {
let i = 0;
const n = nums.length;
// Cyclic sort - only for valid range [1, n]
while (i < n) {
const correctIndex = nums[i] - 1;
// Only swap if value in valid range and not at correct position
if (nums[i] > 0 &&
nums[i] <= n &&
nums[i] !== nums[correctIndex]) {
[nums[i], nums[correctIndex]] = [nums[correctIndex], nums[i]];
} else {
i++;
}
}
// Find first missing positive
for (let i = 0; i < n; i++) {
if (nums[i] !== i + 1) {
return i + 1;
}
}
// All [1..n] present, answer is n+1
return n + 1;
}
// [1,2,0] -> 3
// [3,4,-1,1] -> 2
// [7,8,9,11,12] -> 1
Note: This is one of the hardest cyclic sort problems. The key is recognizing that the first missing positive must be in range [1, n+1], so we only need to consider elements in [1, n] during sorting.
6. Range Validation Techniques
| Validation Type | Check | Purpose | Example |
|---|---|---|---|
| Bounds Check | 0 <= val < n or 1 <= val <= n |
Prevent out-of-bounds access | Avoid array index errors |
| Duplicate Position | nums[i] === nums[correctIndex] |
Detect when element already correct | Avoid infinite swap loops |
| Self-loop Prevention | i !== correctIndex |
Don't swap element with itself | Optimization, not required |
| Valid Value Range | Filter out negatives, zeros, or too large | Skip irrelevant values | First missing positive problem |
Example: Safe cyclic sort with validation
function safeCyclicSort(nums) {
let i = 0;
while (i < nums.length) {
const val = nums[i];
const correctIndex = val - 1;
// Comprehensive validation
if (val >= 1 && // Lower bound
val <= nums.length && // Upper bound
val !== nums[correctIndex]) { // Not duplicate
[nums[i], nums[correctIndex]] =
[nums[correctIndex], nums[i]];
} else {
i++;
}
}
return nums;
}
Example: Validation for edge cases
function validateAndSort(nums) {
// Handle edge cases
if (!nums || nums.length === 0) {
return [];
}
if (nums.length === 1) {
return nums;
}
let i = 0;
while (i < nums.length) {
const correctIndex = nums[i] - 1;
if (correctIndex >= 0 &&
correctIndex < nums.length &&
nums[i] !== nums[correctIndex]) {
[nums[i], nums[correctIndex]] =
[nums[correctIndex], nums[i]];
} else {
i++;
}
}
return nums;
}
Cyclic Sort Pattern Summary
- When to Use: Array contains numbers in range [1,n] or [0,n-1]
- Core Pattern: Swap elements until each is at correct index position
- Time Complexity: O(n) - each element moved at most once
- Space Complexity: O(1) - in-place sorting
- Applications: Missing numbers, duplicates, corrupt pairs
- Key Validation: Check bounds and prevent duplicate swaps
Note: Cyclic sort achieves O(n) time with O(1) space by leveraging the property that array indices can store values. The pattern works because each swap places at least one element in its correct position, guaranteeing linear time.
Warning: Always validate before swapping: check
nums[i] !== nums[correctIndex] to prevent infinite loops when duplicate exists. Ensure correctIndex is within bounds [0, n-1] to avoid out-of-bounds access. For first missing positive, skip values outside [1, n] range.