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 be2 -> 1 -> 4 -> 3
- If the input list is
1 -> 2 -> 3
, the output should be2 -> 1 -> 3
- If the input list is empty or has only one node, return it as is
The solution uses a recursive approach where:
- The base case handles when there are no nodes or only one node left (nothing to swap)
- 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.
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 toA
- Make
A
point to whatever comes afterB
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:
- First, recursively swap all pairs starting from the third node onwards
- 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:
-
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 variablet
will hold the head of the already-swapped remainder of the list. -
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 tohead
(reversing the link between first and second nodes) - Make
head.next
point tot
(connecting the first node to the swapped remainder)
- Save the second node in variable
-
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)
callsswapPairs(3)
- Second call:
swapPairs(3)
callsswapPairs(None)
- Third call:
swapPairs(None)
returnsNone
- 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 EvaluatorExample 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
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!