LeetCode287 寻找重复数

64 阅读2分钟

leetcode.cn/problems/fi…

image.png

解法一:原地哈希

为了控制时间复杂度O(n),空间复杂度O(1),很容易想到原地哈希策略

func findDuplicate(nums []int) int {
    // 原地哈希
    for i:=0; i<len(nums); i++{
        // 每个元素范围是[1, len(nums)-1]
        // 可以将每个元素置换到其值-1的下标上,那么如果有一个重复的元素,那么它一定和下标对应不上
        for nums[i] >= 1 && nums[i] <= len(nums) && nums[i] != nums[nums[i]-1]{
            nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i]
        }
    }
    // 找到第一个下标+1不等于元素值的数字
    for i, v := range nums{
        if i+1 != v{
            return v
        }
    }
    return 0
}

但是题目要求了我们不能修改原数组

解法二:快慢指针

借鉴环形链表寻找环起点的解题思路,关键是要理解如何将输入的数组看作为链表。

nums 中的数字范围是 [1,n],总共有n+1个数字。考虑一下两种情况:

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

0->1  
1->3  
2->4  
3->2

如果我们从下标为 0 出发,根据 f(n) 计算出一个值,以这个值为新的下标,再根据 f(n) 计算出一个值,以此类推,直到下标越界,那么我们会得到一个类似链表一样的序列0->1->3->2->4->null

2.如果数组中有重复的数,以数组 [1,3,4,2,2] 为例,其映射关系 i->f(i)为:

0->1  
1->3  
2->4  
3->2  
4->2

同样的,得到一个类似链表一样的序列0->1->3->2->4->2->4->2->……,这里 2->4 是一个循环

image.png

因此,数组中如果有重复的数,那么就会产生多对一的映射,这样形成的链表就一定会有环。那么现在题目就抽象为:

  1. 数组中存在一个重复的数 -》形成的链表存在环
  2. 找出这个重复的数字 -》找到链表的环起点

我们知道环形链表可以采用快慢指针技巧,那么落在这道题的数组中,该怎么走呢?根据上述数组转链表的映射关系,走一步就是执行一次映射函数f(i),可推出:

  1. 慢指针走一步 slow = slow.Next -> slow = nums[slow]
  2. 快指针走两步 fast = fast.Next.Next -> fast = nums[nums[fast]]
func findDuplicate(nums []int) int {
	slow, fast := 0, 0
	for fast < len(nums) && nums[fast] < len(nums) {
		slow = nums[slow]
		fast = nums[nums[fast]]
		if slow == fast {
			// 相遇后,慢指针回到起点,块指针还在相遇点,以相同速度前进
			slow = 0
			for slow != fast {
				slow = nums[slow]
				fast = nums[fast]
			}
            return slow
		}
	}
	return -1
}