18-二分-值域二分-寻找重复数

这是二分法的第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
}

当然,超时了:

18-二分-值域二分-寻找重复数_第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
}

你可能感兴趣的:(白话算法,算法,数据结构)