https://leetcode.com/problems/design-linked-list/

Solution

from typing import Optional, Self
 
 
class Node:
    def __init__(self, val: int = 0, next: Optional[Self] = None):
        self.val: int = val
        self.next: Optional[Self] = next
 
 
class MyLinkedList(object):
 
    def __init__(self, head: Optional[Node] = None):
        self.head: Optional[Node] = head
 
    def get(self, index: int) -> int:
        curr = self.head
 
        for _ in range(index):
            if curr is None:
                return -1
            curr = curr.next
 
        if curr is None:
            return -1
 
        return curr.val
 
    def addAtHead(self, val: int):
        self.head = Node(val, self.head)
        
 
    def addAtTail(self, val: int):
        if self.head is None:
            self.addAtHead(val)
            return
 
        curr = self.head
        while curr.next is not None:
            curr = curr.next
 
        curr.next = Node(val)
        
 
    def addAtIndex(self, index: int, val: int):
        if index == 0:  # CASE(index): Index points to head
            self.addAtHead(val)
            return
        # INVARIANT(index): Index points to middle, tail, or out of bounds
 
        # CASE(list): List is empty
        # NOTE: Index does not point to head, and list is empty.
        # Therefore, index is out of bound
        if self.head is None:
            return
        # INVARIANT(list): List is not empty
 
        # NOTE: We need to hold two references to nodes in the list to do the insert:
        # R1. Node before the node to insert
        # R2. Node after the node to insert
        # Just R1 is sufficient because it holds R2
        prev = self.head
        for _ in range(index - 1):
            # CASE(index): Index out of bounds
            if prev.next is None:
                return 
            prev = prev.next
        # INVARIANT(index): Index points to middle or tail
 
        # ASSUMPTION: prev must not be None
        prev.next = Node(val, prev.next)
        
 
    def deleteAtIndex(self, index: int):
        if self.head is None:  # CASE(list): List is empty. NOTE: Therefore any index is invalid
            return
        if index == 0:  # CASE(index): Index points to head
            self.head = self.head.next
            return
        # INVARIANT(list): List is not empty
        # INVARIANT(index): Index >= 1 and points to middle, tail, or out of bounds
 
        prev = self.head
        for _ in range(index - 1):
            # CASE(index): Index out of bounds
            if prev.next is None:
                return
            prev = prev.next
        # INVARIANT(index): Index points to middle or tail
 
        # NOTE: prev.next is the node we want to remove
        if prev.next is None:
            return
 
        # ASSUMPTION: prev.next must not be None
        prev.next = prev.next.next