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;
}
}