弗洛伊德判圈算法(快慢指针法)解决问题之洛谷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.循环遍历

int findDuplicate(int* nums, int numsSize)
{
    for (int i = 0; i < = numsSize-1; i++)//第一层循环表示某一个元素
    {
      
        for (int j = i + 1 ; j < = numsSize-1; j++)//第二层循环表示遍历剩下的元素
        {
            if (nums[i] == nums[j])//若两个元素相等则使标记点加一
            {
              return nums[i];
            }
        }

    }
    return 0;
}

2.数组排序法

int findDuplicate(int* nums, int numsSize)
{
    for(int i=0;i<numsSize-1;i++)
    {
        for(int j=0;j<numsSize-i-1;j++)
        {
            if(nums[j]<nums[j+1])
            {
                int t=nums[j];
               nums[j]=nums[j+1];
               nums[j+1]=t;
            }
        }
    }//冒泡排序排成有序数列
    
    for(int i=0;i<numsSize;i++)//遍历数组
    {
        if(nums[i]==nums[i+1])//只要有元素相等就将该元素值赋给标记
    return nums[i];
    }

    return 0;
}

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

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

2.正确思路

1.快排解决

int cmp(const void* a, const void* b)
	{
		return *(int*)a - *(int*)b;
	}
	int findDuplicate(int* nums, int numsSize) 
	{
		
		qsort(nums, numsSize,sizeof(int), cmp); 
		for (int i = 1; i < numsSize; ++i)
		{
			if (nums[i] == nums[i - 1])
			{
				return nums[i];
			}
		}
		return 0;
	}

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

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

2.哈希表方法解决

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

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

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

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

1.算法描述:

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

2.实现思路:

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

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

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

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

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

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

4.找环起点:

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

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

int findDuplicate(int* nums, int numsSize) 
{
    int slow = 0, fast = 0;//定义快慢指针
    do {
        slow = nums[slow];
//slow初始代表上图的下标,nums[slow]则为上图中的值,此处即改变一次,也就是慢指针走一步
        fast = nums[nums[fast]];
//同上,但此处相当于进行了两次赋值,也就是前进两步
    } while (slow != fast);//此处为快慢指针第一次相遇
    
    slow = 0;//将慢指针拖回起点


    while (slow != fast) //第二次相遇
    {
        slow = nums[slow];
        fast = nums[fast];//快慢指针均走一步

    }
    //最终相遇时相遇的地方是环开始的地方
    return slow;//返回此时slow或fast的值均可,此时得到的即是重复的数字
}

补充:异或法

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

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

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

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

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

此处仅仅一种思路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值