当前位置:   article > 正文

弗洛伊德判圈算法(快慢指针法)解决问题之洛谷287_快慢指针 洛谷

快慢指针 洛谷

287.寻找重复数

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

示例 1:

输入:nums = [1,3,4,2,2]
输出:2

示例 2:

输入:nums = [3,1,3,4,2]
输出:3

提示:

  • 1 <= n <= 105
  • nums.length == n + 1
  • 1 <= nums[i] <= n
  • nums 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次

进阶:

  • 如何证明 nums 中至少存在一个重复的数字?
  • 你可以设计一个线性级时间复杂度 O(n) 的解决方案吗?

1.前言

 经学长推荐发现这道题,在拿到手后想到了2种方法。

1.循环遍历

  1. int findDuplicate(int* nums, int numsSize)
  2. {
  3. for (int i = 0; i < = numsSize-1; i++)//第一层循环表示某一个元素
  4. {
  5. for (int j = i + 1 ; j < = numsSize-1; j++)//第二层循环表示遍历剩下的元素
  6. {
  7. if (nums[i] == nums[j])//若两个元素相等则使标记点加一
  8. {
  9. return nums[i];
  10. }
  11. }
  12. }
  13. return 0;
  14. }

2.数组排序法

  1. int findDuplicate(int* nums, int numsSize)
  2. {
  3. for(int i=0;i<numsSize-1;i++)
  4. {
  5. for(int j=0;j<numsSize-i-1;j++)
  6. {
  7. if(nums[j]<nums[j+1])
  8. {
  9. int t=nums[j];
  10. nums[j]=nums[j+1];
  11. nums[j+1]=t;
  12. }
  13. }
  14. }//冒泡排序排成有序数列
  15. for(int i=0;i<numsSize;i++)//遍历数组
  16. {
  17. if(nums[i]==nums[i+1])//只要有元素相等就将该元素值赋给标记
  18. return nums[i];
  19. }
  20. return 0;
  21. }

 上述两种方法出来的结果如下:

可见,该两种方法都是超时的方法。

2.正确思路

1.快排解决

  1. int cmp(const void* a, const void* b)
  2. {
  3. return *(int*)a - *(int*)b;
  4. }
  5. int findDuplicate(int* nums, int numsSize)
  6. {
  7. qsort(nums, numsSize,sizeof(int), cmp);
  8. for (int i = 1; i < numsSize; ++i)
  9. {
  10. if (nums[i] == nums[i - 1])
  11. {
  12. return nums[i];
  13. }
  14. }
  15. return 0;
  16. }

引用c中的qsort函数与自定义的cmp函数通过快速排序直接比较。

可见通过了测试,但是无论是时间还是内存占用都比较大。

2.哈希表方法解决

  1. int findDuplicate(int* nums, int numsSize) {
  2. int hash[100001] = {0}; // 创建一个大小为100001的哈希表,初始值都为0
  3. for (int i = 0; i < numsSize; ++i) {
  4. if (hash[nums[i]]) { // 如果哈希表中nums[i]位置的值不为0,说明该数字已经出现过
  5. return nums[i]; // 返回重复的数字
  6. }
  7. hash[nums[i]] = 1; // 将哈希表中nums[i]位置的值设为1,表示该数字已经出现过
  8. }
  9. return -1; // 如果没有找到重复的数字,返回-1
  10. }

当然,哈希表并不是本文介绍的重点

接下来将使用更好的办法,也是将要介绍的算法

弗洛伊德判圈思想(快慢指针法)

1.算法描述:

如果有限状态机、迭代函数或者链表存在环,那么一定存在一个起点可以到达某个环的某处(这个起点也可以在某个环上)。

2.实现思路:

使用了两个指针,一个慢指针(龟)每次前进一步,快指针(兔)指针每次前进两步(两步或多步效果是等价的,只要一个比另一个快就行。但是如果移动步数增加,算法的复杂度可能增加)。如果两者在链表头以外(不包含开始情况)的某一点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了链表的结尾(如果存在结尾,肯定无环),那么说明没环。

3.在本题中运用该算法的思路:

如果将数组的下标和值都当成一个点,从下标引一条线指向值,再从值引条线指向下标

此时由下标索引元素,再由元素的值索引下标,当出现重复值的时候就会进入环

如图所示,我们发现在出现重复数字的时候出现了一个环,正好符合弗洛伊德判圈的思想。

那么此时,我们的目标就从找重复数字变化成了找环的起点。

4.找环起点:

给定两个指针, 分别命名为 slow 和 fast,起始位置在链表的开头。每次 fast 前进两步,slow 前进一步。如果 fast 可以走到尽头,那么说明没有环路;如果fast可以无限走下去,那么说明一定有环路,且一定存 在一个时刻slow和fast相遇。当slow和fast第一次相遇时,我们将slow重新移动到链表开头,并让slow和fast每次都前进一步。当slow和fast第二次相遇时,相遇的节点即为环路的开始点。

所以我们以如下代码来尝试实现。

  1. int findDuplicate(int* nums, int numsSize)
  2. {
  3. int slow = 0, fast = 0;//定义快慢指针
  4. do {
  5. slow = nums[slow];
  6. //slow初始代表上图的下标,nums[slow]则为上图中的值,此处即改变一次,也就是慢指针走一步
  7. fast = nums[nums[fast]];
  8. //同上,但此处相当于进行了两次赋值,也就是前进两步
  9. } while (slow != fast);//此处为快慢指针第一次相遇
  10. slow = 0;//将慢指针拖回起点
  11. while (slow != fast) //第二次相遇
  12. {
  13. slow = nums[slow];
  14. fast = nums[fast];//快慢指针均走一步
  15. }
  16. //最终相遇时相遇的地方是环开始的地方
  17. return slow;//返回此时slow或fast的值均可,此时得到的即是重复的数字
  18. }

补充:异或法

运用位运算中的^(异或运算符)

二进制下相同为0,相异为1,便有

a^a=0;自己异或自己等于0

a^0=a;任何数字和0异或等于他自己

a^b^c=a^c^b;异或具有交换律

此处仅仅一种思路

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/514938
推荐阅读
相关标签
  

闽ICP备14008679号