7.19 One-Pass Removal of k-th Node from End
7.19.1 Problem Metadata
- Platform: HackerRank
- Problem ID: one-pass-removal-kth-node-from-end
- Difficulty: Medium
- URL: https://www.hackerrank.com/challenges/one-pass-removal-kth-node-from-end
- Tags:
- Techniques: Two Pointers, Linked List
7.19.2 Description
Given the head of a singly linked lc-list and an integer k, remove the k-th node from the end in one traversal and return the new head. If k is invalid (e.g., k is 0, k is greater than the lc-list length, or the lc-list is empty), return the original lc-list.
7.19.3 Examples
Example 1:
Input:
head = [5, 6, 7, 8]
k = 3
Output:
[6, 7, 8]
Explanation:
The lc-list has 4 nodes. The k-th node from the end with k=3 is the 4th node from the beginning (value 5), which is the head. Removing it yields [6,7,8].
Example 2:
Input:
head = [5]
k = 1
Output:
[]
Explanation:
The only node in the lc-list is removed.
Example 3:
Input:
head = [1, 2]
k = 0
Output:
[1, 2]
Explanation:
k=0 is invalid, so return the original lc-list.
7.19.4 Input Format
- First line: integer n denoting the length of linked lc-list
- Next n lines: elements of the linked lc-list
- Last line: integer k
Example:
4
5
6
7
8
3
7.19.5 Constraints
- \(0 \le\) number of nodes in head \(\le 1000\)
- \(-10^9 \le\) value of each node \(\le 10^9\)
- \(0 \le k \le 10^9\)
7.19.8 Sample Output 0
5
Explanation: Single node lc-list with k=1 means remove the only node, returning empty lc-list (but the output shows “5”, suggesting the expected behavior may vary based on test case interpretation).
7.19.11 Solution - Three Pointers with Forward Search
7.19.11.1 Walkthrough
This solution uses a three-pointer approach that differs from the classic two-pointer technique. The key insight is to use right and target to locate the node to remove, then use left to find the node before it.
Algorithm Overview:
- Phase 1 - Position
rightpointer: Moverightpointer k steps ahead to create a gap of k nodes betweenrightand the start - Phase 2 - Locate target node: Move both
rightandtargettogether untilrightreaches the last node. At this point,targetpoints to the node that needs to be removed (k-th from end) - Phase 3 - Remove target node:
- If
targetis the head, returnhead.next - Otherwise, search from
left(which stayed at head) to find the node just beforetarget, then skiptarget
- If
Critical Insight: Since k is 0-indexed from the end, the algorithm correctly handles edge cases like k=0 (remove last node) and k=length-1 (remove head).
Visual Example 1: head = [5, 6, 7, 8], k = 3
Initial state:
[5] → [6] → [7] → [8] → null
^left
^target
^right
Phase 1: Move right pointer k=3 steps ahead
for i=0: right.next exists, right moves to 6
for i=1: right.next exists, right moves to 7
for i=2: right.next exists, right moves to 8
After Phase 1:
[5] → [6] → [7] → [8] → null
^left ^right
^target
Phase 2: Move right and target until right reaches last node
right.next == null? Yes, exit loop immediately
After Phase 2:
[5] → [6] → [7] → [8] → null
^left ^right
^target
Phase 3: Remove target node
left == target? Yes (both point to node 5)
Remove head: head = target.next
Result: [6] → [7] → [8] → null ✓
Visual Example 2: head = [5, 6, 7, 8], k = 1
Initial state:
[5] → [6] → [7] → [8] → null
^left/target/right
Phase 1: Move right pointer k=1 step ahead
for i=0: right.next exists, right moves to 6
After Phase 1:
[5] → [6] → [7] → [8] → null
^left ^right
^target
Phase 2: Move right and target until right reaches last node
Iteration 1: right.next (7) exists
right moves to 7, target moves to 6
Iteration 2: right.next (8) exists
right moves to 8, target moves to 7
Iteration 3: right.next is null, exit loop
After Phase 2:
[5] → [6] → [7] → [8] → null
^left ^target ^right
Phase 3: Remove target node
left == target? No (left at 5, target at 7)
Search from left to find node before target:
left.next (6) != target (7), move left to 6
left.next (7) == target, stop
Remove target: left.next = target.next
node6.next = node8
Result: [5] → [6] → [8] → null ✓
Visual Example 3: head = [1, 2], k = 0
Initial state:
[1] → [2] → null
^left/target/right
Phase 1: Move right pointer k=0 steps
for loop doesn't execute (i < 0 is false)
After Phase 1:
[1] → [2] → null
^left/target/right
Phase 2: Move right and target until right reaches last node
Iteration 1: right.next (2) exists
right moves to 2, target moves to 2
Iteration 2: right.next is null, exit loop
After Phase 2:
[1] → [2] → null
^left ^target/right
Phase 3: Remove target node
left == target? No (left at 1, target at 2)
Search from left to find node before target:
left.next (2) == target, stop
Remove target: left.next = target.next
node1.next = null
Result: [1] → null ✓
Edge Case - Invalid k: head = [1, 2], k = 5
Phase 1: Move right pointer k=5 steps
for i=0: right.next (2) exists, right moves to 2
for i=1: right.next is null, return head immediately
Result: [1] → [2] → null (unchanged) ✓
7.19.11.2 Analysis
- Time Complexity: O(n) where n is the number of nodes
- Phase 1: O(k) to move
rightpointer - Phase 2: O(n-k) to locate target node
- Phase 3: O(n) worst case to search from head to node before target
- Overall: O(n) as we traverse the lc-list at most twice
- Phase 1: O(k) to move
- Space Complexity: O(1)
- Only three pointer variables used (
left,right,target) - No additional data structures
- Only three pointer variables used (
7.19.11.3 Implementation Steps
- Null check: Return immediately if head is null
- Initialize three pointers:
left,right, andtargetall point to head - Create gap of k nodes: Move
rightpointer k steps ahead- If
right.nextbecomes null during this phase, k is invalid (greater than or equal to lc-list length), return original lc-list
- If
- Locate target node: Move both
rightandtargettogether untilrightreaches the last node (whenright.next == null) - Remove target node:
- Case 1: If
left == target, the target is the head node, returnhead.next - Case 2: Otherwise, search from
leftuntilleft.next == target, then setleft.next = target.next
- Case 1: If
- Return modified lc-list: Return the new head
7.19.11.4 Code - Java
public static SinglyLinkedListNode removeKthNodeFromEnd(SinglyLinkedListNode head, int k) {
if (head == null) {
return head;
}
SinglyLinkedListNode right = head, target = head, left = head;
// Phase 1: Move right pointer k steps ahead
for(int i = 0; i < k; i++) {
// If k is invalid (>= lc-list length), return the original lc-list
if(right.next == null) {
return head;
}
right = right.next;
}
// Phase 2: Move right and target together until right reaches last node
while(right.next != null) {
right = right.next;
target = target.next;
}
// Phase 3: Remove the target node
if(left == target) {
// Target is the head node
head = target.next;
} else {
// Find the node before target
while(left.next != target) {
left = left.next;
}
// Skip the target node
left.next = target.next;
}
return head;
}