(总结自labuladong的算法小抄, 向大家推荐这本书)
1. 反转整个单链表(leetcode#206)
// 循环的方法
var reverseList = function(head) {
let pre = null, cur = head
while(cur) {
let tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
}
return pre
};
// 递归的方法
var reverseList = function(head) {
if(!head || !head.next) return head
let newHead = reverseList(head.next)
head.next.next = head
head.next = null
return newHead
};
对于递归解法几点注意事项:
a.对于所有递归问题,要坚定不移的相信递归函数的定义,包括参数和返回值,reverseList传入一个链表的头节点,反转这个链表并返回它的新的头节点,不要进入递归陷阱中去;
b.本质其实是链表的后序遍历。层层压栈,在遇到base case后,从倒数第一个开始组装反转后的链表,再层层出栈;
c.这个问题的递归解法也为我们提供了复杂问题的解决办法
2.反转链表的前N个节点
example case:
n = 3
1->2->3->4->5 =》 5<-4<-1<-2<-3
let successor
var reverseN = function (head,n) {
if (n === 1){
successor = head.next
return head
}
const newHead = reverseN(head.next,n-1)
head.next.next = head
head.next = successor
return newHead
}
递归的思想是一样的,不同之处在于需要一个global的变量来记录最后一个被反转节点的next,来作为反转后之前头节点的next。
3.反转链表的一部分(leetcode#92)
var reverseBetween = function(head, left, right) {
if(left === 1) {
return reverseN(head,right)
}
head.next = reverseBetween(head.next,left-1,right-1)
return head
};
let successor
var reverseN = function (head,n) {
if (n === 1){
successor = head.next
return head
}
const newHead = reverseN(head.next,n-1)
head.next.next = head
head.next = successor
return newHead
}
递归的思想在这里更为明显,反转head链表的left到right即head连接上反转head.next链表的left-1到right-1,base case为从头反转,即问题2反转链表的头n个节点。
4.k个一组反转链表
var reverseKGroup = function (head, k) {
if(!head) return null
let headNxt = head
// 判断链表长度是否大于等于k的同时也找到了第k+1个节点作为递归的入口
for(let i = 0; i < k; i++){
if(!headNxt) return head
headNxt = headNxt.next
}
let newHead = reverseK(head, headNxt)
// head此时已经是当前链表反转前k个后的前半段的最后一个节点
// 比如1<-2 3->4->5 此时head就是1
head.next = reverseKGroup(headNxt,k)
return newHead
}
// 类似于循环反转链表,只不过终止条件变为node,此时返回的整条链表被反转的那部分,并没有连接起来
var reverseK = function(head, node) {
let cur = head, pre = null
while(cur !== node){
let tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
}
return pre
}