19. Remove Nth Node From End of List

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:

  • The number of nodes in the list is sz.
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

Problem Description

The problem presents us with a linked list and requires us to remove the nth node from the end of the list. A linked list is a linear collection of elements called nodes, where each node contains data and a reference (or link) to the next node in the sequence. The head of a linked list refers to the first node in this sequence. Our goal is to remove the nth node from the last node and return the updated list’s head.

In simple terms, if we were to count nodes starting from the last node back to the first, we need to find and remove the node that sits at position ‘n’. For example, if the linked list is 1->2->3->4->5 and n is 2, the resulting linked list after the operation should be 1->2->3->5, since the 4 is the 2nd node from the end.

The challenge with this task is that we can’t navigate backwards in a singly linked list, so we need to figure out a way to reach the nth node from the end only by moving forward.

Intuition

To address the problem, we apply a two-pointer approach, which is a common technique in linked list problems. The intuition here lies in maintaining two pointers that we’ll call fast and slow. Initially, both of these pointers will start at a dummy node that we create at the beginning of the list, which is an auxiliary node to simplify edge cases, such as when the list contains only one node, or when we need to remove the first node of the list.

The fast pointer advances n nodes into the list first. Then, both slow and fast pointers move at the same pace until fast reaches the end of the list. At this point, slow will be right before the node we want to remove. By updating the next reference of the slow pointer to skip the target node, we effectively remove the nth node from the end of the list.

The use of a dummy node is a clever trick to avoid having to separately handle the special case where the node to remove is the first node of the list. By ensuring that our slow pointer starts before the head of the actual list, we can use the same logic for removing the nth node, regardless of its position in the list.

After we’ve completed the removal, we return dummy.next, which is the updated list’s head, after the dummy node.

Solution Approach

The solution implements an efficient approach to solving the problem by using two pointers and a dummy node. Here’s a step-by-step walkthrough of the implementation:

  1. Create a Dummy Node: A dummy node is created and its next pointer is set to the head of the list. This dummy node serves as an anchor and helps to simplify the removal process, especially if the head needs to be removed.dummy = ListNode(next=head)
  2. Initialize Two Pointers: Two pointers fast and slow are introduced and both are set to the dummy node. This allows for them to start from the very beginning of the augmented list (which includes the dummy node).fast = slow = dummy
  3. Advance Fast Pointer: The fast pointer advances n steps into the list.for _ in range(n): fast = fast.next
  4. Move Both Pointers: Both pointers then move together one step at a time until the fast pointer reaches the end of the list. By this time, slow is at the position right before the nth node from the end.while fast.next: slow, fast = slow.next, fast.next
  5. Remove the Target Node: Once slow is in position, its next pointer is changed to skip the target node which effectively removes it from the list.slow.next = slow.next.next
  6. Return Updated List: Finally, the next node after the dummy is returned as the new head of the updated list.return dummy.next

The algorithm fundamentally relies on the two-pointer technique to find the nth node from the end. The use of a dummy node facilitates the removal of nodes, including the edge case of the head node, with a consistent method. The space complexity is O(1) since no additional space is required aside from the pointers, and the time complexity is O(L), where L is the length of the list, as we traverse the list with two pointers in a single pass.

Example Walkthrough

Let’s use the linked list 1->2->3->4->5 as an example to illustrate the solution approach described above, where n is 2, meaning we want to remove the 2nd node from the end, which is the node with the value 4. Here’s how the algorithm would be applied:

  1. Create a Dummy Node: We create a dummy node with None as its value and link it to the head of our list. Our list now looks like dummy->1->2->3->4->5.
  2. Initialize Two Pointers: We set both fast and slow pointers to the dummy node. They both point to dummy initially.
  3. Advance Fast Pointer: As n is 2, we advance the fast pointer n steps into the list. fast becomes:
    • After 1st step: fast points to 1
    • After 2nd step: fast points to 2
  4. Move Both Pointers: Now we move both slow and fast pointers together one step at a time until fast reaches the last node:
    • slow points to dummy and fast points to 2 (Start)
    • slow points to 1 and fast points to 3
    • slow points to 2 and fast points to 4
    • slow points to 3 and fast points to 5 (End)
    At the end of this step, slow points to the 3 and fast points to the last node 5.
  5. Remove the Target Node: Now, slow.next is pointing to the node 4, which we want to remove. We update slow.next to point to slow.next.next (which is 5), effectively removing the 4 from the list.
  6. Return Updated List: Finally, we return the next node after the dummy, which is the head of our updated list (1->2->3->5). The updated list after removal is as follows: 1->2->3->5.

The problem is solved in one pass and the node is successfully removed according to the given constraints.

Solution Implementation


class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *prev;
        ListNode *dummy = new ListNode();

        dummy->next = head;
        prev = dummy;
        int t = 0;
        while(prev != NULL){
            prev = prev->next;
            t++;
        }
        if(t == 1){
            head = NULL;
            return head;
        }
        prev = dummy;
        t = t - n;
        for(int i = 0; i<t-1; i++){
            prev = prev->next;
        }
        prev->next = prev->next->next;

        return dummy->next;
    }
};
        
Copied!

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
// Creating a dummy node to simplify the removal of head
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        ListNode slow = dummy;
        ListNode fast = dummy;
        
        // 1. Move fast n+1 step forward so that slow is in front of the node being deleted
        for (int i = 0; i <= n; i++) {
            fast = fast.next;
        }
        
        // 2. Move fast to the end, and slow to the node before the deleted one
        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
        }
        
        // 3. Remove the nth node from the end (slow.next points to it)
slow.next = slow.next.next;
        
        // Returning a new head (dummy.next could have changed if the head was deleted)
        return dummy.next;
    }
}
        
Copied!

class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        count = 0
        curr = head
        dummy = curr

        while curr:
            count += 1
            curr = curr.next
        # if count == 1 and n == 1:
        #     return None

        if count == n:
            head = head.next

        remove = count - n
        count = 0
        while dummy:
            count +=1
            if count == remove:
                dummy.next = dummy.next.next
                break
            dummy = dummy.next

        return head
        
Copied!

struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
    struct ListNode* temp=head;
    int count=0;
    while(temp!=NULL){
        temp=temp->next;
        count=count+1;

    }
     if (count == n) {
        struct ListNode* newHead = head->next;
        free(head);
        return newHead;
    }
    struct ListNode* tempa=head;
    for(int i=0;i<count-n-1;i++){
        tempa=tempa->next;

    }
    struct ListNode* tem=tempa->next;
     tempa->next=tempa->next->next;
    free(tem);
   
   return head;
}
        
Copied!

public class Solution {
    public ListNode RemoveNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        ListNode fast = dummy;
        ListNode slow = dummy;

        // Move fast pointer n+1 steps ahead
        for (int i = 0; i <= n; i++) {
            fast = fast.next;
        }

        // Move both pointers until fast reaches the end
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }

        // Remove the nth node from the end
        slow.next = slow.next.next;

        return dummy.next;

    }
}
        
Copied!

var removeNthFromEnd = function(head, n) {
    let fast = head, slow = head
    for (let i = 0; i < n; i++) fast = fast.next
    if (!fast) return head.next
    while (fast.next) fast = fast.next, slow = slow.next
    slow.next = slow.next.next
    return head
};
        
Copied!

// We assume ListNode is provided by the environment and should not be redefined.

function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
    // Create a dummy node to simplify edge cases
    const dummy = new ListNode(0);
    dummy.next = head;
    let first: ListNode | null = dummy;
    let second: ListNode | null = dummy;

    // Move the first pointer n+1 steps ahead
    for (let i = 0; i <= n; i++) {
        first = first?.next ?? null;
    }

    // Move both pointers until the first pointer reaches the end
    while (first !== null) {
        first = first.next;
        second = second?.next ?? null;
    }

    // Remove the nth node from the end
    second!.next = second?.next?.next ?? null;

    // Return the new head of the list
    return dummy.next;
}
        
Copied!

class Solution {

    /**
     * @param ListNode $head
     * @param Integer $n
     * @return ListNode
     */
    function removeNthFromEnd($head, $n) {
        $dummy = new ListNode(0, $head);
        $first = $dummy;
        $second = $dummy;

        for ($i = 0; $i <= $n; $i++) {
            $first = $first->next;
        }
        
        while ($first !== null) {
            $first = $first->next;
            $second = $second->next;
        }
        $second->next = $second->next->next;

        return $dummy->next;        
    }
}
        
Copied!

class Solution {
    func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? {
        let node = ListNode(0)
        node.next = head
        
        var prev: ListNode? = node
        var post: ListNode? = node
        
        for _ in 0..<n {
            guard let next = post?.next else { continue }
            post = next
        }
        
        while let postNext = post?.next, let prevNext = prev?.next {
            prev = prevNext
            post = postNext
        }
        
        prev!.next = prev!.next!.next
        
        return node.next
    }
}
        
Copied!

class Solution {
    fun removeNthFromEnd(head: ListNode, n: Int): ListNode? {
        head0.next = head
        var node: ListNode? = head0
        var steps = getNodeCount(head0) - n
        while (steps > 1) {
            steps--
            node = node?.next
        }
        node?.next = node?.next?.next
        return head0.next
    }

    private fun getNodeCount(head: ListNode): Int {
        var count = 0
        var node: ListNode? = head
        while (node != null) {
            node = node.next
            count++
        }
        return count
    }

    companion object {
        val head0 = ListNode(0)
    }
}
        
Copied!

class Solution {
ListNode? removeNthFromEnd(ListNode? head, int n) {
  final dummy = ListNode(0, head);
  ListNode? left = dummy;
  ListNode? right = head;

  while(n > 0 && right != null) {
    right = right.next;
    n--;
  }
  while(right != null) {
    right = right.next;
    left = left?.next;
  }
  left?.next = left.next?.next;
  return dummy.next;
}
}
        
Copied!

func removeNthFromEnd(head *ListNode, n int) *ListNode {
    // Создаем фиктивный узел перед головным узлом
    dummy := &ListNode{0, head}
    slow, fast := dummy, dummy

    // Перемещаем fast на n+1 шаг вперед
    for i := 0; i <= n; i++ {
        fast = fast.Next
    }

    // Двигаем slow и fast одновременно
    for fast != nil {
        slow = slow.Next
        fast = fast.Next
    }

    // Удаляем узел, на который указывает slow.Next
    slow.Next = slow.Next.Next

    // Возвращаем обновленный список, пропуская фиктивный узел
    return dummy.Next
}
        
Copied!

Time and Space Complexity

The given code is designed to remove the nth node from the end of a singly linked list. It employs the two-pointer technique with fast and slow pointers. Let’s analyze the time and space complexity:

Time Complexity

The time complexity is determined by the number of operations required to traverse the linked list. There are two main steps in this algorithm:

  1. The fast pointer moves n steps ahead – this takes O(n) time.
  2. Both fast and slow pointers move together until fast reaches the last node. The worst-case scenario is when n is 1, which would cause the slow pointer to traverse the entire list of size L (list length), taking O(L) time.

Since the list is traversed at most once, the overall time complexity is O(L).

Space Complexity

The space complexity is determined by the amount of additional space used by the algorithm, which does not depend on the input size:

  • The dummy node and the pointers (fast and slow) use constant extra space, regardless of the linked list’s size.
  • Therefore, the space complexity is O(1) for the extra space used by the pointers and the dummy node.

One thought on “19. Remove Nth Node From End of List

Leave a Reply

Your email address will not be published. Required fields are marked *