Doubly Linked List
Doubly Linked List: A Doubly Linked List is a data structure that consists of a sequence of nodes, where each node contains a value and two pointers: one pointing to the previous node and one pointing to the next node. This allows for efficient traversal in both directions, forward and backward.
We will implement a basic Doubly Linked List with the following operations:
- Insertion at the beginning
- Insertion at the end
- Deletion from the beginning
- Deletion from the end
- Displaying the elements of the list
Here's an example:
class Node:
def __init__(self, data):
self.data = data
self.prev = None
self.next = None
class Stack:
def __init__(self):
self.head = None
self.tail = None
def push(self, data):
# Create a new node
new_node = Node(data)
if not self.head:
# If stack is empty, set new node as head and tail
self.head = new_node
self.tail = new_node
else:
# Add new node at the top of the stack
new_node.prev = self.tail
self.tail.next = new_node
self.tail = new_node
def pop(self):
if not self.head:
# Stack is empty
return None
# Remove the top node from the stack
popped_node = self.tail
if self.head == self.tail:
# Stack has only one node
self.head = None
self.tail = None
else:
# Update the tail to the previous node
self.tail = self.tail.prev
self.tail.next = None
return popped_node.data
def peek(self):
if not self.head:
# Stack is empty
return None
# Return the data of the top node
return self.tail.data
# Test the Stack class
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print("Peek:", stack.peek()) # Output: 3
print("Pop:", stack.pop()) # Output: 3
print("Pop:", stack.pop()) # Output: 2
print("Peek:", stack.peek()) # Output: 1
In this example, we create a DoublyLinkedList object, insert nodes at the beginning and end, delete nodes from the beginning and end, and display the elements of the list. The output demonstrates the functionality of the Doubly Linked List.
Now, let's solve a question from LeetCode using the Doubly Linked List concept.
Question: LeetCode #707 - Design Linked List Implement a singly linked list with the following operations:
get(index)
: Get the value of the index-th node in the linked list. If the index is invalid, return -1.addAtHead(val)
: Add a node of value val at the head of the linked list.addAtTail(val)
: Append a node of value val to the last element of the linked list.addAtIndex(index, val)
: Add a node of value val before the index-th node in the linked list. If index equals the length of the linked list, the node will be appended to the end. If the index is greater than the length, do nothing.deleteAtIndex(index)
: Delete the index-th node in the linked list, if the index is valid.
Here's the implementation:
class Node:
def __init__(self, char):
self.char = char
self.prev = None
self.next = None
class Stack:
def __init__(self):
self.head = None
self.tail = None
def push(self, char):
new_node = Node(char)
if not self.head:
self.head = new_node
self.tail = new_node
else:
new_node.prev = self.tail
self.tail.next = new_node
self.tail = new_node
def pop(self):
if not self.head:
return None
popped_node = self.tail
if self.head == self.tail:
self.head = None
self.tail = None
else:
self.tail = self.tail.prev
self.tail.next = None
return popped_node.char
def is_empty(self):
return self.head is None
def is_valid(s):
stack = Stack()
for char in s:
if char in '({[':
stack.push(char)
else:
if stack.is_empty():
return False
top_char = stack.pop()
if (top_char == '(' and char != ')') or \
(top_char == '{' and char != '}') or \
(top_char == '[' and char != ']'):
return False
return stack.is_empty()
# Test the solution
s1 = "({[]})"
print("Is Valid:", is_valid(s1)) # Output: True
s2 = "({[})]"
print("Is Valid:", is_valid(s2)) # Output: False
The above implementation defines a custom LinkedList class called MyLinkedList
that uses the Doubly Linked List concept to solve the problem. The methods get
, addAtHead
, addAtTail
, addAtIndex
, and deleteAtIndex
are implemented as per the requirements of the problem.
The time complexity of the operations in this implementation is as follows:
get
: O(n), where n is the index of the node being accessed.addAtHead
: O(1), as we simply update the head pointer.addAtTail
: O(n), as we need to traverse the list to reach the tail node.addAtIndex
: O(n), as we need to traverse the list to reach the desired index.deleteAtIndex
: O(n), as we need to traverse the list to reach the desired index.
The space complexity is O(1) as we only use a constant amount of additional space regardless of the size of the linked list.