Giới thiệu

Linked List (Danh sách được liên kết) là một cấu trúc dữ liệu bao gồm một chuỗi các phần tử, trong đó mỗi phần tử được gọi là một nút và mỗi nút có hai trường: trường dữ liệu lưu trữ phần tử và trường liên kết lưu trữ địa chỉ của nút tiếp theo . Nút đầu tiên được gọi là đầu head và nút cuối cùng được gọi là đuôi tail và trường liên kết của đuôi trỏ đến null.

Một Linked List khi triển khai thủ công trong Java sẽ gồm có 2 class là NodeLinkedList. Chú ý class Node chỉ lưu giá trị kiểu int, rất phổ biến trong các bài LeetCode trong thực tế thì sẽ đa dạng hơn rất nhiều.

Thực tế bạn có thể dùng sẵn class LinkedList trong package java.util. Class này được thiết kế để chấp nhận bất kỳ kiểu giá trị mà Node sẽ lưu trữ.
LinkedList

Node

Khai báo Node lưu giá trị kiểu int

public class Node {
  int val;
  Node next;

  Node(int val) {  //Constructor chỉ khởi tạo giá trị val
    this.val = val;
  }

  Node(int val, Node next) {  //Constructor khởi tạo giá trị và biến 
    this.val = val;
    this.next = next;
  }
}
java

Khai báo Node lưu kiểu bất kỳ sử dụng generic type <T>

class Node<T> {
    T data;
    Node<T> next;

    Node(T data) {
        this.data = data;
        next = null;
    }
}
java

LinkedList

public class LinkedList {
  Node head;
  public LinkedList(Node head) {
    this.head = head;
  }
}
java

Các thao tác lên LinkedList

Hầu hết các thao tác lên LinkedList đều là duyệt tuần tự từ phần tử đầu tiên head đến cuối. Độ phức tạp thuật toán là O(n)O(n)O(n)

1. Thêm một phần tử vào Linked List

Duyệt qua danh sách cho đến khi tìm thấy nút cuối cùng, sau đó tạo một nút mới với dữ liệu mong muốn và đặt trường tiếp theo của nó thành null

public void append(int data) {
     Node newNode = new Node(data);
     if (head == null) { // if the list is empty, set the new node as the head
         head = newNode;
         return;
     }
     Node current = head;
     while (current.next != null) { // traverse the list until the last node
         current = current.next;
     }
     current.next = newNode; // set the new node as the next node of the last node
}
java

2. Đếm số phần tử trong LinkedList

Cách đơn giản nhất là duyệt từ phần tử head cho đến phần tử cuối cùng (trước null), rồi tăng dần biến count. Cách này không nhanh, nhưng luôn đúng.

Cách thứ 2 nhanh hơn nhưng phức tạp hơn là luôn lưu biến thuộc tính trong LinkedList, rồi tăng hay giảm tuỳ theo tháo tác lên LinkedList.

public int size() {
     int count = 0;
     Node current = head;
     while (current != null) {
         count++;
         current = current.next;
     }
     return count;
}
java

3. Xoá một phần tử trong LinkedList

Có 3 kiểu xoá phần tử trong LinkedList

  1. Xoá 1 phần tử đầu tiên chứa giá trị val bằng với tham số đầu vào
  2. Xoá tất cả các phần tử có chứa giá trị val bằng với tham số đầu vào
  3. Xoá phần tử là chính phần tử truyền trong tham số đầu vào

Ví dụ này chỉ minh hoạ cho kiểu số 1

public void remove(int data) {
     if (head == null) {
         return;
     }
     
     if (head.data == data) {
         head = head.next;
         return;
     }
     // Duyệt để xoá
     Node current = head;
     while (current.next != null) {
         if (current.next.data == data) {
             current.next = current.next.next;
             return;
         }
         current = current.next;
     }
}
java

4. Chuyển một LinkedList ra String

public String toString() {
  StringBuilder sb = new StringBuilder();
  Node current = head;
  while (current != null) {
      sb.append(current.data);
      sb.append(" - ");
      current = current.next;
  }
  return sb.toString();
}
java

Kết quả xuất ra chuỗi sẽ có dạng "1 - 4 - 3 - 12 - 5 - 8 - "
Hãy nghĩ cách để loại dấu " - " ở phần tử cuối cùng.

5. Chèn một phần tử đồng thời sắp xếp LinkedList

Khi chèn một phần tử đồng thời sắp xếp tăng dần theo giá trị data trong Node

public void insertSorted(int data) {
     Node newNode = new Node(data);
     if (head == null || head.data >= newNode.data) {
         newNode.next = head;
         head = newNode;
         return;
     }
     Node current = head;
     while (current.next != null && current.next.data < newNode.data) {
         current = current.next;
     }
     newNode.next = current.next;
     current.next = newNode;
}
java

6. Tìm kiếm một phần tử

Nếu tìm thấy thì trả về phần tử, không tìm thấy trả về null

public Node search(int data) {
  Node current = head;
  while (current != null) {
      if (current.data == data) {
          return current;
      }
      current = current.next;
  }
  return null;
}
java

7. Kiểm tra một phần tử có trong LinkedList không?

public boolean contains(int data) {
  Node current = head;
  while (current != null) {
      if (current.data == data) {
          return true;
      }
      current = current.next;
  }
  return false;
}
java

8. Xoá phần tử có giá trị lặp lại trong LinkedList

Sử dụng HashSet để lưu các phần tử lần đầu xuất hiện, nếu phần tử current đó lặp lai (có giá trị trùng với phần tử trong HashSet), thì chỉnh lại biến previous trước đây trỏ vào current nay bỏ qua trỏ thẳng sang current.next

public void removeDuplicates() {
  HashSet<Integer> set = new HashSet<>();
  Node current = head;
  Node previous = null;
  while (current != null) {
      if (set.contains(current.data)) {
          previous.next = current.next;
      } else {
          set.add(current.data);
          previous = current;
      }
      current = current.next;
  }
}
java