Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 25

Singly Linked List Fundamentals

Recommended resources

Hello! We're now moving into a new module focusing on fundamental pointer-based data structures. In the last module, we tackled advanced string searching with the KMP algorithm. Now, we'll take a step back to build one of the most essential data structures from the ground up: the Linked List.

Your goal for this lesson is to implement a singly linked list with insertion, deletion, and traversal operations. Mastering this is a cornerstone for technical interviews, as it tests your understanding of pointers, memory management, and edge cases. We will build a complete SinglyLinkedList class in Java, piece by piece.

1. What is a Linked List?

Unlike an array, which stores elements in a single contiguous block of memory, a linked list is a collection of elements called nodes. Each node is a separate object that contains two key pieces of information:

  1. Data: The actual value stored in the node.
  2. Next: A reference (or "pointer") to the next node in the sequence.

The first node is called the head, and the last node's next reference points to null, signifying the end of the list.

For a great conceptual overview of why we use linked lists and how they differ from arrays, let's watch the beginning of a detailed tutorial.

Linked List Tutorial - Singly + Doubly + Circular (Theory + Code + Implementation)

The video 'Linked List Tutorial' by Kunal Kushwaha provides an excellent introduction to the concept of linked lists, the structure of a node, and the role of head and tail pointers.

Watch from 02:26 to 10:32. This section explains the limitations of arrays that linked lists solve, the visual representation of a linked list, and the basic anatomy of a node.


2. Building the Foundation in Java

To implement this in Java, we typically use two classes:

  1. A private inner Node class to represent each element.
  2. The main LinkedList class to manage the overall structure, keeping track of the head, tail, and size.

Let's see how this structure is set up in code.

Linked List Tutorial - Singly + Doubly + Circular (Theory + Code + Implementation)

Continuing with the same video, let's look at the initial Java implementation of the Node and LinkedList classes.

Watch from 12:26 to 16:16. The instructor will set up the private Node class with value and next fields, and the LinkedList class with head, tail, and size attributes.

Here is the basic structure we will build upon. This organization encapsulates the logic and is a standard approach you'll see in interview problems.

public class SinglyLinkedList {

    private Node head;
    private Node tail;
    private int size;

    public SinglyLinkedList() {
        this.size = 0;
    }

    // All our methods (insert, delete, etc.) will go here.

    private class Node {
        private int value;
        private Node next;

        public Node(int value) {
            this.value = value;
        }

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }
}

3. Core Operations: Traversal, Insertion, and Deletion

Now, let's implement the essential operations. For each operation, it's crucial to visualize how the pointers (next, head, tail) are manipulated.

Traversal (Displaying the List)

To print the elements, we must traverse the list from head to tail. A common mistake is to move the head pointer itself, which would permanently alter the list. Instead, we use a temporary pointer.

Linked List Tutorial - Singly + Doubly + Circular (Theory + Code + Implementation)

Let's learn how to traverse and display the linked list without losing our reference to the head.

Watch from 22:29 to 29:21. Pay close attention to the use of a temp node to iterate through the list and the while (temp != null) loop condition.

Insertion

We'll cover the three main insertion scenarios: at the beginning, at the end, and at a specific position.

  1. Insert at the Beginning (insertFirst): This is an efficient O(1) operation.

    • Create a new node.
    • Set the new node's next to the current head.
    • Update head to point to the new node.
    • Edge Case: If the list was empty, tail must also be set to the new node.
  2. Insert at the End (insertLast): Thanks to our tail pointer, this is also an O(1) operation.

    • Create a new node.
    • Set the current tail's next to the new node.
    • Update tail to point to the new node.
    • Edge Case: If the list was empty, this is the same as inserting first.
Insertion at End in Singly Linked List in Java
This diagram illustrates inserting a node at the end. The `next` pointer of the current last node is changed from `null` to point to the new node, which then becomes the new `tail`.
  1. Insert at a Specific Index (insert): This is an O(N) operation as it may require traversal.
    • Traverse to the node just before the target position.
    • Create a new node.
    • Set the new node's next to the previous node's next.
    • Set the previous node's next to the new node.
Insertion in Singly Linked List
This diagram shows insertion in the middle. To insert node 30 after 20, you make 20's `next` point to 30, and 30's `next` point to 40.

Let's watch the implementation of these three insertion methods.

Linked List Tutorial - Singly + Doubly + Circular (Theory + Code + Implementation)

The following sections of the video walk through the code for each insertion type.

Please watch these three segments: Insert First (16:16 - 22:29): Covers insertion at the head. Insert Last (29:21 - 33:45): Shows how the tail pointer enables O(1) insertion at the end. Insert at Index (33:45 - 39:42): Demonstrates the traversal and pointer manipulation for inserting at a specific position.

Deletion

Like insertion, deletion has three main scenarios. Remember to update the size and handle the edge case where the list becomes empty (updating both head and tail to null).

  1. Delete First (deleteFirst): An O(1) operation.

    • Move head to head.next.
  2. Delete Last (deleteLast): This is a classic interview question for singly linked lists. It is an O(N) operation.

    • Since we can't go backward from tail, we must traverse from the head to find the second-to-last node.
    • Set the second-to-last node's next to null.
    • Update tail to be the second-to-last node.
  3. Delete at a Specific Index (delete): An O(N) operation.

    • Traverse to the node just before the one to be deleted.
    • Update its next pointer to "skip over" the deleted node (i.e., previous.next = previous.next.next).

Let's see the code for these deletion methods.

Linked List Tutorial - Singly + Doubly + Circular (Theory + Code + Implementation)

The final segments cover the implementation of the three deletion methods.

Please watch these three segments: Delete First (39:42 - 42:59): The simplest deletion. Delete Last (42:59 - 47:32): Note why this operation takes O(N) time. Delete at Index (47:32 - 50:49): Similar logic to insertion, involving finding the previous node.

Test your understanding!

Imagine you have an empty SinglyLinkedList. You perform the following operations in order:

  1. insertLast(10)
  2. insertLast(20)
  3. insertFirst(5)
  4. insert(30, 2) (insert 30 at index 2)
  5. deleteLast()
  6. deleteFirst()

What is the final state of the list? What are the values of head.value and tail.value?

Show answer

Let's trace the state of the list:

  1. insertLast(10): [10]. head is 10, tail is 10.
  2. insertLast(20): [10, 20]. head is 10, tail is 20.
  3. insertFirst(5): [5, 10, 20]. head is 5, tail is 20.
  4. insert(30, 2): [5, 10, 30, 20]. head is 5, tail is 20.
  5. deleteLast(): [5, 10, 30]. head is 5, tail is 30.
  6. deleteFirst(): [10, 30]. head is 10, tail is 30.

The final list is 10 -> 30 -> null.
head.value is 10.
tail.value is 30.


4. Complexity Analysis

Understanding the time and space complexity is crucial for interviews.

Operation Time Complexity Space Complexity Notes
Access (get by index) O(N) O(1) Must traverse from the head.
Search (by value) O(N) O(1) Must traverse from the head.
Insertion (at beginning) O(1) O(1) Simple head pointer update.
Insertion (at end) O(1) O(1) Possible because we maintain a tail pointer.
Insertion (at index) O(N) O(1) Worst case requires traversing to the end.
Deletion (at beginning) O(1) O(1) Simple head pointer update.
Deletion (at end) O(N) O(1) Must traverse to find the second-to-last node.
Deletion (at index) O(N) O(1) Worst case requires traversing to the end.

For a concise review of these complexities, the following video is helpful.

Singly Linked List | Insert, Delete, Complexity Analysis

The 'Singly Linked List' video from Blue Tree Code has an excellent final section dedicated to complexity analysis.

Watch from 12:14 to 14:34. This provides a clear summary of the time and space complexity for all the operations we've discussed.


Conclusion

Congratulations! You have now implemented a singly linked list from scratch, including its most common operations. This is a significant first step in mastering pointer-based data structures.

Key Takeaways:

  • A singly linked list is composed of nodes, each with data and a next pointer.
  • The head pointer marks the start, and a null next pointer marks the end.
  • Maintaining a tail pointer allows for efficient O(1) insertion at the end.
  • Operations that require finding a specific index or the end of the list without a tail pointer (like deleteLast) take O(N) time due to traversal.
  • Pointer manipulation requires careful handling of edge cases, such as an empty list or a list with a single node.

Preview of the next lesson:
In this lesson, you saw that some operations, like deleting the last element, were inefficient. In our next lesson, we will address this by exploring the doubly linked list. You will learn to implement a doubly linked list with insertion, deletion, and traversal operations and see how adding a previous pointer can optimize certain actions.