umma.dev

Algorithms + Data Structures: Linked List

In this post I cover the basics of a Linked List, and dive into the differences between a singly, doubly and circular Linked Lists. Looking at the structure for each and problem sets.

What is a Linked List?

There are four main parts when it comes to a Linked List, the head, tail, node and pointer. Each have a specific part to play within the Linked List.

This is the value at the start of the Linked List. The value of the Head can be changed by inserting a node before the current Head or deleting the value at the Head.

Tail

This is the value at the end of the Linked List. Like the Head, the Tail’s value can also be changed by inserting or deleting the last node.

Node

This is the value that the pointer is pointing to. Let’s say for example Head has a Node value of one.

Pointer/Next

The pointer will usually point at the Head or Node and will move along between each Node.

Single Linked List

Structure
class LinkedList {
  constructor() {
    this.head = this.tail = null
  }
}

class Node {
constructor() {
this value = value;
this.next = next || null;
this.prev = prev || null
}
}
Functions

Get/Search

search(value) {
  let currentNode = this.head

  while(currentNode) {
    if(currentNode.value === value) {
      return currentNode
    }
    currentNode = currentNode.next
  }
  return null
}

Add

At Head

addAtHead(value) {
  if(!this.head) {
    // if the list is empty
    this.head = this.tail = new Node(value)
  } else {
    let oldHead = this.head
    this.head = new Node(value)
    oldHead.prev = this.head
    this.head.next = oldHead
  }
}

At Tail

addAtTail (value) {
  if(!this.tail) {
    this.head = this.tail = new Node(value)
  } else {
    let oldTail = this.tail
    this.tail = new Node(value)
    oldTail.next = this.tail
    this.tail.prev = oldTail
  }
}

Delete

At Head

deleteHead() {
  if(!this.head) {
    // if list is empty/has no head
    return null
  } else {
    let removedHead = this.head
    // if list only has one node
    if(this.head === this.tail) {
      this.head = this.tail = null
    } else {
      this.head = this.head.next
      this.head.prev = null
    }
    return removedHead.value
  }
}

At Tail

deleteTail() {
  if(!this.tail) {
    return null
  } else {
    let removedTail = this.tail
    if(this.head === this.tail) {
      this.tail = this.head = null
    } else {
      this.tail = this.tail.prev
      this.tail.next = null
    }
    return removedTail.value
  }
}
Putting It All Together
class LinkedList {
  constructor() {
    this.head = this.tail = null;
  }

  addTail(value) {
    if (!this.tail) {
      this.head = this.tail = new Node(value);
    } else {
      let oldTail = this.tail;
      this.tail = new Node(value);
      oldTail.next = this.tail;
      this.tail.prev = oldTail;
    }
  }

  addHead(value) {
    if (!this.head) {
      this.head = this.tail = new Node(value);
    } else {
      let oldHead = this.head;
      this.head = new Node(value);
      oldHead.prev = this.head;
      this.head.next = oldHead;
    }
  }

  deleteHead() {
    if (!this.head) {
      return null;
    } else {
      let removedHead = this.head;
      if (this.head === this.tail) {
        this.head = this.tail = null;
      } else {
        this.head = this.head.next;
        this.head.prev = null;
      }
      return removedHead.value;
    }
  }

  deleteTail() {
    if (!this.tail) {
      return null;
    } else {
      let removedTail = this.tail;
      if (this.head === this.tail) {
        this.tail = this.head = null;
      } else {
        this.tail = this.tail.prev;
        this.tail.next = null;
      }
      return removedTail.value;
    }
  }

  search(value) {
    let currentNode = this.head;

    while (currentNode) {
      if (currentNode.value === value) {
        return currentNode;
      }
      currentNode = currentNode.next;
    }

    return null;
  }
}

class Node {
  constructor(value, prev, next) {
    this.value = value;
    this.next = next || null;
    this.prev = prev || null;
  }
}

const list = new LinkedList();

list.addTail(1);
list.addHead(3);

list.search(1);
list.search(3);

list.deleteHead();
list.deleteTail();

Doubly Linked List

Structure
class LinkedList {
constructor() {
  this.head = this.tail = null
  this.length = 0;
}
}

class Node {
constructor() {
this value = value;
this.next = next || null;
this.prev = prev || null
}
}
Functions

Get/Search

search(value) {
  let currentNode = this.head

  while(currentNode) {
    if(currentNode.value === value) {
      return currentNode
    }
    currentNode = currentNode.next
  }
  return null
}

Add

At Head

addHead() {
  this.length++
  let newNode = Node(value)

  if(this.head) {
    this.head.prev = newNode
    newNode.next - this.head
    this.head = newNode
    return newNode
  }
   this.head = this.tail = newNode
   return newNode
}

At Tail

addAtTail (value) {
 this.length++
 let newNode = Node(value)

 if(this.tail) {
  this.tail.next = newNode
  newNode.prev = this.tail
  this.tail = newNode
  return newNode
 }

 this.head = this.tail = newNode
 return newNode
}

Delete

At Head

deleteHead() {
  if(this.head) {
    this.length--
    const removedHead = this.head
    this.head = this.head.next

    if(this.head) {
      this.head.prev = null
    } else {
      this.tail = null
    }
    return removedHead
  }
  return undefined
}

At Tail

deleteTail() {
  if(this.tail) {
    this.length--
    const removedTail = this.tail
    this.tail = this.tail.prev

    if(this.tail) {
      this.tail.next = null
    } else {
      this.head = null
    }
    return removedTail
  }
  return undefined
}

Circular Linked List

Structure
class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

class LinkedList {
constructor(value) {
this.head = this.tail = null;
this.length = 0;
}

  if(value) {
    this.initialise(value)
  }

}
Functions

Initialise Circular Linked List

initialise(value) {
const newNode = new Node(value)
newNode.next = newNode
this.head = newNode
this.tail = newNode
this.length++
}

Add

At Head

addHead(value) {
  if(this.length === 0) {
    return this.initialise(value)
  }
  const newNode = new Node(value)
  newNode.next = this.head
  this.tail.next = newNode
  this.head = newNode
  this.length++
}

At Tail

addTail(value) {
  if(this.length === 0) {
    return this.initialise(value)
  }
  const newNode = new Node(value)
  newNode.next = this.head
  this.tail.next = newNode
  this.tail = newNode
  this.length++
}

At Index

 traverseToIndex(index) {
    if (index < 0) return undefined
    // keeps track of traversal
    let counter = 0
    // starting point
    let currentNode = this.head

    // traverse to the target index
    while (counter !== index) {
      currentNode = currentNode.next
      counter++
    }

    return currentNode
  }

insert(index, value) {
  if(index === 0) {
    return this.addHead(value)
  }
  if(!index) return 'index missing'
  if(typeof index !== 'number') return 'index needs to be a number'
  if(index < 0) return 'index needs to be bigger than 0
  if(index >= this.length) {
    return this.append(value)
  }
  const newNode = newNode(value, null)
  const prevIndex = this.traverseToIndex(index-1)
  const targetIndex = prevIndex.next
  prevIndex.next = newNode
  newNode.next = targetIndex
  this.length++
  return this
}

Deleted

At Head

deleteHead() {
  if(this.length === 0) return 'list is empty'
  const currHead = this.head

  if(this.length === 1) {
    const headVal = this.head.value
    this.head = null
    this.tail = null
    this.length--
    return headVal
  }

  const headVal = this.head.value
  const newHead = this.head.next
  this.head = newHead
  this.tail.next = this.head
  this.length--
  return headVal
}

At Tail

deleteTail() {
  if(this.length === 0) return 'list empty'

  if(this.length === 1) {
    const headVal = this.head.value
    this.head = null
    this.tail = null
    this.length--
    return headVal
  }
  const tailVal = this.tail.value
  const newTail = this.traverseToIndex(this.length - 2)
  newTail.next = this.head
  this.tail.next = newTail
  this.length--
  return tailVal
}

At Index

delete(index) {
  if (!index) return 'index missing'
  if (typeof index !== number) return 'index should be a number'
  if (index < 0) return 'index should be bigger than 0'

  if (this.length === 2) {
    if (index === 0) {
      return this.deleteHead()
    }
    if (index > 0) {
      reutrn this.deleteTail()
    }
  }

  let removalType
  if (index === 0) {
    removalType = 'head'
  } else if (index >= this.length) {
    removalType = 'tail'
  } else {
    removalType = 'middle'
  }
  if (removalType === 'head') return this.deleteHead()
  if (removalType === 'tail') return this.deleteTail()
  if (removalType === 'middle') {
    const prevIndex = this.traverseToIndex(index - 1)
    const targetIndex = prevIndex.next
    const targetVal = targetIndex.value
    prevIndex.next = targetIndex.next
    this.length--
    return targetVal
  }
}

Big-O

Cost of accessing elements: O(n)

Insert/remove from beginning: O(1)

Insert/remove from end: O(n)

Insert/remove from middle: O(n)

Problem Sets

Easy

Remove Duplications From Linked List

[algoexpert]

You are given the head of a singly Linked List whose nodes are in osrted order. Write a function that returns a modified version of the Linked List that doesn’t contain any nodes with duplicate values. The Linked List should be modified in place (you don’t need to create a new list) and the modified Linked List should still have it’s nodes sorted.

Sample Input

1 -> 3 -> 3 -> 4 -> 6 -> 6 -> 8

Sample Output

1 -> 3 -> 4 -> 6 -> 8

Code Given

// This is an input class. Do not edit.
class LinkedList {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

function removeDuplicatesFromLinkedList(linkedList) {
  // Write your code here.
  return null;
}

Answer

function removeDuplicatesFromLinkedList(linkedList) {
  let node = linkedList;
  while (node.next) {
    if (node.value === node.next.value) {
      node.next = node.next.next;
    } else {
      node = node.next;
    }
  }
  return linkedList;
}
Medium

Merging Linked Lists

[algoexpert]

You are given two Linked Lists of potentially unequal length. These Linked Lists potentially merge at a shared intersection node. Write a function that returns the intersection node or returns None/null if there isn’t an intersection.

Sample Input

linkedListOne: 2 -> 3 -> 1 -> 4
linked ListTwo: 5 -> 4 -> 1 -> 4

Sample Output

1 -> 4

Code Given

// This is an input class. Do not edit.
class LinkedList {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

function mergingLinkedLists(linkedListOne, linkedListTwo) {
  // Write your code here.
  return null;
}

Answer

function mergingLinkedLists(linkedListOne, linkedListTwo) {
  let currNodeOne = linkedListOne;
  let currNodeTwo = linkedListTwo;

  while (currNodeOne !== currNodeTwo) {
    if (currNodeOne === null) {
      currNodeOne = linkedListTwo;
    } else {
      currNodeOne = currNodeOne.next;
    }

    if (currNodeTwo === null) {
      currNodeTwo = linkedListOne;
    } else {
      currNodeTwo = currNodeTwo.next;
    }
  }
  return currNodeOne;
}
Hard

[leetcode 430]

You are given a doubly linked list, which contains nodes that have a next pointer, a previous pointer, and an additional child pointer. This child pointer may or may not point to a separate doubly linked list, also containing these special nodes. These child lists may have one or more children of their own, and so on, to produce a multilevel data structure as shown in the example below.

Given the head of the first level of the list, flatten the list so that all the nodes appear in a single-level, doubly linked list. Let curr be a node with a child list. The nodes in the child list should appear after curr and before curr.next in the flattened list.

Return the head of the flattened list. The nodes in the list must have all of their child pointers set to null.

Sample Input

head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]

Sample Output

[1,2,3,7,8,11,12,9,10,4,5,6]
1---2---3---4---5---6--NULL
         |
         7---8---9---10--NULL
             |
             11--12--NULL
[1,2,3,4,5,6,null]
[7,8,9,10,null]
[11,12,null]

To merge…

[1,    2,    3, 4, 5, 6, null]
             |
[null, null, 7,    8, 9, 10, null]
                   |
[            null, 11, 12, null]
[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]

Code given

/**
 * // Definition for a Node.
 * function Node(val,prev,next,child) {
 *    this.val = val;
 *    this.prev = prev;
 *    this.next = next;
 *    this.child = child;
 * };
 */

/**
 * @param {Node} head
 * @return {Node}
 */
var flatten = function (head) {};

Answer

function flatten(head) {
  let newHead = head;
  while (newHead) {
    if (head.child) {
      let curr = newHead.next;
      newHead.next = flatten(newHead.child);
      newHead.next.prev = newHead;
      newHead.child = null;
      while (newHead.next !== null) {
        newHead = newHead.next;
      }
      newHead.next = curr;
      if (curr) {
        curr.prev = newHead;
      }
      newHead = newHead.next;
    }
    return head;
  }
}

The code you see here is pulled from different sources but do the same thing in different ways