Hello, brave friends, hello everyone, I am your little five, the king of the mouth, I am in good health, and my brain is not sick.
I have a wealth of hair loss skills, which can make you a veteran celebrity.
It is my main theme that I will write it when I read it, and it is my characteristic to pick my feet. I am humble with a trace of strength. The stupid blessing of a stupid person is the greatest comfort to me.
Welcome to
fifth of
algorithm Series
list.
Preface
This series of articles is based on the two books "An Illustrated Algorithm" and "Learning JavaScript Algorithms" as the core, and the rest of the materials are supplementary, accompanied by the author's humble opinion. Strive to lead everyone to appreciate the wonders of this algorithmic world in simple and interesting language.
The content of this article is a linked list. The author will lead everyone to js , gradually change to explore other forms of linked lists, and finally attach a few exercises to deepen understanding and consolidate what they have learned.
Linked list JavaScript -prototype chain, article portal 👉 big talk prototype chain .
Introduction to linked lists
The author will give you the vernacular from "What is a linked list" and "What is a linked list used for?"
🦥 What is a linked list
The linked list is like a treasure hunt game, treasures are hidden everywhere on the island. At the beginning of the game, you will get a clue, use the clue in your hand to find the location of the next treasure, and so on.
From this we summarize the characteristics of the down-linked list:
- Treasures are scattered around the island-the storage locations of the elements in the linked list are not continuous in the memory
- Determine the next location through clues-each node consists of the storage element itself and a pointer to the next element, and it is impossible to cross a node to reach the next node
🦥 What is it used for
Used to store data. At this time, is there a big question mark on your little head? Why don't you use arrays to store data?
Let's compare the advantages and disadvantages of the following two data structures in combination with their respective characteristics:
👺 array storage location is continuous, but the linked list is not continuous
What happens if you insert an element in the middle of the array? The array is continuous, just like jumping the queue, and the rest of the people have to go one back; and the linked list storage is not continuous, the rest of the elements do not need to be changed when inserting, and the deletion is the same;
Therefore, it can be concluded that when inserting or deleting elements, linked lists have more advantages than arrays
👺 Array directly accesses elements, while linked list finds elements through pointer
Because the array is stored continuously, we can directly access it through the subscript; while the linked list needs to be searched from the head of the table one by one until it is found;
Therefore, it can be concluded that when looking up elements, arrays have more advantages than linked lists
Linked list implementation
👇 Next we use js to simulate a linked list and add the following method to it:
append(element)
: Add new elements to the end of the linked listinsert(element, position)
: Insert a new element into a specific position in the linked listremove(element)
: Remove an element from the linked listindexOf(element)
: Returns the index of the element in the linked list, or -1 if there is no such elementremoveAt(position)
: Remove an element from a specific position in the linked listisEmpty()
: Whether there are elements in the linked listsize()
: the number of elements in the linked list
For the linked list, whether it is an insert operation or a delete operation, it is the process next
🦥 Structure
First of all, we define the node in the down-linked list, which should include the element itself element
and the pointer to the next node next
class Node<T> {
element: T | null;
next: Node<T> | null;
constructor(element: T | null) {
this.element = element;
this.next = null;
}
}
To clarify the structure of the down-linked list, it needs to have a head node head
and a record length of length
class LinkedList<T> {
head: Node<T> | null = null;
length = 0;
}
🦥 append(element)
If it is empty, it is the head node; if it is not empty, it is the tail node of the linked list;
append(element: T) {
let node = new Node<T>(element);
if (!this.head) {
this.head = node;
} else {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = node;
}
this.length++;
}
🦥 insert(element, position)
Find the insertion position, change the next point to
- position = 0,
node.next -> head
- position > 0,
previous.next -> node, node.next -> current
insert(element: T, position: number) {
if (position < 0 || position > this.length) return false;
let node = new Node<T>(element);
let index = 0;
let current = this.head;
let previous: Node<T> | null = null;
if (position === 0) {
node.next = this.head;
this.head = node;
} else {
while (current && index++ < position) {
previous = current;
current = current.next;
}
(previous as Node<T>).next = node;
node.next = current;
}
this.length++;
return true;
}
🦥 removeAt(position)
The idea is the same as insert
, find the insertion position and change the next point
- position = 0,
head -> head.next
- position > 0,
previous.next -> current.next
removeAt(position: number) {
if (!this.head || position < 0 || position >= this.length) return false;
let index = 0;
let current: Node<T> | null = this.head;
let previous: Node<T> | null = null;
if (position === 0) {
this.head = this.head.next;
} else {
while (current && index++ < position) {
previous = current;
current = current.next;
}
(previous as Node<T>).next = current?.next || null;
}
this.length--;
return true;
}
🦥 indexOf(element)
Traverse the linked list from the beginning
indexOf(element: T) {
let current = this.head;
let index = 0;
while (current) {
if (typeof current.element === 'object') {
if (JSON.stringify(element) === JSON.stringify(current.element)) return index;
} else {
if (element === current.element) return index;
}
current = current.next;
index++;
}
return -1;
}
🦥 remove(element)
Integrate removeAt
and indexOf
remove(element: T) {
let position = this.indexOf(element);
return this.removeAt(position);
}
Doubly linked list
The literal meaning says everything, a two-way linked list; you can choose "traverse from the beginning to the end" or "traverse from the end to the beginning";
Therefore, we add a prev
Node
tail
attribute to the linked list, and rewrite the following three methods append
, insert
, removeAt
🦶tips: Just pay attention to the connection of the pointer, and don’t leave it in one direction.
🦥 append(element)
- If it is empty, then
head = node
,tail = node
- If it is not empty, then
tail.next = node
,node.prev = tail
; then updatetail
append(element: T) {
let node = new Node<T>(element);
if (!this.head || !this.tail) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
}
this.length++;
}
🦥 insert(element, position)
- Insert the head or tail, the idea is the same as adding elements, just consider the boundary value
- Insert the middle, next chain:
prevNode.next = node
->node.next = nextNode
; prev chain:nextNode.prev = node
->node.prev = prevNode
;
insert(element: T, position: number) {
if (position < 0 || position > this.length) return false;
let node = new Node<T>(element);
if (position === 0) {
if (this.head) {
this.head.prev = node;
node.next = this.head;
this.head = node;
} else {
this.head = node;
this.tail = node;
}
}
else if (position === this.length) {
(this.tail as Node<T>).next = node;
node.prev = this.tail;
this.tail = node;
}
else {
let current = this.head;
let previous: Node<T> | null = null;
let index = 0;
while (current && index++ < position) {
previous = current;
current = current.next;
}
(previous as Node<T>).next = node;
node.next = current;
(current as Node<T>).prev = node;
node.prev = previous;
}
this.length++;
}
🦥 removeAt(position)
The idea is basically the same as insert
- If there is only one node in the linked list, the head and tail nodes are respectively assigned null
- Delete the head:
head = head.next
,head.prev = null
, the tail is the same - Delete the middle
preNode.next = nextNode
,nextNode.prev = preNode
removeAt(position: number) {
if (!this.head || position < 0 || position >= this.length) return false;
if (position === 0) {
if (this.length === 1) {
this.head = null;
this.tail = null;
} else {
this.head = this.head.next;
(this.head as Node<T>).prev = null;
}
}
else if (position === this.length - 1) {
this.tail = (this.tail as Node<T>).prev;
(this.tail as Node<T>).next = null;
}
else {
let current: Node<T> | null = this.head;
let previous: Node<T> | null = null;
let index = 0;
while (current && index++ < position) {
previous = current;
current = current.next;
}
if (!previous || !current) return false;
previous.next = current.next;
(current.next as Node<T>).prev = previous;
}
this.length--;
return true;
}
Circular linked list
As the name suggests, linking end to end forms a circular linked list tailNode.next = head
; pay attention to the processing of the boundary value when implementing it, especially the processing involving the first and last elements; the idea is basically the same as the above implementation, you may wish to try it out.
👺 1611b2d45a4e8a feet offer the code of the single-loop linked list to connect : single-loop linked list
Small scale chopper
The following questions are all from LeetCode , the author will provide a solution for each question; this idea is by no means the best, you are welcome to think positively and leave your own unique opinions.
In view of the length of the question, please click on the title to view the specific question.
The format of all the ListNode below is as follows:
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)
}
}
LeetCode 141. Circular Linked List
👺 title brief description
The input parameter is the head node, and it is judged whether it can form a loop.
👺 title analysis
To determine whether a loop is formed, the first idea is to borrow the length. If the length is exceeded, the loop can still be looped.
let index = 0;
let current = head;
while (current) {
if (index > size) return true;
current = current.next;
index++;
}
return false;
But this question only has the head node, so we have to change our thinking; a pointer must not work, there is no condition to break the loop, is it an endless loop if there is a loop; then we use two pointers, one is fast and the other is slow, if you don’t understand it It can be compared to the clock or the running loop. If there is a loop, one fast and one slow must meet.
👺 code implementation
const hasCycle = (head: ListNode | null): boolean => {
let slow = head;
let fast = head;
while (slow && fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
return true;
}
}
return false;
};
LeetCode 2. Add two numbers
👺 Brief description of the title
The following figure shows two linked lists stored in reverse order, representing: 342 + 465 = 807
Input parameters: the heads l1 and l2 of the two linked lists, find a new linked list (the linked list form of sum)
👺 title analysis
When the author saw this question, the first thing that came to my mind was to traverse the two linked lists separately, get the corresponding numbers, add them and store them in the new linked list; when the author submits it enthusiastically, the number is big, if it can be big in the project. js and so on to deal with big numbers, this is a practice problem after all, we try to change our thinking.
遍历l1 -> 获取 342
遍历l2 -> 获取 465
342 + 465 = 807
807 存入链表 7 -> 0 -> 8
This is the process of a handwritten addition operation, we add 2 + 5 = 7
, 0611b2d45a5259, 4 + 6 = 10 that is 0 carry 1,
3 + 4 + carry 1 = 8
- Traverse l1, l2, add bitwise
- The new variable represents whether to carry or not, if carry, add 1
- Note that if there is still a carry at the end, 1 needs to be added, such as:
89 + 13 = 102
-------------
9 8
3 1
-----
2 0 1
👺 code implementation
const addTwoNumbers = (
l1: ListNode | null,
l2: ListNode | null,
): ListNode | null => {
let current1 = l1;
let current2 = l2;
let carryOver = 0; // 进位
let sum: ListNode = new ListNode(); // sum链, 给个默认头部, 最后取next即可
let current: ListNode | null = sum;
while (current1 || current2) {
// 两链表未必等长 如 999 + 1
let currentSum = (current1?.val || 0) + (current2?.val || 0) + carryOver;
carryOver = 0;
if (currentSum >= 10) {
carryOver = 1;
currentSum -= 10;
}
current.next = new ListNode(currentSum);
current1 = current1?.next || null;
current2 = current2?.next || null;
current = current.next;
}
if (carryOver > 0) current.next = new ListNode(carryOver);
return sum.next;
};
LeetCode 24. Pairs of exchange nodes in the linked list
👺 Brief description of the title
Given a linked list, exchange adjacent nodes in it in pairs, and return the exchanged linked list
Input parameters: the head node of the linked list
👺 title analysis
The essence of the linked list problem is to fiddle with the pointer. Take the exchange of 1, 3, and 4 as an example.
[1, next: 3] [3, next: 4], [4, next: null]
------------------------------------------
[1, next: 3] -> [1, next: 4]
[4, next: null] -> [4, next: 3]
[3, next: 4 -> [3, next: null]
Pay attention to the lower boundary value, only two nodes are processed in the first cycle, and the new head value needs to be recorded
👺 code implementation
const swapPairs = (head: ListNode | null): ListNode | null => {
let previous: ListNode | null = null;
let current = head;
let index = 0; // 记录是否为首次循环
while (current && current.next) {
previous = current;
current = current.next;
if (index === 0) {
previous.next = current.next;
current.next = previous;
head = current; // 交换后改变头部的值
}
if (index > 0 && current.next) {
let node = current;
current = current.next;
node.next = current.next;
previous.next = current;
current.next = node;
}
current = current.next;
index++;
}
return head;
}
postscript
🔗 This article code Github link: linked list
🔗 Links to other articles in this series: set sail-sorting algorithm , stack and queue
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。