Facebook Pixel

765. Couples Holding Hands

Problem Description

You have n couples (so 2n people total) sitting in a row of 2n seats. Each person has a unique ID number, and couples are paired sequentially: couple 0 consists of persons (0, 1), couple 1 consists of persons (2, 3), couple 2 consists of persons (4, 5), and so on, with the last couple being persons (2n - 2, 2n - 1).

The goal is to arrange the seating so that every couple sits next to each other (side by side). You're given an array row where row[i] represents the ID of the person currently sitting in seat i.

To achieve this arrangement, you can perform swaps. A swap involves selecting any two people in the row, having them stand up, and switching their seats.

Your task is to find the minimum number of swaps needed to ensure all couples are sitting together.

For example, if row = [0, 2, 1, 3], person 0 is sitting in seat 0, person 2 in seat 1, person 1 in seat 2, and person 3 in seat 3. Since couple 0 (persons 0 and 1) are not sitting together, and couple 1 (persons 2 and 3) are not sitting together, we need to swap. One swap of persons in seats 1 and 2 would result in [0, 1, 2, 3], where both couples are now sitting together.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: This problem can be modeled as a graph problem. Each couple can be viewed as a node, and if two couples have their members mixed up in seating positions (i.e., they need to swap to get to correct positions), there's an edge between them. The couples form connected components or cycles that need to be resolved.

Is it a tree?

  • No: The graph formed by the couples is not necessarily a tree. It can have multiple disconnected components, and each component represents a cycle of couples that need to swap among themselves.

Is the problem related to directed acyclic graphs?

  • No: The problem doesn't involve directed edges or acyclic properties. The relationships between couples that need swapping form undirected cycles.

Is the problem related to shortest paths?

  • No: We're not finding shortest paths between nodes. We're identifying connected components (cycles of couples).

Does the problem involve connectivity?

  • Yes: The core of the problem is about connectivity. We need to identify which couples are connected through a cycle of necessary swaps. Each connected component represents a group of couples that need to swap among themselves.

Disjoint Set Union

  • Yes: DSU (Union-Find) is perfect for this problem. We can use it to group couples that are in the same permutation cycle. The solution uses Union-Find to merge couples that are currently sitting in adjacent pairs, forming connected components.

Conclusion: The flowchart leads us to use Disjoint Set Union (Union-Find), which is implemented in the solution. While DFS could also solve connectivity problems, DSU is more efficient here for finding and merging connected components of couples that need to swap positions.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is to think about what happens when couples are sitting in the wrong positions. If couple A has one person sitting with someone from couple B, then couple B must have their other person sitting somewhere else, possibly with someone from couple C, and so on. This creates a cycle of couples that are "entangled" with each other.

Consider this scenario: Person 0 sits with Person 2, Person 1 sits with Person 3. Here, couples 0 and 1 are entangled. To fix this, we need exactly 1 swap - we can swap Person 1 and Person 2, resulting in couples sitting correctly.

Now imagine three couples are entangled: Person 0 sits with Person 2, Person 1 sits with Person 4, and Person 3 sits with Person 5. This forms a cycle of 3 couples, and we need exactly 2 swaps to fix all of them.

The pattern emerges: if k couples form a cycle where they're all mixed up with each other, we need exactly k-1 swaps to arrange them correctly. Why? Because each swap fixes one couple and reduces the problem size by 1. When we're down to the last couple in the cycle, they're automatically in the right position after all previous swaps.

To identify these cycles, we can use Union-Find. As we scan through the seats pair by pair (seats 0-1, seats 2-3, etc.), we check which couples are currently sitting together. If Person i from couple A is sitting with Person j from couple B, we union couples A and B in our Union-Find structure. This builds up the connected components representing our cycles.

If we have n couples total and they form x connected components (cycles), then the total number of swaps needed is (size_1 - 1) + (size_2 - 1) + ... + (size_x - 1) = n - x.

This is why the solution uses Union-Find to group couples and returns n - (number of connected components).

Learn more about Greedy, Depth-First Search, Breadth-First Search, Union Find and Graph patterns.

Solution Approach

The implementation uses Union-Find (Disjoint Set Union) to track which couples form connected components (cycles). Here's how the solution works step by step:

1. Initialize Union-Find Structure

n = len(row) >> 1  # Number of couples (2n people means n couples)
p = list(range(n))  # Parent array for Union-Find, initially each couple is its own parent

We create a parent array p where p[i] = i initially, meaning each couple is in its own set.

2. Map People to Couples Each person's couple number can be calculated as person_id >> 1 (equivalent to person_id // 2):

  • Person 0 and 1 β†’ Couple 0
  • Person 2 and 3 β†’ Couple 1
  • Person 4 and 5 β†’ Couple 2

3. Build Connected Components

for i in range(0, len(row), 2):
    a, b = row[i] >> 1, row[i + 1] >> 1
    p[find(a)] = find(b)

We iterate through seats in pairs (0-1, 2-3, 4-5, etc.). For each pair of adjacent seats:

  • Get the couple numbers of the two people sitting together: a = row[i] >> 1 and b = row[i + 1] >> 1
  • Union these two couples using p[find(a)] = find(b)

This unions couples that have their members mixed up in the current seating arrangement.

4. Find Operation with Path Compression

def find(x: int) -> int:
    if p[x] != x:
        p[x] = find(p[x])  # Path compression
    return p[x]

The find operation locates the root of a set and applies path compression for efficiency.

5. Count Connected Components and Calculate Result

return n - sum(i == find(i) for i in range(n))
  • Count how many couples are roots of their components: sum(i == find(i) for i in range(n))
  • This gives us the number of connected components x
  • Return n - x, which is the minimum number of swaps needed

Why This Works: If k couples form a cycle (connected component), they need k-1 swaps to all sit correctly. With x total components having sizes y₁, yβ‚‚, ..., yβ‚“, the total swaps needed is: (y₁ - 1) + (yβ‚‚ - 1) + ... + (yβ‚“ - 1) = (y₁ + yβ‚‚ + ... + yβ‚“) - x = n - x

The Union-Find efficiently groups the entangled couples and allows us to calculate the answer in O(n) time with nearly O(1) operations for union and find (with path compression).

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with row = [0, 5, 4, 3, 2, 1] (3 couples, 6 people).

Step 1: Identify the couples

  • Couple 0: persons (0, 1)
  • Couple 1: persons (2, 3)
  • Couple 2: persons (4, 5)

Step 2: Check current seating pairs

  • Seats 0-1: Person 0 (Couple 0) sits with Person 5 (Couple 2)
  • Seats 2-3: Person 4 (Couple 2) sits with Person 3 (Couple 1)
  • Seats 4-5: Person 2 (Couple 1) sits with Person 1 (Couple 0)

Notice all three couples are entangled in a cycle!

Step 3: Build Union-Find structure Initialize: p = [0, 1, 2] (each couple is its own parent)

Process seat pairs:

  • Seats 0-1: Person 0 (Couple 0) and Person 5 (Couple 2) β†’ Union couples 0 and 2

    • find(0) = 0, find(2) = 2
    • Set p[0] = 2
    • Now p = [2, 1, 2]
  • Seats 2-3: Person 4 (Couple 2) and Person 3 (Couple 1) β†’ Union couples 2 and 1

    • find(2) = 2, find(1) = 1
    • Set p[2] = 1
    • Now p = [2, 1, 1]
  • Seats 4-5: Person 2 (Couple 1) and Person 1 (Couple 0) β†’ Union couples 1 and 0

    • find(1) = 1, find(0) = find(2) = find(1) = 1
    • Already in same component, no change needed

Step 4: Count connected components Check which couples are roots:

  • Couple 0: find(0) = 1, not a root
  • Couple 1: find(1) = 1, is a root βœ“
  • Couple 2: find(2) = 1, not a root

Number of components = 1

Step 5: Calculate result Minimum swaps = n - components = 3 - 1 = 2

Verification with actual swaps:

  1. Swap persons in seats 1 and 4: [0, 1, 4, 3, 2, 5] - Couple 0 is fixed!
  2. Swap persons in seats 2 and 4: [0, 1, 2, 3, 4, 5] - All couples are fixed!

Indeed, we needed exactly 2 swaps, confirming our formula works correctly.

Solution Implementation

1class Solution:
2    def minSwapsCouples(self, row: List[int]) -> int:
3        """
4        Find minimum swaps needed to seat couples together.
5        Uses Union-Find to group couples that are sitting in wrong positions.
6      
7        Args:
8            row: List of integers representing people (couples are 0-1, 2-3, 4-5, etc.)
9      
10        Returns:
11            Minimum number of swaps needed
12        """
13      
14        def find_root(node: int) -> int:
15            """
16            Find the root of the set containing the given node.
17            Implements path compression for optimization.
18          
19            Args:
20                node: The node to find the root for
21          
22            Returns:
23                The root node of the set
24            """
25            if parent[node] != node:
26                # Path compression: make every node point directly to root
27                parent[node] = find_root(parent[node])
28            return parent[node]
29      
30        # Calculate number of couples (each couple consists of 2 people)
31        num_couples = len(row) // 2
32      
33        # Initialize parent array for Union-Find
34        # Each couple initially forms its own set
35        parent = list(range(num_couples))
36      
37        # Process each seat pair (positions 0-1, 2-3, 4-5, etc.)
38        for seat_index in range(0, len(row), 2):
39            # Get couple IDs for the two people sitting together
40            # Person i belongs to couple i//2
41            couple_a = row[seat_index] // 2
42            couple_b = row[seat_index + 1] // 2
43          
44            # Union: Connect the two couples in the same component
45            # If they're from different couples, they need to be swapped
46            root_a = find_root(couple_a)
47            root_b = find_root(couple_b)
48            parent[root_a] = root_b
49      
50        # Count number of connected components
51        # Each component requires (size - 1) swaps to fix
52        num_components = sum(1 for couple_id in range(num_couples) 
53                           if couple_id == find_root(couple_id))
54      
55        # Total swaps = total couples - number of components
56        return num_couples - num_components
57
1class Solution {
2    // Parent array for Union-Find (Disjoint Set Union) data structure
3    private int[] parent;
4
5    public int minSwapsCouples(int[] row) {
6        // Calculate number of couples (each couple consists of 2 people)
7        int numCouples = row.length / 2;
8      
9        // Initialize parent array for Union-Find
10        parent = new int[numCouples];
11        for (int i = 0; i < numCouples; i++) {
12            parent[i] = i;
13        }
14      
15        // Union couples that are sitting together
16        // Process every pair of adjacent seats
17        for (int i = 0; i < row.length; i += 2) {
18            // Get couple ID for person at position i and i+1
19            // Couple ID is person_number / 2 (persons 0,1 are couple 0; persons 2,3 are couple 1, etc.)
20            int coupleA = row[i] / 2;
21            int coupleB = row[i + 1] / 2;
22          
23            // Union the two couples in the same component
24            parent[find(coupleA)] = find(coupleB);
25        }
26      
27        // Count number of connected components
28        // Each connected component represents a cycle of couples that need to be swapped
29        int connectedComponents = 0;
30        for (int i = 0; i < numCouples; i++) {
31            if (i == find(i)) {
32                connectedComponents++;
33            }
34        }
35      
36        // Minimum swaps needed = total couples - number of connected components
37        // Each component of size k needs (k-1) swaps to fix
38        return numCouples - connectedComponents;
39    }
40
41    /**
42     * Find operation with path compression for Union-Find
43     * Returns the root parent of element x
44     */
45    private int find(int x) {
46        if (parent[x] != x) {
47            // Path compression: make every node point directly to root
48            parent[x] = find(parent[x]);
49        }
50        return parent[x];
51    }
52}
53
1class Solution {
2public:
3    int minSwapsCouples(vector<int>& row) {
4        // Total number of couples (each couple has 2 people)
5        int numCouples = row.size() / 2;
6      
7        // Parent array for Union-Find data structure
8        // Each index represents a couple group
9        int parent[numCouples];
10      
11        // Initialize each couple as its own parent (self-loop)
12        iota(parent, parent + numCouples, 0);
13      
14        // Find function with path compression for Union-Find
15        // Returns the root parent of the given couple group
16        function<int(int)> find = [&](int coupleId) -> int {
17            if (parent[coupleId] != coupleId) {
18                // Path compression: directly connect to root parent
19                parent[coupleId] = find(parent[coupleId]);
20            }
21            return parent[coupleId];
22        };
23      
24        // Process each pair of adjacent seats
25        // Union couples that are sitting together
26        for (int seatIndex = 0; seatIndex < numCouples * 2; seatIndex += 2) {
27            // Get couple IDs by dividing person ID by 2
28            // Person 0,1 belong to couple 0; Person 2,3 belong to couple 1, etc.
29            int coupleA = row[seatIndex] / 2;
30            int coupleB = row[seatIndex + 1] / 2;
31          
32            // Union the two couples if they're sitting together
33            parent[find(coupleA)] = find(coupleB);
34        }
35      
36        // Count the number of connected components
37        // Start with total couples and subtract the number of root parents
38        int minSwaps = numCouples;
39        for (int coupleId = 0; coupleId < numCouples; ++coupleId) {
40            // If this couple is its own root, it represents a connected component
41            if (coupleId == find(coupleId)) {
42                minSwaps--;
43            }
44        }
45      
46        // The minimum swaps needed equals (total couples - number of components)
47        return minSwaps;
48    }
49};
50
1/**
2 * Finds the minimum number of swaps needed to arrange couples sitting together.
3 * Uses Union-Find (Disjoint Set Union) to group couples that are interconnected.
4 * 
5 * @param row - Array representing people where couples are (0,1), (2,3), (4,5), etc.
6 * @returns The minimum number of swaps required
7 */
8function minSwapsCouples(row: number[]): number {
9    // Calculate number of couples (each couple consists of 2 people)
10    const numberOfCouples: number = row.length >> 1;
11  
12    // Initialize parent array for Union-Find structure
13    // Each couple initially points to itself as parent
14    const parent: number[] = Array(numberOfCouples)
15        .fill(0)
16        .map((_, index) => index);
17  
18    /**
19     * Find operation with path compression for Union-Find.
20     * Finds the root parent of a couple group.
21     * 
22     * @param coupleId - The couple ID to find root for
23     * @returns The root parent of the couple group
24     */
25    const findRoot = (coupleId: number): number => {
26        // Path compression: make each node point directly to root
27        if (parent[coupleId] !== coupleId) {
28            parent[coupleId] = findRoot(parent[coupleId]);
29        }
30        return parent[coupleId];
31    };
32  
33    // Process each pair of adjacent seats
34    for (let seatIndex = 0; seatIndex < numberOfCouples << 1; seatIndex += 2) {
35        // Get couple IDs by dividing person IDs by 2
36        // Person 0,1 belong to couple 0; Person 2,3 belong to couple 1, etc.
37        const firstCoupleId = row[seatIndex] >> 1;
38        const secondCoupleId = row[seatIndex + 1] >> 1;
39      
40        // Union operation: connect the two couples in the same component
41        // If they're not from the same couple, they create a cycle requiring swaps
42        parent[findRoot(firstCoupleId)] = findRoot(secondCoupleId);
43    }
44  
45    // Count the number of connected components
46    // Each component represents a group of interconnected couples
47    let connectedComponents = numberOfCouples;
48    for (let coupleId = 0; coupleId < numberOfCouples; ++coupleId) {
49        // If a couple is its own root, it's a component leader
50        if (coupleId === findRoot(coupleId)) {
51            --connectedComponents;
52        }
53    }
54  
55    // The minimum swaps needed equals total couples minus number of components
56    // Each component of size k requires (k-1) swaps to fix
57    return connectedComponents;
58}
59

Time and Space Complexity

Time Complexity: O(n Γ— Ξ±(n))

The algorithm uses a Union-Find (Disjoint Set Union) data structure with path compression. The main operations are:

  • Iterating through the array once: O(n) where n is the length of the row array
  • For each pair of elements (n/2 pairs total), performing two find operations and one union operation
  • The find operation with path compression has an amortized time complexity of O(Ξ±(n)), where Ξ±(n) is the inverse Ackermann function
  • The final sum comprehension iterates through n/2 elements, each calling find once: O(n Γ— Ξ±(n))

Since Ξ±(n) grows extremely slowly and is effectively constant for all practical values of n (it's less than 5 for all values of n less than 2^65536), the overall time complexity is O(n Γ— Ξ±(n)).

Space Complexity: O(n)

The space usage includes:

  • The parent array p of size n/2: O(n)
  • The recursion stack for the find function, which in the worst case (before path compression) could be O(n), but with path compression becomes O(Ξ±(n))
  • Variables a, b, and loop counters: O(1)

The dominant factor is the parent array, giving us a total space complexity of O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Couple ID Calculation

A common mistake is using modulo operation (%) instead of integer division to determine couple IDs. Some might incorrectly think that couples are paired as (0,1), (1,2), (2,3), etc., or try to use person_id % 2 to find partners.

Wrong approach:

# Incorrect: This doesn't properly identify couples
couple_id = row[i] % 2  # Wrong!
partner = row[i] + 1 if row[i] % 2 == 0 else row[i] - 1  # Overcomplicated

Correct approach:

# Each couple ID is simply person_id // 2 (or person_id >> 1)
couple_id = row[i] // 2
# Person 0,1 β†’ Couple 0; Person 2,3 β†’ Couple 1; etc.

2. Forgetting Path Compression in Union-Find

Without path compression, the find operation can degrade to O(n) in the worst case, making the overall algorithm inefficient for large inputs.

Inefficient version:

def find(x):
    while parent[x] != x:
        x = parent[x]  # No path compression
    return x

Optimized version:

def find(x):
    if parent[x] != x:
        parent[x] = find(parent[x])  # Path compression
    return parent[x]

3. Misunderstanding the Problem's Seat Pairing

Some might think any two consecutive people in the array should be couples, but the problem specifically states that seats are paired as (0,1), (2,3), (4,5), etc. This means we should only check pairs at even indices.

Wrong iteration:

# Incorrect: Checking every consecutive pair
for i in range(len(row) - 1):
    couple_a = row[i] // 2
    couple_b = row[i + 1] // 2
    # This would check pairs like (1,2), (3,4) which aren't seat pairs

Correct iteration:

# Check only seat pairs: (0,1), (2,3), (4,5), etc.
for i in range(0, len(row), 2):
    couple_a = row[i] // 2
    couple_b = row[i + 1] // 2

4. Incorrect Component Counting Logic

When counting connected components, you must check if each node is its own root after all unions are complete, not during the union process.

Wrong timing:

components = n  # Start with n components
for i in range(0, len(row), 2):
    if find(couple_a) != find(couple_b):
        components -= 1  # Wrong: This doesn't account for all merges correctly
        union(couple_a, couple_b)

Correct approach:

# First, perform all unions
for i in range(0, len(row), 2):
    parent[find(couple_a)] = find(couple_b)

# Then count components
components = sum(1 for i in range(n) if i == find(i))

5. Not Handling Edge Cases

While the problem guarantees valid input, in practice you might want to validate:

  • Empty array or array with odd length
  • Invalid person IDs (negative or >= 2n)
  • Duplicate person IDs

Robust version with validation:

def minSwapsCouples(self, row: List[int]) -> int:
    if not row or len(row) % 2 != 0:
        return 0  # or raise an exception
  
    n = len(row) // 2
    if len(set(row)) != len(row):  # Check for duplicates
        raise ValueError("Duplicate person IDs found")
  
    # Continue with main algorithm...
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of longest increasing subsequence:

Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.

For example, the length of LIS for [50, 3, 10, 7, 40, 80] is 4 and LIS is [3, 7, 40, 80].

What is the recurrence relation?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More