算法学习记录(九)

160 阅读2分钟

链表

问:

  1. 判断一个单向链表是否有环,有的话返回入环节点,否则返回null
  2. 给定两个不知是否有环的单向链表,求它们是否相交,相交则返回相交的第一个节点,否则返回null

解:

  1. 快慢指针解法,快指针走两步,慢指针走一步,如果有环指针一定会相遇
    function ringEntryNode(head) {
        let slowIdx = head
        let quickIdx = head
        // 如果quick还可以走,并且未相遇就继续循环
        while (quickIdx.next && (quickIdx !== slowIdx || quickIdx === head)) {
            console.log(quickIdx);
            quickIdx = quickIdx.next.next ? quickIdx.next.next : quickIdx.next
            slowIdx = slowIdx.next
        }
        // quickIdx走到尾了
        if (!quickIdx.next) return null
        quickIdx = head
        // quick返回头节点,接下来两指针都只移动一位,下次再相遇就是入环位置(这是定理,证明我不会)
        while (quickIdx !== slowIdx) {
            quickIdx = quickIdx.next
            slowIdx = slowIdx.next
        }
        return slowIdx
    }
  1. 首先判断这两个链表是否有环。分三种情况:
    1).都无环,求无环单链表节点
    2).有一个有环,那必定不相交
    3).都有环,入环节点如果一样,那就等效于求无环单链表节点(把入环节点当作链表的尾),如果入环节点不一样,那么判断是否相交,若相交这两个入环节点都可以当作交点。
    function noneRingIntersectNode(head1, head2, customTail = null) {
        let size1 = 0
        let size2 = 0
        let cur1 = head1
        let cur2 = head2
        // 如果有自定义尾节点,那么到这里也停止
        while (cur1 && cur1 !== customTail) {
            cur1 = cur1.next
            size1++
        }
        while (cur2 && cur2 !== customTail) {
            cur2 = cur2.next
            size2++
        }
        // 如果尾节点不相等,那么不相交
        if (cur1 !== cur2) return null
        // 计算链长度差值,两个指针回到链头部,把长的链多余的部分先走掉,然后两个指针一起走,第一次相遇就是交点
        let moreLength = Math.abs(size1 - size2)
        cur1 = head1
        cur2 = head2
        while (moreLength) {
            size1 > size2 ? cur1 = cur1.next : cur2 = cur2.next
            moreLength--
        }
        while (cur1 !== cur2) {
            cur1 = cur1.next
            cur2 = cur2.next
        }
        return cur1
    }

    function intersectNode(head1, head2) {
        // 入环节点
        const ringEntry1 = ringEntryNode(head1)
        const ringEntry2 = ringEntryNode(head2)
        let cur = ringEntry1
        // 第一种情况,都是无环链表
        if (!ringEntry1 && !ringEntry2) {
            return noneRingIntersectNode(head1, head2)
        }
        // 第二种情况,一个有环一个无环,那么必定不相交
        if ((!ringEntry1 && ringEntry2) || (ringEntry1 && !ringEntry2)) return null
        // 第三种情况,都有环。这种情况有又分三种
        // 同一节点入环,实际上就可以看作单链表相交的问题,入环点看作链表的尾节点
        if (ringEntry1 === ringEntry2) {
            return noneRingIntersectNode(head1, head2, ringEntry1)
        }
        // 不同节点入环
        // 从入环节点开始遍历链表一,若两个链表相交,那么走完一次环的过程中,cur必定会遇到ringEntry2
        while (cur) {
            cur = cur.next
            if (cur === ringEntry1 || cur === ringEntry2) break
        }
        // 遍历完一次链表一的环,如果cur没遇到ringEntry2,就表明不相交。如果遇到了那么随便返回一个入环点都算交点
        return cur !== ringEntry2 ? null : ringEntry1
    }