以下是问题陈述和解决方案。我无法理解解决方案背后的逻辑。
问题陈述:
给定包含n + 1个整数的数组nums,其中每个整数在1和n之间(包括1和n),证明必须存在至少一个重复的数字。假设只有一个重复的数字,找到重复的数字。
注意: 您不能修改数组(假设该数组是只读的)。 您必须仅使用常量O(1)额外空间。 您的运行时复杂度应小于O(n2)。 数组中只有一个重复的数字,但它可以重复多次。
样本输入:[3 4 1 4 1] 输出:1
leetcode上发布的问题的解决方案是:
class Solution(object):
def findDuplicate(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
low = 1
high = len(nums)-1
while low < high:
mid = low+(high-low)/2
count = 0
for i in nums:
if i <= mid:
count+=1
if count <= mid:
low = mid+1
else:
high = mid
return low
上述代码的解释(根据作者): 此解决方案基于二进制搜索。
首先,搜索空间是1到n之间的数字。每次我选择一个数字mid(中间的数字)并计算所有等于或小于mid的数字。然后,如果计数大于mid,则搜索空间将为[1 mid],否则为[mid + 1 n]。我这样做,直到搜索空间只有一个数字。
假设n = 10,我选择mid = 5。然后我计算数组中所有小于等于mid的数字。如果有超过5个小于5的数字,那么通过Pigeonhole Principle(https://en.wikipedia.org/wiki/Pigeonhole_principle),其中一个不止一次发生。所以我将搜索空间从[1 10]缩小到[1 5]。否则重复的数字在下半部分,因此对于下一步,搜索空间将是[6 10]。
怀疑: 在上述解决方案中,count <= mid
时,为什么我们要将low
更改为low = mid + 1
或以其他方式更改{ {1}}? 背后的逻辑是什么?
我无法理解此算法背后的逻辑
答案 0 :(得分:4)
这是二元搜索。您正在将搜索空间减半并重复。
以这种方式思考:您有101个项目的列表,并且您知道它包含值1-100。取中间点,50。计算有多少项小于或等于50.如果有超过50项小于或等于50,则副本在0-50范围内,否则副本为在51-100范围内。
二进制搜索只是将范围缩小了一半。看0-50,取中点25并重复。
我认为这个算法的关键部分是导致混淆的是for循环。我试图解释一下。首先请注意,此算法中的没有使用索引 - 只需检查代码,您就会发现索引引用不存在。其次,请注意,算法在inspection_count
循环的每次迭代中循环遍历整个集合。
让我进行以下更改,然后在每while
次循环后考虑inspection_count=0
for i in nums:
inspection_count+=1
if i <= mid:
count+=1
的值。
inspection_count
当然len(nums)
将等于n
。 for循环迭代整个集合,并且每个元素都检查它是否在候选范围内(值,而不是索引)。
复制测试本身简单而优雅 - 正如其他人所指出的那样,这是鸽子的原则。给定{p..q}
值的集合,其中每个值都在q-p < n
范围内,如果p = 0, q = 5, n = 10
"I have ten values, and every value is between zero and five.
At least one of these values must be duplicated."
,则范围内必须有重复项。想想一些简单的案例 -
p = 50, q = 99, n = 50
"I have a collection of fifty values, and every value is between fifty and ninety-nine.
There are only forty nine *distinct* values in my collection.
Therefore there is a duplicate."
我们可以概括一下,但更有效和相关的例子是
{{1}}
答案 1 :(得分:2)
设置low = mid+1
或high = mid
背后的逻辑基本上是基于binary search的解决方案。搜索空间被分成两半,while
循环仅在下一次迭代中搜索下半部分(high = mid
)或上半部分(low = mid+1
)。
所以我将搜索空间从[1 10]缩小到[1 5]。否则重复的数字在下半部分,因此对于下一步,搜索空间将是[6 10]。
这是关于你的问题的解释的一部分。
答案 2 :(得分:0)
假设您有10个号码。
a=[1,2,2,3,4,5,6,7,8,9]
然后mid = 5 小于或等于5的元素数是6(1,2,2,3,4,5)。 现在count = 6,大于mid。这意味着在前半部分至少有一个副本,所以代码正在做的是将搜索空间设置为从[1-10]到[1-5]的前半部分,依此类推。 否则在下半部分会出现重复,因此搜索空间将为[5-10]。
请告诉我你是否有疑问。
答案 3 :(得分:0)
public static void findDuplicateInArrayTest() {
int[] arr = {1, 7, 7, 3, 6, 7, 2, 4};
int dup = findDuplicateInArray(arr, 0, arr.length - 1);
System.out.println("duplicate: " + dup);
}
public static int findDuplicateInArray(int[] arr, int l, int r) {
while (l != r) {
int m = (l + r) / 2;
int count = 0;
for (int i = 0; i < arr.length; i++)
if (arr[i] <= m)
count++;
if (count > m)
r = m;
else
l = m + 1;
}
return l;
}