基本概念
相较于只适合在表尾插入和删除元素的顺序表,链表是一种能快速在任意位置插入和删除元素的链式存储结构。
常见的链表主要有单链表、双向链表、循环链表和双向循环链表等。
基本应用
从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {number[]}
*/
var reversePrint = function(head) {
let res = []
//一边遍历节点一边调用Array.unshift()方法将链表中的结点值插入到res数组的前面
while(head){
res.unshift(head.val)
head = head.next
}
return res
};
删除链表的节点
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
思路:
设置一个哨兵节点来指向头指针,后面我们就可以用这个哨兵节点找到头指针。
从哨兵节点开始遍历,直到找到要删除的节点,执行删除结点操作,并退出循环。
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var deleteNode = function(head, val) {
//设置哨兵节点并指向头指针
let pre = new ListNode(-1)
pre.next = head
let node = pre
while(node.next){
if(node.next.val == val){
//删除结点操作
node.next = node.next.next
break
}
node = node.next
}
return pre.next
}
反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var deleteNode = function(head, val) {
//设置哨兵节点并指向头指针
let pre = new ListNode(-1)
pre.next = head
let node = pre
while(node.next){
if(node.next.val == val){
//删除结点操作
node.next = node.next.next
break
}
node = node.next
}
return pre.next
}
复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
方法一:
利用map来模拟哈希表,
首先遍历链表并将结点作为键,将复制结点作为键值存入哈希表。
再次遍历链表,读取出复制节点并为复制结点的next和random属性赋值。
由于节点的next与random属性都是指向链表中的某个节点,故利用map.get()方法访问这个两个属性,实际上是访问这两个被指向的结点的复制结点
* // Definition for a Node.
* function Node(val, next, random) {
* this.val = val;
* this.next = next;
* this.random = random;
* };
*/
/**
* @param {Node} head
* @return {Node}
*/
var copyRandomList = function(head) {
if(!head) return head
let map = new Map()
let node = head
//遍历链表并将结点作为键,将复制结点作为键值存入哈希表
while(node){
map.set(node,new Node(node.val))
node = node.next
}
node = head
//再次遍历链表,读取出复制节点并为复制结点的next和random属性赋值
while(node){
map.get(node).next = node.next?map.get(node.next):null
map.get(node).random = node.random?map.get(node.random):null
node = node.next
}
return map.get(head)
}
时间复杂度:O(n),其中 n 是链表的长度。对于每个节点,我们至多访问其 next 和 random 各一次,均摊每个点至多被访问两次。
空间复杂度:O(n),其中 n 是链表的长度。为哈希表的空间开销。
方法二:
迭代 + 结点拆分。
在链表中的每个结点后插入其复制结点;
such as:
1->2->3 => 1->1->2->2->3->3
然后我们遍历链表,分别为复制结点的next和random属性赋值。
if(!head) return
let node = head
//在链表中,每个结点后插入该结点的复制结点
while(node){
let next = node.next
node.next = new ListNode(node.val,node.next,null)
node = next
}
node = head
let copyNode = null
//给复制结点的random赋值
while(node){
let next = node.next.next
copyNode = node.next
copyNode.random = node.random ? node.random.next : null
node = next
}
node = head
//获取复制链表的头结点
let res = head.next
//给复制的结点的next赋值
while(node){
let next = node.next.next
copyNode = node.next
copyNode.next = next ? next.next : null
node.next = next
node = next
}
return res
}
时间复杂度O(n),n 是链表的长度。我们只需要遍历该链表三次。
空间复杂度O(1)