这是我遇到的一个常见的面试问题,但是我没有以它要求的方式改进它。
assume we have an int array int[] A, we want to find the first duplicate entry.
几乎每个人都可以考虑使用HashSet,并在解析时添加它。这将导致O(n)时间和O(n)空间。在此之后,我被要求在没有其他数据结构的情况下解决它。我说最愚蠢的想法是在O(n ^ 2)时间内比较每一个。然后我被要求改善O(n ^ 2)时间。
为了改善它,我想使用固定大小的数组(假设最大数是n),boolean [] b = new boolean [n];但我不允许使用这种方法。
然后我考虑使用int变量,使用位操作,如果最大数小于32,那么对于n,我们可以向左推1到n位并且|检查,然后&检查器到阵列中的下一个条目以检查它是否是> 0。 e.g:
int c = A[i];
if(check & (1 << c) > 0) return false;
check |= 1 << c;
然而,这也是不允许的。
所以有一个暗示我可以将数组本身用作hashset / hashtable和“线性散列”?
任何帮助?感谢
答案 0 :(得分:4)
我有这个想法:当您向下进行数组时,您对已访问过的部分进行排序。通过二进制搜索,您将改善时间;空间是0.排序本身是...插入排序?您基本上正常运行排序,但是当您搜索插入新数字的位置时,如果您点击数字本身,则会喊出“宾果游戏”。这是零空间+ O(n 2 )时间的改进。
答案 1 :(得分:4)
我会问面试官为什么他们不希望你使用“其他数据结构”时,显然有一个为此目的而设计的内置结构 - HashSet
。
来自Java Documentation on the Collections Framework:
集合框架是用于表示的统一架构 并操纵集合,允许它们被操纵 独立于其代表的细节。它减少了 编程工作,同时提高性能。它允许 不相关的API之间的互操作性,减少了设计和工作的努力 学习新API,促进软件重用。
<强>附录强>
以下大部分评论都认为这只是一项练习 - 确定程序员的技能。我对此的反驳很简单:
这个“面试”是针对Java编程的。 Java是一种面向对象的语言,能够执行这些任务,而无需从头开始设计流程(如C语言和其他各种低级语言)。此外,当空间复杂性成为一个问题时,Java不是最佳选择。也就是说,再次阅读上面列表中的条目。
答案 2 :(得分:4)
线性散列作为defined by Wikipedia具有以下优势:调整大小逐渐发生,因为循环以循环方式逐个拆分,保留用于插入调整大小的常量摊销时间复杂度。因此,他们的想法是迭代数组,重新使用已经迭代的元素作为线性散列的存储。
虽然我不是线性散列的专家,但我没有看到任何方法来适应数组中的散列表。当然,要使用线性散列存储n个元素,您可以使用n个桶。但是,存储桶中的元素数量是无限制的,您需要类似链接列表来实现每个存储桶,这会为指针花费额外的O(n)内存。
因此,该算法不会产生比普通HashSet
更好的渐近空间复杂度。但它确实通过常数因子减少了内存消耗。
它的时间复杂度与普通HashSet
相同。
编辑:在我看来,这个答案被忽略了(没有投票,没有评论)。它没用吗?请评论,以便我知道要改进的地方。
答案 3 :(得分:2)
嗯,你自己给出答案:确实存在线性哈希。根据{{3}},它具有复杂度o(1)/ o(1) 所以你要先将数组中的元素一个接一个地取出,然后使用前几个作为哈希映射的内存 但实际上,这是你自己实现的数据结构。
要么采访没有说你必须解决它“没有其他数据结构”,或者采访者确实不明白数据结构是数据结构,即使你自己实现它。
反正rofls,主要是因为这是你要么知道的问题,要么你不知道。在面试中没有办法提出这个问题。我希望你不会为他们工作。答案 4 :(得分:2)
这不使用线性散列,但比O(N 2 )工作得更快:
步骤3..5可以简化。只是哈希元素N + 1 .. 3 * N / 2并搜索此哈希表中数组的所有其他元素。然后对元素3 * N / 2 + 1 .. 2 * N执行相同的操作。这是原始算法的两倍慢,但平均仍为O(N log N)。
其他替代方法是使用前N个空元素为元素N + 1 ... 3 * N / 2构造二叉搜索树,并在此树中搜索该数组的所有其他元素。然后对元素3 * N / 2 + 1 .. 2 * N执行相同的操作。 (仅当数组足够小并且其元素可以用整数值索引时才有效。)
如上所述的算法是概率性的,并且平均在O(N log N)时间内工作。最坏的情况是O(N 2 )。如果树是自平衡的,则使用二叉搜索树的替代方案可能具有O(N log 2 N)最差情况复杂度。但这很复杂。使用更简单的算法可以在O(N log 2 N)最坏情况下完成任务。
此算法按顺序遍历数组并保持以下不变量:最大可能的子数组,其大小为2的幂,适合当前位置的左侧,从索引0开始并已排序;下一个这样的子阵列跟随它并且也被分类;换句话说,当前索引的二进制表示描述了在它之前有多少个排序的子数组。例如,对于索引87(1010111),我们在索引86处具有单个元素,在索引84处具有排序对,在80处具有4个元素的排序子数组,在64处具有16个元素的排序子数组,以及已排序的数组开头的64个元素的子数组。
答案 5 :(得分:0)
我被提出这个额外的限制,没有额外的内存,只有寄存器。这就是我提出的:
outer: for (i = 0; i < arr.length - 1; i++)
for (j = i+1; j < arr.length; j++)
if (arr[i] == arr[j])
break outer;
如果i和j是&lt; arr.length,是第一个重复值的索引,它匹配。
它比O(n ^ 2)好一点,因为j从不覆盖arr的整个长度
答案 6 :(得分:0)
伪代码:
res = -1;
startArray = [...];
sortedArray = mergeSort(startArray);
for i = 1 to n
x = bynary_search(sortedArray, startArray[i]); //array, element
if ((sorted_array[x] == sortedArray[x-1]) || (sorted_array[x] == sortedArray[x+1]))
res = i;
break;
if (res != -1)
print('First duplicate is ',startArray[res]);
else
print('There are no duplicates');
合并排序最差情况 O(n log n)
二进制搜索最坏情况 O(log n)
n次二元搜索最坏情况 O(n log n)
总 O(n log n)
答案 7 :(得分:0)
这是平均算法上的O(n)时间
public static int firstRepeatingElement(int[] elements) {
int index = -1;
Set<Integer> set = new HashSet<Integer>();
for (int i = elements.length - 1; i >=0; i--) {
if (set.contains(elements[i])) {
index = i;
}
set.add(elements[i]);
}
if (index != -1) {
return elements[index];
}
throw new IllegalArgumentException("No repeating elements found");
}
以下是测试用例
@Test
public void firstRepeatingElementTest() {
int [] elements = {1,2,5,7,5,3,10,2};
int element = ArrayUtils.firstRepeatingElement(elements);
assertThat(element, is(2));
}
@Test(expected=IllegalArgumentException.class)
public void firstRepeatingElementTestWithException() {
int [] elements = {1,2,5,7,3,10};
int element = ArrayUtils.firstRepeatingElement(elements);
assertThat(element, is(2));
}
答案 8 :(得分:0)
我相信这是&#34;线性哈希&#34;面试官正在寻找的解决方案。我们首先需要假设两个额外的约束:
通过这些额外的限制,我们可以使用更少的时间和空间解决问题。
好的,让我们来看看代码:
int findFirstDuplicateEntry(int[] A) {
for (int i=0; i<A.length; i++) {
if (A[Math.abs(A[i])-1]<0)
return Math.abs(A[i]);
else {
A[Math.abs(A[i])-1] = -A[Math.abs(A[i])-1];
}
}
return -1;
}
我在这里做的是使用数组本身来存储一些额外的信息。当我遍历数组时,每次遇到一个值时,我都会将该值用作索引。在这个索引我会检查值。如果值为负,我知道我之前一直在这里(因为所有正面约束)。因此,我找到了我的第一个副本,并可以退出。否则,我将否定该指数的价值。