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
k
th node from the beginning of the list - The
k
th 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 k
th 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 k
th node from the end (stored as pointer q
). Finally, the values at positions p
and q
are swapped.
Intuition
The key insight is recognizing that finding the k
th 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 k
th 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 k
th 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 k
th node from the end.
This approach allows us to find both target nodes in a single pass: we mark the k
th node from the beginning when our fast pointer reaches it initially, and we find the k
th 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 k
th 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 k
th 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
isNone
, meaningfast
is at the last nodeslow
is exactlyk
nodes behind the end, which makes it thek
th 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 EvaluatorExample 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 thefast
pointerk-1
steps forward, takingO(k)
time. - Second pass: The
while fast.next
loop moves bothfast
andslow
pointers untilfast
reaches the last node. Sincefast
is already at thek
-th node, it needs to traversen - k
more nodes to reach the end, takingO(n - k)
time. - The value swap operation
p.val, q.val = q.val, p.val
takesO(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
, andq
- 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.
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
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
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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!