Linked List Code Implementation
Prerequisites
Before reading this article, you should first learn:
Key Points
I will present a simple implementation of MyLinkedList
using both doubly and singly linked lists, including basic operations like adding, removing, and updating elements. Here are some key points to focus on when reviewing the code.
Key Point 1: Holding References to Both Head and Tail Nodes
In LeetCode problems, we are typically provided with the head pointer of a singly linked list. However, in real-world development, doubly linked lists are used more frequently, and they generally hold references to both the head and tail nodes.
In software development, adding elements to the end of a container is a very common operation. By holding a reference to the tail node, a doubly linked list can perform this operation in time complexity.
For singly linked lists, holding a reference to the tail node also offers optimization. For instance, if you need to add an element to the end of a singly linked list and you do not have a reference to the tail node, you must traverse the entire list to find the tail, which has a time complexity of . With a reference to the tail node, you can perform the addition in time complexity.
Attentive readers might point out that if you delete the tail node of a singly linked list, the reference to the previous tail becomes invalid, requiring a traversal to find the new tail node.
That's correct. However, consider this: when deleting the tail node of a singly linked list, you need to traverse to the second-to-last node (the predecessor of the tail) to delete the tail node through pointer manipulation. At this point, you can update the tail node reference conveniently.
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
Attentive readers might think that this approach could lead to a memory leak because the next
pointer of the original head node 1
is not disconnected and still points to node 2
.
However, this way of writing is acceptable because Java's garbage collection mechanism determines whether an object is referenced by others and does not care if the object itself still references others.
The next
pointer of node 1
indeed still points to node 2
, but since no other pointers reference node 1
, it will eventually be collected and released by the garbage collector. Therefore, this scenario differs from deleting elements in an array, and you might want to think about it more.
Nonetheless, when deleting a node, it is good practice to set the pointers of the deleted node to null. This habit has no downside and might prevent some potential issues. Hence, in the following implementation, I will set the pointers of the deleted node to null, whether necessary or not.
How to Validate Your Implementation?
You can use LeetCode problem 707 "Design Linked List" to verify if your implementation is correct.
Doubly Linked List Code 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 <-> tail
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;
x.prev = null;
x.next = null;
// head <-> temp
size--;
return x.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;
x.prev = null;
x.next = null;
// temp <-> tail
size--;
return x.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;
x.prev = x.next = null;
// prev <-> next
size--;
return x.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
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;
}
// ***** Add *****
void addLast(E e) {
Node* x = new Node(e);
Node* temp = tail->prev;
// temp <-> tail
temp->next = x;
x->prev = temp;
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 null pointers
Node* x = head->next;
Node* temp = x->next;
// head <-> x <-> temp
head->next = temp;
temp->prev = head;
delete x;
// head <-> temp
size--;
return temp->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;
delete x;
// temp <-> tail
size--;
return temp->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;
delete x;
// prev <-> next
size--;
return next->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;
}
private:
Node* getNode(int index) {
checkElementIndex(index);
Node* p = head->next;
// TODO: Can be optimized by deciding
// whether to traverse from head or tail
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));
}
public:
void display() {
std::cout << "size = " << size << std::endl;
for (Node* p = head->next; p != tail; p = p->next) {
std::cout << p->val << " <-> ";
}
std::cout << "null" << std::endl;
std::cout << std::endl;
}
~MyLinkedList() {
while (size > 0) {
removeFirst();
}
delete head;
delete tail;
}
};
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 <-> tail
temp.next = x
x.prev = temp
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
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
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 <-> tail
temp.next = x;
x.prev = temp;
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
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
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 <-> tail
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{}) {
list.checkPositionIndex(index)
if index == list.size {
list.AddLast(element)
return
}
// 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++
}
// ***** 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) {
list.checkElementIndex(index)
// 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) {
list.checkElementIndex(index)
// 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) {
list.checkElementIndex(index)
// 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 {
list.checkElementIndex(index)
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) {
if !list.isElementIndex(index) {
panic(fmt.Sprintf("Index: %d, Size: %d", index, list.size))
}
}
// Check if the index position can add an element
func (list *MyLinkedList) checkPositionIndex(index int) {
if !list.isPositionIndex(index) {
panic(fmt.Sprintf("Index: %d, Size: %d", index, list.size))
}
}
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
}
Singly Linked List Code 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 getNode(size - 1).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;
}
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 getNode(size_ - 1)->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.get_node(self.size - 1).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 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
// 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.getNode(this.size - 1).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
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.getNode(list.size_ - 1).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
}
}