如何在O(n)时间中找到5个重复值?


15

假设您有一个大小为的数组,其中包含从到(含)在内的整数,并且正好重复了5个。我需要提出一种算法,该算法可以找到O(n)时间中的重复数。我一生都无法想任何事情。我认为排序最好是O(n \ log n)?然后遍历数组将是O(n),从而导致O(n ^ 2 \ log n)。但是,我不确定是否需要排序,因为我已经看到一些棘手的东西,包括链接列表,队列,堆栈等。1n≥61O (n )O (n log n )O (n )O (n 2 log n )n−5O(n)O(nlog⁡n)O(n)O(n2log⁡n)


16
O(nlog⁡n)+O(n)不是。是。如果您进行了n次排序,则结果为。O (n log n )O (n 2 log n )O(n2log⁡n)O(nlog⁡n)O(n2log⁡n)
— 基金莫妮卡的诉讼


1
@leftaroundabout这些算法是,其中是数组的大小,是输入集的大小。由于这些算法适用于ñ ķ ķ = ñ - Ç ö Ñ 小号吨一个Ñ 吨Ô (Ñ 2)O(k⋅n)nkk=n−constantO(n2)
— RomanGräf17年

4
@RomanGräf看来,实际情况是这样的:算法在,其中是域的大小。因此,对于像OP这样的问题,无论是在大小的域上使用这样的算法,还是在无边界大小的域上使用传统的算法,都归结为相同的问题。也有道理。ķ ñ ø (Ñ · 登录Ñ )O(log⁡k⋅n)knO(n⋅log⁡n)
— 大约

5
对于,根据您的描述,唯一允许的数字是。但是,那么必须将重复六次,而不是五次。1 1n=611
— 亚历克斯·雷肯

Answers:


22

你可以创建一个额外的数组大小的。最初将数组的所有元素设置为。然后循环遍历输入数组并为每个将增大1 。之后,您只需检查数组:在循环,如果则重复。你在解决问题时间的内存成本,这是和因为你的整数是与和。n 0 A B [ A [ i ] ] i B A B [ A [ i ] ] > 1 A [ i ] O (n )O (n )1 n - 5Bn0AB[A[i]]iBAB[A[i]]>1A[i]O(n)O(n)1n−5


26

fade2black答案中的解决方案是标准解决方案,但它使用空间。您可以将其改进为O (1 )空间,如下所示:O(n)O(1)

  1. 令数组为。对于d = 1 ,... ,5,计算σ d = Σ Ñ 我= 1名阿[ 我] d。A[1],…,A[n]d=1,…,5σd=∑i=1nA[i]d
  2. 计算(可以使用公知的公式来计算在后者总和Ô (1 ))。注意τ d = 米d 1 + ⋯ + 米d 5,其中米1,... ,米5是重复的编号。τd=σd−∑i=1n−5idO(1)τd=m1d+⋯+m5dm1,…,m5
  3. 计算多项式。此多项式的系数是对称函数米1,... ,米5可以从计算τ 1,...... ,τ 5在Ö (1 )。P(t)=(t−m1)⋯(t−m5)m1,…,m5τ1,…,τ5O(1)
  4. 通过尝试所有n - 5个可能性,找到多项式的所有根。P(t)n−5

该算法假定采用RAM机器模型,其中对位字进行基本算术运算需要O (1 )时间。O(log⁡n)O(1)


制定此解决方案的另一种方法是遵循以下思路:

  1. 计算,并使用公式y 1 = x 1 - ∑ n - 5 i = 1 i推导y 1 = m 1 + ⋯ + m 5。x1=∑i=1nA[i]y1=m1+⋯+m5y1=x1−∑i=1n−5i
  2. 计算在ø (Ñ )使用公式 X 2 = (甲[ 1 ] )甲[ 2 ] + (甲[ 1 ] + 阿[ 2 ] )A [ 3 ] + (A [ 1x2=∑1≤i<j≤A[i]A[j]O(n)
    x2=(A[1])A[2]+(A[1]+A[2])A[3]+(A[1]+A[2]+A[3])A[4]+⋯+(A[1]+⋯+A[n−1])A[n].
  3. 推断使用公式 Ŷ 2 = X 2 - Σ 1 ≤ 我< Ĵ ≤ ñ - 5我Ĵ - (ñ - 5 Σ我= 1我) ÿ 1。y2=∑1≤i<j≤5mimj
    y2=x2−∑1≤i<j≤n−5ij−(∑i=1n−5i)y1.
  4. 计算并沿相似的直线推导y 3,y 4,y 5。x3,x4,x5y3,y4,y5
  5. 的值是(高达符号)的多项式的系数P (吨)从前面的溶液。y1,…,y5P(t)

该解决方案表明,如果将5替换为,则可以使用O (d 2)空间获得(我相信)O (d 2 n )算法,该算法对位长为O的整数执行O (d n )算术运算(d log n ),在任何给定时间最多保留O (d )个。(这需要仔细分析我们执行的乘法,其中大多数涉及一个仅长度为O的操作数(log ndO(d2n)O(d2)O(dn)O(dlog⁡n)O(d)。)可以想象,可以使用模块化算法将其改进为 O (d n )时间和 O (d )空间。O(log⁡n)O(dn)O(d)


的任何解释和τ d,P (吨),中号我等?为什么ð ∈ { 1 ,2 ,3 ,4 ,5 }?σdτdP(t)mid∈{1,2,3,4,5}
— 发泡胶飞

3
解决方案背后的洞察力是求和技巧,它在许多练习中都出现(例如,如何从长度为的数组中找到缺失的元素,该数组包含数字1 ,… ,n中的一个?求和技巧可用于计算任意函数f的f (m 1)+ ⋯ + f (m 5),问题是选择哪个f才能推导出m 1,… ,mn−11,…,nf(m1)+⋯+f(m5)ff。我的答案使用了对称函数基本理论中的熟悉技巧。m1,…,m5
— Yuval Filmus

1
@hoffmale实际上,。O(d2)
— Yuval Filmus

1
@hoffmale每个人都用机器字。d
— Yuval Filmus

1
@BurnsBA这种方法的问题是比(n - 4 )(n - 5 )大得多(n−5)#。大量操作较慢。(n−4)(n−5)2
— Yuval Filmus

8

还有一种基于分区的线性时间和恒定空间算法,如果您尝试将其应用于数学方法无法很好解决的问题的变体,则该方法可能会更加灵活。与数学方法相比,这需要对基础数组进行变异,并且常数因子更差。更具体地说,我相信,就值的总数和重复项d的数量而言,代价是分别为O(n log d )和O(d ),尽管严格证明它将比我目前花费更多的时间。 。ndO(nlog⁡d)O(d)


算法

从对的列表开始,其中第一对是整个数组的范围,如果为1索引,则为。[(1,n)]

重复以下步骤,直到列表为空:

  1. 从列表中取出并删除任何一对。(i,j)
  2. 找到指定子数组的最小和最大值和max。minmax
  3. 如果,则子数组仅包含相等的元素。屈服除一个元素以外的元素,并跳过步骤4至6。min=max
  4. 如果,则子数组不包含重复项。跳过步骤5和6。max−min=j−i
  5. 在min + max附近对子数组进行分区,使得直到某个索引k的元素小于分隔符,而在该索引之上的元素不小于分隔符。min+max2ķ
  6. 将和(k + 1 ,j )添加到列表中。(一世,ķ)(ķ+1,Ĵ)

时间复杂度的课程分析。

步骤1至6花费时间,因为找到最小和最大值以及划分可以在线性时间内完成。Ø(Ĵ-一世)

列表中的每对要么是第一对((1 ,n ),要么是某对的子代,其对应的子数组包含一个重复的元素。有至多d ⌈ 登录2周 Ñ + 1 ⌉这样的父母,由于每个遍历半部的范围内,其中一个重复的就可以了,所以有至多2 d ⌈ 登录2 Ñ + 1个⌉总包括对在子阵列没有时重复。在任何时候,列表的大小不超过2 d(一世,Ĵ)(1,ñ)d⌈日志2⁡ñ+1⌉2d⌈日志2⁡ñ+1⌉2d。

考虑找到任何重复的工作。它由一个在指数递减范围内的成对序列组成,因此总功是几何序列的总和,即。这产生了一个明显的推论,即d个重复项的总功必须为O(n d ),它在n中是线性的。Ø(ñ)dØ(ñd)ñ

要找到更严格的界限,请考虑最大程度地分散副本的最坏情况。直观地讲,搜索过程分为两个阶段,一个阶段每次遍历整个数组,每个阶段逐渐变小,而另一个阶段小于因此只遍历数组的一部分。第一阶段只能是logd深度,因此成本为O(nlogd),第二阶段具有成本O(n),因为要搜索的总面积再次呈指数下降。ñd日志⁡dØ(ñ日志⁡d)Ø(ñ)


谢谢你的解释。现在我明白了。一个非常漂亮的算法!
— DW

5

将此作为答案,因为它需要的空间比评论所需要的空间大。

当您建议一种方法时,会在OP中出错。排序列表,然后遍历时间,而不是O (n 2 log n )时间。当您依次执行两项操作(分别取O (f )和O (g ))时,结果时间复杂度为O (f + g )= O (max f ,g)(在大多数情况下)。Ø(ñ日志⁡ñ)Ø(ñ2日志⁡ñ)Ø(F)Ø(G)Ø(F+G)=Ø(最大值F,G)

为了增加时间复杂度,您需要使用for循环。如果您有一个长度为的循环,并且对于循环中的每个值,您都执行一个函数O (g ),那么您将获得O (f g )时间。FØ(G)Ø(FG)

因此,在您的情况下,您以排序,然后以O (n )进行横向运算,从而得出O (n log n + n )= O (n log n )。如果对于排序算法的每次比较,您都必须进行O (n )的计算,则它将采用O (n 2 log n ),但是这里不是这种情况。Ø(ñ日志⁡ñ)Ø(ñ)Ø(ñ日志⁡ñ+ñ)=Ø(ñ日志⁡ñ)Ø(ñ)Ø(ñ2日志⁡ñ)


如果您对我声称感到好奇,请务必注意并非总是如此。但是,如果˚F ∈ Ô (克)或克∈ ø (˚F )(其适用于常用功能整个主机),它会保持。它最不常用的时间是当涉及到其他参数时,您会得到诸如O (2 c n + n log n )的表达式。Ø(F+G)=Ø(最大值F,G)F∈Ø(G)G∈Ø(F)Ø(2Cñ+ñ日志⁡ñ)


3

布尔数组技术有一个很明显的就地变体,它使用元素的顺序作为存储(arr[x] == x“找到”元素的位置)。不像分区变体可以被证明更通用,我不确定何时真正需要这样的东西,但这很简单。

for idx from n-4 to n
    while arr[arr[idx]] != arr[idx]
        swap(arr[arr[idx]], arr[idx])

这只是反复放置arr[idx]在该位置,arr[idx]直到您找到该位置为止,此时该位置必须是重复的。请注意,交换的总数受限制,因为每次交换都会使其退出条件正确。ñ


您将不得不提出某种论点,即内部while循环平均在恒定时间内运行。否则,这不是线性时间算法。
— David Richerby '17

@DavidRicherby它不会平均运行恒定时间,但是外部循环只能运行5次,这样就可以了。请注意,交换的总数受限制,因为每次交换都会使其退出条件正确,因此,即使重复值的数量增加,总时间也仍然是线性的(aka。需要n步而不是n d)。ñññd
— Veedrac

糟糕,我不知何故没有注意到外循环运行了恒定的次数!(经过编辑,以包括您有关掉期数量的注释,也可以使我
— 退缩

1

从和中减去你具有值。∑一世=1ñ一世=(ñ-1)⋅ñ2

于是,经过时间(假设算术是O(1),它是不是真的,但让我们假装)你有一笔σ 1 1到n之间的5整数:Θ(ñ)σ1

X1+X2+X3+X4+X5=σ1

据说这不好,对吧?您可能无法弄清楚如何将其分解为5个不同的数字。

啊,但这就是有趣的地方!现在执行与以前相同的操作,但从∑ n i = 1 i 2中减去值的平方。现在您有了:∑一世=1ñ一世2

X12+X22+X32+X42+X52=σ2

看到我要去哪里了吗?对3、4和5的幂进行相同的操作,您将在5个变量中拥有5个独立方程。我很确定您可以解决。X→

注意事项:算术不是真的 O(1)。另外,您需要一些空间来表示您的总和。但并不像你想象的-你可以做模块化最一切,只要你有,哦,位; 应该这样做。⌈日志⁡(5ñ6)⌉


@YuvalFilmus是否提出相同的解决方案?
— fade2black

@ fade2black:哦,是的,的确如此,对不起,我刚刚看到了他的解决方案的第一行。
— einpoklum

0

解决问题的最简单方法是创建一个数组,在该数组中,我们将对原始数组中每个数字的出现次数进行计数,然后遍历从到n - 5的所有数字,并检查该数字是否出现一次以上,因此这很复杂内存和时间中的解都是线性的,或者是O (N )1ñ-5Ø(ñ)


1
这与@ fade2black的答案相同(尽管在眼睛上容易一些)
— LangeHaare

0

将数组映射到1 << A[i],然后将所有内容异或。您的重复项将是关闭相应位的数字。


一共有五个重复项,因此xor技巧在某些情况下不会中断。
— Evil

1
该程序的运行时间为。每个位向量的长度为n位,因此您每个位向量的操作花费O (n )时间,并且对原始数组的每个元素执行一次位向量操作,总共需要O (n 2)时间。Ø(ñ2)ñØ(ñ)Ø(ñ2)
— DW

@DW但是鉴于我们通常使用的机器固定为32位或64位,并且它们在运行时不会改变(即它们是恒定的),所以为什么不应该将它们这样对待并假设位操作是在而不是O (n )中?O(1)O(n)
— code_dredd

1
@ray,我想您回答了您自己的问题。假设我们通常使用的机器固定为64位,则对位向量进行运算的运行时间为O (n ),而不是O (1 )。在n位向量的所有n位上执行某些操作都需要n / 64条指令,并且n / 64是O (n ),而不是O (1 )。nO(n)O(1)n/64nnn/64O(n)O(1)
— DW

@DW我从以前得到的东西。有人评论说,位向量是指大小的数组中的单个元素,位向量是64位,这就是我要指的常数。显然,如果我们假设每个元素有k位并且数组中的元素数为n,则处理大小为n的数组将花费O (k n )时间。但是k = 64,因此对数组元素w /恒定位数的操作应为O (1 )而不是O (k ),并且数组OnnO(kn)knk=64O(1)O(k)代替 O (k n )。您是出于完整性/正确性而保留 k还是我遗漏了其他东西?O(n)O(kn)k
— code_dredd

-2
DATA=[1,2,2,2,2,2]

from collections import defaultdict

collated=defaultdict(list):
for item in DATA:
    collated[item].append(item)
    if len(collated) == 5:
        return item.

# n time

4
欢迎来到该网站。我们是计算机科学站点,因此我们正在寻找算法和解释,而不是需要了解特定语言及其库的代码转储。特别是,您声称此代码以线性时间运行的主张是假设其collated[item].append(item)以恒定时间运行。是真的吗
— David Richerby '17

3
此外,您正在寻找一个重复五次的值。相反,OP寻找五个值,每个值重复两次。
— Yuval Filmus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.