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

news2024/9/21 1:29:58

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/358667.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

比特数据结构与算法(第三章_上)栈的概念和实现(力扣:20. 有效的括号)

一、栈&#xff08;stack&#xff09;栈的概念&#xff1a;① 栈是一种特殊的线性表&#xff0c;它只允许在固定的一端进行插入和删除元素的操作。② 进行数据插入的删除和操作的一端&#xff0c;称为栈顶 。另一端则称为 栈底 。③ 栈中的元素遵守后进先出的原则&#xff0c;即…

推荐系统[三]:粗排算法常用模型汇总(集合选择和精准预估),技术发展历史(向量內积,WideDeep等模型)以及前沿技术

1.前言:召回排序流程策略算法简介 推荐可分为以下四个流程,分别是召回、粗排、精排以及重排: 召回是源头,在某种意义上决定着整个推荐的天花板;粗排是初筛,一般不会上复杂模型;精排是整个推荐环节的重中之重,在特征和模型上都会做的比较复杂;重排,一般是做打散或满足…

Android OTA 相关工具(一) 虚拟 A/B 之 snapshotctl

Android 虚拟 A/B 分区推出快三年了&#xff0c;不论是 google 还是百度结果&#xff0c;除了源代码之外&#xff0c;竟然没有人提到这个 Android Virtual A/B 的调试工具 &#xff0c;着实让人感觉意外。 所以我相信还有不少人不知道 Android OTA 到底都有哪些调试工具&#…

【React】react-router 路由详解

&#x1f6a9;&#x1f6a9;&#x1f6a9; &#x1f48e;个人主页: 阿选不出来 &#x1f4a8;&#x1f4a8;&#x1f4a8; &#x1f48e;个人简介: 一名大二在校生,学习方向前端,不定时更新自己学习道路上的一些笔记. &#x1f4a8;&#x1f4a8;&#x1f4a8; &#x1f48e;目…

力扣-查找重复的电子邮箱

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;182. 查找重复的电子邮箱二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果总结…

物联网对供应链管理的影响

物联网对于许多行业来说都是一项革命性技术&#xff0c;其应用领域涉及零售、交通、金融、医疗保健和能源等行业。物联网在供应链等流程中已经展示了其深度的潜力。管理、预测和监督应用程序有助于车队运输经理提高配送的运营效率&#xff0c;并增加决策的准确性。如今&#xf…

网络服务与应用

14.1网络服务与应用概述 14.2实验一&#xff1a;FTP 1、实验环境&#xff1a;如图&#xff0c;AR1作为FTP sever、AR2作为FTP client &#xff0c;实现AR1与AR2之间的文件传输。 2、实验拓扑&#xff1a; 3、实验步骤&#xff1a; 步骤1&#xff1a;配置设备ip地址 AR1: …

固定值电阻的检测方法总结

🏡《总目录》 目录 1,概述2,测量方法3,检测方法3.1,读值3.2,测量3.3,排故4,总结1,概述 本文简单总结固定值电阻的测量与检查方法要点和注意事项。 2,测量方法 对于固定值电阻的测量来讲,直接将万用表红黑表笔分别插入到如下图所示的红色和黑色接线端。然后将万用表…

【MySQL】MySQL表的增删改查(进阶)

✨个人主页&#xff1a;bit me&#x1f447; ✨当前专栏&#xff1a;MySQL数据库&#x1f447; ✨算法专栏&#xff1a;算法基础&#x1f447; ✨每日一语&#xff1a;悟已往之不谏&#xff0c;知来者之可追。实迷途其未远&#xff0c;觉今是而昨非。 目 录&#x1f384;一. 数…

黑马点评项目总结(未完待续)

黑马点评项目总结0. 整体架构1. 短信登录模块1.1 基于session&#xff08;1&#xff09;后台发送验证码Code&#xff08;2&#xff09;登录、注册&#xff08;3&#xff09;校验登录状态1.2 基于Redis&#xff08;1&#xff09;后台发送验证码Code&#xff08;2&#xff09;登录…

ur3+robotiq ft sensor+robotiq 2f 140配置rviz仿真环境

ur3robotiq ft sensorrobotiq 2f 140配置rviz仿真环境 搭建环境&#xff1a; ubuntu: 20.04 ros: Nonetic sensor: robotiq_ft300 gripper: robotiq_2f_140_gripper UR: UR3 在安装sensor和gripper之前&#xff0c;先简单配置一下UR机械臂的仿真环境&#xff0c;可参考这篇博…

零入门kubernetes网络实战-16->使用golang给docker环境下某个容器里添加一个额外的网卡

《零入门kubernetes网络实战》视频专栏地址 https://www.ixigua.com/7193641905282875942 本篇文章视频地址(稍后上传) 上一篇文章&#xff0c;我们使用了golang在veth pair链接的网络命名空间里添加了网卡&#xff0c; 本篇文章&#xff0c;我尝试&#xff0c;在docker环境下…

新建idea项目

目录IDEA系列之创建各种项目 https://blog.csdn.net/LOVEQD123/article/details/105886077 idea 创建项目的三种方式 https://blog.csdn.net/weixin_50034122/article/details/118754521 创建空项目 https://blog.csdn.net/qq_44537956/article/details/123075134 创建 spri…

百度百科词条怎么做?百度百科词条创建攻略分享

只要是想要将自己宣传出去的企业或是个人&#xff0c;都建议创建属于自己的百度百科词条&#xff0c;因为百度百科词条流量大、权重高、排名靠前&#xff0c;创建百度百科词条可以提高企业或是个人的知名度和口碑。 百度百科词条怎么做&#xff1f;每天都有用户在百度上搜索这…

携手亚马逊云科技,大地量子高精度功率预测系统助力清洁能源消纳提速增效

近年来&#xff0c;我国光伏和风电并网装机容量持续增长&#xff0c;截至2021年底&#xff0c;全国可再生能源装机规模突破10亿千瓦&#xff0c;占总发电装机容量的44.8%。其中&#xff0c;风电装机3.28亿千瓦、光伏发电装机3.06亿千瓦。风光电总装机和新增装机规模多年来位居全…

图解 script 标签中的 async 和 defer 属性

图解 script 标签中的 async 和 defer 属性 我们在工作中经常会碰到 script 标签,一般会有以下三种形式 <script srcxxx></script> <script srcxxx async></script> <script srcxxx defer></script>那么这三种形式的 script 标签有什么区…

java 多线程

1.什么是进程&#xff1f;什么是线程&#xff1f; 进程是:一个应用程序&#xff08;1个进程是一个软件&#xff09;。 线程是&#xff1a;一个进程中的执行场景/执行单元。 注意&#xff1a;一个进程可以启动多个线程。 我们在启动java程序的时候&#xff0c;会先启动JVM&am…

【2】MYSQL数据的导入与导出

文章目录 MYSQL-库(相同库名称)的导入导出MYSQL-库(不同库名称)的导入导出MYSQL-表的导入导出MYSQL-表的指定查询记录导入导出前提: 客户端工具是:SQLyog MYSQL-库(相同库名称)的导入导出 1、选中指定库——右键,选择【将数据库复制到不同的主机/数据库】 2、选中指…

分布式之Raft共识算法分析

写在前面 在分布式之Paxos共识算法分析 一文中我们分析了paxos算法&#xff0c;知道了其包括basic paxos和multi paxos&#xff0c;并了解了multi paxos只是一种分布式共识算法的思想&#xff0c;而非具体算法&#xff0c;但可根据其设计具体的算法&#xff0c;本文就一起来看…

SORT与DeepSORT简介

一、MOT( mutil-object tracking)步骤 在《DEEP LEARNING IN VIDEO MUTIL-OBJECT TEACKING: A SURVEY》这篇基于深度学习多目标跟踪综述中&#xff0c;描绘了MOT问题的四个主要步骤 1.跟定视频原始帧 2.使用目标检测器如Faster-rcnn, YOLO, SSD等进行检测&#xff0c;获取目标…