287.寻找重复数

打卡!!!每日一题

今天给大家带来一道很有意思的题目,带你认识一下不一样的链表。

题目描述:287. 寻找重复数

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

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

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

题目示例:
在这里插入图片描述

这道题我拿过来的第一个思路就是使用位运算,当然位运算也完全没问题,因为一旦涉及到限制空间复杂度的时候,建议大家也要往位运算的方向去思考。

然后我又想到JDK 中的 Arrays.sort(int[] a) 是用双枢轴快速排序算法实现的,该算法具有O(nlogn) 平均复杂度并且就地执行(例如不需要额外的空间),所以我们可以先排序在相邻元素进行比较,因为思路过于简单,就不在过多的讨论,代码如下:

public int findDuplicate(int[] nums) {
    
    
        int length = nums.length;
        if (length <= 1) return nums[0];
        Arrays.sort(nums);
        for (int i = 0; i < length - 1; i++) {
    
    
            if (nums[i] == nums[i + 1]) {
    
    
                return nums[i];
            }
        }
        return nums[0];
    }

不过本文的重点既不是位运算,也不是排序,而是循环链表的思路,可能有小伙伴读到这儿感觉一脸懵了,这就是个数组怎么会和链表扯上关系,别着急,听我慢慢给你道来。

我给大家举两个例子:
1.数组中没有重复的数,以数组 [1,3,4,2]为例,们将数组下标 n 和数 nums[n] 建立一个映射关系 f(n),其映射关系 n->f(n)为:
0->1
1->3
2->4
3->2

所构成的链表如下所示:
0->1->3->2->4->null

可以看出上面所构成的链表是单向链表,所以不存在重复元素。

2.数组中有重复的数,以数组 [1,3,4,2,2] 为例,我们将数组下标 n 和数 nums[n] 建立一个映射关系 f(n),其映射关系 n->f(n)为:
0->1
1->3
2->4
3->2
4->2

所构成的链表如下所示:
0->1->3->2->4->2->4->2->……

这里 2->4 是一个循环,那么这个链表可以抽象为下图:
在这里插入图片描述

很明显这个链表是有环存在的,这是因为重复元素所导致的,所以解决本题的关键就是找到这个链表的环入口

找到环入口最经典的解决办法就是:快慢指针

  • 慢指针走一步 slow = slow.next ==> 本题 slow = nums[slow]
  • 快指针走两步 fast = fast.next.next ==> 本题 fast = nums[nums[fast]]

相信上面的这两个快慢指针的定义应该不难理解,因为我们的链表就是由下标–>元素值

初始值:slow,fast均指向0号位
第一次:slow->1,fast->3
第二次:slow->3,fast->4
第三次:slow->2,fast->4
第四次:slow->4,fast->4

slow和fast会在环中相遇,先假设一些量:

  • 环长为 KL
  • 从起点到环的入口的步数是 a
  • 从环的入口继续走 b 步到达相遇位置
  • 从相遇位置继续走 c 步回到环的入口

则有 b + c = L b+c=L b+c=L 其中 L、a、b、c 都是正整数

根据上述定义,慢指针走了 a+b 步,快指针走了 2(a+b)步。

从另一个角度考虑,在相遇位置,快指针比慢指针多走了若干圈,因此快指针走的步数还可以表示成 a + b + k L a+b+kL a+b+kL,其中 k 表示快指针在环上走的圈数。联立等式,可以得到
2 ( a + b ) = a + b + k L 2(a+b)=a+b+kL 2(a+b)=a+b+kL
解得 a = k L − b a=kL−b a=kLb ,整理可得
a = ( k − 1 ) L + ( L − b ) = ( k − 1 ) L + c a=(k−1)L+(L−b)=(k−1)L+c a=(k1)L+(Lb)=(k1)L+c
所以我们在重新一个指针finder指向起始位置,让慢指针从相遇位置一步一步的往前走,刚好会在环的入口处相遇,因为在finder指针刚好走a步的时候,慢指针走了(k-1)L+c步,而(k-1)L相当于绕环走了若干圈又回到和快指针相遇的位置,而c步又刚好回到环的入口。

    public int findDuplicate(int[] nums) {
    
    
        int slow = 0;
        int fast = 0;
        slow = nums[slow];
        fast = nums[nums[fast]];
        while (slow != fast) {
    
    
            slow = nums[slow];
            fast = nums[nums[fast]];
        }
        int finder = 0;//指向起始位置
        while (finder != slow) {
    
    
            finder = nums[finder];
            slow = nums[slow];
        }
        return finder;
    }

猜你喜欢

转载自blog.csdn.net/zhiyikeji/article/details/126178496