layout: posttitle: PHP 快慢指针的进阶题
subtitle: PHP 快慢指针的进阶题
date: 2020-10-10
author: he xiaodong
header-img: img/default-post-bg.jpg
catalog: true
tags:
- Go
- PHP
- LeetCode 142
- 环形链表
- LeetCode 287
- 寻找重复数
- 快慢指针

环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/linked-list-cycle-ii

解题思路 1

遍历链表,同时将每次的结果放到 map 中,首次遇到重复元素,则直接返回这个元素就行,代码和上一篇文章的类似。

解题思路 2

快慢指针求解:我们定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。

确定有环形之后,就是推理找到入环点:
我们使用两个指针,fastslow。它们起始都位于链表的头部。随后,slow 指针每次向后移动一个位置,而 fast 指针向后移动两个位置。如果链表中存在环,则 fast 指针最终将再次与 slow 指针在环中相遇。

如下图所示,设链表中环外部分的长度为 aa。slow 指针进入环后,又走了 bb 的距离与 fast 相遇。此时,fast 指针已经走完了环的 nn 圈,因此它走过的总距离为 a + n(b + c) + b = a + (n + 1)b + nc

2020-10-10-PHP 快慢指针的进阶题 - 图1

根据题意,任意时刻,fast 指针走过的距离都为 slow 指针的 22 倍。因此,我们有

a + (n + 1)b + nc = 2(a+b) ⟹ a = c + (n − 1)(b + c)

有了 a = c + (n - 1)(b + c) 的等量关系,我们会发现:从相遇点到入环点的距离加上 n-1 圈的环长,恰好等于从链表头部到入环点的距离。

因此,当发现 slowfast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/huan-xing-lian-biao-ii-by-leetcode-solution/
来源:力扣(LeetCode)

  1. /**
  2. * Definition for a singly-linked list.
  3. * class ListNode {
  4. * public $val = 0;
  5. * public $next = null;
  6. * function __construct($val) { $this->val = $val; }
  7. * }
  8. */
  9. class Solution {
  10. /**
  11. * @param ListNode $head
  12. * @return ListNode
  13. */
  14. function detectCycle($head) {
  15. if($head == null || $head->next == null) return null;
  16. $fast = $head;
  17. $slow = $head;
  18. // 在相遇点跳出循环
  19. while ($fast != null && $fast->next != null) {
  20. $fast = $fast->next->next;
  21. $slow = $slow->next;
  22. if ($fast === $slow) {
  23. break;
  24. }
  25. }
  26. // 声明 ptr 指针,步长为 1 的向前走
  27. $ptr = $head;
  28. while ($ptr !== $slow) {
  29. $ptr = $ptr->next;
  30. $slow = $slow->next;
  31. }
  32. // ptr 即为入口
  33. return $ptr;
  34. }
  35. }

类似的另外一题是:

寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

  1. 输入: [1,3,4,2,2]
  2. 输出: 2

示例 2:

  1. 输入: [3,1,3,4,2]
  2. 输出: 3

说明:

不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-the-duplicate-number

解题思路

和上面的快慢指针一样的,只是借助索引将数组想象成链表就可以。

代码
  1. class Solution {
  2. /**
  3. * @param Integer[] $nums
  4. * @return Integer
  5. */
  6. function findDuplicate($nums) {
  7. $slow = 0;
  8. $fast = 0;
  9. $slow = $nums[$slow];
  10. $fast = $nums[$nums[$fast]];
  11. while($slow != $fast){
  12. $slow = $nums[$slow];
  13. $fast = $nums[$nums[$fast]];
  14. }
  15. $pre1 = 0;
  16. $pre2 = $slow;
  17. while($pre1 != $pre2){
  18. $pre1 = $nums[$pre1];
  19. $pre2 = $nums[$pre2];
  20. }
  21. return $pre1;
  22. }
  23. }

参考链接:

  1. 环形链表 Ⅱ 官方题解

最后恰饭 阿里云全系列产品/短信包特惠购买 中小企业上云最佳选择 阿里云内部优惠券