A Linked List is a data structure that stores a list of items where each element, which is called a Node, is an object that stores a value and also a pointer to the next Node in the list.

• No fixed size
• Adding an element to the front takes O(1) time
• Removing an element to the front takes O(1) time

### Downsides of a linked list

• It will take O(n) runtime to access a value by the index since we must start from the head and traverse until we find the desired index.
• Searching for an element will take O(n) runtime as we would have to start from the head and traverse the entire list.

### Node Object

value Value stored by this node
next Pointer to next node in the list

Given:

Input: 1 => 2 => 3 => 4 => NULL

4 => 3 => 2 => 1 => NULL


Reverse the pointers on each node so that each node points to the next node. To do this you’ll need 3 temporary pointers prev, current, next. We will traverse the list and set the current.next = prev each step. We require 3 temporary pointers because otherwise, we won’t be able to track the next Node in the list since we are redirecting the next pointers of current.

### Contiguous memory

Arrays require a block of contiguous memory where as linked list do not. This results in O(1) lookups for arrays but O(n) for linked lists.

### Time complexity

Insertion/Deletion at the beginning of the array or linked list given a value O(n) O(1)
Access Element O(1) O(n)
Contiguous Memory Yes No
• Array Insertion for an array is O(n) because all elements needed to be shifted to the right.
• Linked list Insertion at the HEAD is O(1) because all you have to do is update the pointer.

• Every node as a pointer to the next node and a pointer to the previous node.

class Node:
def __init__(self, item, next=None):
self.item = item
self.next = next

def __init__(self):
pass

def __str__(self):
pass

def insertFront(self, item):
pass

def insertLast(self, item):
pass

def removeBeginning(self):
pass


class Node:
def __init__(self, data):
self.data = data
self.next = None

def __init__(self):

def append(self, data):
new_node = Node(data)
return
# start from the head pointer
# iterate through each node starting from head until we hit the
# last_node which is none
while last_node.next:
last_node = last_node.next
# once there insert new_node
last_node.next = new_node

def prepend(self, data):
new_node = Node(data)

def insert_after_node(self, prev_node, data):
if not prev_node:
print("Previous node does not exist.")
return
new_node = Node(data)
new_node.next = prev_node.next
prev_node.next = new_node

def print_list(self):
# similar to append, we start from head
while cur_node:
print(cur_node.data)
cur_node = cur_node.next

def delete_node(self, key):

if cur_node and cur_node.data == key:
cur_node = None
return

prev = None
while cur_node and cur_node.data != key:
prev = cur_node
cur_node = cur_node.next

if cur_node is None:
return

prev.next = cur_node.next
cur_node = None

if __name__ == "__main__":