Facebook Pixel

24. Swap Nodes in Pairs

Problem Description

You are given a singly linked list. Your task is to swap every two adjacent nodes in the list and return the head of the modified list.

The key constraint is that you cannot modify the values inside the nodes - you can only change the node connections themselves. This means you need to rearrange the pointers between nodes rather than swapping the data they contain.

For example:

  • If the input list is 1 -> 2 -> 3 -> 4, the output should be 2 -> 1 -> 4 -> 3
  • If the input list is 1 -> 2 -> 3, the output should be 2 -> 1 -> 3
  • If the input list is empty or has only one node, return it as is

The solution uses a recursive approach where:

  1. The base case handles when there are no nodes or only one node left (nothing to swap)
  2. For each pair of nodes, we:
    • Recursively process the rest of the list starting from the third node
    • Save the second node as p
    • Make p point to the first node
    • Make the first node point to the result of the recursive call
    • Return p as the new head of this portion

This effectively swaps each pair of nodes throughout the entire linked list.

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

Intuition

When we need to swap pairs of nodes in a linked list, we can think of the problem as repeatedly performing the same operation: taking two nodes and swapping them. This repetitive nature suggests that recursion might be a clean solution.

Let's think about what happens when we swap just one pair of nodes. If we have A -> B -> rest, we want to transform it into B -> A -> rest. To do this, we need to:

  • Make B point to A
  • Make A point to whatever comes after B

But here's the key insight: "whatever comes after B" might also need to be swapped! If the rest of the list is C -> D -> E, then after swapping, it should become D -> C -> E. This means we need to process the rest of the list first before we can properly connect our swapped pair.

This naturally leads to a recursive approach:

  1. First, recursively swap all pairs starting from the third node onwards
  2. Then swap the current pair and connect it to the already-swapped rest of the list

The beauty of this approach is that each recursive call only needs to worry about swapping one pair and trusting that the recursive call will handle the rest correctly. When we reach the end of the list (no nodes or just one node left), we've hit our base case where no swapping is needed.

By working backwards from the end of the list to the beginning, each pair gets properly connected to an already-processed remainder, ensuring all the pointers are correctly updated without needing to track multiple pointers or use dummy nodes.

Learn more about Recursion and Linked List patterns.

Solution Approach

The recursive solution implements the swapping of node pairs by processing the linked list from the end towards the beginning.

Let's walk through the implementation step by step:

Base Case:

if head is None or head.next is None:
    return head

This handles two scenarios:

  • Empty list (head is None)
  • Single node (head.next is None)

In both cases, no swapping is possible, so we return the head as-is.

Recursive Case:

  1. Process the rest of the list first:

    t = self.swapPairs(head.next.next)

    We recursively call swapPairs on the sublist starting from the third node (head.next.next). The variable t will hold the head of the already-swapped remainder of the list.

  2. Perform the swap for current pair:

    p = head.next
    p.next = head
    head.next = t
    • Save the second node in variable p (this will become the new head of this pair)
    • Make p.next point to head (reversing the link between first and second nodes)
    • Make head.next point to t (connecting the first node to the swapped remainder)
  3. Return the new head:

    return p

    The second node of the original pair is now the first node, so we return p.

Example Walkthrough:

For list 1 -> 2 -> 3 -> 4:

  • First call: swapPairs(1) calls swapPairs(3)
  • Second call: swapPairs(3) calls swapPairs(None)
  • Third call: swapPairs(None) returns None
  • Back to second call: Swaps 3 and 4, returns node 4 (which points to 3)
  • Back to first call: Swaps 1 and 2, connects 1 to node 4, returns node 2

Final result: 2 -> 1 -> 4 -> 3

The time complexity is O(n) where n is the number of nodes, as we visit each node once. The space complexity is O(n) due to the recursion stack depth.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's trace through the algorithm with a simple 4-node list: 1 -> 2 -> 3 -> 4

Initial Setup:

head points to node 1
1 -> 2 -> 3 -> 4 -> None

Call Stack Progression:

Call 1: swapPairs(node 1)

  • head = node 1, head.next = node 2 (not null, so continue)
  • Before swapping this pair, recursively process from node 3: t = swapPairs(node 3)

Call 2: swapPairs(node 3)

  • head = node 3, head.next = node 4 (not null, so continue)
  • Before swapping this pair, recursively process from node 5 (None): t = swapPairs(None)

Call 3: swapPairs(None)

  • head = None triggers base case
  • Returns None

Back to Call 2: Now swap nodes 3 and 4

  • t = None (returned from Call 3)
  • p = node 4 (save the second node)
  • p.next = node 3 (node 4 now points to node 3)
  • node3.next = None (node 3 points to the processed remainder)
  • Returns node 4
  • Current state: 4 -> 3 -> None

Back to Call 1: Now swap nodes 1 and 2

  • t = node 4 (returned from Call 2, this is the head of "4 -> 3")
  • p = node 2 (save the second node)
  • p.next = node 1 (node 2 now points to node 1)
  • node1.next = node 4 (node 1 points to the processed remainder)
  • Returns node 2
  • Final state: 2 -> 1 -> 4 -> 3 -> None

Key Observations:

  • The recursion processes pairs from the end of the list backwards
  • Each recursive call handles exactly one pair swap
  • The returned value from each call becomes the new head of that portion
  • Connections are made after the recursive call returns, ensuring proper linking

Solution Implementation

1# Definition for singly-linked list.
2# class ListNode:
3#     def __init__(self, val=0, next=None):
4#         self.val = val
5#         self.next = next
6
7from typing import Optional
8
9class Solution:
10    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
11        """
12        Swaps every two adjacent nodes in a linked list recursively.
13      
14        Args:
15            head: The head of the linked list
16          
17        Returns:
18            The new head of the linked list after swapping pairs
19        """
20        # Base case: if list is empty or has only one node, no swap needed
21        if head is None or head.next is None:
22            return head
23      
24        # Recursively swap the remaining pairs after the current pair
25        remaining_swapped = self.swapPairs(head.next.next)
26      
27        # Store the second node which will become the new first node
28        new_first = head.next
29      
30        # Make the second node point to the first node (swap)
31        new_first.next = head
32      
33        # Connect the first node (now second) to the rest of the swapped list
34        head.next = remaining_swapped
35      
36        # Return the new first node of this pair
37        return new_first
38
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 *     int val;
5 *     ListNode next;
6 *     ListNode() {}
7 *     ListNode(int val) { this.val = val; }
8 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12    /**
13     * Swaps every two adjacent nodes in a linked list recursively.
14     * For example: 1->2->3->4 becomes 2->1->4->3
15     * 
16     * @param head The head of the linked list
17     * @return The new head of the modified linked list
18     */
19    public ListNode swapPairs(ListNode head) {
20        // Base case: if list is empty or has only one node, no swap needed
21        if (head == null || head.next == null) {
22            return head;
23        }
24      
25        // Recursively swap pairs in the remaining list (starting from the third node)
26        ListNode remainingList = swapPairs(head.next.next);
27      
28        // Store the second node which will become the new head of this pair
29        ListNode newHead = head.next;
30      
31        // Perform the swap: second node points to first node
32        newHead.next = head;
33      
34        // First node now points to the remaining swapped list
35        head.next = remainingList;
36      
37        // Return the new head (originally the second node)
38        return newHead;
39    }
40}
41
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 *     int val;
5 *     ListNode *next;
6 *     ListNode() : val(0), next(nullptr) {}
7 *     ListNode(int x) : val(x), next(nullptr) {}
8 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13    /**
14     * Swaps every two adjacent nodes in a linked list recursively
15     * @param head: pointer to the head of the linked list
16     * @return: pointer to the new head after swapping pairs
17     */
18    ListNode* swapPairs(ListNode* head) {
19        // Base case: if list is empty or has only one node, no swap needed
20        if (!head || !head->next) {
21            return head;
22        }
23      
24        // Recursively swap the remaining pairs starting from the third node
25        ListNode* remainingList = swapPairs(head->next->next);
26      
27        // Store the second node which will become the new head of this pair
28        ListNode* newHead = head->next;
29      
30        // Perform the swap: second node points to first node
31        newHead->next = head;
32      
33        // First node now points to the result of swapping remaining pairs
34        head->next = remainingList;
35      
36        // Return the new head (originally the second node)
37        return newHead;
38    }
39};
40
1/**
2 * Definition for singly-linked list node
3 */
4class ListNode {
5    val: number;
6    next: ListNode | null;
7  
8    constructor(val?: number, next?: ListNode | null) {
9        this.val = (val === undefined ? 0 : val);
10        this.next = (next === undefined ? null : next);
11    }
12}
13
14/**
15 * Swaps every two adjacent nodes in a linked list recursively
16 * @param head - The head of the linked list
17 * @returns The new head of the list after swapping pairs
18 * 
19 * Example: 1->2->3->4 becomes 2->1->4->3
20 */
21function swapPairs(head: ListNode | null): ListNode | null {
22    // Base case: if list is empty or has only one node, no swap needed
23    if (!head || !head.next) {
24        return head;
25    }
26  
27    // Recursively swap the rest of the list starting from the third node
28    const remainingList: ListNode | null = swapPairs(head.next.next);
29  
30    // Store the second node which will become the new head of this pair
31    const newHead: ListNode = head.next;
32  
33    // Swap: make the second node point to the first node
34    newHead.next = head;
35  
36    // Connect the first node to the remaining swapped list
37    head.next = remainingList;
38  
39    // Return the new head of this swapped pair
40    return newHead;
41}
42

Time and Space Complexity

Time Complexity: O(n)

The algorithm recursively processes each pair of nodes in the linked list. Since the recursive function is called once for every pair of nodes (moving forward by 2 nodes each time with head.next.next), it will make approximately n/2 recursive calls where n is the total number of nodes. Each recursive call performs a constant amount of work O(1) - just rearranging pointers. Therefore, the total time complexity is O(n/2) * O(1) = O(n).

Space Complexity: O(n)

The space complexity is determined by the recursive call stack. Since the function calls itself recursively until it reaches the end of the list (base case), the maximum depth of the recursion stack will be n/2 (as we process pairs of nodes). Each recursive call uses O(1) space for its local variables (t and p). Therefore, the total space complexity is O(n/2) = O(n) due to the recursive call stack.

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

Common Pitfalls

1. Losing Track of Node References During Swapping

One of the most common mistakes when swapping nodes is accidentally losing references to nodes, which can break the linked list or create incorrect connections.

Pitfall Example:

def swapPairs(self, head):
    if head is None or head.next is None:
        return head
  
    # WRONG: Modifying head.next before saving necessary references
    head.next = self.swapPairs(head.next.next)  # Lost reference to second node!
    # Now we can't access the original second node to complete the swap

Solution: Always save the nodes you need before modifying any next pointers:

def swapPairs(self, head):
    if head is None or head.next is None:
        return head
  
    # Save the second node FIRST
    second = head.next
    # Then proceed with recursive call and swapping
    head.next = self.swapPairs(second.next)
    second.next = head
    return second

2. Incorrect Order of Pointer Reassignments

The order in which you update the next pointers matters significantly. Doing them in the wrong order can create cycles or break connections.

Pitfall Example:

def swapPairs(self, head):
    if head is None or head.next is None:
        return head
  
    second = head.next
    # WRONG ORDER: This creates a cycle!
    second.next = head  # Now second -> head
    head.next = second.next  # This makes head.next point to itself!
  
    return second

Solution: Process the rest of the list first, then update pointers in the correct order:

def swapPairs(self, head):
    if head is None or head.next is None:
        return head
  
    # First, process the rest of the list
    remaining = self.swapPairs(head.next.next)
  
    # Then update pointers in correct order
    second = head.next
    second.next = head
    head.next = remaining  # Connect to the already-processed remainder
  
    return second

3. Forgetting Edge Cases in Base Condition

Another common mistake is having an incomplete base case that doesn't handle all scenarios where swapping shouldn't occur.

Pitfall Example:

def swapPairs(self, head):
    # WRONG: Only checking for empty list
    if head is None:
        return head
  
    # This will cause an error when head.next is None
    remaining = self.swapPairs(head.next.next)  # AttributeError!

Solution: Always check both conditions - empty list AND single node:

def swapPairs(self, head):
    # Correct: Check for both empty list and single node
    if head is None or head.next is None:
        return head
  
    # Now safe to access head.next.next
    remaining = self.swapPairs(head.next.next)

4. Attempting to Modify Values Instead of Pointers

While not directly a bug in the recursive approach, developers sometimes try to "simplify" by swapping values instead of nodes, which violates the problem constraints.

Pitfall Example:

def swapPairs(self, head):
    if head is None or head.next is None:
        return head
  
    # WRONG: This violates the constraint of not modifying node values
    head.val, head.next.val = head.next.val, head.val
    self.swapPairs(head.next.next)
    return head

Solution: Stick to pointer manipulation only:

def swapPairs(self, head):
    if head is None or head.next is None:
        return head
  
    # Correct: Only manipulate pointers, not values
    remaining = self.swapPairs(head.next.next)
    second = head.next
    second.next = head
    head.next = remaining
    return second
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More