链表:快慢指针

本文目录

1 简介

2 链表中点

3 判断有无环

4 确定环入口

5 判断是否交叉

6 相关文章


1 简介

快慢指针法是链表算法中常用的一种方法,通过两个步长不同的移动指针,可以帮助解决一些链表中的问题。

通常,慢指针每次移动一个节点,快指针每次移动两个节点,因此,二者的路径存在一个二倍的数学关系(偶数节点情况下)。

2 链表中点

通过快慢指针,可以确定链表中间节点,可用于二分查找等算法。

// head为头部节点,head.next表示链表第一个节点。
ListNode slowNode = head, fastNode = head;
while (fastNode != null && fastNode.next != null) {
  slowNode = slowNode.next;
  fastNode = fastNode.next.next;
}
/*
 * 运行至此,各节点情况:
 * 奇数节点:fastNode为null,slowNode为中间节点;
 * 偶数节点:fastNode.next为null,slowNode为中前节点(前半段最后一个节点)。
 */

3 判断有无环

(1)无环:当快指针检查到无下一节点时,说明该链表无环。

当链表中不存在环时,快指针终将优先到达链表尾部。

(2)有环:当快指针与慢指针相遇(指向同一个节点)时,说明该链表有环。

当链表中存在环时,快指针终将进入一个环中,无限循环的移动下去,而慢指针也终将进入这个环中;

由于快指针每次比慢指针多移动一个节点,因此,在环中,快指针将“追逐”慢指针,并最终会在某个节点“追上(相遇)”。

public boolean existLoop(ListNode head) {
  ListNode slowNode = head, fastNode = head;
  while (fastNode != null && fastNode.next != null) {
    slowNode = slowNode.next;
    fastNode = fastNode.next.next;
    if (slowNode == fastNode) {
      return true;
    }
  }
  return false;
}

4 确定环入口

假设一个链表中存在环,如何找到环的入口位置?

3 判断有无环 可知,快慢指针必将在环中某个节点相遇,而相遇时,存在如下关系:

  • 设 到达环入口之前的距离为 L(L ≥ 0),环长度(周长)为 P(P ≥ 2),相遇位置距离环入口长度为 S(0 ≤ S ≤ P)。
  • 慢指针路径长度 = L + S(相遇时,若非在环入口相遇,慢指针必定未走满一个环)
  • 快指针路径长度 = L + n * P + S(相遇时,快指针已经走满n个环)
  • 由于快慢指针路径二倍关系可得  L + S = n * P,则 L = n * P - S

当前相遇位置距离环入口为 S,若从该位置移动 L 距离,将正好停在环的入口位置,数学关系如下:

  • S + L = S + n * P - S = n * P

因此,当快慢指针首次相遇时,启动两个新指针p1和p2,分别从头部节点和相遇节点开始移动,且每次均移动一步,当两个指针相遇时,相遇节点即为环的入口节点,验证如下:

  • p1移动 L 步之后,处于环的入口位置;
  • p2移动 L 步之后,也处于环的入口位置;
  • 因此,两个指针首次相遇必定在环入口位置。
public ListNode findLoopBeginNode(ListNode head) {

  ListNode slowNode = head, fastNode = head;

  do {
    slowNode = slowNode.next;
    fastNode = fastNode.next.next;
  } while (slowNode != fastNode);

  ListNode p1 = head, p2 = fastNode;
  while (p1 != p2) {
    p1 = p1.next;
    p2 = p2.next;
  }

  return p1;
}

5 判断是否交叉

(1)方法一:

分别遍历两个链表,记录两个链表长度及末端节点;

若末端节点相同,则表示存在交叉,若末端节点不同,则不存在交叉;

存在交叉情况下,长链表指针先移动 长度差值 步数(此时剩余链表节点数相同),此后,两个链表指针一起移动,首次相遇的节点即为交叉节点。

public ListNode findCrossNode(ListNode head1, ListNode head2) {
  
  ListNode node1 = head1, node2 = head2;
  int count1 = 0, count2 = 0;
  
  while (node1.next != null) {
    node1 = node1.next;
    count1++;
  }
  while (node2.next != null) {
    node2 = node2.next;
    count2++;
  }
  if (node1 != node2) {
    return null;  // 无交叉
  }

  node1 = head1;
  node2 = head2;
  while (count1 != count2) {
    if (count1 > count2) {
      node1 = node1.next;
      count1--;
    } else {
      node2 = node2.next;
      count2--;
    }
  }
  while (node1 != node2) {
    node1 = node1.next;
    node2 = node2.next;
  }
  return node1;  // 交叉点
}

(2)方法二:(仅提供思路,不再赘述代码)

将其中一个链表首尾相连,然后通过 3 判断有无环 方法来判断另一个链表是否存在环;

若存在环,则存在交叉点,若不存在环,则不存在交叉点;

若存在环,则通过 4 确定环入口 方法来找到交叉点位置。

6 相关文章

《全排列(Java)》

《位运算:减法与补码》

《异或(^)的性质与应用》

《图解:常用排序算法(冒泡、选择、插入、希尔、快速、归并、堆)》

《回溯算法(试探算法)》

《动态规划:鸡蛋掉落》

《动态规划:单词拆分》

《状态机:只出现一次的数字II》

《最小堆:TopK问题》

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值