链表
1. 链表理论基础
2. 移除链表元素
算法应用
- 购物车中删除商品:当我们在网上购物时,我们可能需要从购物车中删除一个或多个商品。这可以通过链表来实现,每个节点表示一个商品,当我们想要删除一个商品时,我们可以遍历链表,找到包含该商品信息的节点,并将其删除。
- 删除社交媒体中的帖子:社交媒体网站通常会有类似新闻推送、个人主页、论坛帖子等功能。当我们想要删除一个帖子时,可以使用链表来管理这些帖子,当我们想要删除一个帖子时,可以遍历链表,找到包含该帖子信息的节点,并将其删除。
- 图书馆管理系统的删除操作:在图书馆管理系统中,我们可能需要删除某个图书的信息。这可以通过链表来实现,每个节点表示一本图书,当我们想要删除一本图书时,可以遍历链表,找到包含该书信息的节点,并将其删除。
算法逻辑
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
const ret = new ListNode(0, head);
let cur = ret;
while(cur.next) {
if(cur.next.val === val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return ret.next;
};
3. 设计链表
class LinkNode {
constructor(val, next) {
this.val = val;
this.next = next;
}
}
/**
* Initialize your data structure here.
* 单链表 储存头尾节点 和 节点数量
*/
var MyLinkedList = function() {
this._size = 0;
this._tail = null;
this._head = null;
};
/**
* Get the value of the index-th node in the linked list. If the index is invalid, return -1.
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.getNode = function(index) {
if(index < 0 || index >= this._size) return null;
// 创建虚拟头节点
let cur = new LinkNode(0, this._head);
// 0 -> head
while(index-- >= 0) {
cur = cur.next;
}
return cur;
};
MyLinkedList.prototype.get = function(index) {
if(index < 0 || index >= this._size) return -1;
// 获取当前节点
return this.getNode(index).val;
};
/**
* Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function(val) {
const node = new LinkNode(val, this._head);
this._head = node;
this._size++;
if(!this._tail) {
this._tail = node;
}
};
/**
* Append a node of value val to the last element of the linked list.
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function(val) {
const node = new LinkNode(val, null);
this._size++;
if(this._tail) {
this._tail.next = node;
this._tail = node;
return;
}
this._tail = node;
this._head = node;
};
/**
* Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
* @param {number} index
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function(index, val) {
if(index > this._size) return;
if(index <= 0) {
this.addAtHead(val);
return;
}
if(index === this._size) {
this.addAtTail(val);
return;
}
// 获取目标节点的上一个的节点
const node = this.getNode(index - 1);
node.next = new LinkNode(val, node.next);
this._size++;
};
/**
* Delete the index-th node in the linked list, if the index is valid.
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function(index) {
if(index < 0 || index >= this._size) return;
if(index === 0) {
this._head = this._head.next;
// 如果删除的这个节点同时是尾节点,要处理尾节点
if(index === this._size - 1){
this._tail = this._head
}
this._size--;
return;
}
// 获取目标节点的上一个的节点
const node = this.getNode(index - 1);
node.next = node.next.next;
// 处理尾节点
if(index === this._size - 1) {
this._tail = node;
}
this._size--;
};
// MyLinkedList.prototype.out = function() {
// let cur = this._head;
// const res = [];
// while(cur) {
// res.push(cur.val);
// cur = cur.next;
// }
// };
/**
* Your MyLinkedList object will be instantiated and called as such:
* var obj = new MyLinkedList()
* var param_1 = obj.get(index)
* obj.addAtHead(val)
* obj.addAtTail(val)
* obj.addAtIndex(index,val)
* obj.deleteAtIndex(index)
*/
4. 翻转链表
应用场景:
- 反转音频:将音频文件看成是一个数据链表,使用翻转链表的算法可以将音频文件反转,实现倒放的功能。
- 反转视频:类似地,将视频文件看成是一个数据链表,使用翻转链表的算法可以将视频文件反转,实现倒放的功能。
- 字符串匹配:在模式匹配中,有时需要对目标字符串进行翻转,再进行匹配操作,这时可以使用翻转链表的算法进行字符串翻转。
- 逆序打印:在计算机编程中,有时需要将链表中的数据逆序打印,这时可以使用翻转链表的算法进行链表翻转,再遍历打印每个节点的数据。
- 反转车道:在道路施工或交通调度中,有时需要将一条路的行车方向反转,这时可以使用翻转链表的算法来实现。
- 翻转家具:在家居装修中,有时需要调整家具的位置或朝向。使用翻转链表的思路,我们可以先将家具沿水平或垂直方向翻转,再将其移动到目标位置,可以实现比直接移动更简单的调整。
- 反转旅行路线:在旅行中,有时需要修改原本规划的路线。使用翻转链表的算法,我们可以将原有的路线按照一定规则翻转,再修改其中的节点,最终得到新的路线。
算法逻辑
(纠正:动画应该是先移动pre,在移动cur)
// 双指针:
var reverseList = function(head) {
//如果链表为空或者链表只有一个节点,那么直接返回该链表,因为反转一个空链表或者只有一个节点的链表不需要进行反转操作
if(!head || !head.next) return head;
let temp = null, pre = null, cur = head;
while(cur) {
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
// temp = cur = null;
return pre;
};
//递归1
var reverse = function(pre,head) {
if (!head) return pre;
let temp = head.next;
head.next = pre;
pre = head;
return reverse(pre, temp);
}
var reverseList = function(head) {
return reverse(null, head);
}
// 递归2
//reverse函数接收一个链表的头节点head
var reverse = function(head) {
//如果head不存在或head的下一个节点不存在,直接返回head
if(!head || !head.next) return head;
//从链表的第二个节点开始递归翻转整个链表,返回翻转后的链表的头结点
const pre = reverse(head.next);
//
head.next = pre.next;
pre.next = head;
return head;
}
//接收一个链表的头节点head
var reverseList = function(head) {
let cur = head;
//当cur存在且cur的下一个节点也存在时,将cur指向下一个节点
//循环结束后,cur指针指向链表的最后一个节点
while(cur && cur.next) {
cur = cur.next;
}
//将链表翻转,并将翻转后的链表的头节点返回
reverse(head);
//最终返回cur,也就是翻转后的链表的头节点
return cur;
};
// 迭代方法:增加虚头结点,使用头插法实现链表翻转
var reverseList = function(head) {
// 创建虚头结点
let dumpyHead = new ListNode(0, null);
// 遍历所有节点
let cur = head;
while(cur != null){
let temp = cur.next;
// 头插法
cur.next = dumpyHead.next;
dumpyHead.next = cur;
cur = temp;
}
return dumpyHead.next;
}
// 使用栈解决反转链表的问题
public ListNode reverseList(ListNode head) {
// 如果链表为空,则返回空
if (head == null) return null;
// 如果链表中只有只有一个元素,则直接返回
if (head.next == null) return head;
// 创建栈 每一个结点都入栈
Stack<ListNode> stack = new Stack<>();
ListNode cur = head;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
// 创建一个虚拟头结点
ListNode pHead = new ListNode(0);
cur = pHead;
while (!stack.isEmpty()) {
ListNode node = stack.pop();
cur.next = node;
cur = cur.next;
}
// 最后一个元素的next要赋值为空
cur.next = null;
return pHead.next;
}
5. 两两交换链表中的节点
应用场景
链表排序适用于需要动态添加或删除元素,以及需要对元素进行排序的场景
- 购物清单:当我们要购买一些商品时,需要制作一个购物清单。由于购物清单中的商品数量可能很多且可能需要随时添加或删除商品,使用链表来存储购物清单会比较方便。可以通过链表排序来将购物清单中的商品按照一定的规则排列,比如按照价格从小到大排列,或按照重要性、优先级等排序。
- 互联网广告:在互联网广告投放中,需要对广告进行排名,以便在搜索结果页面中展示排名最高的广告。由于投放的广告数量可能很多,且需要不断地调整广告的排名,使用链表来存储广告并进行排序会比较方便。
- 优先级队列:优先级队列是一种特殊的队列,其中每个元素都有一个优先级。在优先级队列中,元素的优先级决定了其出队的先后顺序。由于优先级队列中元素的优先级可能会随时发生变化,使用链表实现优先级队列会比较方便。
算法逻辑
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var swapPairs = function(head) {
let dummyHead = new ListNode(0, head);
let cur = dummyHead;
while (cur.next && cur.next.next) {
let temp1 = cur.next;
let temp2 = cur.next.next;
let temp3 = cur.next.next.next;
cur.next = temp2;
temp2.next = temp1;
temp1.next = temp3;
cur = temp1;
}
return dummyHead.next;
};
6. 删除链表的倒数第N个节点
运用场景
- 行程规划:在一条路径上,需要找到倒数第二个节点,以便能够在路径上插入新的节点。
- 数据库查询优化:查询数据库时,有时需要获取表格中的倒数第n行数据,这就可以使用查找链表中倒数第n个结点来解决。
- 运营管理:在运营过程中,可能需要查找过去一段时间内最后一次登录的用户,这时就可以使用查找链表中倒数第n个结点的算法。
- 数据库查询优化:在数据库查询中,有时需要获取表格中的倒数第n行数据,这可以借用链表的倒数第n个结点算法来实现。通过该算法,可以快速定位倒数第n行数据所在的节点。
- 文本编辑器:在文本编辑器中,有时用户需要查找倒数第n个单词,以便在文本中插入新的内容。可以使用链表的倒数第n个结点算法,通过对单词进行链表化,快速定位倒数第n个单词的位置。
- GPS定位:在GPS中,有时需要找到倒数第n个GPS定位数据,以便对车辆行车轨迹进行分析。可以将GPS数据按时间排序,并采用链表的倒数第n个结点算法,快速定位倒数第n个GPS定位数据所在的节点。
- 网络协议设计:在一些网络协议中,需要找到倒数第n个数据包,以便重传丢失的数据包。可以使用链表的倒数第n个结点算法,快速定位倒数第n个数据包所在的节点。
算法逻辑
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
let dummyHead = new ListNode(0, head);
let slow = fast = dummyHead;
//核心逻辑:fast先走n+1步,走到头slow指向倒数n+1个元素
while (n-- >= 0 && fast) {
fast = fast.next;
}
while (fast) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummyHead.next;
};
7. 链表相交
运用场景:
- GPS 导航系统:在GPS 导航系统中,我们可以使用链表交点结点算法来找到两辆车的最短路线。通过车辆的实时位置信息,我们可以将其对应到链表上,然后使用链表交点结点算法来找到两辆车的路径相交点。
- 银行排队系统:在银行排队系统中,我们可以使用链表交点结点算法来找到两个排队队伍的等待时间相同的用户,并将他们的服务时间调整为相同,以提高服务效率。
- 电子商务物流系统:在电子商务物流系统中,我们可以使用链表交点结点算法来找到两个物流路线的交点,从而优化物流配送路线,减少成本和时间。
- 医疗影像处理:在医疗影像处理中,我们可以使用链表交点结点算法来找到两个不同图像的共同交点,从而辅助医生进行诊断和治疗。
总之,链表交点结点算法在实际生活中有着广泛的应用,可以帮助我们优化各种系统的运行效率,提高效益。
算法逻辑:
交点不是数值相等,而是指针相等。我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置。此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
let curA = headA;
let curB = headB;
let lenA = 0, lenB = 0;
while (curA) {// 求链表A的长度
lenA++;
curA = curA.next;
}
while (curB) {// 求链表B的长度
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
[lenB, lenA] = [lenA, lenB];
[curA, curB] = [curB, curA];
}
// 求长度差
let gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap--) {
curA = curA.next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA) {
if (curA === curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
};
8. 环形链表II
现实应用
- 生态环境研究:在生态环境研究中,我们经常需要对生态系统进行模拟和分析。因为生态系统中各种因素相互作用,往往形成环形循环的链表结构。在分析过程中,我们可以使用环形链表找环入口算法来找到生态系统中的环形循环,从而深入研究生态系统的特征和规律。
- 数据库查询优化:在数据库查询优化中,我们需要对数据库中的数据进行分析和优化,以提高查询效率。因为数据库中的数据可能存在环形结构,因此我们可以使用环形链表找环入口算法来查找环形结构,从而优化查询过程。
- 虚拟机垃圾回收:在虚拟机垃圾回收中,我们需要对内存中的对象进行分析和回收。因为内存中的对象可能存在环形链表结构,因此我们可以使用环形链表找环入口算法来找到对象之间的环形引用关系,从而实现内存的回收和优化。
- 银行排队系统:在银行排队系统中,由于用户的到来和服务结束存在环形循环的关系,因此我们可以使用环形链表找环入口算法来优化用户服务的时间和效率。
总之,环形链表找环入口算法在现实生活中有着广泛的应用,可以帮助我们深入研究各种系统的特征和规律,优化系统的效率和服务质量。
算法逻辑
1.fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇
2.从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function(head) {
let fast = head;
let slow = head;
while (fast && fast.next) {//可能有环
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {//快慢指针相遇,有环
index1 = head;
index2 = slow;
while (index1 != index2) {//找环入口
index1 = index1.next;
index2 = index2.next
}
return index2;
}
}
return null
};