Doubly Linked Lists
Let's use Design Linked List to design our Doubly Linked List
.
from typing import Generic, Optional, TypeVar
T = TypeVar("T")
class ListNode(Generic[T]):
def __init__(
self,
val: T,
prev_ptr: Optional["ListNode"] = None,
next_ptr: Optional["ListNode"] = None,
) -> None:
self.val = val
self.prev = prev_ptr
self.next = next_ptr
class MyLinkedList:
def __init__(self):
self.size = 0
self.head = ListNode(-1)
self.tail = ListNode(-1)
self.head.next = self.tail
self.tail.prev = self.head
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
return self._getNodeAtIndex(index + 1).val
def addAtHead(self, val: int) -> None:
self.addAtIndex(0, val)
def addAtTail(self, val: int) -> None:
self.addAtIndex(self.size, val)
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.size:
return
p = self._getNodeAtIndex(index)
n = p.next
node = ListNode(val)
node.prev = p
node.next = n
p.next = node
n.prev = node
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
p = self._getNodeAtIndex(index)
n = p.next.next
p.next = n
n.prev = p
self.size -= 1
def _getNodeAtIndex(self, index: int) -> ListNode[int]:
p = self.head
for _ in range(index):
p = p.next
return p
As for the Singly Linked List,
besides the dummy head
node we need the dummy tail
as well.
For the same reason,
we can use such nodes to avoid to write code to handle edge cases.
Except for addAtHead
, which is for the time complexity,
all the other operations are .
The space complexity is for each operation,
however, after operations we might end up having a list of elements
(assuming that they are all valid consecutive insert
operations).