Algorithm and data structure interview guide - detailed explanation of linked lists

linked list

Memory space is a common resource for all programs. In a complex system operating environment, free memory space may be scattered throughout the memory. We know that the memory space for storing arrays must be continuous, and when the array is very large, the memory may not be able to provide such a large continuous space. At this time, the flexibility advantage of the linked list is reflected.

"Linked list" is a linear data structure in which each element is a node object, and each node is connected through "references". The reference records the memory address of the next node through which the next node can be accessed from the current node.

The design of the linked list allows each node to be stored dispersedly throughout the memory, and their memory addresses do not need to be consecutive.

Insert image description here

Observing the figure above, the unit of the linked list is the "node" object. Each node contains two pieces of data: the node's "value" and a "reference" to the next node.

  • The first node of the linked list is called the "head node" and the last node is called the "tail node".
  • The tail node points to "null", which is recorded as null \text{null} in Java, C++ and Python respectively.null nullptr \text{nullptr} nullptr None \text{None} None
  • In languages ​​that support pointers, such as C, C++, Go and Rust, the above "reference" should be replaced by "pointer".

ListNodeAs shown in the following code, in addition to containing the value, the linked list node also needs to save an additional reference (pointer). Therefore, for the same amount of data, a linked list takes up more memory space than an array .

=== “Python”

```python title=""
class ListNode:
    """链表节点类"""
    def __init__(self, val: int):
        self.val: int = val                  # 节点值
        self.next: Optional[ListNode] = None # 指向下一节点的引用
```

=== “C++”

```cpp title=""
/* 链表节点结构体 */
struct ListNode {
    int val;         // 节点值
    ListNode *next;  // 指向下一节点的指针
    ListNode(int x) : val(x), next(nullptr) {}  // 构造函数
};
```

=== “Java”

```java title=""
/* 链表节点类 */
class ListNode {
    int val;        // 节点值
    ListNode next;  // 指向下一节点的引用
    ListNode(int x) { val = x; }  // 构造函数
}
```

=== “C#”

```csharp title=""
/* 链表节点类 */
class ListNode {
    int val;         // 节点值
    ListNode next;   // 指向下一节点的引用
    ListNode(int x) => val = x;  //构造函数
}
```

=== “Go”

```go title=""
/* 链表节点结构体 */
type ListNode struct {
    Val  int       // 节点值
    Next *ListNode // 指向下一节点的指针
}

// NewListNode 构造函数,创建一个新的链表
func NewListNode(val int) *ListNode {
    return &ListNode{
        Val:  val,
        Next: nil,
    }
}
```

=== “Swift”

```swift title=""
/* 链表节点类 */
class ListNode {
    var val: Int // 节点值
    var next: ListNode? // 指向下一节点的引用

    init(x: Int) { // 构造函数
        val = x
    }
}
```

=== “JS”

```javascript title=""
/* 链表节点类 */
class ListNode {
    val;
    next;
    constructor(val, next) {
        this.val = (val === undefined ? 0 : val);       // 节点值
        this.next = (next === undefined ? null : next); // 指向下一节点的引用
    }
}
```

=== “TS”

```typescript title=""
/* 链表节点类 */
class ListNode {
    val: number;
    next: ListNode | null;
    constructor(val?: number, next?: ListNode | null) {
        this.val = val === undefined ? 0 : val;        // 节点值
        this.next = next === undefined ? null : next;  // 指向下一节点的引用
    }
}
```

=== “Dart”

```dart title=""
/* 链表节点类 */
class ListNode {
  int val; // 节点值
  ListNode? next; // 指向下一节点的引用
  ListNode(this.val, [this.next]); // 构造函数
}
```

=== “Rust”

```rust title=""
use std::rc::Rc;
use std::cell::RefCell;
/* 链表节点类 */
#[derive(Debug)]
struct ListNode {
    val: i32, // 节点值
    next: Option<Rc<RefCell<ListNode>>>, // 指向下一节点的指针
}
```

=== “C”

```c title=""
/* 链表节点结构体 */
struct ListNode {
    int val;               // 节点值
    struct ListNode *next; // 指向下一节点的指针
};

typedef struct ListNode ListNode;

/* 构造函数 */
ListNode *newListNode(int val) {
    ListNode *node, *next;
    node = (ListNode *) malloc(sizeof(ListNode));
    node->val = val;
    node->next = NULL;
    return node;
}
```

=== “Zig”

```zig title=""
// 链表节点类
pub fn ListNode(comptime T: type) type {
    return struct {
        const Self = @This();

        val: T = 0, // 节点值
        next: ?*Self = null, // 指向下一节点的指针

        // 构造函数
        pub fn init(self: *Self, x: i32) void {
            self.val = x;
            self.next = null;
        }
    };
}
```

Common operations on linked lists

Initialize linked list

There are two steps to establish a linked list. The first step is to initialize each node object, and the second step is to build a reference pointing relationship. After the initialization is completed, we can start from the head node of the linked list and nextaccess all nodes in sequence through reference points.

=== “Python”

```python title="linked_list.py"
# 初始化链表 1 -> 3 -> 2 -> 5 -> 4
# 初始化各个节点
n0 = ListNode(1)
n1 = ListNode(3)
n2 = ListNode(2)
n3 = ListNode(5)
n4 = ListNode(4)
# 构建引用指向
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
```

=== “C++”

```cpp title="linked_list.cpp"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点
ListNode* n0 = new ListNode(1);
ListNode* n1 = new ListNode(3);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(5);
ListNode* n4 = new ListNode(4);
// 构建引用指向
n0->next = n1;
n1->next = n2;
n2->next = n3;
n3->next = n4;
```

=== “Java”

```java title="linked_list.java"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点
ListNode n0 = new ListNode(1);
ListNode n1 = new ListNode(3);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(5);
ListNode n4 = new ListNode(4);
// 构建引用指向
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
```

=== “C#”

```csharp title="linked_list.cs"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点
ListNode n0 = new ListNode(1);
ListNode n1 = new ListNode(3);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(5);
ListNode n4 = new ListNode(4);
// 构建引用指向
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
```

=== “Go”

```go title="linked_list.go"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点
n0 := NewListNode(1)
n1 := NewListNode(3)
n2 := NewListNode(2)
n3 := NewListNode(5)
n4 := NewListNode(4)
// 构建引用指向
n0.Next = n1
n1.Next = n2
n2.Next = n3
n3.Next = n4
```

=== “Swift”

```swift title="linked_list.swift"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点
let n0 = ListNode(x: 1)
let n1 = ListNode(x: 3)
let n2 = ListNode(x: 2)
let n3 = ListNode(x: 5)
let n4 = ListNode(x: 4)
// 构建引用指向
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
```

=== “JS”

```javascript title="linked_list.js"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点
const n0 = new ListNode(1);
const n1 = new ListNode(3);
const n2 = new ListNode(2);
const n3 = new ListNode(5);
const n4 = new ListNode(4);
// 构建引用指向
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
```

=== “TS”

```typescript title="linked_list.ts"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点
const n0 = new ListNode(1);
const n1 = new ListNode(3);
const n2 = new ListNode(2);
const n3 = new ListNode(5);
const n4 = new ListNode(4);
// 构建引用指向
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
```

=== “Dart”

```dart title="linked_list.dart"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */\
// 初始化各个节点
ListNode n0 = ListNode(1);
ListNode n1 = ListNode(3);
ListNode n2 = ListNode(2);
ListNode n3 = ListNode(5);
ListNode n4 = ListNode(4);
// 构建引用指向
n0.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
```

=== “Rust”

```rust title="linked_list.rs"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点
let n0 = Rc::new(RefCell::new(ListNode { val: 1, next: None }));
let n1 = Rc::new(RefCell::new(ListNode { val: 3, next: None }));
let n2 = Rc::new(RefCell::new(ListNode { val: 2, next: None }));
let n3 = Rc::new(RefCell::new(ListNode { val: 5, next: None }));
let n4 = Rc::new(RefCell::new(ListNode { val: 4, next: None }));

// 构建引用指向
n0.borrow_mut().next = Some(n1.clone());
n1.borrow_mut().next = Some(n2.clone());
n2.borrow_mut().next = Some(n3.clone());
n3.borrow_mut().next = Some(n4.clone());
```

=== “C”

```c title="linked_list.c"
/* 初始化链表 1 -> 3 -> 2 -> 5 -> 4 */
// 初始化各个节点
ListNode* n0 = newListNode(1);
ListNode* n1 = newListNode(3);
ListNode* n2 = newListNode(2);
ListNode* n3 = newListNode(5);
ListNode* n4 = newListNode(4);
// 构建引用指向
n0->next = n1;
n1->next = n2;
n2->next = n3;
n3->next = n4;
```

=== “Zig”

```zig title="linked_list.zig"
// 初始化链表
// 初始化各个节点
var n0 = inc.ListNode(i32){.val = 1};
var n1 = inc.ListNode(i32){.val = 3};
var n2 = inc.ListNode(i32){.val = 2};
var n3 = inc.ListNode(i32){.val = 5};
var n4 = inc.ListNode(i32){.val = 4};
// 构建引用指向
n0.next = &n1;
n1.next = &n2;
n2.next = &n3;
n3.next = &n4;
```

The array as a whole is a variable, for example, the array numscontains elements nums[0]and nums[1]etc., while the linked list is composed of multiple independent node objects. We usually use the head node as a representative name for a linked list . For example, the linked list in the above code can be recorded as a linked list n0.

Insert node

Inserting nodes into a linked list is very easy. As shown in the figure below, assuming we want to insert a new node between two adjacent nodes n0and , we only need to change the two node references (pointers) , and the time complexity is O ( 1 ) O(1)n1PO(1)

In comparison, the time complexity of inserting an element into an array is O ( n ) O(n)O ( n ) , which is less efficient under large data volumes.

Insert image description here

=== “Python”

```python title="linked_list.py"
[class]{}-[func]{insert}
```

=== “C++”

```cpp title="linked_list.cpp"
[class]{}-[func]{insert}
```

=== “Java”

```java title="linked_list.java"
[class]{linked_list}-[func]{insert}
```

=== “C#”

```csharp title="linked_list.cs"
[class]{linked_list}-[func]{insert}
```

=== “Go”

```go title="linked_list.go"
[class]{}-[func]{insertNode}
```

=== “Swift”

```swift title="linked_list.swift"
[class]{}-[func]{insert}
```

=== “JS”

```javascript title="linked_list.js"
[class]{}-[func]{insert}
```

=== “TS”

```typescript title="linked_list.ts"
[class]{}-[func]{insert}
```

=== “Dart”

```dart title="linked_list.dart"
[class]{}-[func]{insert}
```

=== “Rust”

```rust title="linked_list.rs"
[class]{}-[func]{insert}
```

=== “C”

```c title="linked_list.c"
[class]{}-[func]{insert}
```

=== “Zig”

```zig title="linked_list.zig"
[class]{}-[func]{insert}
```

Delete node

As shown in the figure below, it is also very convenient to delete nodes in the linked list. You only need to change the reference (pointer) of a node .

PPlease note that although the node still points to it after the deletion operation is completed n1, it is actually inaccessible by traversing the linked list P, which means that Pit no longer belongs to the linked list.

Insert image description here

=== “Python”

```python title="linked_list.py"
[class]{}-[func]{remove}
```

=== “C++”

```cpp title="linked_list.cpp"
[class]{}-[func]{remove}
```

=== “Java”

```java title="linked_list.java"
[class]{linked_list}-[func]{remove}
```

=== “C#”

```csharp title="linked_list.cs"
[class]{linked_list}-[func]{remove}
```

=== “Go”

```go title="linked_list.go"
[class]{}-[func]{removeNode}
```

=== “Swift”

```swift title="linked_list.swift"
[class]{}-[func]{remove}
```

=== “JS”

```javascript title="linked_list.js"
[class]{}-[func]{remove}
```

=== “TS”

```typescript title="linked_list.ts"
[class]{}-[func]{remove}
```

=== “Dart”

```dart title="linked_list.dart"
[class]{}-[func]{remove}
```

=== “Rust”

```rust title="linked_list.rs"
[class]{}-[func]{remove}
```

=== “C”

```c title="linked_list.c"
[class]{}-[func]{removeNode}
```

=== “Zig”

```zig title="linked_list.zig"
[class]{}-[func]{remove}
```

access node

Accessing nodes in a linked list is inefficient . As mentioned in the previous section, we can do this in O ( 1 ) O(1)Access any element in the array in O ( 1 ) time. This is not the case with linked lists. The program needs to start from the head node and traverse backward one by one until the target node is found. In other words, accessing theiithi nodes require loopi − 1 i − 1i1 round, time complexity isO ( n ) O(n)O ( n )

=== “Python”

```python title="linked_list.py"
[class]{}-[func]{access}
```

=== “C++”

```cpp title="linked_list.cpp"
[class]{}-[func]{access}
```

=== “Java”

```java title="linked_list.java"
[class]{linked_list}-[func]{access}
```

=== “C#”

```csharp title="linked_list.cs"
[class]{linked_list}-[func]{access}
```

=== “Go”

```go title="linked_list.go"
[class]{}-[func]{access}
```

=== “Swift”

```swift title="linked_list.swift"
[class]{}-[func]{access}
```

=== “JS”

```javascript title="linked_list.js"
[class]{}-[func]{access}
```

=== “TS”

```typescript title="linked_list.ts"
[class]{}-[func]{access}
```

=== “Dart”

```dart title="linked_list.dart"
[class]{}-[func]{access}
```

=== “Rust”

```rust title="linked_list.rs"
[class]{}-[func]{access}
```

=== “C”

```c title="linked_list.c"
[class]{}-[func]{access}
```

=== “Zig”

```zig title="linked_list.zig"
[class]{}-[func]{access}
```

Find node

Traverse the linked list, find targetthe node with value in the linked list, and output the index of the node in the linked list. This process is also a linear search.

=== “Python”

```python title="linked_list.py"
[class]{}-[func]{find}
```

=== “C++”

```cpp title="linked_list.cpp"
[class]{}-[func]{find}
```

=== “Java”

```java title="linked_list.java"
[class]{linked_list}-[func]{find}
```

=== “C#”

```csharp title="linked_list.cs"
[class]{linked_list}-[func]{find}
```

=== “Go”

```go title="linked_list.go"
[class]{}-[func]{findNode}
```

=== “Swift”

```swift title="linked_list.swift"
[class]{}-[func]{find}
```

=== “JS”

```javascript title="linked_list.js"
[class]{}-[func]{find}
```

=== “TS”

```typescript title="linked_list.ts"
[class]{}-[func]{find}
```

=== “Dart”

```dart title="linked_list.dart"
[class]{}-[func]{find}
```

=== “Rust”

```rust title="linked_list.rs"
[class]{}-[func]{find}
```

=== “C”

```c title="linked_list.c"
[class]{}-[func]{find}
```

=== “Zig”

```zig title="linked_list.zig"
[class]{}-[func]{find}
```

Array VS linked list

The following table summarizes and compares the characteristics and operation efficiency of arrays and linked lists. Since they adopt two opposite storage strategies, their various properties and operating efficiencies also present opposing characteristics.

Table Efficiency comparison of arrays and linked lists

array linked list
Storage method contiguous memory space discrete memory space
cache locality friendly unfriendly
Capacity expansion Immutable length Flexible to expand
memory efficiency Occupies little memory and wastes some space Taking up a lot of memory
access element O ( 1 ) O(1)O(1) O ( n ) O(n)O ( n )
Add element O ( n ) O(n)O ( n ) O ( 1 ) O(1)O(1)
Delete element O ( n ) O(n)O ( n ) O ( 1 ) O(1)O(1)

Common linked list types

As shown in the figure below, there are three common types of linked lists.

  • One-way linked list : the ordinary linked list introduced above. The nodes of a one-way linked list contain two pieces of data: a value and a reference to the next node. We call the first node the head node, the last node the tail node, and the tail node points to the empty None \text{None}None
  • Circular linked list : If we make the tail node of the one-way linked list point to the head node (that is, connected end to end), we will get a circular linked list. In a circular linked list, any node can be regarded as the head node.
  • Doubly linked list : Compared with singly linked list, doubly linked list records references in both directions. The node definition of a doubly linked list contains references (pointers) to both the successor node (next node) and the predecessor node (previous node). Compared with one-way linked lists, doubly linked lists are more flexible and can traverse the linked list in both directions, but they also require more memory space.

=== “Python”

```python title=""
class ListNode:
    """双向链表节点类"""
    def __init__(self, val: int):
        self.val: int = val                   # 节点值
        self.next: Optional[ListNode] = None  # 指向后继节点的引用
        self.prev: Optional[ListNode] = None  # 指向前驱节点的引用
```

=== “C++”

```cpp title=""
/* 双向链表节点结构体 */
struct ListNode {
    int val;         // 节点值
    ListNode *next;  // 指向后继节点的指针
    ListNode *prev;  // 指向前驱节点的指针
    ListNode(int x) : val(x), next(nullptr), prev(nullptr) {}  // 构造函数
};
```

=== “Java”

```java title=""
/* 双向链表节点类 */
class ListNode {
    int val;        // 节点值
    ListNode next;  // 指向后继节点的引用
    ListNode prev;  // 指向前驱节点的引用
    ListNode(int x) { val = x; }  // 构造函数
}
```

=== “C#”

```csharp title=""
/* 双向链表节点类 */
class ListNode {
    int val;        // 节点值
    ListNode next;  // 指向后继节点的引用
    ListNode prev;  // 指向前驱节点的引用
    ListNode(int x) => val = x;  // 构造函数
}
```

=== “Go”

```go title=""
/* 双向链表节点结构体 */
type DoublyListNode struct {
    Val  int             // 节点值
    Next *DoublyListNode // 指向后继节点的指针
    Prev *DoublyListNode // 指向前驱节点的指针
}

// NewDoublyListNode 初始化
func NewDoublyListNode(val int) *DoublyListNode {
    return &DoublyListNode{
        Val:  val,
        Next: nil,
        Prev: nil,
    }
}
```

=== “Swift”

```swift title=""
/* 双向链表节点类 */
class ListNode {
    var val: Int // 节点值
    var next: ListNode? // 指向后继节点的引用
    var prev: ListNode? // 指向前驱节点的引用

    init(x: Int) { // 构造函数
        val = x
    }
}
```

=== “JS”

```javascript title=""
/* 双向链表节点类 */
class ListNode {
    val;
    next;
    prev;
    constructor(val, next, prev) {
        this.val = val  ===  undefined ? 0 : val;        // 节点值
        this.next = next  ===  undefined ? null : next;  // 指向后继节点的引用
        this.prev = prev  ===  undefined ? null : prev;  // 指向前驱节点的引用
    }
}
```

=== “TS”

```typescript title=""
/* 双向链表节点类 */
class ListNode {
    val: number;
    next: ListNode | null;
    prev: ListNode | null;
    constructor(val?: number, next?: ListNode | null, prev?: ListNode | null) {
        this.val = val  ===  undefined ? 0 : val;        // 节点值
        this.next = next  ===  undefined ? null : next;  // 指向后继节点的引用
        this.prev = prev  ===  undefined ? null : prev;  // 指向前驱节点的引用
    }
}
```

=== “Dart”

```dart title=""
/* 双向链表节点类 */
class ListNode {
    int val;        // 节点值
    ListNode next;  // 指向后继节点的引用
    ListNode prev;  // 指向前驱节点的引用
    ListNode(this.val, [this.next, this.prev]);  // 构造函数
}
```

=== “Rust”

```rust title=""
use std::rc::Rc;
use std::cell::RefCell;

/* 双向链表节点类型 */
#[derive(Debug)]
struct ListNode {
    val: i32, // 节点值
    next: Option<Rc<RefCell<ListNode>>>, // 指向后继节点的指针
    prev: Option<Rc<RefCell<ListNode>>>, // 指向前驱节点的指针
}

/* 构造函数 */
impl ListNode {
    fn new(val: i32) -> Self {
        ListNode {
            val,
            next: None,
            prev: None,
        }
    }
}
```

=== “C”

```c title=""
/* 双向链表节点结构体 */
struct ListNode {
    int val;               // 节点值
    struct ListNode *next; // 指向后继节点的指针
    struct ListNode *prev; // 指向前驱节点的指针
};

typedef struct ListNode ListNode;

/* 构造函数 */
ListNode *newListNode(int val) {
    ListNode *node, *next;
    node = (ListNode *) malloc(sizeof(ListNode));
    node->val = val;
    node->next = NULL;
    node->prev = NULL;
    return node;
}
```

=== “Zig”

```zig title=""
// 双向链表节点类
pub fn ListNode(comptime T: type) type {
    return struct {
        const Self = @This();

        val: T = 0, // 节点值
        next: ?*Self = null, // 指向后继节点的指针
        prev: ?*Self = null, // 指向前驱节点的指针

        // 构造函数
        pub fn init(self: *Self, x: i32) void {
            self.val = x;
            self.next = null;
            self.prev = null;
        }
    };
}
```

Insert image description here

Typical applications of linked lists

One-way linked lists are commonly used to implement data structures such as stacks, queues, hash tables, and graphs.

  • Stack and queue : When the insertion and deletion operations are performed at one end of the linked list, it exhibits the first-in-last-out characteristic, corresponding to the stack; when the insertion operation is performed at one end of the linked list, and the deletion operation is performed at the other end of the linked list, it exhibits The first-in-first-out feature corresponds to the queue.
  • Hash table : The chain address method is one of the mainstream solutions to solve hash conflicts. In this solution, all conflicting elements will be placed in a linked list.
  • Graph : An adjacency list is a common way to represent a graph, in which each vertex of the graph is associated with a linked list, and each element of the linked list represents other vertices connected to that vertex.

Doubly linked lists are often used in scenarios where the previous and next elements need to be quickly found.

  • Advanced data structures : For example, in red-black trees and B-trees, we need to access the parent node of a node. This can be achieved by saving a reference to the parent node in the node, similar to a doubly linked list.
  • Browser history : In a web browser, when a user clicks the forward or back button, the browser needs to know the previous and next web pages the user has visited. The characteristics of doubly linked lists make this operation easy.
  • LRU algorithm : In the cache eviction algorithm (LRU), we need to quickly find the least recently used data and support the rapid addition and removal of nodes. At this time, it is very appropriate to use a doubly linked list.

Circular linked lists are often used in scenarios that require periodic operations, such as operating system resource scheduling.

  • Time slice round robin scheduling algorithm : In the operating system, the time slice round robin scheduling algorithm is a common CPU scheduling algorithm that needs to cycle through a group of processes. Each process is given a time slice, and when the time slice runs out, the CPU switches to the next process. This cyclic operation can be achieved through a circular linked list.
  • Data buffer : In some implementations of data buffers, circular linked lists may also be used. For example, in audio and video players, the data stream may be divided into multiple buffer blocks and put into a circular linked list to achieve seamless playback.

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132865215