Facebook Pixel

1721. Swapping Nodes in a Linked List

Problem Description

You are given the head of a linked list and an integer k.

Your task is to swap the values of two specific nodes in the linked list:

  • The kth node from the beginning of the list
  • The kth node from the end of the list

The list uses 1-based indexing, meaning the first node is at position 1, the second node is at position 2, and so on.

After swapping the values of these two nodes, return the head of the modified linked list.

For example, if you have a linked list 1 -> 2 -> 3 -> 4 -> 5 and k = 2:

  • The 2nd node from the beginning has value 2
  • The 2nd node from the end has value 4
  • After swapping, the list becomes 1 -> 4 -> 3 -> 2 -> 5

The solution uses a two-pointer technique. First, a fast pointer moves k-1 steps to reach the kth node from the beginning (stored as pointer p). Then, both fast and slow pointers move together until the fast pointer reaches the end. At this point, the slow pointer will be at the kth node from the end (stored as pointer q). Finally, the values at positions p and q are swapped.

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

Intuition

The key insight is recognizing that finding the kth node from the end is the challenging part, since we can't traverse a singly-linked list backwards. However, we can use the relationship between distances to solve this elegantly.

Think about it this way: if the linked list has n nodes total, then the kth node from the end is actually the (n - k + 1)th node from the beginning. But we don't know n initially, and counting all nodes first would require an extra pass through the list.

Here's where the two-pointer technique becomes clever. If we position one pointer k nodes ahead of another, and then move both pointers together until the leading pointer reaches the end, the trailing pointer will naturally land on the kth node from the end.

Why does this work? When the fast pointer has moved k steps from the start, it has (n - k) more steps to reach the end. If we start a slow pointer from the beginning at this moment and move both pointers together, when the fast pointer completes those (n - k) steps to reach the end, the slow pointer will have moved (n - k) steps from the start, positioning it at the (n - k + 1)th node from the beginning - which is exactly the kth node from the end.

This approach allows us to find both target nodes in a single pass: we mark the kth node from the beginning when our fast pointer reaches it initially, and we find the kth node from the end when our fast pointer reaches the list's end. Then we simply swap the values at these two positions.

Learn more about Linked List and Two Pointers patterns.

Solution Approach

The implementation uses a two-pointer technique to locate both target nodes efficiently in a single pass through the linked list.

Step 1: Find the kth node from the beginning

We initialize two pointers, fast and slow, both pointing to the head of the linked list. We then move the fast pointer forward by k - 1 steps using a loop:

for _ in range(k - 1):
    fast = fast.next

After this loop, fast points to the kth node from the beginning. We store this position in pointer p for later use:

p = fast

Step 2: Find the kth node from the end

Now we need to find the kth node from the end. The trick is to maintain a gap of k nodes between two pointers. Since fast is already k - 1 steps ahead of the head, we can now move both fast and slow pointers together until fast reaches the last node:

while fast.next:
    fast, slow = fast.next, slow.next

When this loop terminates:

  • fast.next is None, meaning fast is at the last node
  • slow is exactly k nodes behind the end, which makes it the kth node from the end

We store this position in pointer q:

q = slow

Step 3: Swap the values

Instead of manipulating the linked list structure (which would be complex), we simply swap the values of the two nodes:

p.val, q.val = q.val, p.val

Step 4: Return the result

Since we only swapped values and didn't change the structure of the linked list, we return the original head:

return head

The time complexity is O(n) where n is the length of the linked list, as we traverse the list only once. The space complexity is O(1) as we only use a constant number of pointers.

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 walk through the solution with a concrete example: linked list 1 -> 2 -> 3 -> 4 -> 5 and k = 2.

Initial Setup:

List: 1 -> 2 -> 3 -> 4 -> 5
k = 2 (we want to swap 2nd from beginning with 2nd from end)
fast = head (pointing to 1)
slow = head (pointing to 1)

Step 1: Find the 2nd node from the beginning

Move fast pointer forward by k - 1 = 1 step:

List: 1 -> 2 -> 3 -> 4 -> 5
         fast
slow

After moving, fast points to node with value 2. Store this as p:

p = fast (pointing to node 2)

Step 2: Find the 2nd node from the end

Move both fast and slow together until fast.next is None:

First iteration:

List: 1 -> 2 -> 3 -> 4 -> 5
            slow  fast

Second iteration:

List: 1 -> 2 -> 3 -> 4 -> 5
               slow     fast

Third iteration:

List: 1 -> 2 -> 3 -> 4 -> 5
                  slow     fast

Now fast.next is None (fast is at the last node), so we stop. slow points to node with value 4, which is the 2nd node from the end. Store this as q:

q = slow (pointing to node 4)

Step 3: Swap the values

We have:

  • p pointing to node with value 2 (2nd from beginning)
  • q pointing to node with value 4 (2nd from end)

Swap their values:

p.val, q.val = q.val, p.val

After swapping:

  • Node at position 2 now has value 4
  • Node at position 4 now has value 2

Step 4: Return the result

The final linked list is: 1 -> 4 -> 3 -> 2 -> 5

Return the original head pointer, which still points to the first node of the modified list.

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 swapNodes(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
11        # Initialize two pointers at the head
12        fast_pointer = slow_pointer = head
13      
14        # Move fast_pointer to the kth node from the beginning (1-indexed)
15        for _ in range(k - 1):
16            fast_pointer = fast_pointer.next
17      
18        # Store reference to the kth node from the beginning
19        kth_from_start = fast_pointer
20      
21        # Move fast_pointer to the end while moving slow_pointer
22        # This ensures slow_pointer ends up at the kth node from the end
23        while fast_pointer.next:
24            fast_pointer = fast_pointer.next
25            slow_pointer = slow_pointer.next
26      
27        # Store reference to the kth node from the end
28        kth_from_end = slow_pointer
29      
30        # Swap the values of the two nodes
31        kth_from_start.val, kth_from_end.val = kth_from_end.val, kth_from_start.val
32      
33        # Return the modified linked list
34        return head
35
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    public ListNode swapNodes(ListNode head, int k) {
13        // Find the k-th node from the beginning
14        ListNode kthFromStart = head;
15        int position = k;
16        while (--position > 0) {
17            kthFromStart = kthFromStart.next;
18        }
19      
20        // Store reference to the k-th node from beginning
21        ListNode firstNode = kthFromStart;
22      
23        // Use two-pointer technique to find k-th node from the end
24        // Start slow pointer from head
25        ListNode kthFromEnd = head;
26      
27        // Move the fast pointer (kthFromStart) to the end
28        // While moving slow pointer simultaneously
29        // This maintains a gap of k nodes between them
30        while (kthFromStart.next != null) {
31            kthFromStart = kthFromStart.next;
32            kthFromEnd = kthFromEnd.next;
33        }
34      
35        // Store reference to the k-th node from end
36        ListNode secondNode = kthFromEnd;
37      
38        // Swap the values of the two nodes
39        int temp = firstNode.val;
40        firstNode.val = secondNode.val;
41        secondNode.val = temp;
42      
43        return head;
44    }
45}
46
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    ListNode* swapNodes(ListNode* head, int k) {
14        // Find the k-th node from the beginning
15        ListNode* frontRunner = head;
16        while (--k) {
17            frontRunner = frontRunner->next;
18        }
19      
20        // Store the k-th node from the beginning
21        ListNode* kthFromBeginning = frontRunner;
22      
23        // Use two pointers to find the k-th node from the end
24        // When frontRunner reaches the end, backRunner will be at k-th node from end
25        ListNode* backRunner = head;
26        while (frontRunner->next) {
27            frontRunner = frontRunner->next;
28            backRunner = backRunner->next;
29        }
30      
31        // Store the k-th node from the end
32        ListNode* kthFromEnd = backRunner;
33      
34        // Swap the values of the two nodes
35        std::swap(kthFromBeginning->val, kthFromEnd->val);
36      
37        return head;
38    }
39};
40
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 *     val: number
5 *     next: ListNode | null
6 *     constructor(val?: number, next?: ListNode | null) {
7 *         this.val = (val===undefined ? 0 : val)
8 *         this.next = (next===undefined ? null : next)
9 *     }
10 * }
11 */
12
13/**
14 * Swaps the values of the kth node from the beginning and the kth node from the end
15 * in a singly-linked list.
16 * 
17 * @param head - The head of the linked list
18 * @param k - The position from both beginning and end (1-indexed)
19 * @returns The head of the modified linked list
20 */
21function swapNodes(head: ListNode | null, k: number): ListNode | null {
22    // Initialize two pointers starting at the head
23    let fastPointer: ListNode | null = head;
24    let slowPointer: ListNode | null = head;
25  
26    // Move fast pointer to the kth node from the beginning
27    let remainingSteps: number = k - 1;
28    while (remainingSteps > 0) {
29        fastPointer = fastPointer!.next;
30        remainingSteps--;
31    }
32  
33    // Store reference to the kth node from the beginning
34    const kthFromBeginning: ListNode = fastPointer!;
35  
36    // Move fast pointer to the end while moving slow pointer
37    // This positions slow pointer at the kth node from the end
38    while (fastPointer!.next !== null) {
39        fastPointer = fastPointer!.next;
40        slowPointer = slowPointer!.next;
41    }
42  
43    // Store reference to the kth node from the end
44    const kthFromEnd: ListNode = slowPointer!;
45  
46    // Swap the values of the two nodes
47    const tempValue: number = kthFromBeginning.val;
48    kthFromBeginning.val = kthFromEnd.val;
49    kthFromEnd.val = tempValue;
50  
51    return head;
52}
53

Time and Space Complexity

Time Complexity: O(n), where n is the length of the linked list.

The algorithm makes two passes through portions of the linked list:

  • First pass: The loop for _ in range(k - 1) moves the fast pointer k-1 steps forward, taking O(k) time.
  • Second pass: The while fast.next loop moves both fast and slow pointers until fast reaches the last node. Since fast is already at the k-th node, it needs to traverse n - k more nodes to reach the end, taking O(n - k) time.
  • The value swap operation p.val, q.val = q.val, p.val takes O(1) time.

Total time: O(k) + O(n - k) + O(1) = O(n)

Space Complexity: O(1)

The algorithm uses only a constant amount of extra space:

  • Four pointer variables: fast, slow, p, and q
  • No additional data structures are created
  • The swap operation is done in-place by exchanging values

All operations are performed using a fixed number of variables regardless of the input size, resulting in O(1) space complexity.

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

Common Pitfalls

1. Off-by-One Error in Finding the kth Node

Pitfall: A common mistake is confusion about 1-based vs 0-based indexing. Developers might incorrectly move the pointer k times instead of k-1 times to reach the kth node from the beginning.

Incorrect approach:

# Wrong: This moves k steps, landing on the (k+1)th node
for _ in range(k):
    fast_pointer = fast_pointer.next

Correct approach:

# Correct: Move k-1 steps to reach the kth node (1-indexed)
for _ in range(k - 1):
    fast_pointer = fast_pointer.next

2. Not Handling Edge Cases

Pitfall: The code might fail when:

  • The linked list has only one node
  • k equals the length of the list (swapping first and last nodes)
  • k is at the middle of an odd-length list (swapping a node with itself)

Solution: The current implementation naturally handles these cases because:

  • For single node: Both pointers point to the same node, swapping has no effect
  • For k = n: The algorithm correctly identifies first and last nodes
  • For middle node: Both pointers converge to the same node, self-swap is harmless

3. Attempting to Swap Nodes Instead of Values

Pitfall: Trying to swap the actual nodes by manipulating pointers is significantly more complex and error-prone:

# Complex and error-prone approach
# Need to track previous nodes, handle head changes, etc.
prev_p.next = q
prev_q.next = p
temp = p.next
p.next = q.next
q.next = temp

Better approach: Simply swap the values:

# Simple and clean
kth_from_start.val, kth_from_end.val = kth_from_end.val, kth_from_start.val

4. Incorrect Distance Calculation for kth Node from End

Pitfall: Misunderstanding how the two-pointer technique maintains the correct distance:

# Wrong: Starting slow_pointer movement too early or late
slow_pointer = head
for _ in range(k):  # Wrong offset
    fast_pointer = fast_pointer.next

Correct approach: After positioning fast_pointer at the kth node (k-1 moves), moving both pointers together maintains exactly the right gap to find the kth node from the end.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings

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

Load More