给定一个n+1
整数数组,每个整数在1
到n
范围内,找到一个重复的整数。
在面试时我被问到这个问题。这是我的答案:鸽笼原则说必须重复一遍。我尝试使用二进制搜索方法,所以我在Matlab中这样做,因为这就是我所知道的:
top = 0;
bot = 0;
for i=1:n+1
if P[i] > n/2
top = top+1;
else
bot = bot+1;
end
那么我认为其中一个,top
或bot
必须再次由PhP大于n/2
。取这个范围并重复。
我认为这是一个非常好的解决方案,但面试官有点暗示一个人可以做得更好。请发布您知道的更好的解决方案。
答案 0 :(得分:18)
我不确定你是如何定义“更好”的,但也许这有资格。至少它与您的解决方案和链接列表问题的解决方案(双关语意图)不同。
如果我们制作路径
n+1 --> array[n+1] --> array[array[n+1]] --> ...
然后此路径包含一个周期,当且仅当array^k[n+1] = array^l[n+1]
用于某些k != l
时,即当且仅当存在重复时。现在问题变成了一个常见的链表问题,可以通过以下方式解决。
在第一个节点上启动两个粒子。让第一个粒子以单位速度移动,让第二个粒子以两倍的单位速度移动。然后,如果有一个循环,第二个粒子将最终循环回到第一个粒子后面,最终它们将是相同的。为什么?好吧,如果你把粒子想象成一个圆圈(它们将在找到环路时它们),每次单位时第二个粒子得到一个更接近第一个粒子的定向台阶。因此他们最终必须相互碰撞。他们一个人,你找到了一个循环。要找到重复的值,只需通过让一个粒子静止而另一个粒子再次运行循环来获得循环的长度。然后再次启动两个粒子,让一个粒子移动前面的长度,然后将两个粒子一起运行,它们之间保持恒定距离,直到它们在循环开始时再次相遇。
由于一些评论员感到愤怒,我没有包含如何在链表中找到循环的所有细节,现在就是。没有承诺这不是错误的(毕竟它是Matlab特有的伪代码),但它至少应该解释这个想法。
%% STEP 1: find a point in the cycle of the linked list using a slow and fast particle
slow = n+1;
fast = n+1;
for i=1 to n+1
slow = array[slow];
fast = array[array[fast]];
if (slow == fast)
break;
end
%% STEP 2: find the length of the cycle by holding one particle fixed
length = 1;
fast = array[fast]
while fast != slow
fast = array[fast];
length = length+1;
end
%% STEP 3: find the repeated element by maintaining constant distance between particles
slow = n+1;
fast = n+1;
for i=1 to length
fast = array[fast];
end
while fast != slow
fast = array[fast];
slow = array[slow];
end
%% STEP 4: return the repeated entry
return slow;
我从n+1
开始,因为array[i]
介于1和n之间,因此这两个粒子都不会被发送回n+1
。这使得最多一次通过数组(虽然不按顺序)并跟踪两个粒子(慢和快)和一个整数(长度)。因此空间为O(1),时间为O(n)。
答案 1 :(得分:3)
如果您知道只有一个重复的数字,您可以通过将所有数字相加并从1到n减去数字之和来找到它:
duplicate = sum P[i] - n(n+1)/2
如果没有,那么您可以遍历数组并将每个数字放在哈希表中。如果该数字已经存在则那就是副本。假设哈希表操作是O(1),这也是O(n)。
或事件更好 - 为了避免哈希表,你可以使用大小为n的布尔数组:
int[] P = new int[] { 3, 2, 5, 1, 4, 2 };
bool[] Q = new bool[6];
foreach( var p in P ){
if ( Q[p] ) {
Console.WriteLine("Duplicate: " + p);
break;
}
Q[p] = true;
}
答案 2 :(得分:0)
这个简单的解决方案怎么样:
开始从数组创建二叉搜索树。 当你插入BST时,只要存在一个已经存在的元素,然后将该元素存储在另一个重复元素数组中并继续循环。我们甚至不需要对数组进行排序以在此处查找重复项。 / p>
这只是我的想法。我在其中一次采访中被问到同样的问题,这是我的答案。
答案 3 :(得分:0)
这与@ PengOne的答案类似,但我相信它更简单。
<强>解释强>
此方法将数组视为图形,其中索引i
处的值指向索引a[i]-1
(因此值1
指向索引0
)。至少有1个重复数字,因此图形将是循环的。有n+1
个元素,最大值为n
,因此最后一个节点a[n+1]
永远不会成为循环的一部分,但会进入一个循环。这很重要,因为最后一个节点是遍历的start node
。请注意,如果作为循环一部分的节点被用作start node
slow
(1x)和fast
(2x)指针,那么它们会在同一个节点处相遇,这对于无效。我们将聚合节点称为meet node
。如果meet node
距离k
cycle node
跳跃,则start node
也会k
跳过cycle node
。该逻辑与在循环链表中查找循环节点相同。数组最多遍历3次,因此O(n)
时间和O(1)
空间。
<强> ALGO:强>
a[n+1]
)开始,使用meet node
(1x)和slow
(2x)指针找到fast
。meet node
和start node
推进两个指针(1x),它们将会聚在cycle node
(重复数字指向cycle node
)。 <强>代码:强>
//pseudocode
//O(n) time, O(1) space
findrepeating(a):
x = findrepeating(a, findmeet(a), a[a.length() -1])
return x
findmeet(a):
slow = fast = a[a.length() -1]
while true:
slow = a[slow-1]
fast = a[a[fast-1]-1]
if slow == fast:
break
meet = slow // or fast
return meet
findrepeating(a, meet, start):
m = meet
s = start
while m != s:
m = a[m-1]
s = a[s-1]
return m // or s
答案 4 :(得分:0)
我们使用circle detection的想法来解决此问题。
我们需要做的是首先找到圈子的开始,然后然后找到圈子中重复的一个。
这是c ++中的代码:
int findDuplicate(vector<int>& nums) {
int slow = nums[0];
int fast = nums[nums[0]];
while(slow != fast){
slow = nums[slow];
fast = nums[nums[fast]];
}
fast = 0;
while(slow != fast){
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
答案 5 :(得分:-1)
for(int i=n+1;i!=a[i];i=a[i]);
cout<<i;