【常用算法思路分析系列】链表相关高频题集

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shakespeare001/article/details/51487553

本文是【常用算法思路分析系列】的第四篇,总结链表相关的高频题目和解题思路。本文分析如下几个问题:1、环形链表的差值问题;2、只能访问单个结点的删除问题;3、链表的分化;4、打印两个链表的公共部分;5、把链表的每k个结点逆序;6、删除链表中指定结点;7、判断链表是否为回文结构;8、复杂链表的复制;9、判断链表是否有环;10、判断两个无环链表是否相交;11、判断两个有环链表是否相交;12、判断两个链表(状态未定)是否相交。

本系列前三篇导航:
【常用算法思路分析系列】排序高频题集
【常用算法思路分析系列】字符串高频题集

【常用算法思路分析系列】栈和队列高频题集(修改版)


1、环形链表插值问题

有一个整数val,如何在节点值有序的环形链表中插入一个节点值为val的节点,并且保证这个环形单链表依然有序。

给定链表的信息,及元素的值A及对应的nxt指向的元素编号同时给定val,请构造出这个环形链表,并插入该值。

测试样例:
[1,3,4,5,7],[1,2,3,4,0],2
返回:{1,2,3,4,5,7}
public class InsertValue {
    /**
     * @param args
     */
    public static void main(String[] args) {
        int[] a = {1,3,4,5,7};
        int[] b = {1,2,3,4,0};
//        insert(a,b,2);
        System.out.println("dd");
    }

    public static class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }
    }

    public static ListNode insert(int[] A, int[] nxt, int val) {
        ListNode head = null;
        if(A == null || A.length == 0){
            head = new ListNode(val);
            head.next = null;
        }else{
            //构造环形链表
            ListNode curNode = new ListNode(A[0]);
            head = curNode;
            for(int i = 0; i < nxt.length - 1; i++){
                ListNode node = new ListNode(A[nxt[i]]);
                curNode.next = node;
                curNode = node;
            }
            curNode.next = head;
            ListNode  pre = head;
            curNode = head.next;
            boolean flag = false;
            while(curNode != head){
                if(val <= curNode.val){
                    ListNode node = new ListNode(val);
                    node.next = curNode;
                    pre.next = node;
                    flag = true;
                    break;
                }else{
                    pre = curNode;
                    curNode = curNode.next;
                }
            }
            if(!flag){//表示没有找到合适的位置,需要插入到头结点的前面(可能是val大于所有值或小于所有节点值)
                ListNode node = new ListNode(val);
                node.next = head;
                pre.next = node;
                if(val <= head.val){
                    head = node;
                }
            }
        }
        return head;    
    }
}
我这个没有通过牛客的测试,说是超时,但我自己测试并没有发现有死循环的可能啊。。有知道的可以提示一下~

2、只能访问单个节点的删除

实现一个算法,删除单向链表中间的某个结点,没有给定头结点,你只能访问该结点

给定带删除的节点,请执行删除操作,若该节点为尾节点,返回false,否则返回true

思路:
将删除节点的下一个结点值赋给要删除的当前结点,然后当前结点指向它下一个结点的下一个结点。即实质是把 删除节点的下一个结点值赋给当前节点,然后删除当前节点的下一个结点。这种情况下,最后一个节点是不能删除的。
public class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }
    }

    public boolean removeNode(ListNode pNode) {
        if(pNode == null || pNode.next == null)
            return false;
        ListNode nextNode = pNode.next;
        pNode.val = nextNode.val;
        pNode.next = nextNode.next;
        return true;
    }

3、链表的分化

对于一个链表,我们需要用一个特定阈值完成对它的分化,使得小于等于这个值的结点移到前面,大于该值的结点在后面,同时保证两类结点内部的位置关系不变。

给定一个链表的头结点head,同时给定阈值val,请返回一个链表,使小于等于它的结点在前,大于等于它的在后,保证结点值不重复。

测试样例:
{1,4,2,5},3
{1,2,4,5}
思路:
定义一个指向结点值<=val的结点的链表,一个结点值>val的结点的链表,开始从头结点开始遍历,遍历到当前结点的时候,先用next指向当前结点的后面链表,取出当前结点,对当前结点的值进行比较,决定放到哪个链表中。这里要特别注意, 对于这种链表分化的操作,我们在遍历链表的时候,一定要把当前结点取出来,将其next置为null,即脱离原来的链表,否则,如果不把当前结点取出next置null,在组拼两个链表后,组拼后的链表中可能存在环
public class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode listDivide(ListNode head, int val) {
        if(head == null)
            return null;
        ListNode minList = null,maxList = null;
        ListNode minListHead = null,maxListHead = null;
        ListNode next = null;
        while(head != null){
            next = head.next;
            head.next = null;
            if(head.val <= val){//如果当前结点值<=val,将当前结点放入到小于阀值的链表
                if(minList == null){
                    minList = head;
                    minListHead = head;
                }else{
                    minList.next = head;
                    minList = head;
                }
            }else{//如果当前结点值>val,将当前结点放入到大于阀值的链表
                if(maxList == null){
                    maxList = head;
                    maxListHead = head;
                }else{
                    maxList.next = head;
                    maxList = head;
                }
            }
            head = next;
        }

        if(minList != null){
            minList.next = maxListHead;
        }
        return minListHead != null ? minListHead : maxListHead;
    }

4、打印两个链表的公共值部分

现有两个升序链表,且链表中均无重复元素。请设计一个高效的算法,打印两个链表的公共值部分。

给定两个链表的头指针headAheadB,请返回一个vector,元素为两个链表的公共部分。请保证返回数组的升序。两个链表的元素个数均小于等于500。保证一定有公共值

测试样例:
{1,2,3,4,5,6,7},{2,4,6,8,10}
返回:[2.4.6]
思路:
由于链表都是有序的,因此从头结点开始遍历两个链表,如果headA指向的结点值小于headB指向的结点值,headA往后移动一次,如果 headA指向的结点值大于headB指向的结点值,headB往后移动一次,如果相等,则可以打印该值,两个链表同时移动。如下:
public class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }
    }

    public int[] findCommonParts(ListNode headA, ListNode headB) {
        if(headA == null || headB == null){
            return null;
        }
        List<Integer> list = new ArrayList<Integer>();
        while(headA != null && headB != null){
            if(headA.val < headB.val){
                headA = headA.next;
            }else if(headA.val > headB.val){
                headB = headB.next;
            }else{//相等,打印值
                list.add(headA.val);
                headA = headA.next;
                headB = headB.next;
            }
        }
        int[] res = new int[list.size()];
        for(int i = 0; i < list.size(); i++){
            res[i] = list.get(i);
        }
        return res;
    }

5、把链表的每k个元素逆序

有一个单链表,请设计一个算法,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整最后几个节点。例如链表1->2->3->4->5->6->7->8->null,K=3这个例子。调整后为,3->2->1->6->5->4->7->8->null。因为K==3,所以每三个节点之间逆序,但其中的7,8不调整,因为只有两个节点不够一组。

给定一个单链表的头指针head,同时给定K值,返回逆序后的链表的头指针。


思路一:
利用一个栈进行反转排序,从头开始遍历元素,每遍历一个元素,将其取下(next置为null),放入到栈中,当栈中元素达到k个时,对栈中的元素进行出栈,记录好头部和尾部结点,并链接到上一个反转后的尾部。由于利用了一个栈,因此这种算法时间复杂度为O(N),空间复杂度为 O(K)。代码如下:     
public class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode inverse(ListNode head, int k) {
        if(head == null || head.next == null || k < 2){
            return head;
        }
        Stack<ListNode> stack = new Stack<ListNode>();
        ListNode next = null;
        ListNode resHead = null;//反转后的链表的头结点
        ListNode header = null, tail = null;//记录每次反转过程中的头结点和尾结点
        ListNode lastTail = null;//上一次反转过程中的尾结点
        while(head != null){
            next = head.next;
            head.next = null;
            stack.push(head);
            if(stack.size() == k){
                //先退栈反转这k个元素,找到这k个元素的头和尾部
                header = tail = stack.pop();
                while(!stack.isEmpty()){
                    tail.next = stack.pop();
                    tail = tail.next;
                }
                if(resHead == null){//是否是第一次反转前k个元素
                    resHead = header;
                    lastTail = tail;
                }else{
                    lastTail.next = header;
                    lastTail = tail;
                }
            }
            head = next;
        }
        if(!stack.isEmpty()){//栈中还有元素,表示最后几个元素没有满足k个数量,保持原有顺序
            header = stack.pop();
            header.next = null;
            ListNode temp ;
            while(!stack.isEmpty()){//头插法,维持原来顺序
                temp = stack.pop();
                temp.next = header;
                header = temp;
            }
            lastTail.next = header;
        }
        return resHead;
    }

思路二:
不借助栈,每次从链表中截取处k个元素,对这k个元素利用头插法进行反转。时间复杂度为O(N),空间复杂度为O(1),如下:
public class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode inverse(ListNode head, int k) {
        if(head == null || head.next == null || k < 2){
            return head;
        }
        ListNode next = null;
        ListNode resHead = null;//反转后的链表的头结点
        ListNode header = null, tail = null;//记录每次截取k个元素过程中的头结点和尾结点
        ListNode lastTail = null;//上一次反转过程中的尾结点
        boolean flag = true;
        while(flag){
            header = null;
            tail = null;
            //每次拿出k个元素
            for(int i = 0; i < k; i++){
                if(head == null){//表示最后一组不满足k个元素
                    flag = false;
                    break;
                }else{
                    next = head.next;
                    head.next = null;
                    if(i == 0){
                        header = head;
                        tail = head;
                    }else{
                        tail.next = head;
                        tail = head;
                    }
                    head = next;
                }
            }
            if(flag){//反转这k个元素
                ListNode tempListHeader = header;
                ListNode temp = tempListHeader;
                tempListHeader = tempListHeader.next;
                temp.next = null;//摘取出来
                while(tempListHeader != null){
                    next = tempListHeader.next;
                    tempListHeader.next = temp;
                    temp = tempListHeader;
                    tempListHeader = next;
                }
                if(resHead == null){
                    resHead = tail;
                    lastTail = header;
                }else{
                    lastTail.next = tail;
                    lastTail = header;
                }
            }else{//最后一组不满足k个元素,直接放到反转后的链表后面
                lastTail.next = header;
            }
        }
        return resHead;
    }

6、删除链表中指定的元素

现在有一个单链表。链表中每个节点保存一个整数,再给定一个值val,把所有等于val的节点删掉。

给定一个单链表的头结点head,同时给定一个值val,请返回清除后的链表的头结点,保证链表中有不等于该值的其它值。请保证其他元素的相对顺序。

测试样例:
{1,2,3,4,3,2,1},2
{1,3,4,3,1}
思路一:
定义first、tail链表结点,用于指向新链表的头结点和尾结点,遍历链表,当前结点值!=val时,将当前结点加入到tail的后面,如下:
public class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }
    }

    public ListNode clear(ListNode head, int val) {
        if(head == null)
            return null;
        ListNode first = null,tail = null,next = null;
        while(head != null){
            next = head.next;
            head.next = null;
            if(head.val != val){
                if(first == null){
                    first = head;
                    tail = head;
                }else{
                    tail.next = head;
                    tail = head;
                }
            }
            head = next;
        }
        return first;
    }

思路二:
思路一相当于用了一个新链表,虽然空间复杂度还是为O(1),我们用一种在原链表上进行操作的办法。
对于单链表,要进行删除操作,重要的是记录当前删除结点的前一个结点,因此,我们定义pre、cur结点指针。如下:
public ListNode clear(ListNode head, intnum) {
        while(head != null) {
            if(head.val != num) {
                break;
            }
            head = head.next;
        }
        ListNode pre = head;
        ListNode cur = head;
        while(cur != null) {
            if(cur.val == num) {
                pre.next = cur.next;
            } else{
                pre = cur;
            }
            cur = cur.next;
        }
        return head;
    }
  

7、判断链表是否为回文结构

编写一个函数,检查链表是否为回文。回文链表结构如下:1->2->3->2->1为回文结构,1->2->3->1不是回文结构。即回文结构是左右对称的。
思路一:
利用栈结构,第一遍遍历链表,将所有结点入栈;第二遍遍历链表,将当前遍历结点的值依次与栈中弹出的栈顶结点比较,只有全部相同才为回文结构。如下:
public class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }
    }

    public boolean isPalindrome(ListNode pHead) {
        if(pHead == null){
            return false;
        }
        Stack<ListNode> stack = new Stack<ListNode>();
        ListNode first = pHead;
        while(first != null){//链表中全部结点先入栈
            stack.push(first);
            first = first.next;
        }
        ListNode temp;
        while(pHead != null && !stack.isEmpty()){//再遍历一遍链表
            temp = stack.pop();
            if(temp.val != pHead.val){
                return false;
            }else{
                pHead = pHead.next;
            }
        }
        return true;
    }
时间复杂度为O(N),空间复杂度为O(N)。

思路二:
同样是利用一个栈结构,利用回文的特性,即左右结构对称,定义快、慢两个指针,初始都指向头结点,快指针每次走2步,慢指针每次走1步,把慢指针指向的结点入栈。当快指针到达尾部时,慢指针到达链表中部,如果链表结点数量为奇数,则慢指针指向的中间结点不入栈。此后,慢指针继续遍历后面的元素,每遍历一个结点,和栈中弹出的栈顶结点值进行比较,只有完全相同的时候,才为回文结构。、

思路二比思路一主要的优化在于栈中元素少了一半,为N/2,代码如下:     
public boolean isPalindrome(ListNode pHead) {
        if(pHead == null){
            return false;
        }
        Stack<ListNode> stack = new Stack<ListNode>();
        ListNode fast = pHead;
        ListNode slow = pHead;
        while(fast != null){
            if(fast.next == null){//表示到快指针达尾部,说明链表结点个数为奇数,此时慢指针到达中部,且此时慢指针指向的结点不需要入栈
                slow = slow.next;//链表结点数为奇数时,跳过中间结点
                break;
            }
            stack.push(slow);
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode temp;
        while(slow != null && !stack.isEmpty()){//后面慢指针和栈顶结点值依次比较
            temp = stack.pop();
            if(temp.val != slow.val){
                return false;
            }else{
                slow = slow.next;
            }
        }
        return true;
    }

思路三(最优解):
不借助栈结构,同样是使用快慢指针,利用快慢指针,先找到链表中点位置,将中点位置的后半段链表进行反转,得到反转后的链表,对反转后的链表和原来链表的前半段进行依次遍历操作,比较两者的结点值是否相同,只有都相同才是回文结构。记得,不论是否是回文结构,都要把反转后的链表再次反转回来。示意图如下:


代码如下:     
public boolean isPalindrome(ListNode pHead) {
        if(pHead == null){
            return false;
        }
        ListNode fast = pHead;
        ListNode slow = pHead;
        ListNode lastHead = null;
        while(fast.next != null && fast.next.next != null){//寻找到慢指针指向的中点
            slow = slow.next;
            fast = fast.next.next;
        }
        if(fast.next != null){//表示链表节点数量为偶数
            lastHead = slow.next;
        }else{
            lastHead = slow;
        }
        ListNode head = lastHead;//指向反转后的头结点
        lastHead = lastHead.next;
        ListNode next = null;
        head.next = null;
        while(lastHead != null){//反转链表
            next = lastHead.next;
            lastHead.next = head;
            head = lastHead;
            lastHead = next;
        }
        boolean res = true;
        ListNode tempList = head;
        while(tempList != null && pHead != null){
            if(tempList.val != pHead.val){
                res = false;
                break;
            }
            tempList = tempList.next;
            pHead = pHead.next;
        }
        //再把后面反转的链表反转回来
        ListNode head2 = head;
        head = head.next;
        head2.next = null;
        while(head != null){
            next = head.next;
            head.next = head2;
            head2 = head;
            head = next;
        }
        slow.next = head2;
        return res;
    }

8、复杂链表的复制

一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),要求复制一份这种链表。
思路:
首先,第一遍遍历结点,每遍历到一个结点,复制一个相同的结点,并加入到当前结点的下一个位置;
第二遍遍历结点,设置新结点的random指针。可以结合图,看一下,每个新结点的random指针,指向的是,它的前面一个结点(父本)的random指向的结点的下一个结点。
第三遍遍历结点,还原原来链表,取出所有的新结点;
示意图如下:

代码如下:     
public class RandomListNode {
        int label;
        RandomListNode next = null;
        RandomListNode random = null;

        RandomListNode(int label) {
            this.label = label;
        }
    }

    public RandomListNode Clone(RandomListNode pHead){
        if(pHead == null){
            return null;
        }
        RandomListNode list = pHead;
        //第一遍遍历结点,每遍历到一个结点,复制一个相同的结点,并加入到当前结点的下一个位置;
        while(list != null){
            RandomListNode node = new RandomListNode(list.label);
            node.next = list.next;
            list.next = node;
            list = list.next.next;
        }
        //第二遍遍历结点,设置新结点的random指针
        list = pHead;
        while(list != null){
            if(list.random != null){
                list.next.random = list.random.next;
            }else{
                list.next.random = null;
            }
            list = list.next.next;
        }
        list = pHead;
        RandomListNode newList = null;
        RandomListNode temp = null;        
        RandomListNode res = null;    
        //第三遍遍历结点,还原原来链表,取出所有的新结点;
        while(list != null){
            temp = list.next;
            list.next = list.next.next;
            temp.next = null;
            list = list.next;
            if(newList == null){
                newList = temp;
                res = temp;
            }else{
                newList.next = temp;
                newList = temp;
            }
        }
        return res;
    }

9、判断链表是否有环

如何判断一个单链表是否有环?有环的话返回进入环的第一个节点的值,无环的话返回-1。如果链表的长度为N,请做到时间复杂度O(N),额外空间复杂度O(1)。

思路一:
有点暴力破解的意思,利用哈希表,遍历链表过程中,在将结点放入到哈希表中前,先去判断哈希表中是否有该结点,如果有表示有环,没有就加入到哈希表中,如果无环,会因为null退出循环。注意,哈希表中方的是结点对象,而不是存放的结点值,因此,只有在放入前发现前面已经放入过相同的对象,则表示这是第一个进入环的地方。代码如下:    
public class ListNode {
        int val;
        ListNode next = null;
        ListNode(int val) {
            this.val = val;
        }
    }
     public int chkLoop(ListNode head) {
        if(head == null){
            return -1;
        }
        int res = -1;
        Map<ListNode,Integer> map = new HashMap<ListNode,Integer>();
        while(head != null){
            if(map.containsKey(head)){
                return head.val;
            }
            map.put(head, 1);
            head = head.next;
        }
        return res;
    }
思路一时间复杂度为O(N),空间复杂度为O(N)。

思路二(推荐):
利用 快慢指针遍历链表,确定是否有环。快指针走两步,慢指针走一步,如果快指针指向了null,表示无环;如果快指针和慢指针最后指向的同一个结点,表示链表中有环。再来确定第一个进入环的位置,当前面快指针和慢指针指向同一个结点时,快指针重新指向链表头结点,此后,慢指针和快指针同步以一个步伐的速度前进,当再一次相遇时,这个结点就是第一次进入环的结点。     
public int chkLoop(ListNode head) {
        if(head == null || head.next == null){//链表为空或者只有一个结点
            return -1;
        }
        ListNode fast = head.next.next;//我的判断条件中是fast!=slow,因此这里不能设置fast=slow=head;
        ListNode slow = head.next;
        while(fast != null && fast.next != null && fast != slow){//如果无环,则fast或fast.next会指向null,如果有环,某一时刻fast==slow
            fast = fast.next.next;
            slow = slow.next;
        }
        if(fast == slow){//表示有环
            fast = head;
            while(fast != slow){
                fast = fast.next;
                slow = slow.next;
            }
            return fast.val;
        }
        return -1;
    }
思路二时间复杂度为O(N),空间复杂度为O(1)。

10、判断两个无环链表是否相交

现在有两个无环单链表,若两个链表的长度分别为m和n,请设计一个时间复杂度为O(n + m),额外空间复杂度为O(1)的算法,判断这两个链表是否相交。

给定两个链表的头结点headAheadB,请返回一个bool值,代表这两个链表是否相交。

思路一:
利用哈希表,首先遍历一遍链表A,将A的所有结点放入到哈希表中,再遍历一遍B,遍历时,判断哈希表中是否有相同的结点,有表示相交;
此时空间复杂度为O(N),不满足要求;
思路二:
如果两个链表相交,后端对齐后,那说明两个链表的最后一个结点相同。因此,这种思路是,分别遍历链表A和B,指针指向两个链表的最后一个结点,如果租后一个结点相同,则表示相交。这种方式,虽然空间复杂度为O(1),但是 这种方式不能够知道最初相交的结点在哪里。如下:
public boolean chkIntersect(ListNode headA, ListNode headB) {
        // write code here
        ListNode alast=headA,blast=headB;
        while(alast.next!=null){
            alast=alast.next;
        }
        while(blast.next!=null){
            blast=blast.next;
        }
        if(alast==blast)
            returntrue;
        else
            returnfalse;
    }

思路三:
如果要知道最初相交的结点在哪里,可用如下方法。首先分别遍历一遍链表A和B,得到A和B的长度m和n,如果m>n,A从头开始先遍历m-n步,如果m<n,B先遍历n-m步,此后A、B同时移动,如果遍历过程中A和B的当前结点相同,表示两个链表相交,且当前结点为相交的第一个结点。如下:     
public boolean chkIntersect(ListNode headA, ListNode headB) {
        if(headA == null || headB == null){
            return false;
        }
        ListNode listA = headA;
        ListNode listB = headB;
        int m = 0;//链表A的长度
        int n = 0;//链表B的长度
        int dst = 0;//两个链表的长度差值
        while(listA != null){
            m++;
            listA = listA.next;
        }
        while(listB != null){
            n++;
            listB = listB.next;
        }
        listA = headA;
        listB = headB;
        if(m >= n){
            dst = m - n;
            for(int i = 0; i < dst; i++){
                listA = listA.next;
            }
        }else{
            dst = n - m;
            for(int i = 0; i < dst; i++){
                listB = listB.next;
            }
        }
        while(listA != null && listB != null && listA != listB){
            listA = listA.next;
            listB = listB.next;
        }
        if(listA != null && listB != null && listA == listB){
            return true;
        }
        return false;
    }
额外空间复杂度为O(1)。

11、有环单链表相交判断

如何判断两个有环单链表是否相交?相交的话返回第一个相交的节点,不想交的话返回空。如果两个链表长度分别为N和M,请做到时间复杂度O(N+M),额外空间复杂度O(1)。
思路:
对于两个有环链表,通过上面介绍的寻找单链表的入环点的方法,拿到两个链表的入环点进行比较:
(1)如果 这两个链表的入环结点相同,那说明这两个链表一定相交。此时,是如下这种情况:

A、B第一次相交的结点在共同入环结点的上面,这时,和上面判断两个无环链表是否相交一样,分别计算链表A和B到共同入环结点出的长度,让长度更长的结点先走长度的差值,然后两个链表共同移动,第一次相等的结点即为A和B第一次相交的结点。

(2)如果两个链表的入环结点不相同,此时,又有两种情况,如下:

这种情况下,只需要对链表A从入环结点出开始遍历一下,在再次回到入环结点前,看遍历节点中是否有B的入环结点。此时,A和B第一次相交的结点可以是A的入环结点或者是B的入环结点。

代码实现如下:     
public class ListNode {
        int val;
        ListNode next = null;

        ListNode(int val) {
            this.val = val;
        }
    }

    public boolean chkInter(ListNode head1, ListNode head2) {
        if(head1 == null || head2 == null){
            return false;
        }
        ListNode loopNode1 = getLoopStartNode(head1);//拿到链表head1的入环结点
        ListNode loopNode2 = getLoopStartNode(head2);//拿到链表head2的入环结点
        if(loopNode1 == null || loopNode2 == null)
            return false;
        if(loopNode1 == loopNode2){//表示两个链表的入环结点相同,则两个链表肯定相交
            return true;
        }else{
            ListNode temp = loopNode1.next;
            while(temp != loopNode1){
                if(temp == loopNode2){//表示环中有相交结点
                    return true;
                }
                temp = temp.next;
            }
        }
        return false;
    }

    public ListNode getLoopStartNode(ListNode head){
        ListNode fast = head.next.next;
        ListNode slow = head.next;
        while(fast != null && fast.next != null && fast != slow){
            fast = fast.next.next;
            slow = slow.next;
        }
        if(fast == slow){
            fast = head;
            while(fast != slow){
                fast = fast.next;
                slow = slow.next;
            }
            return fast;
        }
        return null;
    }

12、判断两个链表(状态未知)是否相交

给定两个单链表的头节点head1和head2,如何判断两个链表是否相交?相交的话返回true,不想交的话返回false。

思路:
这个问题是前面9、10、11问题的综合,因为这里的两个链表状态未知,即不清楚是否有环,或者哪个有环,哪个无环。因此,首先要通过第9节的算法,拿到两个单链表的入环结点loop1和loop2进行分析比较:
(1)一个入环结点不为null,另一个入环结点为null,即一个链表有环,一个无环。这种情况下,两个链表不可能相交;
(2)两个入环结点都为null,此时,问题回到第10节的算法,判断两个无环结点是否相交的问题;
(3)如果入环结点都不为null,问题回到第11节的算法,判断两个有环结点是否相交的问题。
代码如下:     
public class ListNode {
        int val;
        ListNode next = null;
        ListNode(int val) {
            this.val = val;
        }
    }

    public boolean chkInter(ListNode head1, ListNode head2, int adjust0, int adjust1) {
        if(head1 == null || head2 == null){
            return false;
        }
        ListNode loopNode1 = getLoopStartNode(head1);//拿到链表head1的入环结点
        ListNode loopNode2 = getLoopStartNode(head2);//拿到链表head2的入环结点
        //如果一个链表有环,一个链表无环,那么这两个链表不可能相交
        if((loopNode1 != null && loopNode2 == null) || (loopNode2 != null && loopNode1 == null)){
            return false;
        }
        //如果两个链表都没有环,则问题回到两个无环链表是否相交的判断上
        if(loopNode1 == null && loopNode2 == null){
            return chkNoLoopList(head1,head2);
        }
        //如果两个入环结点都不为空,即问题回到两个有环链表是否相交的判断上
        if(loopNode1 != null && loopNode2 != null){
            return chkHasLoopList(loopNode1,loopNode2);
        }
        return false;
    }

     /**
      * 判断两个有环链表是否相交
      * @param loopNode1
      * @param loopNode2
      * @return
      */
     public boolean chkHasLoopList(ListNode loopNode1, ListNode loopNode2) {
        if(loopNode1 == loopNode2){//表示两个链表的入环结点相同,则两个链表肯定相交
            return true;
        }else{
            ListNode temp = loopNode1.next;
            while(temp != loopNode1){
                if(temp == loopNode2){//表示环中有相交结点
                    return true;
                }
                temp = temp.next;
            }
        }
        return false;
    }

    /**
     * 判断两个无环链表是否相交
     * @param headA
     * @param headB
     * @return
     */
    public boolean chkNoLoopList(ListNode headA, ListNode headB) {
        if(headA == null || headB == null){
            return false;
        }
        ListNode listA = headA;
        ListNode listB = headB;
        int m = 0;//链表A的长度
        int n = 0;//链表B的长度
        int dst = 0;//两个链表的长度差值
        while(listA != null){
            m++;
            listA = listA.next;
        }
        while(listB != null){
            n++;
            listB = listB.next;
        }
        listA = headA;
        listB = headB;
        if(m >= n){
            dst = m - n;
            for(int i = 0; i < dst; i++){
                listA = listA.next;
            }
        }else{
            dst = n - m;
            for(int i = 0; i < dst; i++){
                listB = listB.next;
            }
        }
        while(listA != null && listB != null && listA != listB){
            listA = listA.next;
            listB = listB.next;
        }
        if(listA != null && listB != null && listA == listB){
            return true;
        }
        return false;
    }

    /**
     * 获取链表的入环结点
     * @param head
     * @return
     */
    public ListNode getLoopStartNode(ListNode head){
        if(head == null || head.next == null){//链表为null或者只有一个结点,此时没有入环结点
            return null;
        }
        ListNode fast = head.next.next;
        ListNode slow = head.next;
        while(fast != null && fast.next != null && fast != slow){
            fast = fast.next.next;
            slow = slow.next;
        }
        if(fast == slow){
            fast = head;
            while(fast != slow){
                fast = fast.next;
                slow = slow.next;
            }
            return fast;
        }
        return null;
    }


下一篇将是与二分搜索相关。



猜你喜欢

转载自blog.csdn.net/shakespeare001/article/details/51487553