我在面试论坛上遇到过这个问题。
给定一个可能包含重复项的int数组,找到形成序列的最大子集。 例如。 {} 1,6,10,4,7,9,5 然后ans是4,5,6,7 排序是一个明显的解决方案。这可以在O(n)时间内完成。
我对这个问题的看法是,O(n)时间和时间不能做到这一点。原因是如果我们可以在O(n)时间内完成这个,我们也可以在O(n)时间内进行排序(不知道上限)。 随机数组可以按顺序包含所有元素,但是按随机顺序排列。
这听起来似乎是合理的解释吗?你的想法。
答案 0 :(得分:4)
我相信它可以在O(n)中解决,如果你假设你有足够的内存来分配一个大小等于最大值的未初始化数组,并且该分配可以在恒定时间内完成。诀窍是使用一个惰性数组,这使您能够在线性时间内创建一组项目,并在恒定时间内进行成员资格测试。
阶段1:浏览每个项目并将其添加到惰性数组中。
阶段2:浏览每个未删除的项目,并删除所有连续项目。
在阶段2中,您确定范围并记住它是否是迄今为止最大的范围。可以使用双向链表在固定时间内删除项目。
这是一些令人难以置信的kludgy代码,用于演示这个想法:
int main(int argc,char **argv)
{
static const int n = 8;
int values[n] = {1,6,10,4,7,9,5,5};
int index[n];
int lists[n];
int prev[n];
int next_existing[n]; //
int prev_existing[n];
int index_size = 0;
int n_lists = 0;
// Find largest value
int max_value = 0;
for (int i=0; i!=n; ++i) {
int v=values[i];
if (v>max_value) max_value=v;
}
// Allocate a lazy array
int *lazy = (int *)malloc((max_value+1)*sizeof(int));
// Set items in the lazy array and build the lists of indices for
// items with a particular value.
for (int i=0; i!=n; ++i) {
next_existing[i] = i+1;
prev_existing[i] = i-1;
int v = values[i];
int l = lazy[v];
if (l>=0 && l<index_size && index[l]==v) {
// already there, add it to the list
prev[n_lists] = lists[l];
lists[l] = n_lists++;
}
else {
// not there -- create a new list
l = index_size;
lazy[v] = l;
index[l] = v;
++index_size;
prev[n_lists] = -1;
lists[l] = n_lists++;
}
}
// Go through each contiguous range of values and delete them, determining
// what the range is.
int max_count = 0;
int max_begin = -1;
int max_end = -1;
int i = 0;
while (i<n) {
// Start by searching backwards for a value that isn't in the lazy array
int dir = -1;
int v_mid = values[i];
int v = v_mid;
int begin = -1;
for (;;) {
int l = lazy[v];
if (l<0 || l>=index_size || index[l]!=v) {
// Value not in the lazy array
if (dir==1) {
// Hit the end
if (v-begin>max_count) {
max_count = v-begin;
max_begin = begin;
max_end = v;
}
break;
}
// Hit the beginning
begin = v+1;
dir = 1;
v = v_mid+1;
}
else {
// Remove all the items with value v
int k = lists[l];
while (k>=0) {
if (k!=i) {
next_existing[prev_existing[l]] = next_existing[l];
prev_existing[next_existing[l]] = prev_existing[l];
}
k = prev[k];
}
v += dir;
}
}
// Go to the next existing item
i = next_existing[i];
}
// Print the largest range
for (int i=max_begin; i!=max_end; ++i) {
if (i!=max_begin) fprintf(stderr,",");
fprintf(stderr,"%d",i);
}
fprintf(stderr,"\n");
free(lazy);
}
答案 1 :(得分:1)
我想说有办法做到这一点。该算法是您已经描述过的算法,但只使用O(n)排序算法。因此,对于某些输入(Bucket Sort,Radix Sort)存在这种情况(这也与您的论证密切相关,为什么它不起作用)。
Vaughn Cato建议实现就像这样(它的工作方式就像一个桶式排序,懒惰的数组作为按需存储桶工作)。
答案 2 :(得分:1)
如M. Ben-Or在代数计算树的下界所示,Proc。第15届ACM Sympos。 Theory Comput。,pp.80-86。 1983年由J. Erickson在pdf Finding Longest Arithmetic Progressions 中引用,这个问题在使用时不能在小于O(n log n)的时间内解决(即使输入已按顺序排序)代数决策树计算模型。
之前,我在评论中发布了以下示例,以说明对数字进行排序并不能轻松回答问题:假设数组已经按升序排序。例如,让它(20 30 35 40 47 60 70 80 85 95 100)。在输入的任何子序列中找到的最长序列是20,40,60,80,100而不是30,35,40或60,70,80。
关于这个问题的O(n)代数决策树解决方案是否会提供O(n)代数决策树排序方法:正如其他人所指出的那样,对于给定多重集合的这个子序列问题的解决方案不提供解决该多重集的排序问题。例如,考虑设置{2,4,6,x,y,z}。只要x,y,z是大数而不是算术序列,子序列求解器就会给出结果(2,4,6),并且它不会告诉你关于x,y,z的顺序。
答案 3 :(得分:0)
这个怎么样?填充哈希表,以便每个值存储到目前为止为该数字看到的范围的开始,除了存储范围结束的头元素。 O(n)时间,O(n)空间。一个暂定的Python实现(你可以通过一次遍历来保留一些状态变量,但这种方式似乎更清楚):
def longest_subset(xs):
table = {}
for x in xs:
start = table.get(x-1, x)
end = table.get(x+1, x)
if x+1 in table:
table[end] = start
if x-1 in table:
table[start] = end
table[x] = (start if x-1 in table else end)
start, end = max(table.items(), key=lambda pair: pair[1]-pair[0])
return list(range(start, end+1))
print(longest_subset([1, 6, 10, 4, 7, 9, 5]))
# [4, 5, 6, 7]
答案 4 :(得分:0)
这是一个未经优化的O(n)实现,也许你会发现它很有用:
hash_tb={}
A=[1,6,10,4,7,9,5]
for i in range(0,len(A)):
if not hash_tb.has_key(A[i]):
hash_tb[A[i]]=A[i]
max_sq=[];cur_seq=[]
for i in range(0,max(A)):
if hash_tb.has_key(i):
cur_seq.append(i)
else:
if len(cur_seq)>len(max_sq):
max_sq=cur_seq
cur_seq=[]
print max_sq