算法18:LeetCode_链表相关算法题

链表无小事,只要是涉及到链表的算法题,边界值的设定尤为重要,而且及其容易出错误。这就要求我们平时多加练习。但是,我们在面试和笔试的过程中往往会碰到链表相关的题目,所以我们在笔试的时候一般都会借助系统提供的工具类进行解答,但是在面试的过程中,面试官往往想听到的答案是和链表直接相关的解答。这就要求我们有2套不同的应答方案。

 

题目1:给定一个单链表的头节点head,请判断该链表是否为回文结构 

package code03.链表_01;

import java.util.Stack;

/**
 * 给定一个单链表的头节点head,请判断该链表是否为回文结构
 * https://leetcode.cn/problems/palindrome-linked-list/
 *
 * 1)哈希表方法特别简单(笔试用)
 * 2)改原链表的方法就需要注意边界了(面试用)
 */
public class Palindrome_01 {

    static class ListNode {
      int val;
      ListNode next;
      ListNode() {}
      ListNode(int val) { this.val = val; }
      ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  }

    //笔试用, 怎么简单怎么来,安全保险最重要
    public boolean isPalindrome (ListNode head)
    {
        //Letcode 默认仅有一个节点的链表也是回文
        if (head == null /*|| head.next == null*/) {
            return false;
        }
        //后进先出,正好和node是反着的
        Stack stack = new Stack();
        ListNode node = head;
        while (node != null) {
            stack.push(node);
            node = node.next;
        }

        boolean isPadinDrom = true;
        while (head != null) {
            if (head.val != ((ListNode) stack.pop()).val) {
                isPadinDrom = false;
                break;
            }
            head = head.next;
        }
        return isPadinDrom;
    }

    //面试用, 对链表进行操作,大体思路对即可,高端大气上档次
    //这种写法节省了额外空间复杂度 Stack
    public boolean isPalindrome2 (ListNode header)
    {
        //Letcode 默认仅有一个节点的链表也是回文
        if (header == null /*|| header.next == null*/) {
            return false;
        }

        ListNode fast = header;
        ListNode slow = header;
        while (fast != null && fast.next != null && fast.next.next != null) { // fast.next.next != null 判断特别重要
            fast = fast.next.next;
            slow = slow.next;
        }

        //奇数,slow正好是中间节点。 偶数,slow是2个中间节点靠后的一个
        ListNode next = slow.next;
        slow.next = null;
        ListNode reverseNode = slow; //前一个逆转的node节点、
        ListNode node2 = null;
        //逆转回文后半部分链表节点
        //假设原有链表是1->2->3->2->1 逆转后得到 1 ->2 -> 3 ->null 和 1 -> 2 ->3 -> null结构
        //假设原有链表是1->2->3->3->2->1 逆转后得到 1 ->2 -> 3 ->null 和 1 -> 2 ->3 -> 3->null结构
        while (next != null && reverseNode != null) {
            node2 = next.next;  //记录下一个节点
            next.next = reverseNode; //当前节点指向之前被逆转的节点
            reverseNode = next;      //更新被逆转的节点
            next = node2;            //当前节点来到下一个节点处
        }

        //开始比较
        //因为我们根据快慢指针进行切割并且第一个3作为中间节点,前一段链表是标准的结构;后一段链表存在以下2种情况
        //1) 奇数,前后链表相同个数;偶数,厚一点链表多一个值。 因此以第一个链表为参考即可. 参考上一个while的备注
        ListNode cur = header;
        boolean isPadinDrom = true;
        node2 = reverseNode;
        while (cur!= null) {
            if (cur.val != reverseNode.val) {
                isPadinDrom = false;
                break;
            }
            cur = cur.next;
            reverseNode = reverseNode.next;
        }

        /**
         * 如果我们不在意原有链表是否被破坏,那么以下while可以省略
         * 如果我们还想要保持原有链表结构不被破坏,此处我们需要修复原有链表
         * 假设原有链表是1->2->3->2->1 逆转后得到 1 ->2 -> 3 ->null 和 1 -> 2 ->3 -> null结构
         * 假设原有链表是1->2->3->3->2->1 逆转后得到 1 ->2 -> 3 ->null 和 1 -> 2 ->3 -> 3->null结构
         * 两个链表的最后一个节点在内存中是相同的,因此仅需要参考后一个链表进行修复即可(此处需要重点理解)
         */
        reverseNode = null;
        while (node2 != null) {
            next = node2.next;
            node2.next = reverseNode;
            reverseNode = node2;
            node2 = next;
        }
        return isPadinDrom;
    }

    public static void printNode (ListNode node) {
        if (node == null) {
            System.out.println("链表不存在");
        }
        System.out.println("当前链表的值为: " + node.val);
        //递归的方式逐层打印Node的子节点
        if(node.next != null) {
            printNode(node.next);
        }
    }

    public static void main(String[] args) {
        Palindrome_01 test = new Palindrome_01();

        ListNode node = new ListNode(1);
        node.next = new ListNode(2);
        node.next.next = new ListNode(3);
        node.next.next.next = new ListNode(2);
        node.next.next.next.next = new ListNode(1);
        //node.next.next.next.next.next = new Node(1);

        boolean isPadinDrom = test.isPalindrome(node);
        System.out.println(isPadinDrom);
        boolean isPadinDrom2 = test.isPalindrome2(node);
        System.out.println("测试原链表是否被修复");
        printNode(node);
        System.out.println(isPadinDrom2);


        ListNode node2 = new ListNode(1);
        node2.next = new ListNode(2);
        node2.next.next = new ListNode(3);
        node2.next.next.next = new ListNode(3);
        node2.next.next.next.next = new ListNode(2);
        node2.next.next.next.next.next = new ListNode(1);
        //node2.next.next.next.next.next.next = new Node(1);

        boolean isPadinDrom3 = test.isPalindrome(node2);
        System.out.println(isPadinDrom3);
        boolean isPadinDrom4 = test.isPalindrome2(node2);
        System.out.println("测试原链表是否被修复");
        printNode(node2);
        System.out.println(isPadinDrom4);
    }
}

 

 

题目2:将单向链表按某值划分成左边小、中间相等、右边大的形式

笔试用:

package code03.链表_01;

import java.lang.reflect.Array;
import java.util.ArrayList;

/**
 * 将单向链表按某值划分成左边小、中间相等、右边大的形式
 * 笔试用,典型的快排
 */
public class SmallEqualBig_02 {

    static class Node {
        int value;
        Node next;

        Node(int value) {
            this.value = value;
        }
    }

    public static void swap(Node[] nodeArr, int a, int b) {
        Node tmp = nodeArr[a];
        nodeArr[a] = nodeArr[b];
        nodeArr[b] = tmp;
    }

    public void partition (Node[] nodes, int left, int right) {
        //以最后一个值为参考值
        Node pavoit =  nodes[right];

        int min = left;
        int max = right;
        int cur = left;

        while (cur < max) {
            if (nodes[cur].value < pavoit.value) {
                swap(nodes, min++, cur++);
            }
            else if (nodes[cur].value > pavoit.value) {
                swap(nodes, cur, --max);
            }
            else {
               cur++;
            }
        }
        swap(nodes, cur, right);
    }

    public Node sort(Node node)
    {
        if(node == null || node.next == null) {
            return node;
        }

        int n = 0;
        Node cur = node;
        while (cur != null) {
            cur = cur.next;
            n++;
        }

        Node[] arr = new Node[n];
        cur = node;
        for (int i =0; i < arr.length; i++) {
            arr[i] = cur;
            cur = cur.next;
        }

        partition(arr, 0, n-1);

        cur = arr[0];
        node = cur;
        for (int i =1; i < arr.length; i++) {
            cur.next = arr[i];
            cur = cur.next;
        }
        cur.next = null;

        return node;
    }

    public static void printNode (Node node) {
        if (node == null) {
            System.out.println("链表不存在");
        }
        System.out.println("当前链表的值为: " + node.value);
        //递归的方式逐层打印Node的子节点
        if(node.next != null) {
            printNode(node.next);
        }
    }

    public static void main(String[] args) {
        Node head1 = new Node(7);
        head1.next = new Node(9);
        head1.next.next = new Node(1);
        head1.next.next.next = new Node(8);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(2);
        head1.next.next.next.next.next.next = new Node(5);

        SmallEqualBig_02 test = new SmallEqualBig_02();
        System.out.println("排序前的节点");
        printNode(head1);

        System.out.println("=================");
        Node n2 = test.sort(head1);
        printNode(n2);
    }
}

 

面试用:

package code03.链表_01;

//将单向链表按某值划分成左边小、中间相等、右边大的形式
public class SmallEqualBig_03 {

    static class Node {
        int value;
        Node next;

        Node(int value) {
            this.value = value;
        }
    }

    //笔试用, 不借助任何的新对象,纯粹借住现有的链表结构进行操作
    public Node sort(Node node, int povit) {

        Node maxStart = null;
        Node maxEnd = null;
        Node minStart = null;
        Node minEnd = null;
        Node equalStart = null;
        Node equalEnd = null;

        while (node != null) {
            if(node.value < povit) {
                if (minStart == null) {
                    minStart = node;
                    minEnd = node;
                }
                else {
                    minEnd.next = node;
                    minEnd = node;
                }
            }
            else if(node.value > povit) {
                if (maxStart == null) {
                    maxStart = node;
                    maxEnd = node;
                }
                else {
                    maxEnd.next = node;
                    maxEnd = node;
                }
            }
            else {
                if (equalStart == null) {
                    equalStart = node;
                    equalEnd = node;
                }
                else {
                    equalEnd.next = node;
                    equalEnd = node;
                }
            }
            node = node.next;
        }

        if (minEnd != null) {
            if (equalStart != null) {
                minEnd.next = equalStart;
                equalEnd.next = null;
            }
            else if (maxStart != null) {
                minEnd.next = maxStart;
                maxEnd.next = null;
            }
        }

        if (equalEnd != null){
            if (maxStart != null) {
                equalEnd.next = maxStart;
                maxEnd.next = null;
            }
            else
            {
                equalEnd.next = null;
            }
        }

        return minStart != null ? minStart : equalStart != null ? equalStart : maxStart;
    }

    public static void printNode (Node node) {
        if (node == null) {
            System.out.println("链表不存在");
        }
        System.out.println("当前链表的值为: " + node.value);
        //递归的方式逐层打印Node的子节点
        if(node.next != null) {
            printNode(node.next);
        }
    }

    public static void main(String[] args) {
        Node head1 = new Node(7);
        head1.next = new Node(9);
        head1.next.next = new Node(1);
        head1.next.next.next = new Node(8);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(2);
        head1.next.next.next.next.next.next = new Node(5);

        SmallEqualBig_03 test = new SmallEqualBig_03();
        System.out.println("排序前的节点");
        printNode(head1);

        System.out.println("=================");
        Node n2 = test.sort(head1, 5);
        printNode(n2);
    }

}

 

题目3:

一种特殊的单链表节点类描述如下
class Node {
int value;
Node next;
Node rand;
Node(int val) { value = val; }
}
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。
给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
【要求】
时间复杂度O(N),额外空间复杂度O(1)

package code03.链表_01;

import java.util.HashMap;
import java.util.Map;

/**
 * 一种特殊的单链表节点类描述如下
 * class Node {
 * int value;
 * Node next;
 * Node rand;
 * Node(int val) { value = val; }
 * }
 * rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。
 * 给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
 * 【要求】
 * 时间复杂度O(N),额外空间复杂度O(1)
 *
 * 解题思路:
 * 1. 借助系统容器,逐个深拷贝每个指针对象,然后将拷贝的指针串成新指针返回 (笔试用)
 *
 * 2.深拷贝每个指针对象,并且串联进当前的指针当中,然后将当前链表进行切割,生成新链表并返回(面试用)
 */
public class DeepCopyNode_04
{
    static class Node {
        int value;
        Node next;
        Node rand;

        Node(int val) {
            value = val;
        }
    }

    //借助系统容器,笔试用(快准稳)
    public Node deepCopy1 (Node node)
    {
        //额外空间复杂度O(1), 此处只生成了一个map对象
        //而深拷贝N个指针,是题目本身就要求的事情,因此可以忽略题目要求的O(N)空间复杂度
        Map<Node, Node> map = new HashMap<>();
        if (node == null) {
            return node;
        }

        //深拷贝每个node
        Node cur = node;
        while (cur != null) {
            Node n = new Node(cur.value);
            map.put(cur, n);
            cur = cur.next;
        }

        //构造新链表
        cur = node;
        while (cur != null) {
            map.get(cur).next = map.get(cur.next);
            map.get(cur).rand = map.get(cur.rand);
            cur = cur.next;
        }

        return (Node) map.get(node);
    }

    //纯链表实现,面试用
    public Node deepCopy2 (Node node)
    {
        if (node == null) {
            return node;
        }

        Node cur = node;
        //假设链表为1-2-3  拷贝完以后就是 1-1-2-2-3-3
        while (cur != null) {
            //深拷贝
            Node n = new Node(cur.value);
            Node next = cur.next;
            cur.next = n;
            n.next = next;
            cur = next;
        }

        cur = node;
        Node copy = cur.next;
        //rand有可能是后面指向前面,所以不能提前断开,否则找不到copy后后面的指针对象的rand
        while (cur != null && cur.next != null) { //cur.next != null等价于copy != null
            //假设 b 是copy a的节点,那么b的rand节点肯定就在a.rand节点后面
            copy.rand = cur.rand != null ? cur.rand.next : null;
            cur = cur.next.next;  //等价于cur = copy.next
            copy = cur != null ? cur.next : null;
        }

        //最后分离
        cur = node;
        copy = cur.next;
        Node ans = copy;
        while (cur != null && cur.next != null) {
            Node curNext = cur.next.next;
            Node copyNext = curNext != null ? curNext.next : null;

            cur.next = curNext;
            copy.next = copyNext;

            cur = curNext;
            copy = copyNext;
        }
        return ans;
    }

    public static void printNode (Node node) {
        if (node == null) {
            System.out.println("链表不存在");
        }
        System.out.println("当前链表的值为: " + node.value);
        System.out.println("当前链表的random值为: " + (node.rand == null ? null : node.rand.value));
        //递归的方式逐层打印Node的子节点
        if(node.next != null) {
            printNode(node.next);
        }
    }

    public static void main(String[] args) {
        Node head1 = new Node(7);
        head1.next = new Node(9);
        head1.next.next = new Node(1);
        head1.next.next.next = new Node(8);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(2);
        head1.next.next.next.next.next.next = new Node(5);

        head1.next.next.rand = head1.next.next.next.next;
        head1.next.next.next.next.next.next.rand = head1;

        DeepCopyNode_04 test = new DeepCopyNode_04();
        Node n = test.deepCopy1(head1);
        printNode(n);

        System.out.println("==================");
        Node n2 = test.deepCopy2(head1);
        printNode(n2);
    }
}

 

题目4:给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。如果链表中存在环 ,则返回 true 。 否则,返回 false 。

package code03.链表_01;

/**
 * 给你一个链表的头节点 head ,判断链表中是否有环。
 *
 * 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
 * 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
 * 注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
 *
 * 如果链表中存在环 ,则返回 true 。 否则,返回 false 。
 * 链接:https://leetcode.cn/problems/linked-list-cycle
 */
public class LoopNode_05 {
    static class ListNode {
        int value;
        ListNode next;

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

    public boolean hasCycle(ListNode node)
    {
        //需要使用快慢指针
        if (node == null || node.next == null || node.next.next == null) {
            return false;
        }
        ListNode fast = node.next.next;
        ListNode slow = node.next;
        //跳出当前循环
        //1) fast == slow 循环链表
        //2) fast.next 或 fast.next.next 为 null 不是循环链表
        while (fast.next != null && fast.next.next != null) {
            if (fast == slow) {
                break;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        //无环链表
        if (fast.next == null || fast.next.next == null) {
            return false;
        }
        return true;
    }

}

 

 

题目5:给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

package code03.链表_01;

/**
 * 给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
 *
 * 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
 * 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
 * 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
 *  https://leetcode.cn/problems/linked-list-cycle-ii
 */
public class LoopNode_05_2
{
    static class ListNode {
        int value;
        ListNode next;

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

    public ListNode detectCycle(ListNode node)
    {
        //需要使用快慢指针
        if (node == null || node.next == null || node.next.next == null) {
            return null;
        }
        ListNode fast = node.next.next;
        ListNode slow = node.next;
        //跳出当前循环
        //1) fast == slow 循环链表
        //2) fast.next 或 fast.next.next 为 null 不是循环链表
        while (fast.next != null && fast.next.next != null) {
            if (fast == slow) {
                break;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        //无环链表
        if (fast.next == null || fast.next.next == null) {
            return null;
        }
        //有环链表
        fast = node;
        //备注1: 找出第一个相交节点,此处根据数据推导公式得出
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        //返回fast 或 slow 都对
        return fast;
    }

    public static void main(String[] args) {
        ListNode head1 = new ListNode(7);
        ListNode head2 = new ListNode(9); head1.next = head2;
        ListNode head3 = new ListNode(1); head2.next = head3;
        ListNode head4 = new ListNode(8); head3.next = head4;
        ListNode head5 = new ListNode(5); head4.next = head5;
        ListNode head6 = new ListNode(6); head5.next = head6;
        ListNode head7 = new ListNode(5); head6.next = head7;

        LoopNode_05_2 loop = new LoopNode_05_2();
        //测试无环
        ListNode loopNode = loop.detectCycle(head1);
        System.out.println((loopNode != null ? loopNode.value : "null"));

        //测试有环
        head7.next = head4; // head4作为环形链表的第一个值
        ListNode loopNode2 = loop.detectCycle(head1);
        System.out.println("第一个入环节点的值为: " + (loopNode2 != null ? loopNode2.value : "null"));

    }
}

 

此处,需要对代码中的 “备注1” 进行解释一下,为什么fast = node;  while (fast != slow) { fast = fast.next; slow = slow.next;} 当 fast = slow的时候,fast或slow就是相交的第一个节点? 不理解这一点,下面的题目无法继续进行下去

 

 

 

 

由此图,我们可知: 快指针从A点重新出现,跑了x距离。 那么慢指针就是跑 N圈额外加z一段距离,此时他们正好会在第一个相交的节点相遇。

 

题目6:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。题目数据 保证 整个链式结构中不存在环。函数返回结果后,链表必须 保持其原始结构 。

 

package code03.链表_01;

import java.util.HashMap;
import java.util.Map;

/**
 * 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
 * 题目数据 保证 整个链式结构中不存在环。函数返回结果后,链表必须 保持其原始结构 。
 * https://leetcode.cn/problems/intersection-of-two-linked-lists/
 *
 */
public class DoubleLoopNodes_06
{
    static class ListNode {
        int value;
        ListNode next;

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

    //借助java系统提供的容器
    public ListNode getIntersectionNode(ListNode headA, ListNode headB)
    {
        if (headA == null || headB == null) {
            return null;
        }
        Map<ListNode, ListNode> map = new HashMap();

        ListNode cur = headA;
        while (cur != null) {
            map.put(cur, cur);
            cur = cur.next;
        }

        cur = headB;
        while (cur != null) {
            if (map.containsKey(cur)) {
                return map.get(cur);
            }
            cur = cur.next;
        }

        return null;
    }

    //纯链表实现
    public ListNode getIntersectionNode2(ListNode headA, ListNode headB)
    {
        if (headA == null || headB == null) {
            return null;
        }

        ListNode cur = headA;
        int n = 0;
        while (cur != null) {
           n++;
           cur = cur.next;
        }

        cur = headB;
        while (cur != null) {
            n--;
            cur = cur.next;
        }

        ListNode lNode = n > 0 ? headA : headB;   //长链表
        ListNode sNode = lNode == headA ? headB : headA; //短链表
        n = Math.abs(n);
        while (n > 0) {
            lNode = lNode.next;
            n--;
        }

        //此刻,长链表剩下的节点和短链表剩下的节点个数相同
        while (lNode != sNode) {
            lNode = lNode.next;
            sNode = sNode.next;
            if(lNode == null || sNode == null) {
                return null;
            }
        }
        return lNode;
    }

    public static void main(String[] args) {
        //链表1
        ListNode node1 = new ListNode(4);
        ListNode node2 = new ListNode(1);

        //链表2
        ListNode node3 = new ListNode(5);
        ListNode node4 = new ListNode(6);
        ListNode node5 = new ListNode(2);

        //相交节点
        ListNode node6 = new ListNode(8);
        ListNode node7 = new ListNode(7);
        ListNode node8 = new ListNode(3);

        node1.next = node2; node2.next = node6; node6.next = node7; node7.next = node8;
        node3.next = node4; node4.next = node5; node5.next = node6;

        DoubleLoopNodes_06 test = new DoubleLoopNodes_06();
        ListNode n1 = test.getIntersectionNode(node1, node3);
        System.out.println("相交节点的值为 :" + (n1 != null ? n1.value : null));

        ListNode n2 = test.getIntersectionNode2(node1, node3);
        System.out.println("相交节点的值为 :" + (n2 != null ? n2.value : null));
    }
} 

 

说了这么多,终极大boss终于要登场了。

题目7:给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null

【要求】

如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)

解题思路:根据排除所得,要么两条链表无环相交,要么有环相交,只存在这两种情况

package code03.链表_01;

/**
 * 给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null
 * 【要求】
 * 如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。
 *
 * 解题思路:根据排除所得,要么两条链表无环相交,要么有环相交,只存在这两种情况
 */
public class DoubleLoopNodes_06_2
{
    static class ListNode {
        int value;
        ListNode next;

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

    //获取单链表相交的第一个节点,不想交则返回null
    public ListNode getLoopNode(ListNode node)
    {
        //需要使用快慢指针
        if (node == null || node.next == null || node.next.next == null) {
            return null;
        }
        ListNode fast = node.next.next;
        ListNode slow = node.next;
        //跳出当前循环
        //1) fast == slow 循环链表
        //2) fast.next 或 fast.next.next 为 null 不是循环链表
        while (fast.next != null && fast.next.next != null) {
            if (fast == slow) {
                break;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        //无环链表
        if (fast.next == null || fast.next.next == null) {
            return null;
        }
        //有环链表
        fast = node;
        //备注1: 找出第一个相交节点,此处根据数据推导公式得出
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        //返回fast 或 slow 都对
        return fast;
    }

    //两个都没有循环链表的相交节点
    public ListNode bothNoCycleNodes(ListNode headA, ListNode headB)
    {
        if (headA == null || headB == null) {
            return null;
        }

        ListNode cur = headA;
        int n = 0;
        while (cur != null) {
            n++;
            cur = cur.next;
        }

        cur = headB;
        while (cur != null) {
            n--;
            cur = cur.next;
        }

        ListNode lNode = n > 0 ? headA : headB;   //长链表
        ListNode sNode = lNode == headA ? headB : headA; //短链表
        n = Math.abs(n);
        while (n > 0) {
            lNode = lNode.next;
            n--;
        }

        //此刻,长链表剩下的节点和短链表剩下的节点个数相同
        while (lNode != sNode) {
            lNode = lNode.next;
            sNode = sNode.next;
            if(lNode == null || sNode == null) {
                return null;
            }
        }
        return lNode;
    }

    public ListNode bothCycleNodes(ListNode headA, ListNode loopA, ListNode headB,  ListNode loopB)
    {
        //2种情况: 环外相交,正好第一个相交节点相交。 这种情况可以视为2个无环链表相交
        if (loopA == loopB) {
            int n = 0;
            ListNode cur = headA;
            while (cur != loopA) {
                n++;
                cur = cur.next;
            }

            cur = headB;
            while (cur != loopB) {
                n--;
                cur = cur.next;
            }

            ListNode lNode = n > 0 ? headA : headB;   //长链表
            ListNode sNode = lNode == headA ? headB : headA; //短链表
            n = Math.abs(n);
            while (n > 0) {
                lNode = lNode.next;
                n--;
            }

            //此刻,长链表剩下的节点和短链表剩下的节点个数相同
            while (lNode != sNode) {
                lNode = lNode.next;
                sNode = sNode.next;
            }
            return lNode;
        }
        else { //环内相交
            ListNode cur1 = loopA.next;
            while (cur1 != loopA) {  //如果跑一圈都没找到相交节点,则无相交节点
                if (cur1 == loopB) { //中途找到了相交节点
                    return loopB;    //返回loopA 或 loopB 都行。 环内相交,说不清谁是第一个
                }
                cur1 = cur1.next;
            }
            return null;
        }
    }

    public ListNode getIntersectionNode (ListNode node1, ListNode node2)
    {
        if (node1 == null || node2 == null) {
            return null;
        }

        ListNode loopNode1 = getLoopNode(node1);
        ListNode loopNode2 = getLoopNode(node2);

        ListNode ansNode = null;
        if (loopNode1 == null && loopNode2 == null) {  //两个都没有环的节点
            ansNode = bothNoCycleNodes(node1, node2);
        }
        else if (loopNode1 != null && loopNode2 != null) {  //两个环形链表相交
            ansNode = bothCycleNodes(node1,loopNode1, node2,loopNode2);
        }
        return  ansNode;
    }

    public static void main(String[] args) {
        System.out.println("================测试2个无环聊表相交===========================");
        //链表1
        ListNode node1 = new ListNode(4);
        ListNode node2 = new ListNode(1);

        //链表2
        ListNode node3 = new ListNode(5);
        ListNode node4 = new ListNode(6);
        ListNode node5 = new ListNode(2);

        //相交节点
        ListNode node6 = new ListNode(8);
        ListNode node7 = new ListNode(7);
        ListNode node8 = new ListNode(3);

        node1.next = node2; node2.next = node6; node6.next = node7; node7.next = node8;
        node3.next = node4; node4.next = node5;

        DoubleLoopNodes_06_2 test = new DoubleLoopNodes_06_2();
        //不相交
        ListNode m1 = test.getIntersectionNode(node1, node3);
        System.out.println("无环 不相交 :" + (m1 != null ? m1.value : null));

        //相交
        node5.next = node6;
        ListNode m2 = test.getIntersectionNode(node1, node3);
        System.out.println("无环 相交节点的值为 :" + (m2 != null ? m2.value : null));


        System.out.println("================测试2个环形链表 环外 相交===========================");
        //链表1
        ListNode n1 = new ListNode(4);
        ListNode n2 = new ListNode(1);

        //链表2
        ListNode n3 = new ListNode(5);
        ListNode n4 = new ListNode(6);
        ListNode n5 = new ListNode(2);

        //相交节点
        ListNode n6 = new ListNode(8);
        ListNode n7 = new ListNode(7);
        ListNode n8 = new ListNode(3);

        n1.next = n2; n2.next = n3; n3.next = n4; n4.next = n2;
        n5.next = n6; n6.next = n7; n7.next = n8; n8.next = n6;
        ListNode m3 = test.getIntersectionNode(n1, n5);
        System.out.println("有环  不相交 :" + (m3 != null ? m3.value : null));  //不相交


        n1.next = n2; n2.next = n3; n3.next = n4; n4.next = n5; n5.next = n3;
        n6.next = n7; n7.next = n8; n8.next = n3;
        ListNode m4 = test.getIntersectionNode(n1, n6);
        System.out.println("有环  第一个相交点相交 :" + (m4 != null ? m4.value : null));   //n3对应的值是5


        n1.next = n2; n2.next = n3; n3.next = n4; n4.next = n5; n5.next = n3;
        n6.next = n7; n7.next = n8; n8.next = n2;
        ListNode m5 = test.getIntersectionNode(n1, n6);
        System.out.println("有环  环外相交 :" + (m5 != null ? m5.value : null));   //n2对应的值是1

        System.out.println("================测试2个环形链表 环内 相交===========================");
        n6.next = n7; n7.next = n8; n8.next = n4;
        ListNode m6 = test.getIntersectionNode(n1, n6);
        System.out.println("有环  环外相交 :" + (m6 != null ? m6.value : null));   //n2对应的值是1 n4对应6
    }
}

 

posted @ 2023-02-20 16:35  街头小瘪三  阅读(25)  评论(0编辑  收藏  举报