我在求职面试中被问过这个问题,而且我一直在想正确的答案。
您有一个从0到n-1的数字数组,其中一个数字被删除,并替换为数组中已有数字的数字,该数字会复制该数字。我们怎样才能及时发现这个重复 O(n)?
例如,1,2,3,4
的数组将变为1,2,2,4
。
时间 O(n 2 )的简单解决方案是使用嵌套循环来查找每个元素的副本。
答案 0 :(得分:150)
这可以在O(n)
时间和O(1)
空间内完成。
(该算法仅起作用,因为数字是已知范围内的连续整数):
在单次通过向量时,计算所有数字的总和,以及所有数字的平方和。
从N(N-1)/2
中减去所有数字的总和。请拨打此A
。
从N(N-1)(2N-1)/6
减去正方形的总和。除以A
。调用结果B
。
删除的号码为(B + A)/2
,其替换的号码为(B - A)/2
。
向量为[0, 1, 1, 2, 3, 5]
:
N = 6
向量之和为0 + 1 + 1 + 2 + 3 + 5 = 12.N(N-1)/ 2为15. A = 3.
平方和为0 + 1 + 1 + 4 + 9 + 25 = 40.N(N-1)(2N-1)/ 6为55. B =(55 - 40)/ A = 5.
删除的数字是(5 + 3)/ 2 = 4.
被替换的号码是(5 - 3)/ 2 = 1.
原始向量[0, ..., N-1]
的总和为N(N-1)/2
。假设值a
已被删除并替换为b
。现在,修改后的矢量总和为N(N-1)/2 + b - a
。如果我们从N(N-1)/2
中减去修改后的向量的总和,我们得到a - b
。所以A = a - b
。
类似地,原始矢量的平方和是N(N-1)(2N-1)/6
。修改后的矢量的平方和为N(N-1)(2N-1)/6 + b2 - a2
。从原始总和中减去修改矢量的平方和得到a2 - b2
,这与(a+b)(a-b)
相同。因此,如果我们将其除以a - b
(即A
),我们会得到B = a + b
。
现在B + A = a + b + a - b = 2a
和B - A = a + b - (a - b) = 2b
。
答案 1 :(得分:34)
我们有原始数组int A[N];
创建第二个数组bool B[N]
,类型为bool=false
。迭代第一个数组并设置B[A[i]]=true
如果为false,否则bing!
答案 2 :(得分:29)
你可以在O(N)时间内完成,没有任何额外的空间。以下是算法的工作原理:
以下列方式迭代数组:
对于遇到的每个元素,将其对应的索引值设置为负数。 例如:如果找到[0] = 2.得到[2]并否定该值。
通过执行此操作,您可以将其标记为遇到。既然你知道你不能有负数,你也知道你是否是否定了它。
检查对应于该值的索引是否已标记为否定,如果是,则获取重复元素。例如:如果a [0] = 2,转到[2]并检查它是否为负数。
假设你有以下数组:
int a[] = {2,1,2,3,4};
在第一个元素后,您的数组将是:
int a[] = {2,1,-2,3,4};
在第二个元素之后,您的数组将是:
int a[] = {2,-1,-2,3,4};
当你到达第三个元素时,你会转到[2]并看到它已经是负数。你得到了副本。
答案 3 :(得分:10)
扫描阵列3次:
A
。将从0到N-1的所有数字XOR一起 - > B
。现在A XOR B = X XOR D
,其中X是删除的元素,D是重复的元素。A XOR B
中的任何非零位。 对设置此位的所有数组元素进行XOR - > A1
。将0到N-1的所有数字XOR组合在一起,其中该位置位 - > B1
。现在A1 XOR B1 = X
或A1 XOR B1 = D
。A1 XOR B1
。如果找到,则这是重复元素。如果不是,则重复元素为A XOR B XOR A1 XOR B1
。答案 4 :(得分:7)
我建议使用BitSet。我们知道N足够小,可用于数组索引,因此BitSet的大小合理。
对于数组的每个元素,检查与其值对应的位。如果已经设置,那就是重复。如果没有,请设置该位。
答案 5 :(得分:6)
使用HashSet
保存已经看过的所有号码。它以(摊销)O(1)
时间运行,因此总数为O(N)
。
答案 6 :(得分:3)
使用哈希表。在哈希表中包含元素是O(1)。
答案 7 :(得分:2)
一个有效的解决方案:
asume number是整数
创建一个[0 .. N]
的数组int[] counter = new int[N];
然后迭代读取并递增计数器:
if (counter[val] >0) {
// duplicate
} else {
counter[val]++;
}
答案 8 :(得分:2)
@rici对时间和空间的使用是正确的:“这可以在O(n)时间和O(1)空间中完成。”
然而,问题可以扩展到更广泛的要求:没有必要只有一个重复的数字,而数字可能不是连续的。
OJ这样说here: (注3显然可以缩小)
给定包含n + 1个整数的数组nums,其中每个整数在1和n之间(包括1和n),证明必须存在至少一个重复的数字。假设只有一个重复的数字,找到重复的数字。
注意:
- 您不能修改数组(假设数组是只读的)。
- 您必须只使用常数,O(1)额外空格。
- 您的运行时复杂度应小于O(n2)。
- 数组中只有一个重复的数字,但可以重复多次。
问题是非常由Keith Schwarz使用here算法解释并回答Floyd's cycle-finding:
我们需要用来解决这个问题的主要技巧是注意,因为我们有一个n个元素的数组,范围从0到n - 2,我们可以认为数组是从集合{0定义函数f ,1,...,n - 1}到自己身上。该函数由f(i)= A [i]定义。给定这种设置,重复值对应于一对索引i!= j,使得f(i)= f(j)。因此,我们的挑战是找到这对(i,j)。一旦我们拥有它,我们就可以通过选择f(i)= A [i]轻松找到重复的值。
但我们如何才能找到这个重复的价值呢?事实证明,这是计算机科学中一个被充分研究的问题,称为循环检测。问题的一般形式如下。我们给了函数f。将序列x_i定义为
x_0 = k (for some k)
x_1 = f(x_0)
x_2 = f(f(x_0))
...
x_{n+1} = f(x_n)
假设f从域映射到自身,此函数将具有三种形式之一。首先,如果域是无限的,则序列可以是无限长且不重复的。例如,整数上的函数f(n)= n + 1具有此属性 - 不会复制任何数字。其次,序列可以是闭环,这意味着有一些i使得x_0 = x_i。在这种情况下,序列无限循环通过一些固定的值集。最后,序列可以是“rho形”。在这种情况下,序列看起来像这样:
x_0 -> x_1 -> ... x_k -> x_{k+1} ... -> x_{k+j}
^ |
| |
+-----------------------+
也就是说,序列以一系列进入循环的元素开始,然后无限循环。我们将表示循环的第一个元素,它在循环的“入口”序列中达到。
也可以找到python实现here:
def findDuplicate(self, nums):
# The "tortoise and hare" step. We start at the end of the array and try
# to find an intersection point in the cycle.
slow = 0
fast = 0
# Keep advancing 'slow' by one step and 'fast' by two steps until they
# meet inside the loop.
while True:
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast:
break
# Start up another pointer from the end of the array and march it forward
# until it hits the pointer inside the array.
finder = 0
while True:
slow = nums[slow]
finder = nums[finder]
# If the two hit, the intersection index is the duplicate element.
if slow == finder:
return slow
答案 9 :(得分:1)
这是O(n)
时间和O(1)
空间的替代解决方案。它类似于rici's。我发现它更容易理解,但在实践中,它会溢出得更快。
让X
成为缺失的数字,R
是重复的数字。
我们可以假设数字来自[1..n]
,即不出现零。实际上,在循环遍历数组时,我们可以测试是否找到了零,如果没有则立即返回。
现在考虑:
sum(A) = n (n + 1) / 2 - X + R
product(A) = n! R / X
其中product(A)
是A
中跳过零的所有元素的乘积。我们有两个未知数的方程,X
和R
可以代数推导出来。
修改:根据大众需求,这是一个经过实践证明的例子:
我们设置:
S = sum(A) - n (n + 1) / 2
P = n! / product(A)
然后我们的方程变为:
R - X = S
X = R P
可以解决:
R = S / (1 - P)
X = P R = P S / (1 - P)
示例:
A = [0 1 2 2 4]
n = A.length - 1 = 4
S = (1 + 2 + 2 + 4) - 4 * 5 / 2 = -1
P = 4! / (1 * 2 * 2 * 4) = 3 / 2
R = -1 / (1 - 3/2) = -1 / -1/2 = 2
X = 3/2 * 2 = 3
答案 10 :(得分:0)
此视频If Programming Was An Anime太有趣了,无法共享。这是同样的问题,视频有答案:
注意:此问题比实际问题更多是琐事问题。除了在稀疏的有限ram情况下(例如嵌入式编程),hashmap之外的任何解决方案都是过早的优化。
此外,您在现实世界中最后一次看到的数组是什么,数组中的所有变量都适合数组的大小?例如,如果数组中的数据为字节(0-255),那么当您拥有一个256个元素或更大的数组,且其中没有null或inf时,您是否需要查找重复的数字?这种情况非常罕见,您可能在整个职业生涯中都永远不会使用这种技巧。
因为这是一个琐事问题,而不是现实世界中的问题,所以我会谨慎地接受一家公司提出这样的琐事问题的提议,因为人们会通过运气而不是技巧来通过面试。这意味着不能保证那里的开发人员会熟练,除非您可以教授高级技能,否则您可能会遇到困难。
答案 11 :(得分:0)
int[] a = {5, 6, 8, 9, 3, 4, 2, 9 };
int[] b = {5, 6, 8, 9, 3, 6, 1, 9 };
for (int i = 0; i < a.Length; i++)
{
if (a[i] != b[i])
{
Console.Write("Original Array manipulated at position {0} + "\t\n"
+ "and the element is {1} replaced by {2} ", i,
a[i],b[i] + "\t\n" );
break;
}
}
Console.Read();
///use break if want to check only one manipulation in original array.
///If want to check more then one manipulation in original array, remove break
答案 12 :(得分:0)
这是在O(n)时间内使用哈希映射的简单解决方案。
#include<iostream>
#include<map>
using namespace std;
int main()
{
int a[]={1,3,2,7,5,1,8,3,6,10};
map<int,int> mp;
for(int i=0;i<10;i++){
if(mp.find(a[i]) == mp.end())
mp.insert({a[i],1});
else
mp[a[i]]++;
}
for(auto i=mp.begin();i!=mp.end();++i){
if(i->second > 1)
cout<<i->first<<" ";
}
}
答案 13 :(得分:0)
这可以在 O(n)时间和 O(1)空间中完成。 无需修改输入数组
slow = a[0]
fast = a[a[0]]
slow = 0
while(slow != fast){
slow = a[slow];
fast = a[fast];
}
这是Java实现:
class Solution {
public int findDuplicate(int[] nums) {
if(nums.length <= 1) return -1;
int slow = nums[0], fast = nums[nums[0]]; //slow = head.next, fast = head.next.next
while(slow != fast){ //check for loop
slow = nums[slow];
fast = nums[nums[fast]];
}
if(slow != fast) return -1;
slow = 0; //reset one pointer
while(slow != fast){ //find starting point of loop
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
答案 14 :(得分:0)
如上所述,
你有一个从0到n-1的数字数组,其中一个是数字 删除,并替换为数组中已有的数字 这个数字的副本。
我假设数组中的元素除了重复条目外都是排序的。如果是这种情况,我们可以轻松实现目标:
public static void main(String[] args) {
//int arr[] = { 0, 1, 2, 2, 3 };
int arr[] = { 1, 2, 3, 4, 3, 6 };
int len = arr.length;
int iMax = arr[0];
for (int i = 1; i < len; i++) {
iMax = Math.max(iMax, arr[i]);
if (arr[i] < iMax) {
System.out.println(arr[i]);
break;
}else if(arr[i+1] <= iMax) {
System.out.println(arr[i+1]);
break;
}
}
}
答案 15 :(得分:0)
遍历数组并检查array[abs(array[i])]
的符号,如果为正,则将其设为负数,如果为负数,则打印出来,如下所示:
import static java.lang.Math.abs;
public class FindRepeatedNumber {
private static void findRepeatedNumber(int arr[]) {
int i;
for (i = 0; i < arr.length; i++) {
if (arr[abs(arr[i])] > 0)
arr[abs(arr[i])] = -arr[abs(arr[i])];
else {
System.out.print(abs(arr[i]) + ",");
}
}
}
public static void main(String[] args) {
int arr[] = { 4, 2, 4, 5, 2, 3, 1 };
findRepeatedNumber(arr);
}
}
参考: http://www.geeksforgeeks.org/find-duplicates-in-on-time-and-constant-extra-space/
答案 16 :(得分:0)
使用滑动窗口技巧遍历数组O(n)
空间是O(1)
Arrays.sort(input);
for(int i = 0, j = 1; j < input.length ; j++, i++){
if( input[i] == input[j]){
System.out.println(input[i]);
while(j < input.length && input[i] == input[j]) j++;
i = j - 1;
}
}
测试用例int [] {1,2,3,7,7,8,3,5,7,1,2,7}
输出1,2,3,7
答案 17 :(得分:0)
这个程序基于c#,如果你想用另一种编程语言编写这个程序,你必须首先按加速顺序更改数组,然后将第一个元素与第二个元素进行比较。如果它相等,则找到重复的数字。程序是
int[] array=new int[]{1,2,3,4,5,6,7,8,9,4};
Array.Sort(array);
for(int a=0;a<array.Length-1;a++)
{
if(array[a]==array[a+1]
{
Console.WriteLine("This {0} element is repeated",array[a]);
}
}
Console.WriteLine("Not repeated number in array");
答案 18 :(得分:0)
我们可以有效地使用hashMap:
Integer[] a = {1,2,3,4,0,1,5,2,1,1,1,};
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int x : a)
{
if (map.containsKey(x)) map.put(x,map.get(x)+1);
else map.put(x,1);
}
Integer [] keys = map.keySet().toArray(new Integer[map.size()]);
for(int x : keys)
{
if(map.get(x)!=1)
{
System.out.println(x+" repeats : "+map.get(x));
}
}
答案 19 :(得分:0)
//这类似于HashSet方法,但只使用一种数据结构:
int[] a = { 1, 4, 6, 7, 4, 6, 5, 22, 33, 44, 11, 5 };
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer, Integer>();
for (int i : a) {
map.put(i, map.containsKey(i) ? (map.get(i)) + 1 : 1);
}
Set<Entry<Integer, Integer>> es = map.entrySet();
Iterator<Entry<Integer, Integer>> it = es.iterator();
while (it.hasNext()) {
Entry<Integer, Integer> e = it.next();
if (e.getValue() > 1) {
System.out.println("Dupe " + e.getKey());
}
}
答案 20 :(得分:0)
public void duplicateNumberInArray {
int a[] = new int[10];
Scanner inp = new Scanner(System.in);
for(int i=1;i<=5;i++){
System.out.println("enter no. ");
a[i] = inp.nextInt();
}
Set<Integer> st = new HashSet<Integer>();
Set<Integer> s = new HashSet<Integer>();
for(int i=1;i<=5;i++){
if(!st.add(a[i])){
s.add(a[i]);
}
}
Iterator<Integer> itr = s.iterator();
System.out.println("Duplicate numbers are");
while(itr.hasNext()){
System.out.println(itr.next());
}
}
首先使用Scanner类创建一个整数数组。然后迭代一个循环遍历数字并检查数字是否可以添加到set(只有当该特定数字不应该已经设置时才可以添加数字,意味着set不允许重复的数字添加并返回一个布尔值vale FALSE添加重复值)。如果没有。无法添加意味着它是重复的,所以将重复的数字添加到另一个集合中,以便我们以后打印。请注意,我们正在将重复的数字添加到集合中,因为重复的数字可能会重复多次,因此只能添加一次。最后我们使用Iterator打印设置。
答案 21 :(得分:0)
public class FindDuplicate {
public static void main(String[] args) {
// assume the array is sorted, otherwise first we have to sort it.
// time efficiency is o(n)
int elementData[] = new int[] { 1, 2, 3, 3, 4, 5, 6, 8, 8 };
int count = 1;
int element1;
int element2;
for (int i = 0; i < elementData.length - 1; i++) {
element1 = elementData[i];
element2 = elementData[count];
count++;
if (element1 == element2) {
System.out.println(element2);
}
}
}
}
答案 22 :(得分:0)
您可以按以下步骤操作:
答案 23 :(得分:-2)
int a[] = {2,1,2,3,4};
int b[] = {0};
for(int i = 0; i < a.size; i++)
{
if(a[i] == a[i+1])
{
//duplicate found
//copy it to second array
b[i] = a[i];
}
}