707. Design Linked List
On LeetCode ->Problem¶
Implement MyLinkedList, a zero-indexed linked list supporting:
get(index): return the value, or-1for an invalid index.addAtHead(val)andaddAtTail(val): insert at either end.addAtIndex(index, val): insert beforeindex; allowindex == length, but ignore larger indices.deleteAtIndex(index): delete a valid index; otherwise do nothing.
Operation State Return
addAtHead(1) [1] None
addAtTail(3) [1, 3] None
addAtIndex(1, 2) [1, 2, 3] None
get(1) [1, 2, 3] 2
deleteAtIndex(1) [1, 3] None
get(1) [1, 3] 3
class MyLinkedList:
def __init__(self):
pass
def get(self, index: int) -> int:
pass
def addAtHead(self, val: int) -> None:
pass
def addAtTail(self, val: int) -> None:
pass
def addAtIndex(self, index: int, val: int) -> None:
pass
def deleteAtIndex(self, index: int) -> None:
pass
Key trick¶
Use a permanent dummy head to make every insertion/deletion a change to a predecessor's next; maintain a tail pointer for constant-time appending.
Trap¶
Insertion accepts index == length, but lookup/deletion do not; every operation must preserve the tail pointer, including transitions to and from an empty list.
Why is it interesting?¶
It tests pointer invariants and boundary handling rather than a complicated algorithm.
Python solution¶
class MyLinkedList:
class Node:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def __init__(self):
self.head = self.Node() # Permanent dummy; not a list element.
self.tail = self.head # Empty lists end at the dummy.
self.length = 0
def _prev(self, index: int):
# Caller guarantees 0 <= index <= length.
if index == self.length:
return self.tail
node = self.head
for _ in range(index):
node = node.next
return node
def get(self, index: int) -> int:
if not 0 <= index < self.length:
return -1
return self._prev(index).next.val
def addAtHead(self, val: int) -> None:
self.addAtIndex(0, val)
def addAtTail(self, val: int) -> None:
self.addAtIndex(self.length, val)
def addAtIndex(self, index: int, val: int) -> None:
if not 0 <= index <= self.length:
return
prev = self._prev(index)
node = self.Node(val, prev.next)
prev.next = node
if index == self.length:
self.tail = node
self.length += 1
def deleteAtIndex(self, index: int) -> None:
if not 0 <= index < self.length:
return
prev = self._prev(index)
node = prev.next
prev.next = node.next
if node is self.tail:
self.tail = prev # Becomes the dummy when deleting the sole node.
self.length -= 1
Time: addAtHead and addAtTail take \(O(1)\); indexed operations take \(O(n)\) worst case. Insertion at index \(n\) and deletion at index \(0\) take \(O(1)\).
Space: \(O(n)\) storage, with \(O(1)\) auxiliary space per operation.
Comment on my solution¶
- Your predecessor-based insertion/deletion approach is sound, and appending is already \(O(1)\).
addAtIndexnever updatesself.tail. Inserting into an empty list leaves itNone; inserting at the end leaves it pointing to the previous last node.- Deleting the sole node leaves
self.tailpointing to a temporary dummy rather thanNone. Your subsequent insertions sometimes repair this, but the empty-list invariant is broken.
# d . . . .
# d
# . . . .
# d
# . . . .
# d
# . . . .
# WRONG
# Runtime Error: 12/52 testcases passed
class MyLinkedList:
class Node:
def __init__(self, val, next=None):
self.val = val
self.next = next
def __init__(self):
self.length = 0
self.head = None
self.tail = None
def get(self, index: int) -> int:
if index >= self.length:
return -1
node = self.head
for _ in range(index):
node = node.next
return node.val
def addAtHead(self, val: int) -> None:
self.head = self.Node(val, self.head)
self.length += 1
if self.length == 1:
self.tail = self.head
def addAtTail(self, val: int) -> None:
tail = self.Node(val)
self.length += 1
if self.length == 1:
self.head = tail
self.tail = tail
else:
self.tail.next = tail
self.tail = tail
def addAtIndex(self, index: int, val: int) -> None:
if index > self.length:
return
dummy = self.Node(0, self.head)
node = dummy
for _ in range(index):
node = node.next
nxt = node.next
node.next = self.Node(val, nxt)
self.head = dummy.next
self.length += 1
def deleteAtIndex(self, index: int) -> None:
if index >= self.length:
return
dummy = self.Node(0, self.head)
node = dummy
for _ in range(index):
node = node.next
node.next = node.next.next
self.head = dummy.next
if index == self.length - 1:
self.tail = node
self.length -= 1
# WORKS
# Written after reading AI comments
# I just had to add this to addAtTail()
# if index == self.length - 1:
# self.tail = node.next
class MyLinkedList:
class Node:
def __init__(self, val, next=None):
self.val = val
self.next = next
def __init__(self):
self.length = 0
self.head = None
self.tail = None
def get(self, index: int) -> int:
if index >= self.length:
return -1
node = self.head
for _ in range(index):
node = node.next
return node.val
def addAtHead(self, val: int) -> None:
self.head = self.Node(val, self.head)
self.length += 1
if self.length == 1:
self.tail = self.head
def addAtTail(self, val: int) -> None:
tail = self.Node(val)
self.length += 1
if self.length == 1:
self.head = tail
self.tail = tail
else:
self.tail.next = tail
self.tail = tail
def addAtIndex(self, index: int, val: int) -> None:
if index > self.length:
return
dummy = self.Node(0, self.head)
node = dummy
for _ in range(index):
node = node.next
nxt = node.next
node.next = self.Node(val, nxt)
self.length += 1
self.head = dummy.next
if index == self.length - 1:
self.tail = node.next
def deleteAtIndex(self, index: int) -> None:
if index >= self.length:
return
dummy = self.Node(0, self.head)
node = dummy
for _ in range(index):
node = node.next
node.next = node.next.next
self.head = dummy.next
if index == self.length - 1:
self.tail = node
self.length -= 1