前端面试算法篇——链表反转问题

989 阅读1分钟

(总结自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
}