在时间O(n)中查找数组中的重复元素

时间:2013-02-18 20:09:18

标签: java arrays algorithm

我在求职面试中被问过这个问题,而且我一直在想正确的答案。

您有一个从0到n-1的数字数组,其中一个数字被删除,并替换为数组中已有数字的数字,该数字会复制该数字。我们怎样才能及时发现这个重复 O(n)

例如,1,2,3,4的数组将变为1,2,2,4

时间 O(n 2 的简单解决方案是使用嵌套循环来查找每个元素的副本。

24 个答案:

答案 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 = 2aB - 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)时间内完成,没有任何额外的空间。以下是算法的工作原理:

以下列方式迭代数组:

  1. 对于遇到的每个元素,将其对应的索引值设置为负数。 例如:如果找到[0] = 2.得到[2]并否定该值。

    通过执行此操作,您可以将其标记为遇到。既然你知道你不能有负数,你也知道你是否是否定了它。

  2. 检查对应于该值的索引是否已标记为否定,如果是,则获取重复元素。例如:如果a [0] = 2,转到[2]并检查它是否为负数。

  3. 假设你有以下数组:

    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次:

  1. 对所有数组元素进行XOR - > A。将从0到N-1的所有数字XOR一起 - > B。现在A XOR B = X XOR D,其中X是删除的元素,D是重复的元素。
  2. 选择A XOR B中的任何非零位。 对设置此位的所有数组元素进行XOR - > A1。将0到N-1的所有数字XOR组合在一起,其中该位置位 - > B1。现在A1 XOR B1 = XA1 XOR B1 = D
  3. 再次扫描阵列并尝试查找 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. 我们可以假设数字来自[1..n],即不出现零。实际上,在循环遍历数组时,我们可以测试是否找到了零,如果没有则立即返回。

  2. 现在考虑:

    sum(A) = n (n + 1) / 2 - X + R
    
    product(A) = n! R / X
    
  3. 其中product(A)A中跳过零的所有元素的乘积。我们有两个未知数的方程,XR可以代数推导出来。

    修改:根据大众需求,这是一个经过实践证明的例子:

    我们设置:

    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太有趣了,无法共享。这是同样的问题,视频有答案:

  1. 排序
  2. 创建哈希图/词典。
  3. 创建数组。 (尽管部分跳过了。)
  4. 使用草龟和野兔算法。

注意:此问题比实际问题更多是琐事问题。除了在稀疏的有限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)空间中完成。 无需修改输入数组

  1. 这个想法类似于在链表中找到循环的起始节点。
  2. 保持两个指针:快速和慢速
slow = a[0]
fast = a[a[0]]
  1. 循环直到慢速!=快速
  2. 一旦找到循环(慢==快速)
  3. 将慢速重置为零
slow = 0
  1. 找到起始节点
while(slow != fast){
    slow = a[slow];
    fast = a[fast];
}
  1. slow是您的重复号码。

这是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;
        }
    }
}
  • O(n)时间和O(1)空间;请分享您的想法。

答案 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)

  1. 对数组O(n ln n)
  2. 进行排序
  3. 使用滑动窗口技巧遍历数组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;
        }
    }
    
  4. 测试用例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)

您可以按以下步骤操作:

  1. 使用线性时间排序算法(例如,计数排序) - O(N)
  2. 对数组进行排序
  3. 扫描已排序的数组,并在两个连续元素相等时立即停止 - O(N)

答案 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];
    }
}