🧠
One Pass with Two Pointers (Fast and Slow)
💡 This approach introduces the fast-slow pointer technique, a fundamental pattern in linked list problems. It reduces the traversal to one pass, improving efficiency.
Intuition
Move a fast pointer n steps ahead first. Then move both fast and slow pointers together until fast reaches the end. Slow will then point to the nth node from the end.
Algorithm
- Initialize two pointers, fast and slow, at the head.
- Move fast pointer n steps ahead.
- Move both pointers forward until fast reaches the end.
- Slow pointer now points to the nth node from the end; return its value.
💡 The key is maintaining the gap of n between fast and slow pointers to find the target in one pass.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def nth_from_end(head, n):
fast = slow = head
for _ in range(n):
if not fast:
return None
fast = fast.next
while fast:
fast = fast.next
slow = slow.next
return slow.val if slow else None
# Example usage:
if __name__ == '__main__':
head = ListNode(10, ListNode(20, ListNode(30, ListNode(40, ListNode(50)))))
print(nth_from_end(head, 2)) # Output: 40
Line Notes
fast = slow = headInitialize both pointers at the start
for _ in range(n):Advance fast pointer n steps ahead
if not fast:Check if n is larger than list length
while fast:Move both pointers until fast reaches the end
class ListNode {
int val;
ListNode next;
ListNode(int val) { this.val = val; }
}
public class Solution {
public static Integer nthFromEnd(ListNode head, int n) {
ListNode fast = head, slow = head;
for (int i = 0; i < n; i++) {
if (fast == null) return null;
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow != null ? slow.val : null;
}
public static void main(String[] args) {
ListNode head = new ListNode(10);
head.next = new ListNode(20);
head.next.next = new ListNode(30);
head.next.next.next = new ListNode(40);
head.next.next.next.next = new ListNode(50);
System.out.println(nthFromEnd(head, 2)); // Output: 40
}
}
Line Notes
ListNode fast = head, slow = head;Initialize two pointers at head
for (int i = 0; i < n; i++)Move fast pointer n steps ahead
if (fast == null) return null;Check if n exceeds list length
while (fast != null)Move both pointers until fast reaches end
#include <iostream>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
int nthFromEnd(ListNode* head, int n) {
ListNode* fast = head;
ListNode* slow = head;
for (int i = 0; i < n; i++) {
if (!fast) return -1;
fast = fast->next;
}
while (fast) {
fast = fast->next;
slow = slow->next;
}
return slow ? slow->val : -1;
}
int main() {
ListNode* head = new ListNode(10);
head->next = new ListNode(20);
head->next->next = new ListNode(30);
head->next->next->next = new ListNode(40);
head->next->next->next->next = new ListNode(50);
cout << nthFromEnd(head, 2) << endl; // Output: 40
return 0;
}
Line Notes
ListNode* fast = head;Initialize fast pointer at head
for (int i = 0; i < n; i++)Advance fast pointer n steps
if (!fast) return -1;Return sentinel if n is invalid
while (fast)Move both pointers until fast reaches end
class ListNode {
constructor(val = 0, next = null) {
this.val = val;
this.next = next;
}
}
function nthFromEnd(head, n) {
let fast = head, slow = head;
for (let i = 0; i < n; i++) {
if (!fast) return null;
fast = fast.next;
}
while (fast) {
fast = fast.next;
slow = slow.next;
}
return slow ? slow.val : null;
}
// Example usage:
const head = new ListNode(10, new ListNode(20, new ListNode(30, new ListNode(40, new ListNode(50)))));
console.log(nthFromEnd(head, 2)); // Output: 40
Line Notes
let fast = head, slow = head;Initialize two pointers at head
for (let i = 0; i < n; i++)Advance fast pointer n steps ahead
if (!fast) return null;Check if n is larger than list length
while (fast)Move both pointers until fast reaches the end
Only one traversal of the list is needed, moving pointers in a single pass.
💡 For n=20, this means walking the list once (20 steps), which is twice as fast as the brute force.
Interview Verdict: Accepted and optimal for time
This is the preferred approach in interviews due to its efficiency and elegance.