给定一个数组,其中每个数字的出现次数是奇数,除了一个出现次数为偶数的数字。找出偶数出现的数字。
e.g。
1, 1, 2, 3, 1, 2, 5, 3, 3
输出应为:
2
以下是限制因素:
由于上述限制,我的所有想法都失败了:基于比较的排序,计数排序,BST,散列,暴力。
我很想知道:XORing会在这里工作吗?如果是,怎么样?
答案 0 :(得分:4)
这个问题一直占据我的地铁乘车数天。这是我的想法。
如果A.韦伯是对的并且这个问题来自面试或是某种学术问题,我们应该考虑我们正在做的(错误的)假设,并且可能尝试探索一些简单的案例。
我想到的两个极端子问题如下:
也许我们应该根据不同值的数量的复杂性来分割案例。
如果我们假设不同值的数量为O(1),则每个数组都会有m
个不同的值,m
独立于n
。在这种情况下,我们可以遍历原始数组擦除和计算每个值的出现次数。在示例中,它将给出
1, 1, 2, 3, 1, 2, 5, 3, 3 -> First value is 1 so count and erase all 1
2, 3, 2, 5, 3, 3 -> Second value is 2, count and erase
-> Stop because 2 was found an even number of times.
这将解决复杂度为O(mn)
的第一个极端示例,其评估结果为O(n)
。
最好:如果不同值的数量是O(1)
,我们可以计算哈希映射中的值外观,在读取整个数组后返回它们并返回出现偶数次的值。这仍然被认为是O(1)
记忆。
第二极端情况将包括查找数组中唯一的重复值。
这在O(n)
中似乎是不可能的,但有一些特殊情况我们可以:如果数组中包含n
个元素和值,则为{1, n-1}
+重复值(或某些变体,如 all x和y之间的数字)。在这种情况下,我们求和所有值,从总和中减去n(n-1)/2
,并检索重复的值。
使用数组内的随机值解决第二个极端情况,或m
在n
上不恒定的一般情况,在常量内存和O(n)
时间似乎不可能。
额外注意事项:此处,XORing不起作用,因为我们想要的数字显示偶数次而其他数字显示为奇数次。如果问题是“给出的数字奇数次数,则所有其他数字显示偶数次数”我们可以对所有值进行异或并找到奇数一个在最后。
我们可以尝试使用这种逻辑来寻找一种方法:我们需要类似函数的东西,在数字上应用奇数次会产生0,偶数次会是同一性。不要以为这是可能的。
答案 1 :(得分:2)
<强>简介强>
这是一个可能的解决方案。这是相当人为的,不实用,但问题也是如此。如果我的分析中有漏洞,我将不胜感激。如果这是一个带有“官方”解决方案的家庭作业或挑战问题,我也很乐意看到如果原始海报仍然存在,考虑到问题已经过了一个多月。
首先,我们需要充实一些不明确的问题细节。所需的时间复杂度为O(N)
,但N
是多少?大多数评论员似乎都认为N
是数组中元素的数量。如果数组中的数字具有固定的最大大小,这将是可以的,在这种情况下,Michael G的基数排序解决方案可以解决问题。但是,在没有原始海报澄清的情况下,我解释约束#1,因为不需要修复最大位数。因此,如果n
(小写)是数组中元素的数量,m
元素的平均长度,则要匹配的总输入大小为{ {1}}。解决方案时间的下限是mn
,因为这是验证解决方案所需的输入的读取时间。因此,我们需要一个与总输入大小O(mn)
呈线性关系的解决方案。
例如,我们可能有N = nm
,即n = m
个sqrt(N)
平均长度的元素。比较排序需要sqrt(N)
次操作,但这不是胜利,因为操作本身平均需要O( log(N) sqrt(N) ) < O(N)
次,所以我们回到O(m) = O(sqrt(N))
。
此外,如果O( N log(N) )
是最大长度而非平均长度,则基数排序需O(mn) = O(N)
。如果假设数字落在某个有界范围内,则最大和平均长度将在相同的顺序上,但如果不是,我们可能有一个小的百分比,具有大的和可变的数字位数和大的百分比具有少量的数字。例如,10%的数字可以是m
长度和90%长度m^1.1
。平均长度为m*(1-10%*m^0.1)/90%
,但最大长度为m
,因此基数排序为m^1.1
。
为了避免出现过于严重改变问题定义的问题,我的目标仍然是描述一个时间复杂度与元素数量呈线性关系的算法,即O(m^1.1 n) > O(N)
。但是,我还需要在每个元素的长度上执行线性时间复杂度的操作,以便平均在所有元素上执行这些操作O(n)
。这些操作将是乘法和加法,以计算元素和比较上的散列函数。如果这个解决方案确实解决了O(m)
中的问题,那么这应该是最佳复杂性,因为它需要相同的时间来验证答案。
问题定义中省略的另一个细节是我们是否可以在处理数据时销毁数据。为了简单起见,我打算这样做,但我认为可以避免使用它。
可能的解决方案
首先,可能存在负数的约束是空的。通过一次数据传递,我们将记录最小元素O(N) = O(nm)
和元素数z
。在第二遍中,我们将n
添加到每个元素,因此最小元素现在是3.(注意,结果可能会出现一定数量的数字,所以我们应该通过一定数量的额外传递数据首先测试这些解决方案。)一旦我们得到了我们的解决方案,我们只需减去(3-z)
将其恢复为原始形式。现在我们有三个特殊的标记值(3-z)
,0
和1
,它们本身不是元素。
第1步
使用median-of-medians selection algorithm确定数组2
的第90个百分位元素p
,并将数组分为两组A
和S
其中T
的{{1}}元素大于S
且10% of n
的元素小于p
。这需要T
个步骤(步骤平均花费p
个时间O(n)
。匹配O(m)
的元素可以放在O(N)
或p
中,但为了简单起见,请运行一次数组并测试S
并将其替换为{ {1}}。设置T
最初跨越索引p
,其中0
约为S
0..s
,并设置s
跨越剩余的90%索引{ {1}}。
第2步
现在我们将循环遍历10%
,对于每个元素n
,我们将计算哈希函数T
到s+1..n
。我们将使用universal hashing来获得统一分布。因此,我们的散列函数将进行乘法和加法,并在每个元素的长度上采用线性时间。
我们将使用修改的线性探测策略进行碰撞:
i in 0..s
被e_i
的成员占用(意为h(e_i)
,但不是标记s+1..n
或h(e_i)
)或T
1}}。这是哈希表未命中。通过交换广告位A[ h(e_i) ] < p
和1
中的元素来插入2
。
0
被e_i
(意为i
)或标记h(e_i)
或h(e_i)
的成员占用。这是哈希表冲突。进行线性探测,直到遇到S
或A[ h(e_i) ] > p
或1
成员的副本。
如果是2
的成员,这又是一个哈希表未命中,请通过交换到e_i
来插入T
,如0
所示。< / p>
如果是T
的副本,则这是哈希表命中。检查下一个元素。如果该元素为e_i
或(1.)
,我们已经多次看到i
,将e_i
更改为1
,反之亦然平价变化。如果下一个元素不是2
或e_i
,那么我们之前只看过1
一次。我们希望将2
存储到下一个元素中,以表明我们现在看到1
偶数次。我们寻找下一个“空”插槽,即2
成员占用的插槽,我们将移动到插槽e_i
或0,并将元素移回到索引{{ 1}}所以我们在2
旁边有空间来存储我们的奇偶校验信息。请注意,我们不需要再次存储e_i
本身,因此我们没有使用额外的空间。
所以基本上我们有一个函数哈希表,其槽数是我们希望哈希的元素的9倍。一旦我们开始获得点击,我们也开始存储奇偶校验信息,因此我们最终可能只有4.5倍的插槽数,仍然是一个非常低的负载系数。有几种碰撞策略可以在这里工作,但由于我们的载荷因子很低,平均碰撞次数也应该很低,线性探测应该以平均适当的时间复杂度解决它们。
第3步
我们将T
的元素哈希到i
后,我们遍历h(e_i)+1
。如果我们找到一个S的元素后跟一个h(e_i)
,那就是我们的目标元素,我们就完成了。 e_i
的任何元素0..s
后面跟s+1..n
的另一个元素表示s+1..n
只遇到一次,可以归零。同样地,2
后跟e
表示我们看到S
奇数次,我们可以将S
和标记e
归零。
按需要冲洗并重复
如果我们没有找到目标元素,我们会重复这个过程。我们的第90个百分位分区会将剩余最大元素的e
的10%移至1
的开头,其余元素(包括空e
- 标记位置)移至末尾。我们像以前一样继续散列。我们最多需要10次这样做,因为我们每次处理10%的e
。
结论分析
通过中位数算法进行分区的时间复杂度为1
,我们做了10次,仍为n
。每个哈希操作平均需要A
,因为哈希表负载较低,并且总中执行了0
个哈希操作(对于10个重复中的每一个,大约有10%的n) )。每个n
元素都有一个为它们计算的散列函数,时间复杂度与它们的长度成线性关系,因此平均值超过所有元素O(N)
。因此,聚合的散列操作是O(N)
。所以,如果我已经正确分析了这个,那么整个算法就是O(1)
。 (如果假设加法,乘法,比较和交换操作相对于输入是恒定时间,那么它也是O(n)
。)
请注意,此算法不利用问题定义的特殊性质,即只有一个元素具有偶数次出现次数。我们没有利用问题定义的这种特殊性质,这使得存在更好(更聪明)算法的可能性,但它最终也必须是O(N)。
答案 2 :(得分:0)
请参阅以下文章:Sorting algorithm that runs in time O(n) and also sorts in place, 假设最大位数是常数,我们可以在O(n)时间内对数组进行就地排序。
在此之后,需要计算每个数字的出现次数,平均需要花费n / 2次才能找到一个出现次数均为偶数的数字。