LeetCode 765 - Couples Holding Hands
This problem gives us a row of seats represented by the array row, where each value is the ID of the person currently sitting in that seat. The row contains 2n people, meaning there are exactly n couples.
Difficulty: 🔴 Hard
Topics: Greedy, Depth-First Search, Breadth-First Search, Union-Find, Graph Theory
Solution
LeetCode 765 - Couples Holding Hands
Problem Understanding
This problem gives us a row of seats represented by the array row, where each value is the ID of the person currently sitting in that seat. The row contains 2n people, meaning there are exactly n couples.
The couples are predefined in a very specific way:
- Person
0is paired with1 - Person
2is paired with3 - Person
4is paired with5 - And so on
In other words, for any person x, their partner can be computed directly:
- If
xis even, their partner isx + 1 - If
xis odd, their partner isx - 1
A convenient mathematical observation is:
partner = x ^ 1
because toggling the last binary bit switches between the two members of a couple.
The goal is to determine the minimum number of swaps required so that every couple sits together in adjacent seats. A swap can exchange any two people in the row, regardless of their current positions.
The important detail is that seat positions are grouped in pairs:
- Seats
(0,1)form one couch - Seats
(2,3)form another couch - Seats
(4,5)form another couch
For every adjacent pair of seats, we want the two people sitting there to belong to the same couple.
The constraints are relatively small:
2 <= n <= 30- Maximum row size is only
60
Even though the input size is small enough that many solutions would pass, the problem is fundamentally about recognizing structure and applying a clean greedy or graph-based strategy.
Several edge cases are important:
- The row may already be perfectly arranged, requiring zero swaps.
- Multiple couples may be cyclically misplaced, requiring careful swapping order.
- A naive strategy that swaps randomly can easily perform unnecessary operations.
- Since all elements are unique, we never need to handle duplicates or ambiguity.
Approaches
Brute Force Approach
A brute force solution would attempt to explore every possible sequence of swaps until all couples are seated together.
At each step, we could:
- Find a pair of adjacent seats that does not contain a valid couple.
- Try swapping one of those people with every other possible person.
- Recursively continue until the arrangement becomes valid.
- Track the minimum swaps seen.
This approach is correct because it explores all valid swap sequences and eventually finds the optimal one.
However, the number of possible seat arrangements grows factorially. Even for relatively small inputs, the search space becomes enormous. Backtracking with pruning may reduce the search somewhat, but it is still impractical compared to the optimal greedy solution.
Optimal Greedy Approach
The key insight is that each seat pair can be fixed independently in a greedy way.
Suppose we look at seats (i, i+1):
- Person
row[i]is already fixed in place. - We know exactly who their partner should be.
- If the partner is not already at
row[i+1], then the optimal move is to swap the correct partner into seati+1.
Why is this always optimal?
Because every valid final arrangement must place the correct partner next to row[i]. Performing that swap immediately resolves the current pair permanently without creating additional problems.
To make swaps efficient, we maintain a hash map from person ID to their current seat index. This allows us to locate any partner in constant time.
This transforms the problem into a straightforward greedy simulation.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | Exponential | O(n) to O(n!) | Tries many swap combinations recursively |
| Optimal Greedy | O(n) | O(n) | Fixes each seat pair independently using index mapping |
Algorithm Walkthrough
Greedy + Hash Map Algorithm
- Create a hash map called
position.
This map stores the current seat index for every person. For example:
position[person] = index
We use this because we frequently need to locate a person's partner quickly. 2. Iterate through the row two seats at a time.
Since couples must occupy adjacent seats, we process indices:
0, 2, 4, 6, ...
Each iteration handles one couch. 3. Let the first person in the couch be:
first_person = row[i]
- Compute their correct partner.
Since couples are consecutive integers:
partner = first_person ^ 1
Examples:
0 ^ 1 = 1
1 ^ 1 = 0
2 ^ 1 = 3
3 ^ 1 = 2
- Check whether the partner is already sitting next to them.
If:
row[i + 1] == partner
then this couch is already correct, so continue. 6. Otherwise, perform one swap.
We know the partner must eventually sit at position i + 1.
Use the hash map to locate the partner:
partner_index = position[partner]
Swap:
row[i + 1], row[partner_index] = row[partner_index], row[i + 1]
- Update the hash map.
After swapping, the moved people have new indices, so update both entries in position.
8. Increment the swap counter.
Each correction requires exactly one swap. 9. Continue until all couches are processed.
Why it works
The algorithm maintains the invariant that every processed couch is permanently correct.
When we process seats (i, i+1), person row[i] must eventually sit beside their partner in any valid solution. Swapping the correct partner directly into seat i+1 immediately fixes that couch with exactly one necessary swap.
Since fixing one couch never breaks previously fixed couches, the greedy strategy always produces the minimum number of swaps.
Python Solution
from typing import List
class Solution:
def minSwapsCouples(self, row: List[int]) -> int:
n = len(row)
# Map each person to their current seat index
position = {}
for index, person in enumerate(row):
position[person] = index
swaps = 0
# Process each couch
for i in range(0, n, 2):
first_person = row[i]
partner = first_person ^ 1
# Already seated correctly
if row[i + 1] == partner:
continue
# Find where the partner currently sits
partner_index = position[partner]
# Person currently sitting beside first_person
second_person = row[i + 1]
# Swap partner into the correct position
row[i + 1], row[partner_index] = (
row[partner_index],
row[i + 1],
)
# Update positions after swap
position[partner] = i + 1
position[second_person] = partner_index
swaps += 1
return swaps
The implementation begins by constructing the position dictionary, which allows constant-time lookup of any person's seat index. Without this structure, each swap would require scanning the array, increasing the complexity to O(n^2).
The main loop advances two seats at a time because every pair of adjacent seats represents one couch. For each couch, the algorithm computes the correct partner using the XOR operation.
If the couch is already correct, the loop continues immediately. Otherwise, the partner's current location is retrieved from the hash map, and a swap is performed to place the partner into the correct adjacent seat.
After the swap, the dictionary must be updated carefully because two people changed positions. Failing to maintain accurate indices would cause incorrect lookups later in the algorithm.
Finally, the total number of swaps is returned.
Go Solution
func minSwapsCouples(row []int) int {
n := len(row)
// Map person -> current index
position := make(map[int]int)
for i, person := range row {
position[person] = i
}
swaps := 0
for i := 0; i < n; i += 2 {
firstPerson := row[i]
partner := firstPerson ^ 1
// Already correct
if row[i+1] == partner {
continue
}
partnerIndex := position[partner]
secondPerson := row[i+1]
// Swap
row[i+1], row[partnerIndex] =
row[partnerIndex], row[i+1]
// Update indices
position[partner] = i + 1
position[secondPerson] = partnerIndex
swaps++
}
return swaps
}
The Go implementation follows exactly the same logic as the Python version. The primary difference is that Go uses a map[int]int for index tracking and explicit variable declarations.
Go slices are reference-like structures, so swaps directly modify the underlying array. Integer overflow is not a concern because the constraints are extremely small.
Worked Examples
Example 1
Input: row = [0,2,1,3]
Initial position map:
| Person | Index |
|---|---|
| 0 | 0 |
| 2 | 1 |
| 1 | 2 |
| 3 | 3 |
Iteration 1
Process seats (0,1):
| Value | Result |
|---|---|
| first_person | 0 |
| partner | 1 |
| row[1] | 2 |
The correct partner is not beside 0.
Find partner location:
position[1] = 2
Swap indices 1 and 2.
Row becomes:
[0,1,2,3]
Update map:
| Person | Index |
|---|---|
| 0 | 0 |
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
Swaps:
1
Iteration 2
Process seats (2,3):
| Value | Result |
|---|---|
| first_person | 2 |
| partner | 3 |
| row[3] | 3 |
Already correct.
Final answer:
1
Example 2
Input: row = [3,2,0,1]
Iteration 1
Seats (0,1):
| Value | Result |
|---|---|
| first_person | 3 |
| partner | 2 |
| row[1] | 2 |
Already correct.
Iteration 2
Seats (2,3):
| Value | Result |
|---|---|
| first_person | 0 |
| partner | 1 |
| row[3] | 1 |
Already correct.
No swaps needed.
Final answer:
0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each person is processed at most once |
| Space | O(n) | Hash map stores positions of all people |
The algorithm iterates through the row exactly once, processing two seats at a time. Every lookup and swap operation is constant time because of the hash map.
The auxiliary space comes entirely from the position dictionary, which stores one entry per person.
Test Cases
from typing import List
class Solution:
def minSwapsCouples(self, row: List[int]) -> int:
n = len(row)
position = {}
for i, person in enumerate(row):
position[person] = i
swaps = 0
for i in range(0, n, 2):
first_person = row[i]
partner = first_person ^ 1
if row[i + 1] == partner:
continue
partner_index = position[partner]
second_person = row[i + 1]
row[i + 1], row[partner_index] = (
row[partner_index],
row[i + 1],
)
position[partner] = i + 1
position[second_person] = partner_index
swaps += 1
return swaps
solution = Solution()
assert solution.minSwapsCouples([0, 2, 1, 3]) == 1 # Simple single swap
assert solution.minSwapsCouples([3, 2, 0, 1]) == 0 # Already correct
assert solution.minSwapsCouples([0, 1, 2, 3]) == 0 # Perfect arrangement
assert solution.minSwapsCouples([1, 0, 3, 2]) == 0 # Couples reversed internally
assert solution.minSwapsCouples([0, 3, 2, 1]) == 1 # One misplaced partner
assert solution.minSwapsCouples([5, 4, 2, 6, 3, 1, 0, 7]) == 2 # Multiple swaps
assert solution.minSwapsCouples([2, 0, 1, 3]) == 1 # Swap needed at beginning
assert solution.minSwapsCouples([4, 5, 2, 3, 0, 1]) == 0 # All correct
assert solution.minSwapsCouples([7, 5, 4, 6, 1, 0, 2, 3]) == 1 # Larger input
assert solution.minSwapsCouples([9, 0, 1, 8, 2, 3, 4, 5, 7, 6]) == 1 # Near-correct large case
Test Summary
| Test | Why |
|---|---|
[0,2,1,3] |
Basic example with one swap |
[3,2,0,1] |
Already solved arrangement |
[0,1,2,3] |
Perfect ascending order |
[1,0,3,2] |
Couples reversed but still adjacent |
[0,3,2,1] |
Single correction needed |
[5,4,2,6,3,1,0,7] |
Multiple interconnected swaps |
[2,0,1,3] |
Incorrect pair at front |
[4,5,2,3,0,1] |
Correct arrangement in different order |
[7,5,4,6,1,0,2,3] |
Larger mixed configuration |
[9,0,1,8,2,3,4,5,7,6] |
Larger near-sorted input |
Edge Cases
Already Correct Seating
One important edge case occurs when every couple is already seated together. A buggy implementation might still perform unnecessary swaps if it does not properly verify adjacency before swapping.
For example:
[0,1,2,3,4,5]
The implementation handles this by immediately checking:
if row[i + 1] == partner:
continue
No swap occurs unless the current couch is invalid.
Couples in Reverse Order
Another subtle case is when couples are seated together but reversed internally:
[1,0,3,2]
A naive implementation might incorrectly assume the smaller ID must come first.
The XOR partner computation avoids this problem completely because:
1 ^ 1 = 0
3 ^ 1 = 2
The algorithm only cares whether the correct partner is adjacent, not the order within the pair.
Cyclic Misplacement
A more difficult case occurs when several couples form a cycle of incorrect placements:
[0,2,4,1,3,5]
Here, multiple couples interfere with each other. A poor greedy strategy could perform extra swaps.
The implementation works correctly because each swap permanently fixes exactly one couch. Previously corrected couches are never broken again, guaranteeing minimal swaps.
Smallest Valid Input
The minimum input size is:
n = 2
Example:
[0,2,1,3]
The algorithm still works because the loop processes exactly one couch pair at a time and never accesses indices outside the array bounds.