前端面试中会考到的数据结构题(链表)

一、单链表翻转(LeetCode 206)

1.迭代法:

var reverseList = function(head) {
    if(!head || !head.next){
        return head;
    }else{
        //先初始化prev和curr
        var prev = null;
        var curr = head;
        //不断地将后一个节点的next指针指向前驱结点,一直到该节点为null,则表示链表结束,此时的prev就是链表的最后一个节点
        while(curr){
            var next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        head = prev;
        return head;
    }
}

2.尾递归法(思想和迭代法相同,只不过是在函数的最后加了递归的方案)

var reverseList = function(head) {
    if(!head || !head.next){
        return head;
    }else{
        head = reverse(null, head);
        return head;
    }
}

function reverse(prev, curr){
    if(!curr) return prev;
    var next = curr.next;
    curr.next = prev;
    //注意,这里必须要有return返回值,因为前面有head = reverse(null, head),如果没有返回值的话head将会是undefined
    return reverse(curr, next);
}

3.递归法(重点)
(1)函数栈加以理解
前端面试中会考到的数据结构题(链表)_第1张图片

(2)实现代码:

var reverseList = function(head) {
    if(!head || !head.next){
        return head;
    }else{
        //先将head移动到倒数第二个节点,将next移动到倒数第一个节点
        var next = head.next;
        //取得最后一个节点
        var reverseHead = reverseList(next);
        //从后面往前递归,将next节点的next指针指向它的前一个节点(也就是head节点)
        next.next = head;
        //将head节点的next指针置空值
        head.next = null;
        return reverseHead;
        //当一个函数在这里结束之后如果前面调用的函数尚未结束的话就会继续执行前面函数直到所有的函数执行完毕
        //使用JS主线程的函数栈的执行顺序来理解,只有当一个函数执行完毕之后这个函数才会出栈(后进先出)
    }
};

二、单链表判断是否有环(LeetCode 141)

1.标记法:给每一个便立国的节点加标记位,遍历链表,当下一个节点已经被标记的时候,则证明该链表中有环

//标记法,给每个遍历过的节点加标记位,遍历链表,当出现下一个节点已被标记时,则证明单链表有环
function hasCycle(head){
    while(head){
        if(head.flag) return true;
        head.flag = true;
        head = head.next;
    }
    return false;
}

2.双指针法(重点):又称快慢指针法,快指针开始的位置和慢指针一致,快指针移动的速度是慢指针的两倍,若两个指针在遍历的过程中相遇的话,则表示该链表中有环。

//双指针法,快指针一下子走两步,满指针一下子走一步,如果链表中有环的话则这两个指针肯定会相遇
var hasCycle = function(head) {
    if(!head || !head.next){
        return false;
    }else{
        var fast = head;
        var slow = head;
        //若fast和fast.next其中有一个是null的时候没必要进行循环了
        while(fast && fast.next){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }
};

三、单链表找环的入口(LeetCode 142)

1.双指针法:快慢指针加额外的慢指针
(1)解题思路:
前端面试中会考到的数据结构题(链表)_第2张图片

由上面的数学公式推导,可以得出当第一个慢指针和新创建的慢指针同时开始往后遍历(慢指针1从与快指针相遇处开始遍历,慢指针2从head开始遍历),它们相遇的位置一定是环的入口节点处

(2)代码实现:

var detectCycle = function(head){
    if(!head || !head.next){
        return null;
    }else{
        //首先使用双指针法找出快慢指针相遇的位置
        //注意,这里的fast和slow都必须从头部开始,才能保证后续的从头部开始的新的慢指针能和旧的慢指针在环的入口点相遇
        var fast = head;
        var slow = head;
        while(fast && fast.next){
            fast = fast.next.next;
            slow = slow.next;
            if(fast === slow){
                //定义一个从头部开始的慢指针,不断向后遍历,两个慢指针相遇的位置就是环的入口位置
                var slow2 = head;
                while(slow !== slow2){
                    slow = slow.next;
                    slow2 = slow2.next;
                }
                return slow2;
            }
        }
        return null;
    }
};

四、求两个链表的相交节点(LeetCode 160)

1.暴力遍历标记法(不推荐):时间复杂度O(n),空间复杂度O(n)(要创建n个变量来记录遍历节点的标签).

var getIntersectionNode = function(headA, headB) {
    //先遍历A链表,为遍历到的每个节点都添加标记
    while(headA) {
        headA.flag = true
        headA = headA.next
    }
    //再遍历B链表,检查B链表中是否有存在的标记,若有,则证明A、B两个链表相交,返回该节点,若没有则不相交
    //时间复杂度O(n) ,空间复杂度:O(n)
    //不推荐
    while(headB) {
        if (headB.flag) return headB
        headB = headB.next
    }
    return null
};

2.双指针法:推荐(时间复杂度O(n), 空间复杂度O(n))

(1)解题思路:如果两个链表有交点的话,两个链表开始从头遍历,遍历到最后一个节点的时候从另外一条链表的头部开始遍历,则两个指针一定会在两条链表的交点处相遇,因为他们走过的路径都是一样的。

(2)代码实现:

//时间复杂度O(n), 空间复杂度O(1)
var getIntersectionNode = function(headA, headB){
    var A = headA;
    var B = headB;
    while(A !== B){
        A = A !== null? A.next: headB;
        B = B !== null? B.next: headA;
    }
    return A;
};

五、单链表找中间节点(LeetCode 876)

1.双指针法(快慢指针法):这里也是需要快慢指针都是从头结点head开始往后遍历。

//使用快慢指针法,当快指针走到最后节点的时候,慢指针刚好走到中间节点
var middleNode = function(head){
    var slow = head;
    var fast = head;
    while(fast && fast.next){
        fast = fast.next.next;
        slow = slow.next;
    }
    return slow;
};

六、合并两个升序链表(LeetCode 21)

1.双指针法:
(1)解题思路:新开辟一块空间来创建一个新链表,然后依次比较l1和l2链表的大小,将较小值作为新建链表l3的前面的节点,依次往后遍历l1和l2两条链表,直到其中一条链表为空的时候,由于另外一条链表剩下的部分也是升序的,而且一定会比l3的所有节点的值都大,所以直接将剩下的部分连接在l3的后面就行了。

var mergeTwoLists = function(l1, l2) {
    //若两条链表一开始就有其中一条是空的话,就返回另外一条链表
    if(l1 == null){
        return l2;
    }
    if(l2 == null){
        return l1;
    }
    //开辟一个新的空间表示一条新的空链表(不传入任何参数)
    var l3 = new ListNode();
    //一开始pre要和l3指向同一个空间(浅复制),pre表示的是l3中遍历到的最新的节点
    var pre = l3;
    while(l1 && l2){
        if(l1.val < l2.val){
            pre.next = l1;
            l1 = l1.next;
        }else{
            pre.next = l2;
            l2 = l2.next;
        }
        //l3每新增一个节点,pre就要往后面走一步
        pre = pre.next;
    }
    //若其中有一条链表为空的时候,直接将另外一条链表剩下的部分连接到l3的后面就行了
    pre.next = l1 != null? l1: l2;
    return l3.next;
};

2.递归法(推荐)
(1)解题思路:遇到递归问题就用函数栈的思想去理解就很好理解。
前端面试中会考到的数据结构题(链表)_第3张图片

(2)代码实现:

//递归函数要到return才会真正结束
var mergeTwoLists = function(l1, l2) {
    if(l1 === null){
        return l2;
    }
    if(l2 === null){
        return l1;
    }
    if(l1.val < l2.val){
        l1.next = mergeTwoLists(l1.next, l2);
        return l1;
    }else{
        l2.next = mergeTwoLists(l1, l2.next);
        return l2;
    }
};

七、另外,附上链表的构造函数

function ListCode(val){
    this.val = val;
    this.next = null;
}

你可能感兴趣的:(前端,数据结构,链表)