Given the head
of a linked list, remove the nth
node from the end of the list and return its head.
Example 1:
Input: head = [1,2,3,4,5], n = 2 Output: [1,2,3,5]
Example 2:
Input: head = [1], n = 1 Output: []
Example 3:
Input: head = [1,2], n = 1 Output: [1]
Constraints:
sz
.1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
Follow up: Could you do this in one pass?
The core challenge of this problem is to remove the nth node from the end of a singly linked list in an efficient manner. This problem is significant in various applications such as managing tasks, memory management, and more. A common pitfall is misunderstanding the position of the nth node from the end, especially in edge cases like when the list has only one node.
To solve this problem, we can use a two-pointer technique to achieve the solution in one pass. Here's a step-by-step approach:
The naive solution involves two passes through the list. In the first pass, we count the total number of nodes. In the second pass, we remove the (total - n + 1)th node from the beginning. This approach is not optimal as it requires two passes.
The optimized solution uses two pointers, `fast` and `slow`. The `fast` pointer is moved n steps ahead first. Then, both `fast` and `slow` pointers are moved one step at a time until `fast` reaches the end. At this point, the `slow` pointer will be at the (n+1)th node from the end, and we can easily remove the nth node.
Here is a step-by-step breakdown of the optimized algorithm:
#include <iostream>
using namespace std;
// Definition for singly-linked list.
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// Create a dummy node to handle edge cases easily
ListNode* dummy = new ListNode(0, head);
ListNode* fast = dummy;
ListNode* slow = dummy;
// Move fast pointer n steps ahead
for (int i = 0; i < n; ++i) {
fast = fast->next;
}
// Move both pointers until fast reaches the end
while (fast->next != nullptr) {
fast = fast->next;
slow = slow->next;
}
// Remove the nth node from the end
ListNode* nodeToRemove = slow->next;
slow->next = slow->next->next;
delete nodeToRemove;
// Return the head of the modified list
ListNode* newHead = dummy->next;
delete dummy;
return newHead;
}
};
// Helper function to print the list
void printList(ListNode* head) {
while (head != nullptr) {
cout << head->val << " ";
head = head->next;
}
cout << endl;
}
// Main function to test the solution
int main() {
Solution solution;
// Example 1
ListNode* head1 = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5)))));
int n1 = 2;
ListNode* result1 = solution.removeNthFromEnd(head1, n1);
printList(result1); // Output: 1 2 3 5
// Example 2
ListNode* head2 = new ListNode(1);
int n2 = 1;
ListNode* result2 = solution.removeNthFromEnd(head2, n2);
printList(result2); // Output: (empty list)
// Example 3
ListNode* head3 = new ListNode(1, new ListNode(2));
int n3 = 1;
ListNode* result3 = solution.removeNthFromEnd(head3, n3);
printList(result3); // Output: 1
return 0;
}
The time complexity of the optimized solution is O(n), where n is the number of nodes in the list. This is because we traverse the list only once. The space complexity is O(1) as we are using only a constant amount of extra space.
Some potential edge cases include:
Each of these cases is handled by the algorithm as described.
To test the solution comprehensively, consider the following test cases:
Using a variety of test cases ensures that the solution handles all possible scenarios.
When approaching such problems, consider the following tips:
In this blog post, we discussed how to remove the nth node from the end of a linked list efficiently using a two-pointer technique. We covered the problem definition, approach, algorithm, code implementation, complexity analysis, edge cases, and testing. Understanding and solving such problems is crucial for improving problem-solving skills and preparing for technical interviews.
For further reading and practice, consider the following resources: