Design Linked List (Full Implementation)
Initialize dummy head, tail, and size
Create a dummy head node with value 0 and no next node. Set tail pointer to dummy and size counter to 0.
self.dummy = Node(0)
self.tail = self.dummy
self.size = 0addAtHead(1): create new node with val=1
Create a new node with value 1, pointing its next to dummy.next (currently null).
newNode = Node(val, self.dummy.next)addAtHead(1): link new node after dummy
Set dummy.next to point to the new node, inserting it at the head of the list.
self.dummy.next = newNodeaddAtHead(1): update tail if list was empty
Since size was 0, update tail pointer to the new node, now the last node in the list.
if self.size == 0:
self.tail = newNodeaddAtHead(1): increment size
Increase the size counter by 1 to reflect the new node added.
self.size += 1addAtTail(3): create new node with val=3
Create a new node with value 3 and next pointer null, ready to append at tail.
newNode = Node(val)addAtTail(3): link new node after tail
Set current tail's next pointer to new node, appending it at the end of the list.
self.tail.next = newNodeaddAtTail(3): update tail pointer to new node
Move tail pointer to the new node, which is now the last node in the list.
self.tail = newNodeaddAtTail(3): increment size
Increase size counter by 1 to reflect the new node added at tail.
self.size += 1addAtIndex(1, 2): check index validity
Verify index 1 is valid (0 <= 1 <= size 2), so insertion proceeds.
if index > self.size or index < 0:
returnaddAtIndex(1, 2): traverse to node before index
Start from dummy and move one step to node with val=1, which is before index 1.
prev = self.dummy
for _ in range(index):
prev = prev.nextaddAtIndex(1, 2): create new node with val=2
Create new node with value 2, pointing its next to prev.next (node with val=3).
newNode = Node(val, prev.next)addAtIndex(1, 2): link new node after prev
Set prev.next to new node, inserting it between nodes 1 and 3.
prev.next = newNodeaddAtIndex(1, 2): increment size
Increase size counter by 1 to reflect the new node added at index 1.
self.size += 1get(1): validate index
Check if index 1 is valid (0 <= 1 < size 3), so proceed to get value.
if index < 0 or index >= self.size:
return -1get(1): traverse to node at index
Start from dummy.next (node 1), move one step to node 2 at index 1.
curr = self.dummy.next
for _ in range(index):
curr = curr.nextget(1): return value of current node
Return the value of the node at index 1, which is 2.
return curr.valdeleteAtIndex(1): validate index
Check if index 1 is valid (0 <= 1 < size 3), so proceed to delete.
if index < 0 or index >= self.size:
returndeleteAtIndex(1): traverse to node before index
Start from dummy and move one step to node 1, which is before the node to delete.
prev = self.dummy
for _ in range(index):
prev = prev.nextdeleteAtIndex(1): unlink node and update tail if needed
Set prev.next to node after to_delete (node 3), unlinking node 2. Since deleted node is not tail, tail remains unchanged.
to_delete = prev.next
prev.next = to_delete.next
if to_delete == self.tail:
self.tail = prevdeleteAtIndex(1): decrement size
Decrease size counter by 1 to reflect node removal.
self.size -= 1get(1): validate index
Check if index 1 is valid (0 <= 1 < size 2), so proceed to get value.
if index < 0 or index >= self.size:
return -1get(1): traverse to node at index
Start from dummy.next (node 1), move one step to node 3 at index 1.
curr = self.dummy.next
for _ in range(index):
curr = curr.nextget(1): return value of current node
Return the value of the node at index 1, which is 3.
return curr.valclass Node:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy = Node(0) # STEP 1
self.tail = self.dummy # STEP 1
self.size = 0 # STEP 1
def get(self, index: int) -> int:
if index < 0 or index >= self.size: # STEP 15,22
return -1
curr = self.dummy.next # STEP 16,23
for _ in range(index):
curr = curr.next # STEP 16,23
return curr.val # STEP 17,24
def addAtHead(self, val: int) -> None:
newNode = Node(val, self.dummy.next) # STEP 2
self.dummy.next = newNode # STEP 3
if self.size == 0: # STEP 4
self.tail = newNode
self.size += 1 # STEP 5
def addAtTail(self, val: int) -> None:
newNode = Node(val) # STEP 6
self.tail.next = newNode # STEP 7
self.tail = newNode # STEP 8
self.size += 1 # STEP 9
def addAtIndex(self, index: int, val: int) -> None:
if index > self.size or index < 0: # STEP 10
return
if index == self.size:
self.addAtTail(val)
return
prev = self.dummy # STEP 11
for _ in range(index):
prev = prev.next # STEP 11
newNode = Node(val, prev.next) # STEP 12
prev.next = newNode # STEP 13
self.size += 1 # STEP 14
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size: # STEP 18
return
prev = self.dummy # STEP 19
for _ in range(index):
prev = prev.next # STEP 19
to_delete = prev.next
prev.next = to_delete.next # STEP 20
if to_delete == self.tail:
self.tail = prev
self.size -= 1 # STEP 21
Key Takeaways
Without dummy, special code is needed for head operations; dummy unifies logic.
Without tail pointer, adding at tail requires traversing entire list, which is inefficient.
Size check quickly rejects invalid indices, saving time and avoiding errors.
