Linked List Code Implementation
Key Points
I will provide a simple implementation of MyLinkedList
using both doubly and singly linked lists, including basic operations like addition, deletion, search, and update. Here are some key points to focus on while reviewing the code.
Key Point 1: Retaining References to Both Head and Tail Nodes
In coding challenges on platforms like LeetCode, you are usually given the head pointer of a singly linked list. However, in real-world development, doubly linked lists are commonly used, which typically retain references to both head and tail nodes.
In software development, adding elements to the end of a container is a common operation. If a doubly linked list holds a reference to the tail node, you can append elements in constant time, .
For singly linked lists, retaining a reference to the tail node can also be beneficial. When adding an element to the end of a singly linked list, if there is no tail node reference, you must traverse the entire list to find the tail node, which is in time complexity. With a tail node reference, this operation can be done in time.
Attentive readers might point out that if you delete a tail node in a singly linked list, the tail node reference becomes invalid, requiring a traversal to find the new tail node.
Indeed, but consider this: when deleting the tail node of a singly linked list, you must traverse to the second-to-last node (the predecessor of the tail) to remove the tail node using pointer manipulation. At that point, you can conveniently update the tail node reference.
Key Point Two: Dummy Head and Tail Nodes
In the previous article Linked List Basics, I mentioned the "dummy head and tail node" technique. The concept is simple: when creating a doubly linked list, a dummy head node and a dummy tail node are established, and these nodes exist whether the list is empty or not. This approach prevents null pointer issues and simplifies handling many edge cases.
For example, assume the dummy head and tail nodes are dummyHead
and dummyTail
, then an empty doubly linked list looks like this:
dummyHead <-> dummyTail
If you add elements 1, 2, 3
, the list will look like this:
dummyHead <-> 1 <-> 2 <-> 3 <-> dummyTail
Previously, you needed to separately handle cases for inserting elements at the head, tail, and in the middle of the list. With dummy head and tail nodes, regardless of whether the list is empty, you only need to consider inserting elements in the middle. This simplifies the code significantly.
Of course, the dummy head node does consume a little extra memory space, but compared to the issues it resolves, this space consumption is worthwhile.
For singly linked lists, a dummy head node simplifies some operations, but a dummy tail node is not very useful.
Dummy Nodes Are Internal and Invisible
Dummy nodes are a technique for implementing data structures internally and are invisible externally. For instance, the get(index)
method to retrieve an element by index calculates the index from real nodes, not from dummy nodes.
Key Point Three: Memory Leak?
In the previous article Dynamic Array Implementation, I mentioned the issue of memory leaks when deleting elements. So, does deleting elements in a linked list also lead to memory leaks?
Especially with the following code, do you think there is an issue?
// Assume the head node of the singly linked list is head = 1 -> 2 -> 3 -> 4 -> 5
// Delete the head node of the singly linked list
head = head.next;
// At this point, head = 2 -> 3 -> 4 -> 5
Careful readers might worry about a potential memory leak here, because the original head node 1
still has its next
pointer pointing to node 2
.
However, this is actually fine. In Java, the garbage collector determines whether an object should be reclaimed based on whether it is still referenced by other objects. It does not care if the object itself still points to other nodes.
Although node 1
's next
still points to node 2
, if no other pointers reference node 1
, it will eventually be reclaimed by the garbage collector. This is different from deleting an element in an array. You can think more about this difference.
That said, when deleting a node, it is still good practice to set all pointers in the deleted node to null. This does not have any real cost and can help prevent potential issues. Therefore, in the following implementation, I will set the pointers of deleted nodes to null, regardless of whether it is strictly necessary.
How to Verify Your Implementation?
You can use LeetCode Problem 707: "Design Linked List" to check whether your implementation is correct. Note that the API names for add, delete, search, and update in Problem 707 are different from the ones used here, so you may need to adjust your code to pass all tests.
Doubly Linked List Implementation
import java.util.NoSuchElementException;
public class MyLinkedList<E> {
// virtual head and tail nodes
final private Node<E> head, tail;
private int size;
// doubly linked list node
private static class Node<E> {
E val;
Node<E> next;
Node<E> prev;
Node(E val) {
this.val = val;
}
}
// constructor initializes virtual head and tail nodes
public MyLinkedList() {
this.head = new Node<>(null);
this.tail = new Node<>(null);
head.next = tail;
tail.prev = head;
this.size = 0;
}
// ***** Add *****
public void addLast(E e) {
Node<E> x = new Node<>(e);
Node<E> temp = tail.prev;
// temp <-> x
temp.next = x;
x.prev = temp;
x.next = tail;
tail.prev = x;
// temp <-> x <-> tail
size++;
}
public void addFirst(E e) {
Node<E> x = new Node<>(e);
Node<E> temp = head.next;
// head <-> temp
temp.prev = x;
x.next = temp;
head.next = x;
x.prev = head;
// head <-> x <-> temp
size++;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size) {
addLast(element);
return;
}
// find the Node corresponding to index
Node<E> p = getNode(index);
Node<E> temp = p.prev;
// temp <-> p
// new Node to be inserted
Node<E> x = new Node<>(element);
p.prev = x;
temp.next = x;
x.prev = temp;
x.next = p;
// temp <-> x <-> p
size++;
}
// ***** Remove *****
public E removeFirst() {
if (size < 1) {
throw new NoSuchElementException();
}
// the existence of virtual nodes prevents us from having to consider null pointers
Node<E> x = head.next;
Node<E> temp = x.next;
// head <-> x <-> temp
head.next = temp;
temp.prev = head;
E val = x.val;
x.prev = null;
x.next = null;
// head <-> temp
size--;
return val;
}
public E removeLast() {
if (size < 1) {
throw new NoSuchElementException();
}
Node<E> x = tail.prev;
Node<E> temp = tail.prev.prev;
// temp <-> x <-> tail
tail.prev = temp;
temp.next = tail;
E val = x.val;
x.prev = null;
x.next = null;
// temp <-> tail
size--;
return val;
}
public E remove(int index) {
checkElementIndex(index);
// find the Node corresponding to index
Node<E> x = getNode(index);
Node<E> prev = x.prev;
Node<E> next = x.next;
// prev <-> x <-> next
prev.next = next;
next.prev = prev;
E val = x.val;
x.prev = null;
x.next = null;
// prev <-> next
size--;
return val;
}
// ***** Get *****
public E get(int index) {
checkElementIndex(index);
// find the Node corresponding to index
Node<E> p = getNode(index);
return p.val;
}
public E getFirst() {
if (size < 1) {
throw new NoSuchElementException();
}
return head.next.val;
}
public E getLast() {
if (size < 1) {
throw new NoSuchElementException();
}
return tail.prev.val;
}
// ***** Set *****
public E set(int index, E val) {
checkElementIndex(index);
// find the Node corresponding to index
Node<E> p = getNode(index);
E oldVal = p.val;
p.val = val;
return oldVal;
}
// ***** Other utility functions *****
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
private Node<E> getNode(int index) {
checkElementIndex(index);
Node<E> p = head.next;
// TODO: Can be optimized by deciding whether to
// traverse from head or tail based on index
for (int i = 0; i < index; i++) {
p = p.next;
}
return p;
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
// Check if the index position can contain an element
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
// Check if the index position can add an element
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
private void display() {
System.out.println("size = " + size);
for (Node<E> p = head.next; p != tail; p = p.next) {
System.out.print(p.val + " <-> ");
}
System.out.println("null");
System.out.println();
}
public static void main(String[] args) {
MyLinkedList<Integer> list = new MyLinkedList<>();
list.addLast(1);
list.addLast(2);
list.addLast(3);
list.addFirst(0);
list.add(2, 100);
list.display();
// size = 5
// 0 <-> 1 <-> 100 <-> 2 -> 3 -> null
}
}
#include <iostream>
#include <stdexcept>
template<typename E>
class MyLinkedList {
// virtual head and tail nodes
struct Node {
E val;
Node* next;
Node* prev;
Node(E value) : val(value), next(nullptr), prev(nullptr) {}
};
Node* head;
Node* tail;
int size;
public:
// constructor initializes virtual head and tail nodes
MyLinkedList() {
head = new Node(E());
tail = new Node(E());
head->next = tail;
tail->prev = head;
size = 0;
}
~MyLinkedList() {
while (size > 0) {
removeFirst();
}
delete head;
delete tail;
}
// ***** Add *****
void addLast(E e) {
Node* x = new Node(e);
Node* temp = tail->prev;
temp->next = x;
x->prev = temp;
// temp <-> x
x->next = tail;
tail->prev = x;
// temp <-> x <-> tail
size++;
}
void addFirst(E e) {
Node* x = new Node(e);
Node* temp = head->next;
// head <-> temp
temp->prev = x;
x->next = temp;
head->next = x;
x->prev = head;
// head <-> x <-> temp
size++;
}
void add(int index, E element) {
checkPositionIndex(index);
if (index == size) {
addLast(element);
return;
}
// find the Node corresponding to index
Node* p = getNode(index);
Node* temp = p->prev;
// temp <-> p
// new Node to be inserted
Node* x = new Node(element);
p->prev = x;
temp->next = x;
x->prev = temp;
x->next = p;
// temp <-> x <-> p
size++;
}
// ***** Remove *****
E removeFirst() {
if (size < 1) {
throw std::out_of_range("No elements to remove");
}
// the existence of virtual nodes prevents us from having to consider nullptr pointers
Node* x = head->next;
Node* temp = x->next;
// head <-> x <-> temp
head->next = temp;
temp->prev = head;
E val = x->val;
delete x;
// head <-> temp
size--;
return val;
}
E removeLast() {
if (size < 1) {
throw std::out_of_range("No elements to remove");
}
Node* x = tail->prev;
Node* temp = tail->prev->prev;
// temp <-> x <-> tail
tail->prev = temp;
temp->next = tail;
E val = x->val;
x->prev = nullptr;
x->next = nullptr;
delete x;
// temp <-> tail
size--;
return val;
}
E remove(int index) {
checkElementIndex(index);
// find the Node corresponding to index
Node* x = getNode(index);
Node* prev = x->prev;
Node* next = x->next;
// prev <-> x <-> next
prev->next = next;
next->prev = prev;
E val = x->val;
x->prev = nullptr;
x->next = nullptr;
delete x;
// prev <-> next
size--;
return val;
}
// ***** Get *****
E get(int index) {
checkElementIndex(index);
// find the Node corresponding to index
Node* p = getNode(index);
return p->val;
}
E getFirst() {
if (size < 1) {
throw std::out_of_range("No elements in the list");
}
return head->next->val;
}
E getLast() {
if (size < 1) {
throw std::out_of_range("No elements in the list");
}
return tail->prev->val;
}
// ***** Set *****
E set(int index, E val) {
checkElementIndex(index);
// find the Node corresponding to index
Node* p = getNode(index);
E oldVal = p->val;
p->val = val;
return oldVal;
}
// ***** Other utility functions *****
int getSize() const {
return size;
}
bool isEmpty() const {
return size == 0;
}
void display() {
std::cout << "size = " << size << std::endl;
for (Node* p = head->next; p != tail; p = p->next) {
std::cout << p->val << " <-> ";
}
std::cout << "nullptr" << std::endl;
std::cout << std::endl;
}
private:
Node* getNode(int index) {
checkElementIndex(index);
Node* p = head->next;
// TODO: Can be optimized by deciding whether to
// traverse from head or tail based on index
for (int i = 0; i < index; i++) {
p = p->next;
}
return p;
}
bool isElementIndex(int index) const {
return index >= 0 && index < size;
}
bool isPositionIndex(int index) const {
return index >= 0 && index <= size;
}
// Check if the index position can contain an element
void checkElementIndex(int index) const {
if (!isElementIndex(index))
throw std::out_of_range("Index: " + std::to_string(index) + ", Size: " + std::to_string(size));
}
// Check if the index position can add an element
void checkPositionIndex(int index) const {
if (!isPositionIndex(index))
throw std::out_of_range("Index: " + std::to_string(index) + ", Size: " + std::to_string(size));
}
};
int main() {
MyLinkedList<int> list;
list.addLast(1);
list.addLast(2);
list.addLast(3);
list.addFirst(0);
list.add(2, 100);
list.display();
// size = 5
// 0 <-> 1 <-> 100 <-> 2 <-> 3 <-> null
return 0;
}
class Node:
def __init__(self, val):
self.val = val
self.next = None
self.prev = None
class MyLinkedList:
# virtual head and tail nodes
def __init__(self):
self.head = Node(None)
self.tail = Node(None)
self.head.next = self.tail
self.tail.prev = self.head
self.size = 0
# ***** Add *****
def add_last(self, e):
x = Node(e)
temp = self.tail.prev
temp.next = x
x.prev = temp
# temp <-> x
x.next = self.tail
self.tail.prev = x
# temp <-> x <-> tail
self.size += 1
def add_first(self, e):
x = Node(e)
temp = self.head.next
# head <-> temp
temp.prev = x
x.next = temp
self.head.next = x
x.prev = self.head
# head <-> x <-> temp
self.size += 1
def add(self, index, element):
self.check_position_index(index)
if index == self.size:
self.add_last(element)
return
# find the Node corresponding to index
p = self.get_node(index)
temp = p.prev
# temp <-> p
# new Node to be inserted
x = Node(element)
p.prev = x
temp.next = x
x.prev = temp
x.next = p
# temp <-> x <-> p
self.size += 1
# ***** Remove *****
def remove_first(self):
if self.size < 1:
raise IndexError("No elements to remove")
# the existence of virtual nodes prevents us from having to consider null pointers
x = self.head.next
temp = x.next
# head <-> x <-> temp
self.head.next = temp
temp.prev = self.head
# head <-> temp
self.size -= 1
return x.val
def remove_last(self):
if self.size < 1:
raise IndexError("No elements to remove")
x = self.tail.prev
temp = x.prev
# temp <-> x <-> tail
self.tail.prev = temp
temp.next = self.tail
# temp <-> tail
self.size -= 1
return x.val
def remove(self, index):
self.check_element_index(index)
# find the Node corresponding to index
x = self.get_node(index)
prev = x.prev
next = x.next
# prev <-> x <-> next
prev.next = next
next.prev = prev
self.size -= 1
return x.val
# ***** Get *****
def get(self, index):
self.check_element_index(index)
# find the Node corresponding to index
p = self.get_node(index)
return p.val
def get_first(self):
if self.size < 1:
raise IndexError("No elements in the list")
return self.head.next.val
def get_last(self):
if self.size < 1:
raise IndexError("No elements in the list")
return self.tail.prev.val
# ***** Set *****
def set(self, index, val):
self.check_element_index(index)
# find the Node corresponding to index
p = self.get_node(index)
old_val = p.val
p.val = val
return old_val
# ***** Other utility functions *****
def size(self):
return self.size
def is_empty(self):
return self.size == 0
def get_node(self, index):
self.check_element_index(index)
p = self.head.next
# TODO: Can be optimized by deciding whether to
# traverse from head or tail based on index
for _ in range(index):
p = p.next
return p
def is_element_index(self, index):
return 0 <= index < self.size
def is_position_index(self, index):
return 0 <= index <= self.size
# Check if the index position can contain an element
def check_element_index(self, index):
if not self.is_element_index(index):
raise IndexError(f"Index: {index}, Size: {self.size}")
# Check if the index position can add an element
def check_position_index(self, index):
if not self.is_position_index(index):
raise IndexError(f"Index: {index}, Size: {self.size}")
def display(self):
print(f"size = {self.size}")
p = self.head.next
while p != self.tail:
print(f"{p.val} <-> ", end="")
p = p.next
print("null\n")
if __name__ == "__main__":
list = MyLinkedList()
list.add_last(1)
list.add_last(2)
list.add_last(3)
list.add_first(0)
list.add(2, 100)
list.display()
# size = 5
# 0 <-> 1 <-> 100 <-> 2 <-> 3 <-> null
package main
import (
"errors"
"fmt"
)
type Node struct {
val interface{}
next *Node
prev *Node
}
type MyLinkedList struct {
head *Node
tail *Node
size int
}
// virtual head and tail nodes
func NewMyLinkedList() *MyLinkedList {
head := &Node{}
tail := &Node{}
head.next = tail
tail.prev = head
return &MyLinkedList{head: head, tail: tail, size: 0}
}
// ***** Add *****
func (list *MyLinkedList) AddLast(e interface{}) {
x := &Node{val: e}
temp := list.tail.prev
// temp <-> x
temp.next = x
x.prev = temp
x.next = list.tail
list.tail.prev = x
// temp <-> x <-> tail
list.size++
}
func (list *MyLinkedList) AddFirst(e interface{}) {
x := &Node{val: e}
temp := list.head.next
// head <-> temp
temp.prev = x
x.next = temp
list.head.next = x
x.prev = list.head
// head <-> x <-> temp
list.size++
}
func (list *MyLinkedList) Add(index int, element interface{}) error {
if err := list.checkPositionIndex(index); err != nil {
return err
}
if index == list.size {
list.AddLast(element)
return nil
}
// find the Node corresponding to index
p := list.getNode(index)
temp := p.prev
// temp <-> p
// new Node to be inserted
x := &Node{val: element}
p.prev = x
temp.next = x
x.prev = temp
x.next = p
// temp <-> x <-> p
list.size++
return nil
}
// ***** Remove *****
func (list *MyLinkedList) RemoveFirst() (interface{}, error) {
if list.size < 1 {
return nil, errors.New("no elements to remove")
}
// the existence of virtual nodes prevents us from having to consider null pointers
x := list.head.next
temp := x.next
// head <-> x <-> temp
list.head.next = temp
temp.prev = list.head
// head <-> temp
list.size--
return x.val, nil
}
func (list *MyLinkedList) RemoveLast() (interface{}, error) {
if list.size < 1 {
return nil, errors.New("no elements to remove")
}
x := list.tail.prev
temp := x.prev
// temp <-> x <-> tail
list.tail.prev = temp
temp.next = list.tail
// temp <-> tail
list.size--
return x.val, nil
}
func (list *MyLinkedList) Remove(index int) (interface{}, error) {
if err := list.checkElementIndex(index); err != nil {
return nil, err
}
// find the Node corresponding to index
x := list.getNode(index)
prev := x.prev
next := x.next
// prev <-> x <-> next
prev.next = next
next.prev = prev
list.size--
return x.val, nil
}
// ***** Get *****
func (list *MyLinkedList) Get(index int) (interface{}, error) {
if err := list.checkElementIndex(index); err != nil {
return nil, err
}
// find the Node corresponding to index
p := list.getNode(index)
return p.val, nil
}
func (list *MyLinkedList) GetFirst() (interface{}, error) {
if list.size < 1 {
return nil, errors.New("no elements in the list")
}
return list.head.next.val, nil
}
func (list *MyLinkedList) GetLast() (interface{}, error) {
if list.size < 1 {
return nil, errors.New("no elements in the list")
}
return list.tail.prev.val, nil
}
// ***** Set *****
func (list *MyLinkedList) Set(index int, val interface{}) (interface{}, error) {
if err := list.checkElementIndex(index); err != nil {
return nil, err
}
// find the Node corresponding to index
p := list.getNode(index)
oldVal := p.val
p.val = val
return oldVal, nil
}
// ***** Other utility functions *****
func (list *MyLinkedList) Size() int {
return list.size
}
func (list *MyLinkedList) IsEmpty() bool {
return list.size == 0
}
func (list *MyLinkedList) getNode(index int) *Node {
p := list.head.next
// TODO: Can be optimized by deciding whether to traverse from head or tail based on index
for i := 0; i < index; i++ {
p = p.next
}
return p
}
func (list *MyLinkedList) isElementIndex(index int) bool {
return index >= 0 && index < list.size
}
func (list *MyLinkedList) isPositionIndex(index int) bool {
return index >= 0 && index <= list.size
}
// Check if the index position can contain an element
func (list *MyLinkedList) checkElementIndex(index int) error {
if !list.isElementIndex(index) {
return fmt.Errorf("Index: %d, Size: %d", index, list.size)
}
return nil
}
// Check if the index position can add an element
func (list *MyLinkedList) checkPositionIndex(index int) error {
if !list.isPositionIndex(index) {
return fmt.Errorf("Index: %d, Size: %d", index, list.size)
}
return nil
}
func (list *MyLinkedList) Display() {
fmt.Printf("size = %d\n", list.size)
p := list.head.next
for p != list.tail {
fmt.Printf("%v <-> ", p.val)
p = p.next
}
fmt.Println("null\n")
}
func main() {
list := NewMyLinkedList()
list.AddLast(1)
list.AddLast(2)
list.AddLast(3)
list.AddFirst(0)
list.Add(2, 100)
list.Display()
// size = 5
// 0 <-> 1 <-> 100 <-> 2 <-> 3 <-> null
}
class Node {
constructor(val) {
this.val = val;
this.next = null;
this.prev = null;
}
}
class MyLinkedList {
// virtual head and tail nodes
constructor() {
this.head = new Node(null);
this.tail = new Node(null);
this.head.next = this.tail;
this.tail.prev = this.head;
this.size = 0;
}
// ***** Add *****
addLast(e) {
const x = new Node(e);
const temp = this.tail.prev;
temp.next = x;
x.prev = temp;
// temp <-> x
x.next = this.tail;
this.tail.prev = x;
// temp <-> x <-> tail
this.size++;
}
addFirst(e) {
const x = new Node(e);
const temp = this.head.next;
// head <-> temp
temp.prev = x;
x.next = temp;
this.head.next = x;
x.prev = this.head;
// head <-> x <-> temp
this.size++;
}
add(index, element) {
this.checkPositionIndex(index);
if (index === this.size) {
this.addLast(element);
return;
}
// find the Node corresponding to index
const p = this.getNode(index);
const temp = p.prev;
// temp <-> p
// new Node to be inserted
const x = new Node(element);
p.prev = x;
temp.next = x;
x.prev = temp;
x.next = p;
// temp <-> x <-> p
this.size++;
}
// ***** Remove *****
removeFirst() {
if (this.size < 1) {
throw new Error("No elements to remove");
}
// the existence of virtual nodes prevents us from having to consider null pointers
const x = this.head.next;
const temp = x.next;
// head <-> x <-> temp
this.head.next = temp;
temp.prev = this.head;
// head <-> temp
this.size--;
return x.val;
}
removeLast() {
if (this.size < 1) {
throw new Error("No elements to remove");
}
const x = this.tail.prev;
const temp = x.prev;
// temp <-> x <-> tail
this.tail.prev = temp;
temp.next = this.tail;
// temp <-> tail
this.size--;
return x.val;
}
remove(index) {
this.checkElementIndex(index);
// find the Node corresponding to index
const x = this.getNode(index);
const prev = x.prev;
const next = x.next;
// prev <-> x <-> next
prev.next = next;
next.prev = prev;
this.size--;
return x.val;
}
// ***** Get *****
get(index) {
this.checkElementIndex(index);
// find the Node corresponding to index
const p = this.getNode(index);
return p.val;
}
getFirst() {
if (this.size < 1) {
throw new Error("No elements in the list");
}
return this.head.next.val;
}
getLast() {
if (this.size < 1) {
throw new Error("No elements in the list");
}
return this.tail.prev.val;
}
// ***** Set *****
set(index, val) {
this.checkElementIndex(index);
// find the Node corresponding to index
const p = this.getNode(index);
const oldVal = p.val;
p.val = val;
return oldVal;
}
// ***** Other utility functions *****
size() {
return this.size;
}
isEmpty() {
return this.size === 0;
}
getNode(index) {
this.checkElementIndex(index);
let p = this.head.next;
// TODO: Can be optimized by deciding whether to
// traverse from head or tail based on index
for (let i = 0; i < index; i++) {
p = p.next;
}
return p;
}
isElementIndex(index) {
return index >= 0 && index < this.size;
}
isPositionIndex(index) {
return index >= 0 && index <= this.size;
}
// Check if the index position can contain an element
checkElementIndex(index) {
if (!this.isElementIndex(index)) {
throw new Error(`Index: ${index}, Size: ${this.size}`);
}
}
// Check if the index position can add an element
checkPositionIndex(index) {
if (!this.isPositionIndex(index)) {
throw new Error(`Index: ${index}, Size: ${this.size}`);
}
}
display() {
console.log(`size = ${this.size}`);
let p = this.head.next;
let str = "";
while (p !== this.tail) {
str += `${p.val} <-> `;
p = p.next;
}
console.log(str + "null\n");
}
}
// Testing the MyLinkedList class
const list = new MyLinkedList();
list.addLast(1);
list.addLast(2);
list.addLast(3);
list.addFirst(0);
list.add(2, 100);
list.display();
// size = 5
// 0 <-> 1 <-> 100 <-> 2 <-> 3 <-> null
Singly Linked List Implementation
import java.util.NoSuchElementException;
public class MyLinkedList2<E> {
private static class Node<E> {
E val;
Node<E> next;
Node(E val) {
this.val = val;
this.next = null;
}
}
private Node<E> head;
// actual reference to the tail node
private Node<E> tail;
private int size;
public MyLinkedList2() {
this.head = new Node<>(null);
this.tail = head;
this.size = 0;
}
public void addFirst(E e) {
Node<E> newNode = new Node<>(e);
newNode.next = head.next;
head.next = newNode;
if (size == 0) {
tail = newNode;
}
size++;
}
public void addLast(E e) {
Node<E> newNode = new Node<>(e);
tail.next = newNode;
tail = newNode;
size++;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size) {
addLast(element);
return;
}
Node<E> prev = head;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node<E> newNode = new Node<>(element);
newNode.next = prev.next;
prev.next = newNode;
size++;
}
public E removeFirst() {
if (isEmpty()) {
throw new NoSuchElementException();
}
Node<E> first = head.next;
head.next = first.next;
if (size == 1) {
tail = head;
}
size--;
return first.val;
}
public E removeLast() {
if (isEmpty()) {
throw new NoSuchElementException();
}
Node<E> prev = head;
while (prev.next != tail) {
prev = prev.next;
}
E val = tail.val;
prev.next = null;
tail = prev;
size--;
return val;
}
public E remove(int index) {
checkElementIndex(index);
Node<E> prev = head;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node<E> nodeToRemove = prev.next;
prev.next = nodeToRemove.next;
// deleting the last element
if (index == size - 1) {
tail = prev;
}
size--;
return nodeToRemove.val;
}
// ***** Retrieve *****
public E getFirst() {
if (isEmpty()) {
throw new NoSuchElementException();
}
return head.next.val;
}
public E getLast() {
if (isEmpty()) {
throw new NoSuchElementException();
}
return tail.val;
}
public E get(int index) {
checkElementIndex(index);
Node<E> p = getNode(index);
return p.val;
}
// ***** Update *****
public E set(int index, E element) {
checkElementIndex(index);
Node<E> p = getNode(index);
E oldVal = p.val;
p.val = element;
return oldVal;
}
// ***** Other Utility Functions *****
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
private boolean isPositionIndex(int index) {
return index >= 0 && index <= size;
}
// Check if the index position can have an element
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
// Check if the index position can add an element
private void checkPositionIndex(int index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
// Return the Node corresponding to the index
// Note: Please ensure that the passed index is valid
private Node<E> getNode(int index) {
Node<E> p = head.next;
for (int i = 0; i < index; i++) {
p = p.next;
}
return p;
}
public static void main(String[] args) {
MyLinkedList2<Integer> list = new MyLinkedList2<>();
list.addFirst(1);
list.addFirst(2);
list.addLast(3);
list.addLast(4);
list.add(2, 5);
System.out.println(list.removeFirst()); // 2
System.out.println(list.removeLast()); // 4
System.out.println(list.remove(1)); // 5
System.out.println(list.getFirst()); // 1
System.out.println(list.getLast()); // 3
System.out.println(list.get(1)); // 3
}
}
#include <iostream>
#include <stdexcept>
template <typename E>
class MyLinkedList2 {
private:
// Node structure
struct Node {
E val;
Node* next;
Node(E value) : val(value), next(nullptr) {}
};
Node* head;
// actual reference to the tail node
Node* tail;
int size_;
public:
MyLinkedList2() {
head = new Node(E());
tail = head;
size_ = 0;
}
~MyLinkedList2() {
Node* current = head;
while (current != nullptr) {
Node* next = current->next;
delete current;
current = next;
}
}
void addFirst(E e) {
Node* newNode = new Node(e);
newNode->next = head->next;
head->next = newNode;
if (size_ == 0) {
tail = newNode;
}
size_++;
}
void addLast(E e) {
Node* newNode = new Node(e);
tail->next = newNode;
tail = newNode;
size_++;
}
void add(int index, E element) {
checkPositionIndex(index);
if (index == size_) {
addLast(element);
return;
}
Node* prev = head;
for (int i = 0; i < index; i++) {
prev = prev->next;
}
Node* newNode = new Node(element);
newNode->next = prev->next;
prev->next = newNode;
size_++;
}
E removeFirst() {
if (isEmpty()) {
throw std::out_of_range("No elements to remove");
}
Node* first = head->next;
head->next = first->next;
if (size_ == 1) {
tail = head;
}
size_--;
E val = first->val;
delete first;
return val;
}
E removeLast() {
if (isEmpty()) {
throw std::out_of_range("No elements to remove");
}
Node* prev = head;
while (prev->next != tail) {
prev = prev->next;
}
E val = tail->val;
delete tail;
prev->next = nullptr;
tail = prev;
size_--;
return val;
}
E remove(int index) {
checkElementIndex(index);
Node* prev = head;
for (int i = 0; i < index; i++) {
prev = prev->next;
}
Node* nodeToRemove = prev->next;
prev->next = nodeToRemove->next;
// deleting the last element
if (index == size_ - 1) {
tail = prev;
}
size_--;
E val = nodeToRemove->val;
delete nodeToRemove;
return val;
}
// ***** Retrieve *****
E getFirst() {
if (isEmpty()) {
throw std::out_of_range("No elements in the list");
}
return head->next->val;
}
E getLast() {
if (isEmpty()) {
throw std::out_of_range("No elements in the list");
}
return tail->val;
}
E get(int index) {
checkElementIndex(index);
Node* p = getNode(index);
return p->val;
}
// ***** Update *****
E set(int index, E element) {
checkElementIndex(index);
Node* p = getNode(index);
E oldVal = p->val;
p->val = element;
return oldVal;
}
// ***** Other Utility Functions *****
int size() {
return size_;
}
bool isEmpty() {
return size_ == 0;
}
private:
bool isElementIndex(int index) {
return index >= 0 && index < size_;
}
bool isPositionIndex(int index) {
return index >= 0 && index <= size_;
}
// Check if the index position can have an element
void checkElementIndex(int index) {
if (!isElementIndex(index)) {
throw std::out_of_range("Index: " + std::to_string(index) + ", size_: " + std::to_string(size_));
}
}
// Check if the index position can add an element
void checkPositionIndex(int index) {
if (!isPositionIndex(index)) {
throw std::out_of_range("Index: " + std::to_string(index) + ", size_: " + std::to_string(size_));
}
}
// Return the Node corresponding to the index
// Note: Please ensure that the passed index is valid
Node* getNode(int index) {
Node* p = head->next;
for (int i = 0; i < index; i++) {
p = p->next;
}
return p;
}
};
int main() {
MyLinkedList2<int> list;
list.addFirst(1);
list.addFirst(2);
list.addLast(3);
list.addLast(4);
list.add(2, 5);
std::cout << list.removeFirst() << std::endl; // 2
std::cout << list.removeLast() << std::endl; // 4
std::cout << list.remove(1) << std::endl; // 5
std::cout << list.getFirst() << std::endl; // 1
std::cout << list.getLast() << std::endl; // 3
std::cout << list.get(1) << std::endl; // 3
return 0;
}
class MyLinkedList2:
class Node:
def __init__(self, val):
self.val = val
self.next = None
def __init__(self):
self.head = self.Node(None)
self.tail = self.head
self.size = 0
def add_first(self, e):
new_node = self.Node(e)
new_node.next = self.head.next
self.head.next = new_node
if self.size == 0:
self.tail = new_node
self.size += 1
def add_last(self, e):
new_node = self.Node(e)
self.tail.next = new_node
self.tail = new_node
self.size += 1
def add(self, index, element):
self.check_position_index(index)
if index == self.size:
self.add_last(element)
return
prev = self.head
for i in range(index):
prev = prev.next
new_node = self.Node(element)
new_node.next = prev.next
prev.next = new_node
self.size += 1
def remove_first(self):
if self.is_empty():
raise Exception("NoSuchElementException")
first = self.head.next
self.head.next = first.next
if self.size == 1:
self.tail = self.head
self.size -= 1
return first.val
def remove_last(self):
if self.is_empty():
raise Exception("NoSuchElementException")
prev = self.head
while prev.next != self.tail:
prev = prev.next
val = self.tail.val
prev.next = None
self.tail = prev
self.size -= 1
return val
def remove(self, index):
self.check_element_index(index)
prev = self.head
for i in range(index):
prev = prev.next
node_to_remove = prev.next
prev.next = node_to_remove.next
# deleting the last element
if index == self.size - 1:
self.tail = prev
self.size -= 1
return node_to_remove.val
# ***** Retrieve *****
def get_first(self):
if self.is_empty():
raise Exception("NoSuchElementException")
return self.head.next.val
def get_last(self):
if self.is_empty():
raise Exception("NoSuchElementException")
return self.tail.val
def get(self, index):
self.check_element_index(index)
p = self.get_node(index)
return p.val
# ***** Update *****
def set(self, index, element):
self.check_element_index(index)
p = self.get_node(index)
old_val = p.val
p.val = element
return old_val
# ***** Other Utility Functions *****
def get_size(self):
return self.size
def is_empty(self):
return self.size == 0
def is_element_index(self, index):
return 0 <= index < self.size
def is_position_index(self, index):
return 0 <= index <= self.size
# Check if the index position can have an element
def check_element_index(self, index):
if not self.is_element_index(index):
raise IndexError(f"Index: {index}, Size: {self.size}")
# Check if the index position can add an element
def check_position_index(self, index):
if not self.is_position_index(index):
raise IndexError(f"Index: {index}, Size: {self.size}")
# Return the Node corresponding to the index
# Note: Please ensure that the passed index is valid
def get_node(self, index):
p = self.head.next
for i in range(index):
p = p.next
return p
if __name__ == "__main__":
list = MyLinkedList2()
list.add_first(1)
list.add_first(2)
list.add_last(3)
list.add_last(4)
list.add(2, 5)
print(list.remove_first()) # 2
print(list.remove_last()) # 4
print(list.remove(1)) # 5
print(list.get_first()) # 1
print(list.get_last()) # 3
print(list.get(1)) # 3
package main
import (
"errors"
"fmt"
)
// Node structure
type Node[E any] struct {
val E
next *Node[E]
}
// MyLinkedList2 structure
type MyLinkedList2[E any] struct {
head *Node[E]
// actual reference to the tail node
tail *Node[E]
size_ int
}
// Creates a new linked list
func NewMyLinkedList2[E any]() *MyLinkedList2[E] {
head := &Node[E]{}
return &MyLinkedList2[E]{head: head, tail: head, size_: 0}
}
// Add element at the beginning
func (list *MyLinkedList2[E]) AddFirst(e E) {
newNode := &Node[E]{val: e}
newNode.next = list.head.next
list.head.next = newNode
if list.size_ == 0 {
list.tail = newNode
}
list.size_++
}
// Add element at the end
func (list *MyLinkedList2[E]) AddLast(e E) {
newNode := &Node[E]{val: e}
list.tail.next = newNode
list.tail = newNode
list.size_++
}
// Add element at a specific index
func (list *MyLinkedList2[E]) Add(index int, element E) error {
if index < 0 || index > list.size_ {
return errors.New("index out of bounds")
}
if index == list.size_ {
list.AddLast(element)
return nil
}
prev := list.head
for i := 0; i < index; i++ {
prev = prev.next
}
newNode := &Node[E]{val: element}
newNode.next = prev.next
prev.next = newNode
list.size_++
return nil
}
// Remove the first element
func (list *MyLinkedList2[E]) RemoveFirst() (E, error) {
if list.IsEmpty() {
return *new(E), errors.New("no elements to remove")
}
first := list.head.next
list.head.next = first.next
if list.size_ == 1 {
list.tail = list.head
}
list.size_--
return first.val, nil
}
// Remove the last element
func (list *MyLinkedList2[E]) RemoveLast() (E, error) {
if list.IsEmpty() {
return *new(E), errors.New("no elements to remove")
}
prev := list.head
for prev.next != list.tail {
prev = prev.next
}
val := list.tail.val
prev.next = nil
list.tail = prev
list.size_--
return val, nil
}
// Remove element at a specific index
func (list *MyLinkedList2[E]) Remove(index int) (E, error) {
if index < 0 || index >= list.size_ {
return *new(E), errors.New("index out of bounds")
}
prev := list.head
for i := 0; i < index; i++ {
prev = prev.next
}
nodeToRemove := prev.next
prev.next = nodeToRemove.next
// deleting the last element
if index == list.size_-1 {
list.tail = prev
}
list.size_--
return nodeToRemove.val, nil
}
// Get the first element
func (list *MyLinkedList2[E]) GetFirst() (E, error) {
if list.IsEmpty() {
return *new(E), errors.New("no elements in the list")
}
return list.head.next.val, nil
}
// Get the last element
func (list *MyLinkedList2[E]) GetLast() (E, error) {
if list.IsEmpty() {
return *new(E), errors.New("no elements in the list")
}
return list.tail.val, nil
}
// Get the element at a specific index
func (list *MyLinkedList2[E]) Get(index int) (E, error) {
if index < 0 || index >= list.size_ {
return *new(E), errors.New("index out of bounds")
}
return list.getNode(index).val, nil
}
// Update the element at a specific index
func (list *MyLinkedList2[E]) Set(index int, element E) (E, error) {
if index < 0 || index >= list.size_ {
return *new(E), errors.New("index out of bounds")
}
node := list.getNode(index)
oldVal := node.val
node.val = element
return oldVal, nil
}
// Get the size of the list
func (list *MyLinkedList2[E]) Size() int {
return list.size_
}
// Check if the list is empty
func (list *MyLinkedList2[E]) IsEmpty() bool {
return list.size_ == 0
}
// Return the node corresponding to the index
func (list *MyLinkedList2[E]) getNode(index int) *Node[E] {
p := list.head.next
for i := 0; i < index; i++ {
p = p.next
}
return p
}
func main() {
list := NewMyLinkedList2[int]()
list.addFirst(1)
list.addFirst(2)
list.addLast(3)
list.addLast(4)
list.add(2, 5)
if val, err := list.removeFirst(); err == nil {
fmt.Println(val) // 2
}
if val, err := list.removeLast(); err == nil {
fmt.Println(val) // 4
}
if val, err := list.remove(1); err == nil {
fmt.Println(val) // 5
}
if val, err := list.getFirst(); err == nil {
fmt.Println(val) // 1
}
if val, err := list.getLast(); err == nil {
fmt.Println(val) // 3
}
if val, err := list.get(1); err == nil {
fmt.Println(val) // 3
}
}
// Node class
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
class MyLinkedList2 {
constructor() {
this.head = new Node(null);
this.tail = this.head;
this.size = 0;
}
addFirst(e) {
const newNode = new Node(e);
newNode.next = this.head.next;
this.head.next = newNode;
if (this.size === 0) {
this.tail = newNode;
}
this.size++;
}
addLast(e) {
const newNode = new Node(e);
this.tail.next = newNode;
this.tail = newNode;
this.size++;
}
add(index, element) {
this.checkPositionIndex(index);
if (index === this.size) {
this.addLast(element);
return;
}
let prev = this.head;
for (let i = 0; i < index; i++) {
prev = prev.next;
}
const newNode = new Node(element);
newNode.next = prev.next;
prev.next = newNode;
this.size++;
}
removeFirst() {
if (this.isEmpty()) {
throw new Error("NoSuchElementException");
}
const first = this.head.next;
this.head.next = first.next;
if (this.size === 1) {
this.tail = this.head;
}
this.size--;
return first.val;
}
removeLast() {
if (this.isEmpty()) {
throw new Error("NoSuchElementException");
}
let prev = this.head;
while (prev.next !== this.tail) {
prev = prev.next;
}
const val = this.tail.val;
prev.next = null;
this.tail = prev;
this.size--;
return val;
}
remove(index) {
this.checkElementIndex(index);
let prev = this.head;
for (let i = 0; i < index; i++) {
prev = prev.next;
}
const nodeToRemove = prev.next;
prev.next = nodeToRemove.next;
// deleting the last element
if (index === this.size - 1) {
this.tail = prev;
}
this.size--;
return nodeToRemove.val;
}
// ***** Retrieve *****
getFirst() {
if (this.isEmpty()) {
throw new Error("NoSuchElementException");
}
return this.head.next.val;
}
getLast() {
if (this.isEmpty()) {
throw new Error("NoSuchElementException");
}
return this.tail.val;
}
get(index) {
this.checkElementIndex(index);
const p = this.getNode(index);
return p.val;
}
// ***** Update *****
set(index, element) {
this.checkElementIndex(index);
const p = this.getNode(index);
const oldVal = p.val;
p.val = element;
return oldVal;
}
// ***** Other Utility Functions *****
size() {
return this.size;
}
isEmpty() {
return this.size === 0;
}
isElementIndex(index) {
return index >= 0 && index < this.size;
}
isPositionIndex(index) {
return index >= 0 && index <= this.size;
}
// Check if the index position can have an element
checkElementIndex(index) {
if (!this.isElementIndex(index)) {
throw new Error(`Index: ${index}, Size: ${this.size}`);
}
}
// Check if the index position can add an element
checkPositionIndex(index) {
if (!this.isPositionIndex(index)) {
throw new Error(`Index: ${index}, Size: ${this.size}`);
}
}
// Return the Node corresponding to the index
// Note: Please ensure that the passed index is valid
getNode(index) {
let p = this.head.next;
for (let i = 0; i < index; i++) {
p = p.next;
}
return p;
}
}
// Example usage
const list = new MyLinkedList2();
list.addFirst(1);
list.addFirst(2);
list.addLast(3);
list.addLast(4);
list.add(2, 5);
console.log(list.removeFirst()); // 2
console.log(list.removeLast()); // 4
console.log(list.remove(1)); // 5
console.log(list.getFirst()); // 1
console.log(list.getLast()); // 3
console.log(list.get(1)); // 3