在数组中查找重复项

Find duplicate in an array

本文关键字:查找 数组      更新时间:2023-10-16

给定一个介于 1 和 n 之间的 n + 1 个整数的只读数组,找到一个使用小于 O(n) 的空间并在流中按 O(1) 次顺序遍历流的线性时间重复的数字。

Sample Input: [3 4 1 4 1]
Sample Output : 1/4(any one of these)

如果有多个可能的答案(如上面的示例案例),请输出任何一个。

如果没有重复项,则输出 -1。

我尝试对此进行解决方案,即:

int Solution::repeatedNumber(const vector<int> &A) {
vector<bool> v(A.size(), true);
for (int i = 0; i < A.size(); i++) {
if (v[A[i]])
v[A[i]] = false;
else
return A[i];
}
}

这正在被接受,但这在内存中如何小于 O(n)?

你想知道为什么会接受这是正确的。这个答案是明显的O(n)空间复杂性。您分配了一些与 n 成正比增长的数据量,使其成为 O(n) 空间。无论什么判断你的程序都是错误地接受它。法官可能会接受您的分数,因为您使用的字节数少于 A 分配的字节数,但这只是猜测。

编辑:下面的代码实际上并不是问题的解决方案。它是按照上述思路解决更简单问题的方法。下面的解决方案忽略了流必须是只读的约束。经过一些研究,这个问题似乎是一系列类似问题的非常困难的版本,这些问题类型为"给定 1 到 n 之间的数字范围,找到重复/缺失的数字"。如果只有一个数字重复,并且只有一个 O(n) 时间要求,则可以使用上述布尔向量。如果只有一个数字重复,但你被限制在常量空间,你可以实现这个解决方案,我们使用高斯公式找到从 1 到 n 的整数之和,然后从数组的总和中减去它。如果数组有两个缺失的数字,并且您被限制为常量时间,则可以实现此解决方案,其中我们使用数组的总和和乘积来创建一组方程组,该方程组可以在O(n)时间内用O(1)空间求解。

为了解决上面提出的问题,看起来必须按照这个怪物的顺序实施一些东西。

以下是在其约束范围内解决此问题的方法:

你可以做这样的事情:

#include<vector>
#include<iostream>
int repeating(std::vector<int>& arr)
{
for (int i = 0; i < arr.size(); i++)
{
if (arr[abs(arr[i])] >= 0)
arr[abs(arr[i])] = -arr[abs(arr[i])];
else {
return abs(arr[i]);
}
}
}
int main()
{
std::vector<int> v{1,2,3,4,5,1};
std::cout<<repeating(v)<<std::endl;
std::cout<<sizeof(v)*sizeof(v[0])<<std::endl;
return 0;
}

上面的程序使用输入数组本身来跟踪重复项。对于每个索引 i,数组计算 arr[i]。数组将 arr(arr[i]) 设置为负数。否定值是一种易于逆转的操作(只需获取元素的绝对值),因此它可用于标记数组的索引,而不会破坏数据的完整性。如果你遇到过 arr[abs(arr[i])] 为负数的索引,你就知道你之前在数组中见过 abs(arr[i]))。这使用 O(1) 空间复杂度,遍历数组一次,并且可以修改以返回任何或所有重复的数字。

>std::vector<bool>是一个位集,所以它将使用n位。在 Big-O 表示法中,O(n/8)=O(n),这意味着空间不小于 O(n)。

我假设他们不查看实际程序,而只在某些示例运行中测量其空间消耗。因此,使用位向量会诱使它相信它比 O(n) 更好。

但我同意你的看法。它不应该被接受。

我有一个解决方案,它需要 O(sqrt(N)) 空间和 O(N) 时间,并遍历列表两次——假设可以在 O(1) 时间内计算整数平方根(对于任意大 N,这可能至少是一个 O(log(N)) 操作)。

  • 首先分配一个大小为 ceil(sqrt(N)) 的整数数组A1,填充为 0。
  • 针对每个元素循环访问数组x
    • 计算k=floor(sqrt(x))
    • 递增A1[k]
    • 如果A1[k]>2k+1,则在(k+1)²-1之间必须至少有一个重复项。(对于k=floor(sqrt(N))阈值为N-k²). Rememberk' 并中断第一次迭代
  • (可选)删除第一个数组
  • 分配一个大小A22k+1填充false的布尔数组。
  • 再次遍历所有x
    • 检查是否设置了A2[x-k²],如果是,x是重复的
    • 否则,递增A2[x-k²]

该解决方案也应该适用于更大和更小的数组(不需要正好是 N+1),如果没有重复项,第一次迭代将运行到最后。两个临时数组都是 O(k)(如果你是迂腐的,第一个是 O(k*log(k)),因为它必须存储最大大小为 sqrt(N) 的整数)。

std::vector<bool>

不像任何其他向量。

std::vector<bool>是针对boolstd::vector的可能节省空间的专业化。

这就是为什么它可能占用更少的内存,因为它可能用一个字节表示多个布尔值,就像位集一样。

@jayson Boubin 在上述答案中提出的解决方案是O(1)-空间方法,它 很好(顺便说一下,它很棒!! ),当允许更改原始数组或意味着更改无关紧要时。但是如果不允许更改原始数组,那么众所周知的解决方案是 O(sqrt(n))-空间和 O(n)-time,这种方法基本上建议我们应该首先考虑sqrt(n)-范围,而第 i 个范围将是 [sqrt(n)*i 到 sqrt(n)*(i+1)],之后我们遍历数组并计数 no。元素位于每个范围中,依此类推...

看看它: Leetcode:查找重复号码

嗯,它在内存中是常量(O(1)),因为你只是在原地进行比较,而不是创建一个新的数据结构来容纳任何东西或任何比较。

您也可以使用像unordered_set这样的哈希表,但这将使用 O(N) 内存 - 但保持 O(N) 时间复杂度。

顺便说一下,我不完全确定这是否是一个"可接受的"解决方案(您发布的内容,因为这是创建一个大小向量 (sizeofA) - 但只是根据您的需求提供解决方案。