【Leetcode每日笔记】287. 寻找重复数(Python)

文章目录

  • 题目
  • 解题思路
    • 字典或者排序
    • 二分查找
    • 快慢指针
  • 代码

题目

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

假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

示例 1:

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

示例 2:

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

示例 3:

输入:nums = [1,1] 输出:1

示例 4:

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

提示:

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

进阶:

如何证明 nums 中至少存在一个重复的数字?
你可以在不修改数组 nums 的情况下解决这个问题吗?
你可以只用常量级 O(1) 的额外空间解决这个问题吗?
你可以设计一个时间复杂度小于 O(n2) 的解决方案吗?

解题思路

字典或者排序

对nums遍历,对nums中的每一个元素进行计数写入字典,如果某个元素的计数值大于1,则该元素就是重复数。
或者对nums排序,找出与前一个元素一样的元素,那么这个就是重复数。

二分查找

二分法的思路是先猜一个数(有效范围 [left, right]里的中间数 mid),然后统计原始数组中小于等于这个中间数的元素的个数 cnt,如果 cnt 严格大于 mid根据抽屉原理,重复元素就在区间 [left, mid] 里。

例如:区间 [1,7][1, 7][1,7] 的中位数是 4,遍历整个数组,统计小于等于 4 的整数的个数,如果不存在重复元素,最多为 444 个。等于 444 的时候区间 [1,4][1, 4][1,4] 内也可能有重复元素。但是,如果整个数组里小于等于 4 的整数的个数严格大于 444 的时候,就可以说明重复的数存在于区间 [1,4][1, 4][1,4]。

快慢指针

可以把nums中的元素看作是指向下一个位置的值,例如[1,3,4,2,2],那么遍历顺序就是1->3->2->4->2->3…,此时就出现了在4->2就开始重复出现同样的顺序,也就是环。所以有重复出现的数字,那么nums所表示的顺序中,一定存在环,而重复的数字就是环开始的地方,即环的起点,如上面的例子,2是重复数字,也就是环的起点。
那么问题就转换为链表中寻找环的起点,也就想到了利用快慢指针。
先设置慢指针 slow \textit{slow} slow 和快指针 fast \textit{fast} fast ,慢指针每次走一步,快指针每次走两步,根据Floyd 判圈算法,两个指针在有环的情况下一定会相遇,此时我们再将 ¥\textit{slow}$ 放置起点 0,两个指针每次同时移动一步,相遇的点就是答案。

假设环长为 L,从起点到环的入口的步数是 a,从环的入口继续走 b 步到达相遇位置,从相遇位置继续走 c 步回到环的入口,则有 b + c = L b+c=L b+c=L,其中 L、a、b、c 都是正整数。根据上述定义,慢指针走了 a + b a+b a+b 步,快指针走了 2 ( a + b ) 2(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
从上述等式可知,如果慢指针从起点出发,快指针从相遇位置出发,每次两个指针都移动一步,则慢指针走了 a 步之后到达环的入口,快指针在环里走了 k − 1 k−1 k1 圈之后又走了 c步,由于从相遇位置继续走 c 步即可回到环的入口,因此快指针也到达环的入口。两个指针在环的入口相遇,相遇点就是答案。

代码

# 字典
class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
    dic = collections.Counter(nums)
    for d in dic:
    	if dic[d] > 1:
	    	return d
# 排序
class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        nums.sort()
        i,j = 0,1
        while j < len(nums):
            if nums[j] == nums[i]:
                return nums[i]
            j += 1
            i += 1
class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        left = 1
        right = len(nums) - 1
        
        while left < right:
            mid = left + (right - left ) // 2
            # 计数, 找小于等于mid的个数
            cnt = 0
            for num in nums:
                if num <= mid:
                    cnt += 1
            # 根据鸽巢原理/抽屉原理
            # <= 说明 重复元素再右半边
            if cnt <=  mid:
                left  = mid + 1
            else:
                right = mid
        return left
class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        slow = nums[0]
        fast = nums[nums[0]]
        while slow != fast:
            # print(slow, fast)
            slow = nums[slow]
            fast = nums[nums[fast]]
        slow = 0
        while slow != fast:
            slow = nums[slow]
            fast = nums[fast]
        return slow

你可能感兴趣的:(LeetCode一周一结,指针,算法,leetcode,数据结构,二分查找)