一、链表基础
链表是不同节点通过指针串联在一起的线性结构,每个节点存储了数据和指向某个节点的指针
链表包含单链表,双链表,循环链表
单链表的每个指针指向节点的下一个节点
双链表的每个节点包含了两个指针,分别指向上个节点和下一个节点
循环链表的首节点和尾节点互相连接
链表的增加和删除的复杂度都是O(1),查找的复杂度是O(n)
二、移除链表元素
问题要点
两种思路:
- 在使用原链表直接删除目标节点
这种思路下,头节点和其他节点的处理逻辑不同,因为头节点没有前面的节点,所以需要先循环判断处理头节点,然后处理剩余节点
如果当前节点值等于目标值,则pre节点跳过cur,指向cur.next
如果当前节点值不等于目标值,则pre节点更新为pre.next
每次遍历都处理cur = cur.next,更新当前节点
- 使用一个虚拟节点进行删除操作
这种思路下,头节点和其他节点可以使用同一个逻辑进行移除
判断当前节点cur的下个节点
如果节点值等于目标值,跳过cur.next,更新cur的下一个节点为cur.next.next
如果节点值不等于目标值,更新cur的下一个节点为cur.next
原地删除
/**
* 原地删除
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
while(head && head.val === val) {
head = head.next
}
if(!head) {
return head
}
let pre = head
let cur = head.next
while(cur) {
if(cur.val === val) {
pre.next = cur.next
} else {
pre = pre.next
}
cur = cur.next
}
return head
};
虚拟节点方式
/**
* 设置虚拟头节点
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
let dummyHead = new ListNode(0, head)
let current = dummyHead
while(current.next) {
if(current.next.val === val) {
// 删除后继续判断,current保持不变
current.next = current.next.next
} else {
current = current.next
}
}
return dummyHead.next
};
三、设计链表
问题要点
抽象一个方法getNode用来获取某个索引下的节点,方便后续方法使用
在新增,删除的方法中要注意正确更新头节点,尾节点和链表数量,注意控制边界条件,如操作的索引正好是头节点或尾节点的时候
var MyLinkedList = function() {
this._size = 0
this._head = null
this._tail = null
};
/**
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.getNode = function(index) {
if(index < 0 || index >= this._size) {
return null
}
let dummy_head = new ListNode(0, this._head)
let cur = dummy_head
while(index-- >= 0) {
cur = cur.next
}
return cur
};
/**
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.get = function(index) {
let res = this.getNode(index)
return res === null ? -1 : res.val
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function(val) {
let newHead = new ListNode(val, this._head)
this._size++
this._head = newHead
if(!this._tail) {
this._tail = newHead
}
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function(val) {
let newTail = new ListNode(val, null)
if(!this._tail) {
this._tail = newTail
this._head = newTail
} else {
this._tail.next = newTail
this._tail = newTail
}
this._size++
};
/**
* @param {number} index
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function(index, val) {
if(index > this._size) {
return
}
if(index ===this._size) {
this.addAtTail(val)
} else if(index <= 0) {
this.addAtHead(val)
} else {
let node = this.getNode(index-1)
node.next = new ListNode(val, node.next)
this._size++
}
};
/**
* @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 = null
}
} else {
let node = this.getNode(index - 1)
node.next = node.next.next
if(index === this._size - 1) {
this._tail = node
}
}
this._size--
};
/**
* 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)
*/
四、反转链表
问题要点
有三种解题思路:
- 双指针法
pre指针表示上一个节点,cur指针表示当前节点,每次遍历先把下一个节点存到temp指针中,然后开改变指向,cur.next = pre, 然后更新pre为cur, cur为temp,继续遍历,知道cur为null,此时pre为反转后的头节点
- 递归法,从前往后
和双指针的转换思路一致,每次递归中都是,cur.next = pre。保证每次递归调用传入正确的pre,和cur,递归终止条件为cur为null,返回pre
- 递归法,从后往前
首先找到链表的尾节点,反转后将为头节点
然后递归找到尾节点,pre.next = head; head = null,递归调用
双指针法
/**
* 双指针法
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
let pre = null
let cur = head
let temp = null
while(cur) {
temp = cur.next
cur.next = pre
pre = cur
cur = temp
}
return pre
};
递归法,从前往后
/**
* 递归法(从前往后)
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
return reverse(null, head)
};
function reverse(pre, cur){
if (!cur) {
return pre
}
let temp = cur.next
cur.next = pre
return reverse(cur, temp)
}
递归法,从后往前
/**
* 递归法(从后往前)
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
let cur = head
while(cur && cur.next) {
cur = cur.next
}
reverse(head)
return cur
};
function reverse(head){
if(!head || !head.next) {
return head
}
let pre = reverse(head.next)
head.next = null
pre.next = head
return head
}