寻找重复数

103 阅读2分钟

今天东坝小胖子又出动了,带着他那灿烂的微笑,谜一般的乘着日落里的东风而去,还是变态跳舞的公司。

今天他要做一道这样的题:

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

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

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

小胖仔一看,挑了挑眉毛,直骂娘。这最后一句多余的话是把路全堵死了啊!总不能暴力搞起吧。

不过好在还有一点希望,题目有暗示数字范围跟数组长度的关系,即其数字都在 [1, n] 范围内,这相当于变相的告诉有序。东坝小胖仔急中生智,搜了一圈八股中的算法,灵光一现感觉二分有可为。于是心里演算了一下开始在刷刷的敲起键盘。

func findDuplicate(nums []int) int {
	result := -1

	left, right := 1, len(nums)-1

	for left <= right {
		mid := (left + right) / 2
		count := 0
		for _, num := range nums {
			if num <= mid { // 比当前值小的数有多少个?
				count++
			}
		}
		if count > mid { // (< mid)的有重复
			right = mid - 1
			result = mid
		} else { // (> mid)的有重复
			left = mid + 1
		}
	}
	return result
}

时间复杂度外面的二分logN,每一次都要遍历nums,所以总时间复杂度为O(NlogN),空间复杂度O(1)。

不过小胖子知道这个复杂度显然是得不到满意的,于是该来的还是来了,还能优化吗?小胖子被问道。

“能肯定是能的”,小胖仔回复到,”但是我想不出“,小胖仔摆出一副努力思考后的愁容。

“考虑到值和数组大小的关系,你可以试试把下标i和i对应的值建立映射,然后把它们串起来,这样你可以形成一个链表”

小胖仔听着一拍大腿,秒,不过这神奇的思路,没有提示估计他这种猪脑一辈子也想不出来。小胖仔在纸上演算图画了一下: i -> f(i),把它们连起来: 0->f(0)->i->f(i)->x->f(x)->m->f(m)->y->f(y)->.....

如果有一个重复的数,假设为x、y位置,那么必然存在f(y)->[0...x],所以这道题可以转换为链表环路的问题,小胖仔突然庆幸刷过这代题,于是开始唰唰唰敲起来。

func findDuplicate(nums []int) int {
	slow, fast := nums[0], nums[nums[0]]
	for slow != fast { // 找到环
		slow, fast = nums[slow], nums[nums[fast]]
	}
	slow = 0
	for slow != fast { // 找到入环的位置
		slow = nums[slow]
		fast = nums[fast]
	}
	return slow
}