企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
```java package com.xiaodai.algorithm; /** * Author :dai * Date :2020/12/25 5:04 下午 * Description: */ public class LinkedListUtil { /** * 链表的节点,可实现成泛型 */ public static class Node { public int value; public Node next; public Node(int data) { value = data; } } /** * 双向列表的节点结构,可实现成泛型 */ public static class DoubleNode { public int value; public DoubleNode last; public DoubleNode next; public DoubleNode(int data) { value = data; } } /** * 1、检测链表是否成环。返回成环是否,第一次相遇并不保证是成环的节点 * * @param head * @return */ public boolean hasCycle(Node head) { if (head == null || head.next == null) { return false; } Node slow = head; Node fast = head.next; while (slow != fast) { if (fast == null || fast.next == null) { return false; } slow = slow.next; fast = fast.next.next; } // 有环的话一定追的上,但不一定是第一次成环的节点 return true; } /** * 2、传入头节点,翻转单项链表 * * @param head * @return */ public static Node reverseLinkedList(Node head) { Node pre = null; Node next = null; while (head != null) { next = head.next; head.next = pre; pre = head; head = next; } return pre; } /** * 3、移除链表中等于值的节点 * <p> * 例如:1->2->3->3->4->5->3, 和 val = 3, 你需要返回删除3之后的链表:1->2->4->5。 * * @param head * @param num * @return */ public static Node removeValue(Node head, int num) { // 从链表的头开始,舍弃掉开头的且连续的等于num的节点 while (head != null) { if (head.value != num) { break; } head = head.next; } // head来到 第一个不需要删的位置 Node pre = head; Node cur = head; // 快慢指针 while (cur != null) { if (cur.value == num) { // 快指针cur向下滑动,如果值等于num,则暂时把下一个节点给慢指针的下一个指向。从而跳过等于num的节点 pre.next = cur.next; } else { // cur此时到了不等于num的节点,则慢指针追赶上去。达到的效果就是等于num的节点都被删掉了 pre = cur; } // 快指针向下滑动 cur = cur.next; } return head; } /** * 4、打印两个有序链表的公共部分 * 例如:head1: 1->2->3->3->4->5 head2: 0->0->1->2->3->3->7->9 * 公共部分为:1 2 3 3 * * @param head1 * @param head2 */ public void printCommonPart(Node head1, Node head2) { System.out.println("Common Part: "); while (head1 != null && head2 != null) { if (head1.value < head2.value) { head1 = head1.next; } else if (head1.value > head2.value) { head2 = head2.next; } else { System.out.println(head1.value); head1 = head1.next; head2 = head2.next; } } System.out.println(); } /** * 5、删除单链表的倒数第k个节点 * * @param head * @param lastKth * @return */ public Node removeLastKthNode(Node head, int lastKth) { if (head == null || lastKth < 1) { return head; } // cur指针也指向链表头节点 Node cur = head; // 检查倒数第lastKth个节点的合法性 while (cur != null) { lastKth--; cur = cur.next; } // 需要删除的是头结点 if (lastKth == 0) { head = head.next; } if (lastKth < 0) { // cur回到头结点 cur = head; while (++lastKth != 0) { cur = cur.next; } // 次吃cur就是要删除的前一个节点。把原cur.next删除 cur.next = cur.next.next; } // lastKth > 0的情况,表示倒数第lastKth节点比原链表程度要大,即不存在 return head; } /** * 6、删除链表中间节点 * 思路:如果链表为空或者只有一个节点,不做处理。链表两个节点删除第一个节点,链表三个节点,删除中间第二个节点,链表四个节点,删除上中点 * * @param head * @return */ public Node removeMidNode(Node head) { // 无节点,或者只有一个节点的情况,直接返回 if (head == null || head.next == null) { return head; } // 链表两个节点,删除第一个节点 if (head.next.next == null) { return head.next; } Node pre = head; Node cur = head.next.next; // 快慢指针 if (cur.next != null && cur.next.next != null) { pre = pre.next; cur = cur.next.next; } // 快指针走到尽头,慢指针奇数长度停留在中点,偶数长度停留在上中点。删除该节点 pre.next = pre.next.next; return head; } /** * 7、给定一个链表,如果成环,返回成环的那个节点 * <p> * 思路: * 1. 快慢指针fast和slow,开始时,fast和slow都指向头节点,fast每次走两步,slow每次走一步 * 2. 快指针向下移动的过程中,如果提前到达null,则链表无环,提前结束 * 3. 如果该链表成环,那么fast和slow一定在环中的某个位置相遇 * 4. 相遇后,立刻让fast回到head头结点,slow不动,fast走两步改为每次走一步。fast和slow共同向下滑动,再次相遇,就是成环节点 * * @param head * @return */ public Node getLoopNode(Node head) { // 节点数目不足以成环,返回不存在成环节点 if (head == null || head.next == null || head.next.next == null) { return null; } Node n1 = head.next; // slow指针 Node n2 = head.next.next; // fast指针 while (n1 != n2) { // 快指针提前到达终点,该链表无环 if (n2.next == null || n2.next.next == null) { return null; } n2 = n2.next.next; n1 = n1.next; } // 确定成环,n2回到头节点 n2 = head; while (n1 != n2) { n2 = n2.next; n1 = n1.next; } // 再次相遇节点,就是成环节点 return n1; } /** * 由于单链表,两个链表相交要不然两个无环链表相交,最后是公共部分;要不然两个链表相交,最后是成环部分 * <p> * 8、判断两个无环链表是否相交,相交则返回相交的第一个节点 * <p> * 思路: * 1. 链表1从头结点遍历,统计长度,和最后节点end1 * 2. 链表2从头结点遍历,统计长度,和最后节点end2 * 3. 如果end1不等一end2则一定不相交,如果相等则相交,算长度差,长的链表遍历到长度差的长度位置,两个链表就汇合在该位置 * * @param head1 * @param head2 * @return */ public Node noLoop(Node head1, Node head2) { if (head1 == null || head2 == null) { return null; } Node cur1 = head1; Node cur2 = head2; int n = 0; while (cur1.next != null) { n++; cur1 = cur1.next; } while (cur2.next != null) { n--; cur2 = cur2.next; } // 最终没汇聚,说明两个链表不相交 if(cur1 != cur2) { return null; } cur1 = n > 0 ? cur1 : cur2; cur2 = cur1 == head1 ? head2 : head1; n = Math.abs(n); while (n != 0) { n--; cur1 = cur1.next; } while (cur1 != cur2) { cur1 = cur1.next; cur2 = cur2.next; } return cur1; } /** * 9、合并两个有序链表 * @param head1 * @param head2 * @return */ public Node mergeTwoList(Node head1, Node head2) { // base case if (head1 == null || head2 == null) { return head1 == null ? head2 : head1; } // 选出两个链表较小的头作为整个合并后的头结点 Node head = head1.value <= head2.value ? head1 : head2; // 链表1的准备合并的节点,就是头结点的下一个节点 Node cur1 = head.next; // 链表2的准备合并的节点,就是另一个链表的头结点 Node cur2 = head == head1 ? head2 : head1; // 最终要返回的头结点,预存为head,使用引用拷贝的pre向下移动 Node pre = head; while (cur1 != null && cur2 != null) { if (cur1.value <= cur2.value) { pre.next = cur1; // 向下滑动 cur1 = cur1.next; } else { pre.next = cur2; // 向下滑动 cur2 = cur2.next; } // pre向下滑动 pre = pre.next; } // 有一个链表耗尽了,没耗尽的链表直接拼上 pre.next = cur1 != null ? cur1 : cur2; return head; } } ```