在小于线性的时间内,在排序数组中找到重复项

Posted

技术标签:

【中文标题】在小于线性的时间内,在排序数组中找到重复项【英文标题】:In less-than-linear time, find the duplicate in a sorted array 【发布时间】:2012-03-29 05:47:44 【问题描述】:

今天,一位面试官问了我这个问题。我的直接反应是我们可以简单地进行线性搜索,将当前元素与数组中的前一个元素进行比较。然后他问我如何在不到线性的时间内解决这个问题。

假设

数组已排序 只有一个重复 数组填充有数字[0, n],其中n 是数组的长度。

示例数组:[0,1,2,3,4,5,6,7,8,8,9]

我试图提出一个分而治之的算法来解决这个问题,但我不确定它是否是正确的答案。有人有什么想法吗?

【问题讨论】:

您的示例仅包含数字[0,n-2](没有1011)只是示例还是一般规则? 【参考方案1】:

可以在 O(log N) 中通过修改后的二进制搜索完成:

从数组的中间开始:如果 array[idx]

【讨论】:

通用版应该是array[idx]-array[0]【参考方案2】:

如果数组中没有缺少任何数字,如示例中所示,则可以使用二进制搜索在 O(log n) 中完成。如果a[i] < i,则重复在i之前,否则在i之后。

如果一个号码缺一个重复,我们仍然知道如果a[i] < i重复必须在i之前,如果a[i] > i,那么缺号码必须在i之前和重复之后。但是,如果是a[i] == i,我们不知道丢失的数字和重复是在i 之前还是在i 之后。在这种情况下,我看不到亚线性算法的方法。

【讨论】:

我很晚了,但是如果你允许丢失数字,那确实是不可能的(假设你不能在 O(1) 中读取任意大量的单元格)。假设我们考虑大小为 n+1 (n>=2) 的条目,并且我们将自己限制在这个条目子集:[0,0,2,...,n], [0,1,1,3,...,n ], ..., [0,1,...,k,k,k+2,...,n], ..., [0,1,...,n-1,n-1]。假设您已经知道最多 (n-2) 个单元格的内容,并且它们是成对不同的,仍然至少有 2 种可能性,您无法区分任何一种。因此,您需要至少读取 (n-1) 个单元格来确定哪个数字是重复的。【参考方案3】:

我试图提出一个分而治之的算法来解决这个问题,但我不确定它是否是正确的答案。

当然,您可以进行二分搜索。

如果arr[i/2] >= i/2,则重复项位于数组的上半部分,否则位于下半部分。

while (lower != upper)
    mid = (lower + upper) / 2
    if (arr[mid] >= mid)
        lower = mid
    else
        upper = mid-1

由于lowerupper 之间的数组在每次迭代中减半,因此算法的运行时间为 O(log n)。

ideone.com demo in Java

【讨论】:

【参考方案4】:

给定数组元素的总和与 0 到 n-1 个自然数的总和之间的差异会为您提供重复的元素。 0 到 n-1 个元素的总和为 (N * N-1)/2 示例数组是 [0,1,2,3,4,5,6,7,8,8,9] 0到9个自然数之和是:45 给定数组元素的总和:53 53-45 = 8 哪个是重复元素

【讨论】:

将所有元素相加是 O(n) - 因此超出预算【参考方案5】:
#include <bits/stdc++.h>
using namespace std;

int find_only_repeating_element(int arr[] , int n)
int low = 0;
int high = n-1;
while(low <= high)
    int mid = low + (high - low)/2;
    if(arr[mid] == arr[mid + 1] || arr[mid] == arr[mid - 1])
        return arr[mid];
    
    if(arr[mid] < mid + 1)
        high = mid - 2;
    else
        low = mid + 1;
    
   
   return -1;


int main(int argc, char const *argv[])

int n , *arr;
cin >> n;
arr = new int[n];
for(int i = 0 ; i < n ; i++)
    cin >> arr[i];

    cout << find_only_repeating_element(arr , n) << endl;
    return 0;

【讨论】:

欢迎来到 SO。请考虑在回答问题时为您的代码添加一些解释。【参考方案6】:

那怎么样? (递归风格)

public static int DuplicateBinaryFind(int[] arr, int left, int right)

   int dup =0;

   if(left==right)
   
      dup = left;
   
   else
   
        int middle = (left+right)\2;
        if(arr[middle]<middle)
        
          dup = DuplicateBinaryFind(arr,left, middle-1);

        
        else
        
           dup = DuplicateBinaryFind(arr, middle+1, right);
        
   

   return dup;


【讨论】:

【参考方案7】:

示例数组与您的问题有点不同。由于 n 是数组的长度,并且数组中只有一个重复项,因此数组中每个元素的值应该在 [0,n-1] 中。

如果是这样,那么这个问题和How to find a duplicate element in an array of shuffled consecutive integers?是同一个问题

以下代码应在 O(n) 时间和 O(1) 空间中找到重复项。

public static int findOnlyDuplicateFromArray(int[] a, boolean startWithZero)
    int xor = 0;
    int offset = 1;
    for(int i=0; i < a.length; i++)
        if(startWithZero)
            xor = xor ^ (a[i] + offset) ^ i;
        else
            xor = xor ^ a[i] ^ i;
        
        if(startWithZero)
            xor = xor - offset;
    return xor;

【讨论】:

对不起,这不是线性时间。应该使用二分查找来达到目的。

以上是关于在小于线性的时间内,在排序数组中找到重复项的主要内容,如果未能解决你的问题,请参考以下文章

删除排序数组中的重复项

从排序数组中删除重复项(不同的最终结果)

删除排序数组中的重复项

删除排序数组中的重复项并将它们推送到新数组中

26. 删除排序数组中的重复项

arts打卡 从排序数组中删除重复项