Linked List Code Implementation
This article will resolve
| LeetCode | Difficulty |
|---|---|
| 707. Design Linked List | 🟠 |
Key Points
Here I will give simple implementations of MyLinkedList for both doubly and singly linked lists. These include basic operations like add, delete, search, and update. Here are some key points you should pay special attention to when reading the code.
Key Point 1: Keep References to Both Head and Tail
In most LeetCode problems, you usually get a head pointer to a singly linked list. But in real software development, we mostly use doubly linked lists. Doubly linked lists often have references to both the head and tail nodes.
This is because adding elements to the end of a list is very common. If you keep a tail reference in a doubly linked list, you can add elements to the end in time.
For singly linked lists, keeping a tail reference also helps. If you don't keep a tail reference and want to add a node to the end, you have to traverse the whole list to find the tail node, which takes time. With a tail reference, you can do it in time.
Some careful readers may say: even so, if you delete the tail node of a singly linked list, the tail reference becomes invalid and has to be updated again by traversing the list.
That’s true, but think a bit more: When deleting the tail node, don't you have to traverse to the second-to-last node anyway, so you can adjust the pointer to remove the last node? At that point, you can also update your tail reference.
Key Point 2: Dummy Head and Tail Nodes
In the previous article Linked List Basics, I mentioned the "dummy head and tail" technique. The idea is simple: when you create a doubly linked list, you also create a dummy head and a dummy tail node. These two nodes always exist, whether the list is empty or not. This avoids null pointer problems and saves you from handling many edge cases.
For example, let's say the dummy head and tail nodes are called dummyHead and dummyTail. An empty doubly linked list looks like:
dummyHead <-> dummyTailIf you add elements 1, 2, 3, the list looks like:
dummyHead <-> 1 <-> 2 <-> 3 <-> dummyTailBefore, you had to treat "insert at the head," "insert at the tail," and "insert in the middle" as different cases. With dummy nodes, there's no need—whether the list is empty or not, you just handle "insert in the middle," which makes your code much cleaner.
Dummy nodes use a bit of extra memory, but it's worth it for all the complexity they save you.
For singly linked lists, a dummy head helps simplify things, but a dummy tail usually does not help much.
Dummy nodes are for internal implementation only
Dummy nodes are used for your own implementation convenience. They should not be seen from outside. For example, when using the get(index) function, the index counting starts from the real node, not from the dummy node.
Key Point 3: Memory Leaks?
In the Dynamic Array Implementation, I mentioned that you should be careful with memory leaks when deleting elements. Does this problem happen in linked lists too?
For example, look at this code:
// Suppose the singly linked list head = 1 -> 2 -> 3 -> 4 -> 5
// Delete the head node
head = head.next;
// Now head = 2 -> 3 -> 4 -> 5Some careful readers may think there is a memory leak here, since the original head node 1 still has its next pointer pointing to node 2.
But actually, this is fine. Java's garbage collector checks if the object is still referenced by someone else. It does not care if the object points to other objects. Since no pointer refers to node 1 anymore, it will be garbage collected. So this is different from deleting elements in an array.
Still, it is a good habit to set the pointers of the deleted node to null, even if it is not strictly necessary. This can avoid some possible hidden problems. In the following implementation, I will set all the pointers on the deleted node to null.
How to test your implementation?
You can use LeetCode Problem 707 "Design Linked List" to check if your code is correct. Note that the API names required by Problem 707 are not exactly the same as those in this article, so you need to adjust them to pass all test cases.
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 <-> nullpackage 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 <-> nullSingly 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)) # 3package 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