这是二分法的第18篇算法,力扣链接。
给定一个包含
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
注意,这个数组并不一定是有序的。
老规矩,上暴力法,暴力法又分为哈希表和排序找重复值还有逐一查找,这里就试一下逐一查找吧,有点像冒泡排序。
func findDuplicate(nums []int) int {
for i := 0; i < len(nums)-1; i++ {
for j := i + 1; j < len(nums); j++ {
if nums[i] == nums[j] {
return nums[i]
}
}
}
return -1
}
当然,超时了:
那我们尝试一下二分算法吧。但是二分算法要求数组是有序的, 这道题怎么用呢?
因为我们忽略了一个条件, n + 1
个整数的数组 nums
,其数字都在 [1, n]
范围内,如果重复,一定存在数字被替换。
观察一下另一个规律,【4,3,2,1】中小于等于1的有1个,比小于等于2的有2个...
如果数字重复:
【4,3,1,1】,小于等于1的有两个,小于等于2的有2个...
【4,2,2,1】,小于等于1的有1个,小于等于2的有3个,小于等于3的有3个..
设被替换的数为target。如果target小于i,对于[i,target]均加一;如果target小于i,对于[target,i]均减1。
我们可以利用这个性质去做二分法,不过每次二分的时候需要遍历数组去统计数字的大小。
func findDuplicate(nums []int) int {
l, r := 0, len(nums)-1
result := -1
for l <= r {
mid := l + (r-l)/2
count := 0
for _, num := range nums {
if num <= mid {
count++
}
}
if count > mid {
r = mid - 1
result = mid
} else {
l = mid + 1
}
}
return result
}
此外,还有一个挺有意思的解法,就是快慢指针。由于数据都是1~n的值,我们可以根据index对应的值找到下一个index,直到我们找到不同index对应的值相同的时候就返回。
func findDuplicate(nums []int) int {
slow, fast := 0, 0
for slow, fast = nums[slow], nums[nums[fast]]; slow != fast; slow, fast = nums[slow], nums[nums[fast]] { }
slow = 0
for slow != fast {
slow = nums[slow]
fast = nums[fast]
}
return slow
}