链表

单链表

链表技巧总结

// 对于快慢指针,定位中间元素
// 若为偶数,取中间元素时为:n/2+1 ,例如为4,则3号元素在中间  这种方式对于偶数多循环一次
// while (fast != null && fast.next != null) {
// 若为偶数,取中间元素时为:n/2 ,例如为4,则2号元素在中间
// while (fast.next != null && fast.next.next != null) {

链表实体定义

@ToString
@AllArgsConstructor
public class Node {
  public int val;
  public Node next;
  public Node(int val) {
    this.val = val;
  }
}
// 初始值
Node node5 = new Node(5);
Node node4 = new Node(4, node5);
Node node3 = new Node(3, node4);
Node node2 = new Node(2, node3);
Node head = new Node(1, node2);

链表头节点插值

Node head = new Node(1);
Node node1 = new Node(2);
// 这部分不能抽取为方法   链表循环反转时有使用到它
node1.next = head;
head = node1;
System.out.println(head);

链表取中

Node getMid(Node head) {
  // 思路,设计一个快指针(两步),一个慢指针(一步)
  Node slow = head;
  Node fast = head;
  // 若为偶数,取中间元素时为:n/2+1 ,例如为4,则3号元素在中间
  // while (slow != null && fast != null && fast.next != null) {
  // 若为偶数,取中间元素时为:n/2 ,例如为4,则2号元素在中间
  while (slow != null && fast.next != null && fast.next.next != null) {
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow;
}

删除单链表的中间节点

给定链表的头节点head,实现删除链表的中间节点的函数

例如:

1->2,删除节点1;

1->2->3,删除节点2;

1->2->3->4,删除节点2;

1->2->3->4-5,删除节点3

Node deleteMid(Node head) {
  if (head == null || head.next == null) {
    return head;
  }
  // 慢指针
  Node slow = head;s
  // 快指针  这样赋值少循环一次,让slow为待删除元素前一个值
  Node fast = head.next.next;
  while (fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
  }
  // 这样是为了便于GC回收
  // Node deleteNode = slow.next;
  // slow.next = slow.next.next;
  // deleteNode.next = null;
  // 可以直接这样来删除后一个元素,但按照可达性分析法,GC不能回收待删除元素
  slow.next = slow.next.next;
  return head;
}

删除链表中 a/b 处的节点

给定链表的头节点 head、整数 a 和 b,实现删除位于 a/b 处节点的函数

例如:

链表:1->2->3->4->5,假设 a/b 的值为 r。

如果 r = 0,不删除任何节点

如果 r 在区间 (0,1/5] 上,删除节点 1;

如果 r 在区间 (3/5,4/5] 上,删除节点 4;

如果 r 在区间 (4/5,1] 上,删除节点 5;

如果 r 大于 1,不删除任何节点。

// 这道题可以转换为删除第 K = (a * n / b)个节点。其中n表示链表节点的个数,但由于(a * n / b)有可能出现小数,所以我们取 K的上限。
// 所谓上限就是大于等于K的最小整数
Node deleteByRadio(Node head, int a, int b) {
  if (a < 1 || a > b)
    return head;
  int n = 0;
  Node cur = head;
  //统计一共有多少个节点
  while (cur != null) {
    n++;
    cur = cur.next;
  }
  // 问题转换为删除第K个节点,取(a * n / b)的整数上限
  int K = (int) Math.ceil((double) (a * n) / (double) b);
  if (K == 1)
    return head.next;
  if (K > 1) {
    cur = head;
    //定位到第K个节点的前驱
    while (--K != 1) {
      cur = cur.next;
    }
    cur.next = cur.next.next;
  }
  return head;
}

删除单链表的第 K个节点

在单链表中删除倒数第 K 个节点

删除的时候会出现三种情况:

  1. 不存在倒数第 K 个节点,此时不用删除
  2. 倒数第 K 个节点就是第一个节点
  3. 倒数第 K 个节点在第一个节点之后

所以我们可以用一个变量 sum 记录链表一共有多少个节点:

  1. 如果 num < K,则属于第一种情况
  2. 如果 num == K,则属于第二种情况
  3. 如果 num > K, 则属于第三种情况,此时删除倒数第 K 个节点等价于删除第 (num - k + 1) 个节点
Node removeLastKthNode(Node head, int K) {
  if (head == null || K < 1)
    return head;
  Node temp = head;
  int num = 0;
  while (temp != null) {
    num++;
    temp = temp.next;
  }
  if (num == K) {
    return head.next;
  }
  if (num > K) {
    temp = head;
    // 删除第(num-k+1)个节点
    // 定位到这个点的前驱
    while (num - K != 0) {
      temp = temp.next;
      num--;
    }
    temp.next = temp.next.next;
  }
  return head;
}

链表反转

反转单链表。例如链表为:1->2->3->4。反转后为 4->3->2->1

循环

当我们在反转一个节点的时候,把一个节点的后驱改为指向它前驱就可以了

注意:当你把当前节点的后驱指向前驱的时候,这个时候链表会被截断,也就是说后面的节点和当前节点分开了,所以我们需要一个变量来保存当前节点的后驱,以访丢失

// 反转链表,时间复杂度:0(n),空间复杂度0(1)
Node reverseList2(Node head) {
  // 指向当前节点的前驱   已完成反转的链表头节点
  Node pre = null;
  // 指向当前节点的后驱   还未完成反转的链表头节点
  Node next = null;
  while (head != null) {
    // 获取当前节点的后驱,表示尚未完成反转的链表
    next = head.next;
    // 断开当前节点与原来节点的关系,pre保存着新链表的首节点
    head.next = pre;
    pre = head;
    // 把当前节点的下一个节点赋值给当前节点,此时它为未反转节点的首节点
    head = next;
  }
  return pre;
}

递归法

递归头:当链表只有一个节点,或者是空表

递归体:这个的等价关系不像 n 是个数值那样,比较容易寻找。但对于链表来说,还是链表的节点个数不断在变小。例如链表1->2->3->4,首先将2->3->4当作一个整体2与1交换。交换规则为:把整体 2 的 next 指向 1,然后把 1 的 next 指向 null。所以定义函数功能为:改变节点1、2的指向

Node reverseList(Node head) {
  // 递归头:当链表只有一个节点,或者是空表
  if (head == null || head.next == null) {
    return head;
  }
  // 递归体:递归反转 子链表
  Node newList = reverseList(head.next);
  // 改变1,2节点的指向  将节点2指向节点1,节点1后置节点置空
  head.next.next = head;
  // 通过head.next获取节点2
  // Node temp = head.next;
  // 让2的next指向1
  // temp.next = head;
  // 让1的next置为null
  head.next = null;
  // 将调整之后的链表返回
  return newList;
}

分析:第一次进入第10行时,是head=Node(val=4, next=Node(val=5, next=null)),newlist为Node(val=5, next=null)

反转部分链表节点

题目:给定一个单向链表的头结点head,以及两个整数from和to,在单项链表上把第from个节点和第to个节点这一部分进行反转

列如: 1->2->3->4->5->null,from=2、to=4 结果:1->4->3->2->5->null

列如:1->2->3->null,from=1、to=3 结果为3->2->1->null

【要求】

  1. 如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)
  2. 如果不满足1<=from<=to<=N,则不调整
    在这里插入图片描述
Node reversePart(Node head, int from, int to) {
  // 在头节点之前定义一个新的节点
  Node preHead = new Node(0);
  preHead.next = head;
  Node cur = preHead;
  for (int i = 1; i < from; i++)
    cur = cur.next;
  // curPre定位到 from-1 处节点
  Node curPre = cur;
  // cur定位到 from 处节点
  cur = cur.next;
  // 从from处节点到to处节点,需要将to-from+1个节点进行翻转,那就是将to-from个节点插在from处节点之前,因此需要to-from次操作
  // 进入循环之前需要定义一个next
  Node next = null;
  for (int i = 0; i < to - from; i++) {
    // for(int i = from; i < to; i++) {
    next = cur.next;
    cur.next = next.next;
    next.next = curPre.next;
    curPre.next = next;
  }
  // 结束后,输出preHead.next即可
  return preHead.next;
}

两两交换链表

1->2->3->4 2->1->4->3

此次图例参考反转from->to链表元素

Node swapPairs(Node head) {
  Node preHead = new Node(0, head);
  Node cur = head;
  Node pre = preHead;
  Node next = null;
  while (cur != null && cur.next != null) {
    next = cur.next;
    cur.next = next.next;
    next.next = cur;
    pre.next = next;
    // 指向下一段的当前节点与下一个节点
    cur = cur.next;
    pre = preHead.next.next;
  }
  return preHead.next;
}

1->2->3->4->5 1->3->2->5->4

Node node1 = reverse(swapPairs(reverse(head)));

将单链表的每K个节点之间逆序

给定一个单链表的头节点head,实现一个调整单链表的函数,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整最后几个节点

例如:

链表:1->2->3->4->5->6->7->8->null, K = 3。调整后:3->2->1->6->5->4->7->8->null。其中 7,8不调整,因为不够一组

可以用递归来实现,假设方法reverseKNode()的功能是将单链表的每K个节点逆序(从头部开始组起的哦);reverse()方法的功能是将一个单链表逆序

那么对于下面的这个单链表,其中 K = 3

在这里插入图片描述

把前K个节点与后面的节点分割出来:

在这里插入图片描述

temp指向的剩余的链表,可以说是原问题的一个子问题。可以调用reverseKNode()方法将temp指向的链表每K个节点进行逆序。再调用reverse()方法把head指向的那3个节点进行逆序,结果如下:

在这里插入图片描述

接着,我们只需要把这两部分给连接起来就可以了。最后的结果如下:

在这里插入图片描述

Node reverseKNodes(Node head, int k) {
  if (head == null || head.next == null) {
    return head;
  }
  Node cur = head;
  for (int i = 1; cur != null && i < k; i++) {
    cur = cur.next;
  }
  // 判断节点的数量是否能够凑成一组
  if (cur == null) {
    return head;
  }
  // temp指向剩余的链表
  Node temp = cur.next;
  cur.next = null;
  //把k个节点进行反转
  Node newHead = reverse(head);
  //把之后的部分链表进行每K个节点逆转
  Node newTemp = reverseKNodes(temp, k);
  //把两部分节点连接起来
  head.next = newTemp;
  return newHead;
}

// 逆序单链表
private static Node reverse(Node head) {
  if (head == null || head.next == null)
    return head;
  Node result = reverse(head.next);
  // 改变1,2节点的指向  将节点2指向节点1,节点1后置节点置空
  head.next.next = head;
  head.next = null;
  return result;
}
反转逆序

给定一个单链表的头节点 head,实现一个调整单链表的函数,使得每K个节点为一组进行逆序,并且从链表的尾部开始组起,头部剩余节点数量不够一组的不需要逆序(不能使用队列或者栈作为辅助)

例如:链表:1->2->3->4->5->6->7->8->null, K = 3。那么 6->7->8,3->4->5,1->2各位一组。调整后:1->2->5->4->3->8->7->6->null。其中 1,2不调整,因为不够一组

这道题的难点在于,是从链表的尾部开始组起的,而不是从链表的头部,如果是头部的话,那我们还是比较容易做的,因为你可以遍历链表,每遍历 k 个就拆分为一组来逆序。但是从尾部的话就不一样了,因为是单链表,不能往后遍历组起

其实这道题只需要先把单链表进行一次逆序,逆序之后就能转化为从头部开始组起了,然后按照我上面的解法,处理完之后,把结果再次逆序即搞定。两次逆序相当于没逆序

例如对于链表(其中 K = 3)

在这里插入图片描述

我们把它从尾部开始组起,每 K 个节点为一组进行逆序。步骤如下:

  1. 先进行逆序
    在这里插入图片描述

    逆序之后就可以把问题转化为从头部开始组起,每 K 个节点为一组进行逆序

  2. 处理后的结果如下:
    在这里插入图片描述

  3. 接着在把结果逆序一次,结果如下:
    在这里插入图片描述

Node solve(Node head, int k) {
  // 调用逆序函数
  head = reverse(head);
  // 调用每 k 个为一组的逆序函数(从头部开始组起)
  head = reverseKNodes(head, k);
  // 在逆序一次
  head = reverse(head);
  return head;
}

约瑟夫环

Josephu 问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列

提示:用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束

【要求】

输入:一个环形单向链表的头节点 head 和报数 m.

返回:最后生存下来的节点,且这个节点自己组成环形单向链表,其他节点都删除掉

这道题如果不考虑时间复杂度的话还是挺简单的,就遍历环形链表,每遍历 m 个节点就删除一个节点,直到链表只剩下一个节点就可以了

Node createCycle(int n) {
  if (n < 1) {
    return null;
  }
  Node head = null;
  // 在形成环形链表时需要一个辅助指针,指向环形链表的最后一个节点
  Node temp = null;
  for (int no = 1; no < n + 1; no++) {
    Node node = new Node(no);
    if (no == 1) {
      head = node;
      // 形成一个环形的链表
      head.next = head;
    } else {
      temp.next = node;
      node.next = head;
    }
    temp = node;
  }
  return head;
}

要注意领会,首节点被遍历到最后一个节点才开始计数

// 时间复杂度为O(n*m)的解决方法
Node josephusJump(Node head, int m) {
  if (head == null || m < 1) {
    return head;
  }
  // 辅助节点,用于定位head到最后一个节点
  Node temp = head;
  while (head.next != temp) {
    head = head.next;
  }
  int count = 0;
  while (head.next != head) {
    if (++count == m) {
      head.next = head.next.next;
      count = 0;
    } else {
      head = head.next;
    }
  }
  return head;
}

合并两个有序链表

两个有序链表默认都是按照从小到大排列的

Node mergeTwoList2(Node head1, Node head2) {
  if (head1 == null || head2 == null) {
    return head1 != null ? head1 : head2;
  }
  Node head = head1.val > head2.val ? head2 : head1;
  // 指向头结点较小的链表
  Node small = head == head1 ? head1 : head2;
  // 指向头结点较大的链表
  Node big = head == head1 ? head2 : head1;
  // 将big中的值放到small中
  // small前一个元素
  Node pre = null;
  // big后一个元素
  Node next = null;
  while (small != null && big != null) {
    if (small.val <= big.val) {
      pre = small;
      small = small.next;
    } else {
      // 把big的元素合并到small中
      next = big.next;
      pre.next = big;
      big.next = small;
      pre = big;
      big = next;
    }
  }
  pre.next = small == null ? big : small;
  return head;
}
Node mergeTwoList(Node head1, Node head2) {
  Node head = null;
  if (head1 == null || head2 == null) {
    return head1 != null ? head1 : head2;
  } else {
    if (head1.val > head2.val) {
      head = head2;
      head.next = mergeTwoList(head1, head2.next);
    } else {
      head = head1;
      head.next = mergeTwoList(head1.next, head2);
    }
  }
  return head;
}

奇数位升序,偶数位降序,对该链表排序

1、8、3、6、5、4、7、2、9

Node reverse(Node head) {
  Node pre = null;
  Node next = null;
  while (head != null) {
    next = head.next;
    head.next = pre;
    pre = head;
    head = next;
  }
  return pre;
}

Node mergeTwoList3(Node head1, Node head2) {
  Node head = null;
  if (head1 == null || head2 == null) {
    return head1 == null ? head2 : head1;
  } else {
    if (head1.val > head2.val) {
      head = head2;
      head.next = mergeTwoList3(head1, head2.next);
    } else {
      head = head1;
      head.next = mergeTwoList3(head1.next, head2);
    }
  }
  return head;
}

/*思路:可以分为三步:
* 1、按照奇数位和偶数位拆分为两个链表
* 2、对偶数位进行反转
* 3、将两个有序链表进行合并*/
public static Node[] getLists(Node head) {
  Node head1 = null;
  Node head2 = null;
  Node cur1 = null;
  Node cur2 = null;
  // 用来计数
  int count = 1;
  while (head != null) {
    if (count % 2 == 1) {
      if (cur1 != null) {
        cur1.next = head;
        cur1 = cur1.next;
      } else {
        cur1 = head;
        head1 = cur1;
      }
    } else {
      if (cur2 != null) {
        cur2.next = head;
        cur2 = cur2.next;
      } else {
        cur2 = head;
        head2 = cur2;
      }
    }
    head = head.next;
    count++;
  }
  cur1.next = null;
  cur2.next = null;
  Node[] nodes = new Node[]{head1, head2};
  return nodes;
}
Node node9 = new Node(9);
Node node2 = new Node(2, node9);
Node node7 = new Node(7, node2);
Node node4 = new Node(4, node7);
Node node5 = new Node(5, node4);
Node node6 = new Node(6, node5);
Node node3 = new Node(3, node6);
Node node8 = new Node(8, node3);
Node head = new Node(1, node8);
Node[] lists = getLists(head);
Node head1 = lists[0];
Node head2 = lists[1];
head2 = reverse(head2);
Node node = mergeTwoList3(head1, head2);
System.out.println(node);

判断回文链表

可以让链表的后半部分入栈,然后把栈中的元素与链表的前半部分对比,例如 1->2->3->2->2。然后逐个出栈,与链表的前半部分(1->2)对比。这样做的话空间复杂度会减少一半

boolean f(Node head) {
  if (head == null || head.next == null) {
    return true;
  }
  Node slow = head;
  Node fast = head;
  // slow最终指向中间节点   5指向3,4指向2
  while (fast.next != null && fast.next.next != null) {
    slow = slow.next;
    fast = fast.next.next;
  }
  Stack<Node> stack = new Stack<>();
  while (slow != null) {
    stack.push(slow);
    slow = slow.next;
  }
  // 进行判断
  while (!stack.empty()) {
    if (head.val != stack.pop().val) {
      return false;
    }
    head = head.next;
  }
  return true;
}

优化

可以把链表的后半部分进行反转,然后再用后半部分与前半部分进行比较就可以了。这种做法额外空间复杂度只需要 O(1),时间复杂度为 O(n)

boolean f2(Node head) {
  if (head == null || head.next == null) {
    return true;
  }
  Node slow = head;
  Node fast = head;
  while (fast.next != null && fast.next.next != null) {
    slow = slow.next;
    fast = fast.next.next;
  }
  // 这里注意,4时slow为2,将cur指向3号位置
  Node cur = slow.next;
  // 链表采用循环方式反转,需要2个辅助参数,一个用来保存反转后的头结点,一个用来保存待保存的头结点
  Node next;
  Node pre = null;
  while (cur != null) {
    // next保存反转后的头结点
    next = cur.next;
    // 下面两步操作,相当于在头结点pre前面加一个节点,在将头节点赋值给pre
    cur.next = pre;
    pre = cur;
    cur = next;
  }
  while (pre != null) {
    if (pre.val != head.val) {
      return false;
    }
    pre = pre.next;
    head = head.next;
  }
  return true;
}

复制带随机指针的链表

2两数相加

将两个链表看成是相同长度的进行遍历,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为 n1,n2,进位值为 carry,则它们的和为 n1+n2+carry;其中,链表相应位置的数字为(n1+n2+carry)%10,而新的进位值为 (n1+n2+carry)/10。如果一个链表较短则在前面补0,比如 987 + 23 = 987 + 023 = 1010

每一位计算的同时需要考虑上一位的进位问题,而当前位计算结束后同样需要更新进位值

如果两个链表全部遍历完毕后,进位值为1,则在新链表最前方添加节点 1

小技巧:对于链表问题,返回结果为头结点时,通常需要先初始化一个预先指针 dummy,该指针的下一个节点指向真正的头结点head。使用预先指针的目的在于链表初始化时无可用节点值,而且链表构造过程需要指针移动,进而会导致头指针丢失,无法返回结果

ListNode addTwoNumbers(ListNode p, ListNode q) {
  ListNode dummy = new ListNode(-1);
  ListNode cur = dummy;
  int carry = 0;
  while (p != null || q != null) {
    int x = p == null ? 0 : p.val;
    int y = q == null ? 0 : q.val;
    int sum = x + y + carry;
    carry = sum > 9 ? 1 : 0;  // carry = sum / 10;
    sum = sum % 10;
    // cur始终指向最尾端结点
    cur.next = new ListNode(sum);
    cur = cur.next;
    if (p != null)
      p = p.next;
    if (q != null)
      q = q.next;
  }
  if (carry == 1) {
    cur.next = new ListNode(carry);
  }
  return dummy.next;
}

判断链表中是否有环

给定一个单链表,判断其中是否有环,已经是一个比较老同时也是比较经典的问题,在网上搜集了一些资料,然后总结一下大概可以涉及到的问题,以及相应的解法

首先,关于单链表中的环,一般涉及到以下问题:

  1. 给一个单链表,判断其中是否存在环
  2. 如果存在环,找出环的入口
  3. 如果存在环,求出环上节点个数
  4. 如果存在环,求出链表长度
  5. 如果存在环,求出环上距离任意一个节点最远的点(对面节点)
  6. (扩展)如何判断两个无环链表是否相交
  7. (扩展)如果相交,求出第一个相交的节点

判断是否有环(链表头指针为head)

对于这个问题可以采用“快慢指针”的方法。就是有两个指针fast和slow,开始时候两个指针都指向链表头head,然后在每一步操作中,slow向前走一步:slow = slow->next,而fast每一步向前两步:fast = fast->next->next

由于fast要比slow移动的快,如果有环,fast一定会先进入环,而slow后进入环。当两个指针都进入环之后,经过一定步的操作之后,二者一定能够在环上相遇,并且此时slow还没有绕环一圈,也就是说一定是在slow走完第一圈之前相遇。证明可以看下图:
在这里插入图片描述

当slow刚进入环时每个指针可能处于上面的情况,接下来slow和fast分别向前走即:

if (slow != null && fast != null && fast.next != null) {
  slow = slow.next;
  fast = fast.next.next;
}

也就是说,slow每次向前走一步,fast向前追了两步,因此每走一步后fast到slow的距离缩短1,这样继续下去就会使得两者之间的距离逐渐缩小:…、5、4、3、2、1、0(相遇)。又因为在同一个环中fast和slow之间的距离不会大于换的长度,因此到二者相遇的时候slow一定还没有走完一周(或者正好走完,这种情况出现在开始的时候fast和slow都在环的入口处)

boolean existLoop(Node head) {
  Node slow = head;
  Node fast = head;
  while (slow != null && fast != null && fast.next != null) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow == fast) {
      return true;
    }
  }
  // 代表不存在环
  return false;
}

找出环的入口点

从上面可知,当fast和slow相遇时,slow还没有走完链表,假设fast已经在环内循环了n(1<= n)圈。slow走了s步,则fast走了2s步,又由于fast走过的步数 = s + n*r(s + 在环上多走的n圈),则有下面的等式:

2*s = s + n  * r ;
=> s = n * r

如果假设整个链表的长度是L,入口和相遇点的距离是x(如上图所示),起点到入口点的距离是a(如上图所示),则有:
在这里插入图片描述

a + x = s = n * r;
// 由环的长度 = 链表总长度 - 起点到入口点的距离求出
a + x = s = (n - 1) * r + r  = (n - 1) * r + (L - a)
a = (n - 1) * r + (L - a - x) // 相遇点到环入口节点的距离

结论:

  1. (L - a - x)为相遇点到环入口节点的距离,即从相遇点开始向前移动(L - a - x)步后,会两次到达环入口节点
  2. 即:从链表的头节点到环入口节点的距离=(n-1)*环内循环+相遇点到环入口点的距离
  3. 于是从链表头与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点(环内的多循环了n-1圈,才等到头节点的相遇)

因此就可以分别用一个指针(headNode, meetingNode),同时从head与相遇点出发,每一次操作走一步,直到headNode == meetingNode,此时的位置也就是入口点

Node findLoopStart(Node head) {
  if (head == null || head.next == null) {
    return head;
  }
  Node slow = head;
  Node fast = head;
  // 最后一个判断节点是存在环的情况
  do {
    slow = slow.next;
    fast = fast.next.next;
  } while(slow!= null && fast != null && fast.next != null && slow != fast);
  // 没有环,返回null
  // if (slow == null || fast.next == null)
  if (slow != fast) {
    return null;
  }
  // 链表开始结点
  Node headNode = head;
  // 环入口点
  Node meetingNode = fast;
  while (headNode != meetingNode) {
    headNode = headNode.next;
    meetingNode = meetingNode.next;
  }
  // 返回环的入口节点
  return meetingNode;
}

存在环,求出环上节点个数

对于这个问题,有两个思路(肯定还有其它跟好的办法):

思路1:相遇之后让slow(或者fast都一样)继续向前走slow = slow.next;一直到slow == fast; 此时经过的步数就是环上节点的个数

思路2: 从相遇点开始slow和fast继续按照原来的方式向前走slow = slow -> next; fast = fast -> next -> next;直到二者再次项目,此时经过的步数就是环上节点的个数

int calculateLoopLength(Node head) {
  if (head == null) {
    return 0;
  }
  if (head.next == null) {
    return 1;
  }
  Node slow = head.next;
  Node fast = head.next.next;
  while (slow != null && fast != null && fast.next != null && slow != fast){
    slow = slow.next;
    fast = fast.next.next;
  }
  // 没有环,返回0
  if (slow != fast) {
    return 0;
  }
  int step = 0;
  do {
    slow = slow.next;
    step++;
  } while (slow != fast);
  return step;
}

存在环,求出链表的长度

到这里,问题已经简单的多了,因为有问题1、2、3中已经做得足够的”准备工作“。可以这样求出整个链表的长度:

链表长度L = 起点到入口点的距离 + 环的长度r

已经知道了起点和入口点的位置,那么两者之间的距离很好求了吧!环的长度也已经知道了,因此该问题也就迎刃而解了

int getLinkLength(Node head) {
  int step = 0;
  Node loopStart = findLoopStart(head);
  if (loopStart == null) {
    return 0;
  }
  if (loopStart == head) {
    return 1;
  }
  // 计算head到环入口的长度
  do {
    head = head.next;
    step++;
  } while (head != loopStart);
  // 计算环长度
  do {
    head = head.next;
    step++;
  } while (head != loopStart);
  return step;
}

存在环,求出环上距离任意一个节点最远的点

def getLongNode(head: SingleNode, LoopNode: SingleNode): SingleNode = {
  // 需要判断 LoopNode 是否是属于环上节点
  var (slow, fast) = (LoopNode, LoopNode)
  do {
    slow = slow.next
    fast = fast.next.next
  } while (fast == LoopNode)
  slow
}

如何判断两个无环链表是否相交

假设有两个链表listA和listB,如果两个链表都无环,并且有交点,那么可以让其中一个链表(不妨设是listA)的首尾节点相连,这样在listB中就一定会出现一个环

如果相交,求出第一个相交的节点

3中已经做得足够的”准备工作“。可以这样求出整个链表的长度:

链表长度L = 起点到入口点的距离 + 环的长度r

已经知道了起点和入口点的位置,那么两者之间的距离很好求了吧!环的长度也已经知道了,因此该问题也就迎刃而解了

int getLinkLength(Node head) {
  int step = 0;
  Node loopStart = findLoopStart(head);
  if (loopStart == null) {
    return 0;
  }
  if (loopStart == head) {
    return 1;
  }
  // 计算head到环入口的长度
  do {
    head = head.next;
    step++;
  } while (head != loopStart);
  // 计算环长度
  do {
    head = head.next;
    step++;
  } while (head != loopStart);
  return step;
}

存在环,求出环上距离任意一个节点最远的点

def getLongNode(head: SingleNode, LoopNode: SingleNode): SingleNode = {
  // 需要判断 LoopNode 是否是属于环上节点
  var (slow, fast) = (LoopNode, LoopNode)
  do {
    slow = slow.next
    fast = fast.next.next
  } while (fast == LoopNode)
  slow
}

如何判断两个无环链表是否相交

假设有两个链表listA和listB,如果两个链表都无环,并且有交点,那么可以让其中一个链表(不妨设是listA)的首尾节点相连,这样在listB中就一定会出现一个环

如果相交,求出第一个相交的节点

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

①笶侕濄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值