在所有其他元素出现两次的数组中查找元素出现一次(不使用XOR)

时间:2020-06-19 10:38:59

标签: arrays algorithm time-complexity bit-manipulation big-o

我已经尝试解决了很长时间,但似乎无法解决。
问题如下:

给定一个数组n个数字,其中的所有数字都出现两次,除了一个(仅出现一次)之外,找到仅出现一次的数字。

现在,我已经在网上找到了许多解决方案,但是没有一个可以满足该问题的其他约束。
解决方案应:

  • 以线性时间(也称为O(n))运行。
  • 不使用哈希表。
  • 假设计算机仅支持比较和算术(加,减,乘,除)。
  • 数组中每个数字的位数约为O(log(n))。

因此,无法使用XOR运算符尝试类似https://stackoverflow.com/a/4772568/7774315的操作,因为我们没有XOR运算符。由于每个数字中的位数大约为O(log(n)),因此尝试使用普通算术(逐位)实现XOR运算符将需要大约O(log(n))动作,这将为我们提供一个总体O(nlog(n))的解。

我要解决的最接近的问题是,如果我有办法在线性时间内获得数组中所有唯一值的总和,我可以从总和中减去该总和的两倍,以获得(负数)仅出现一次,因为如果出现两次的数字是{a1,a2,....,ak}并且出现一次的数字是x,则总和为
总和= 2(a1 + ... + ak)+ x
据我所知,集合是使用哈希表实现的,因此使用它们查找所有唯一值的总和是不好的。

4 个答案:

答案 0 :(得分:6)

让我们想象一下,有一种方法可以找到线性时间的精确中值并划分数组,以便所有较大的元素都在一侧,较小的元素在另一侧。通过期望元素数量的奇偶性,我们可以确定目标元素在哪一侧。现在在我们确定的部分中递归执行此例程。由于该部分每次都减半,因此遍历的元素总数不能超过O(2n)= O(n)。

答案 1 :(得分:2)

问题中的关键要素似乎就是这个:

数组中每个数字的位数约为O(log(n))。

问题是这个线索有点模糊。

第一种方法是考虑最大值为O(n)。然后可以在O(n)操作和O(n)存储器中执行计数排序。

它将包括找到最大值MAX,设置整数数组C [MAX]并直接执行经典计数排序

C[a[i]]++;

在数组C[]中寻找奇数将提供解决方案。

第二种方法,我认为更有效的方法是设置一个大小为n的数组,每个元素都由一个未知大小的数组组成。然后,一种几乎计数的排序将包含在:

C[a[i]%n].append (a[i]);

要找到唯一元素,我们必须找到一个奇数大小的子数组,然后检查该子数组中的元素。

每个子阵列的最大大小k将约为2 *(MAX / n)。根据提示,该值应该非常低。处理这个子数组的复杂度为O(k),例如,通过对b[j]/n执行计数排序,所有元素的模n相等。

我们可以注意到,实际上,这等效于执行一种临时哈希。

全局复杂度为O(n + MAX / n)。

答案 2 :(得分:0)

只要您处理的大小为O(log n)的整数,这应该可以解决问题。这是草绘@גלעד ברקן answer(包括@OneLyner注释)的算法的Python实现,其中,中位数被平均值或中值代替。

def mean(items):
    result = 0
    for i, item in enumerate(items, 1):
        result = (result * (i - 1) + item) / i
    return result


def midval(items):
    min_val = max_val = items[0]
    for item in items:
        if item < min_val:
            min_val = item
        elif item > max_val:
            max_val = item
    return (max_val - min_val) / 2


def find_singleton(items, pivoting=mean):
    n = len(items)
    if n == 1:
        return items[0]
    else:
        # find pivot - O(n)
        pivot = pivoting(items)
        # partition the items - O(n)
        j = 0
        for i, item in enumerate(items):
            if item > pivot:
                items[j], items[i] = items[i], items[j]
                j += 1
        # recursion on the partition with odd number of elements
        if j % 2:
            return find_singleton(items[:j])
        else:
            return find_singleton(items[j:])

以下代码仅用于对随机输入进行一些完整性检查:

def gen_input(n, randomize=True):
    """Generate inputs with unique pairs except one, with size (2 * n + 1)."""
    items = sorted(set(random.randint(-n, n) for _ in range(n)))[:n]
    singleton = items[-1]
    items = items + items[:-1]
    if randomize:
        random.shuffle(items)
    return items, singleton


items, singleton = gen_input(100)
print(singleton, len(items), items.index(singleton), items)
print(find_singleton(items, mean))
print(find_singleton(items, midval))

对于对称分布,中值与平均值或中值重合。 对于条目位数的log(n)要求,一个 可以表明,任何任意子采样都不能完全倾斜以提供超过log(n)个递归。

例如,考虑k = 4的k = log(n)位的情况,并且只有正数,最坏的情况是:[0, 1, 1, 2, 2, 4, 4, 8, 8, 16, 16]。在此,按均值进行枢转将使输入一次减少2,从而导致k + 1个递归调用,但是在输入中添加任何其他对不会增加递归调用的数量,但会增加输入的大小。

(编辑以提供更好的解释。)

答案 3 :(得分:0)

这里是גלעדברקן勾勒出的想法的(未优化)实现。 我正在使用Median_of_medians来获取足够接近中值的值,以确保在最坏情况下的线性时间。

NB:实际上,这仅使用比较,并且只要比较和副本的计数为O(1),无论整数的大小为O(n)。

def median_small(L):
    return sorted(L)[len(L)//2]

def median_of_medians(L):
    if len(L) < 20:
        return median_small(L)
    return median_of_medians([median_small(L[i:i+5]) for i in range(0, len(L), 5)])

def find_single(L): 
    if len(L) == 1: 
        return L[0] 
    pivot = median_of_medians(L) 
    smaller = [i for i in L if i <= pivot] 
    bigger =  [i for i in L if i > pivot] 
    if len(smaller) % 2: 
        return find_single(smaller) 
    else: 
        return find_single(bigger)

此版本需要O(n)额外的空间,但可以使用O(1)来实现。