Interview prep · Lesson 48 of 62 · about 50 min
In-place array patterns
The O(1)-space tricks interviewers ask for in follow-ups: cyclic sort for values in 1..n, marking seen values by negating, XOR to find a missing number, Floyd's cycle detection to find a duplicate, Boyer-Moore majority vote, and next permutation.
On this page · 12 sections
Cyclic sort: every value to its home#
Whenever the input is "n numbers in the range 1..n" (or 0..n), value v has an obvious home: index v − 1. Walk the array; while the current value is not home and its home doesn't already hold a copy, swap it there. When the walk finishes, every slot that holds the wrong value marks a missing number (the index) and a duplicate (the value).
Run the trace
package main
import "fmt"
func main() {
nums := []int{4, 3, 2, 7, 8, 2, 3, 1} // values 1..n, some repeated, some missing
i := 0
for i < len(nums) {
want := nums[i] - 1 // the index where nums[i] belongs
if nums[i] != nums[want] {
nums[i], nums[want] = nums[want], nums[i]
fmt.Printf("i=%d swap %d into index %d -> %v\n", i, nums[want], want, nums)
} else {
i++ // in place, or its spot already holds a copy
}
}
for i, n := range nums {
if n != i+1 {
fmt.Printf("index %d holds %d: %d is missing, %d is a duplicate\n", i, n, i+1, n)
}
}
}i=0 swap 4 into index 3 -> [7 3 2 4 8 2 3 1]
i=0 swap 7 into index 6 -> [3 3 2 4 8 2 7 1]
i=0 swap 3 into index 2 -> [2 3 3 4 8 2 7 1]
i=0 swap 2 into index 1 -> [3 2 3 4 8 2 7 1]
i=4 swap 8 into index 7 -> [3 2 3 4 1 2 7 8]
i=4 swap 1 into index 0 -> [1 2 3 4 3 2 7 8]
index 4 holds 3: 5 is missing, 3 is a duplicate
index 5 holds 2: 6 is missing, 2 is a duplicatei stays at 0 for four swaps, which looks quadratic. It isn't: each swap puts one value in its final place forever, so there are at most n swaps over the whole run.
First Missing Positive
The hard version adds zeros, negatives and huge values. With n slots, the answer is at most n + 1, so values outside 1..n can never be the answer and can stay wherever they are. Add a range check to the swap guard; afterwards, the first index i with nums[i] != i+1 gives the answer i + 1.
Marking with signs#
If you may modify the array but don't want to move values, record "I have seen v" by negating nums[v−1]. Read values with abs, because earlier steps may have already negated them. Seeing a negative slot means v is a duplicate. After one pass, any slot still positive marks a missing value.
XOR: the missing number#
x ^ x == 0 and x ^ 0 == x, and XOR is order-independent. XOR every index 0..n with every value: each present number appears twice and cancels, and only the missing one remains. This is the same O(1)-space trick as Single Number in Bit manipulation. The sum formula n(n+1)/2 − sum also works, but in other languages it can overflow.
Floyd: a duplicate without touching the array#
Find the Duplicate Number forbids modifying the array, which rules out cyclic sort and sign marking. Treat every index as a node with an edge i → nums[i]. Values are in 1..n and there are n + 1 indices, so following edges from 0 must eventually loop, and the duplicate value is the entrance of the loop because two different indices point to it.
Drawing diagram…
For [1, 3, 4, 2, 2], both index 3 and index 4 point to node 2, which is where the cycle starts. Floyd's algorithm from Linked lists finds it: the slow and fast pointers meet somewhere inside the cycle; reset one to the start and step both once at a time, and they meet at the entrance.
Boyer-Moore majority vote#
If one value appears more than n/2 times, pair each of its copies with a different value and cancel them; at least one copy survives. As code, that is one candidate and one counter. When the counter is 0, adopt the current value; a match adds a vote, anything else removes one.
| n | 2 | 2 | 1 | 1 | 1 | 2 | 2 |
|---|---|---|---|---|---|---|---|
| candidate | 2 | 2 | 2 | 2 | 1 | 1 | 2 |
| votes | 1 | 2 | 1 | 0 | 1 | 0 | 1 |
The generalization: at most k − 1 values can appear more than n/k times, so keep k − 1 candidates. For "more than n/3" that's two candidates. A different value cancels one vote from each, and a second pass verifies the survivors, because nothing guarantees they are real majorities.
Next permutation#
To get the next permutation in lexicographic order, change as little as possible, as far right as possible:
- From the right, find the first index
iwithnums[i] < nums[i+1]. Everything afteriis descending, which means it is already the largest arrangement of that suffix. - If there is such an
i, swapnums[i]with the rightmost value larger than it. - Reverse the suffix after
i, so it goes from largest to smallest arrangement. If there was noi, the whole array reverses and wraps around to the first permutation.
The full program#
package main
import (
"fmt"
"slices"
)
// cyclicSort places each value v (1..n) at index v-1. Each swap fixes one value, so it is O(n).
func cyclicSort(nums []int) {
for i := 0; i < len(nums); {
want := nums[i] - 1
if want >= 0 want < len(nums) nums[i] != nums[want] {
nums[i], nums[want] = nums[want], nums[i]
} else {
i++
}
}
}
// findDisappeared returns every value in 1..n that is missing.
func findDisappeared(nums []int) []int {
cyclicSort(nums)
var out []int
for i, n := range nums {
if n != i+1 {
out = append(out, i+1)
}
}
return out
}
// firstMissingPositive ignores values outside 1..n; the first slot that is wrong is the answer.
func firstMissingPositive(nums []int) int {
cyclicSort(nums)
for i, n := range nums {
if n != i+1 {
return i + 1
}
}
return len(nums) + 1
}
// missingNumber in 0..n: XOR every index and value; pairs cancel, the missing one remains.
func missingNumber(nums []int) int {
x := len(nums)
for i, n := range nums {
x ^= i ^ n
}
return x
}
// findDuplicate without modifying nums: values are "next" pointers, so a duplicate is a cycle entrance.
func findDuplicate(nums []int) int {
slow, fast := nums[0], nums[nums[0]]
for slow != fast {
slow, fast = nums[slow], nums[nums[fast]]
}
slow = 0
for slow != fast {
slow, fast = nums[slow], nums[fast]
}
return slow
}
// findDuplicates marks a value v as seen by negating nums[v-1].
func findDuplicates(nums []int) []int {
var out []int
for _, n := range nums {
v := abs(n)
if nums[v-1] < 0 {
out = append(out, v)
} else {
nums[v-1] = -nums[v-1]
}
}
return out
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
// majority: Boyer-Moore vote. A different value cancels one vote; the majority survives.
func majority(nums []int) int {
cand, votes := 0, 0
for _, n := range nums {
if votes == 0 {
cand = n
}
if n == cand {
votes++
} else {
votes--
}
}
return cand
}
// majorityThird: at most two values can appear more than n/3 times. Keep two candidates, then verify.
func majorityThird(nums []int) []int {
c1, c2, v1, v2 := 0, 1, 0, 0 // distinct starting candidates
for _, n := range nums {
switch {
case n == c1:
v1++
case n == c2:
v2++
case v1 == 0:
c1, v1 = n, 1
case v2 == 0:
c2, v2 = n, 1
default:
v1--
v2--
}
}
var out []int
for _, c := range []int{c1, c2} {
count := 0
for _, n := range nums {
if n == c {
count++
}
}
if count > len(nums)/3 {
out = append(out, c)
}
}
return out
}
// nextPermutation rearranges nums into the next larger order, or the smallest if it is the last.
func nextPermutation(nums []int) {
i := len(nums) - 2
for i >= 0 nums[i] >= nums[i+1] { // find the rightmost ascent
i--
}
if i >= 0 {
j := len(nums) - 1
for nums[j] <= nums[i] { // rightmost value larger than nums[i]
j--
}
nums[i], nums[j] = nums[j], nums[i]
}
slices.Reverse(nums[i+1:]) // the suffix was descending; make it ascending
}
func main() {
fmt.Println(findDisappeared([]int{4, 3, 2, 7, 8, 2, 3, 1}))
fmt.Println(firstMissingPositive([]int{3, 4, -1, 1}), firstMissingPositive([]int{7, 8, 9}), firstMissingPositive([]int{1, 2, 0}))
fmt.Println(missingNumber([]int{3, 0, 1}), missingNumber([]int{9, 6, 4, 2, 3, 5, 7, 0, 1}))
fmt.Println(findDuplicate([]int{1, 3, 4, 2, 2}), findDuplicate([]int{3, 1, 3, 4, 2}))
fmt.Println(findDuplicates([]int{4, 3, 2, 7, 8, 2, 3, 1}))
fmt.Println(majority([]int{2, 2, 1, 1, 1, 2, 2}), majorityThird([]int{3, 2, 3}), majorityThird([]int{1, 2}))
for _, p := range [][]int{{1, 2, 3}, {1, 3, 2}, {3, 2, 1}, {1, 5, 8, 4, 7, 6, 5, 3, 1}} {
before := slices.Clone(p)
nextPermutation(p)
fmt.Println(before, "->", p)
}
}[5 6]
2 1 3
2 8
2 3
[2 3]
2 [3] [2 1]
[1 2 3] -> [1 3 2]
[1 3 2] -> [2 1 3]
[3 2 1] -> [1 2 3]
[1 5 8 4 7 6 5 3 1] -> [1 5 8 5 1 3 4 6 7]Complexity#
| Problem | Time | Space | Modifies input |
|---|---|---|---|
| cyclic sort (missing, duplicates) | O(n) | O(1) | yes, reorders |
| first missing positive | O(n) | O(1) | yes |
| sign marking | O(n) | O(1) | yes, restorable |
| missing number by XOR | O(n) | O(1) | no |
| find duplicate by Floyd | O(n) | O(1) | no |
| majority vote (n/2 or n/3) | O(n) | O(1) | no |
| next permutation | O(n) | O(1) | yes |
Recognizing the pattern#
Look for "values in the range 1 to n" (or 0 to n) together with "missing," "duplicate," or "O(1) extra space". That combination almost always means cyclic sort or sign marking. "Without modifying the array" rules those out and points to Floyd or XOR. "Appears more than n/k times" is a vote counter. Next permutation shows up both on its own and as the core of "the next greater number with the same digits."
Summary#
- Values in 1..n name indices: cyclic sort sends each to
v − 1in O(n) total swaps. - After cyclic sort, a wrong slot gives a missing value (the index + 1) and a duplicate (the value it holds).
- Negating
nums[v−1]marks v as seen without moving anything. - XOR of all indices and values leaves the missing number.
- Treating
i → nums[i]as a linked list turns "find the duplicate" into Floyd's cycle entrance. - Boyer-Moore keeps k − 1 candidates for "more than n/k"; verify unless a majority is guaranteed.
- Next permutation: rightmost ascent, swap with the rightmost larger value, reverse the suffix.
Check your understanding
Answer before scrolling back up. If you miss one, reread the matching section, then try again.
Q1.Cyclic sort has a loop inside a loop. Why is it still O(n)?
Q2.First Missing Positive: what do you do with 0, negatives, and values larger than n?
Q3.Find the Duplicate Number must not modify the array and must use O(1) space. Which idea works?
Q4.Boyer-Moore majority vote returns a candidate. When must you verify it with a second pass?
Q5.Next permutation of [1, 3, 2] is:
Practice in Go
Write a table test first, then the solution. On LeetCode, choose Go as the language. Open a hint only after 15 minutes stuck.
- Missing Number ↗easy
Show hint
XOR all indices 0..n and all values; everything pairs up except the missing one. Or n(n+1)/2 minus the sum.
Show hint
Boyer-Moore: one candidate, one counter.
Show hint
Cyclic sort, then report each index i where nums[i] != i+1.
Show hint
Negate nums[|v|-1]; if it is already negative, v was seen before.
Show hint
Floyd: meet inside the cycle, then move one pointer to the start; they meet at the entrance.
- Majority Element II ↗medium
Show hint
Two candidates, two counters, then verify both with a second pass.
- Next Permutation ↗medium
Show hint
Rightmost ascent i; swap with the rightmost value larger than nums[i]; reverse nums[i+1:].
Show hint
Cyclic sort that skips values outside 1..n; the first slot holding the wrong value is the answer.
Finished this lesson?
Mark it done to track your progress. It is saved in this browser only.