我在接受采访时被问到这个问题。给定一个整数列表,我们怎样才能找到给定列表中所有成员的最大间隔?
E.g。给出列表1,3,5,7,4,6,10然后回答是[3,7]。因为它具有3到7之间的所有元素。
我试着回答,但我没有说服力。我采取的方法是先对列表进行排序,然后检查最大间隔。但我被要求在O(n)
中这样做。
答案 0 :(得分:35)
我知道基于散列和动态编程的解决方案。设 f(x) 为哈希函数。诀窍是哈希表值。考虑列表中包含的 最长间隔,该间隔以x 开头或结尾。然后 h [ f(x)] = y ,其中 y 该间隔的另一端 即可。请注意,该间隔的长度为 abs( x - y )+ 1 。算法描述将清楚说明存储该值的原因。
移动列表。让 i 成为当前索引, x := list [ i ] - 当前数。现在
1。如果 h [ f(x)] 不为空,那么我们之前就遇到了数字x。无所事事,继续。
2. 检查 h [ f(x-1)] 和 h [ f(x + 1) ) 即可。
2.1。如果它们都不为空,则表示我们已经遇到 x-1 和 x + 1 ,我们知道一些间隔 [ a..x-1 ] 和 [ x +我们已在列表中遇到的1..b ] 。我们知道这是因为 a = h [ f(x-1)] 和 b = h根据 h 的定义,[ f(x + 1)] 。现在当我们得到 x 时,这意味着我们现在已经满足了整个时间间隔 [ a,b ] ,所以我们按如下方式更新值: h [ f(a)]:= b 和 h [ f(b )]:= a 。
同时将 h [ f(x)] 设置为某个值(假设 x ,不影响答案) ),以便下次我们在列表中遇到 x 时,我们会忽略它。 x 已经完成了他的工作。
2.2。如果只设置其中一个,请说 h [ f(x-1)] = a ,这意味着我们已经遇到了一些区间 [ a..x-1 ] ,现在它已经扩展为 x < / EM> 即可。更新将是 h [ f(a)]:= x 和 h [ f(x)]:= a 。
2.3。如果没有设置,则表示我们既未遇到 x-1 ,也未遇到 x + 1 ,我们已经遇到的包含 x 的最大间隔是单个 [ x < / em>] 本身。所以设置 h [ f(x)]:= x 。
最后,为了得到答案,请传递整个列表并取 最大 abs( x - h [ f(x)所有 x 的em>])+ 1 。
答案 1 :(得分:8)
如果不希望排序,可以使用哈希映射和Disjoint-set data structure的组合。
对于列表中的每个元素,创建一个节点并使用key = element的值将其插入到哈希映射中。然后查询哈希映射的值+ 1和值-1。如果找到任何内容,请将当前节点与相邻节点所属的集合组合。完成列表后,最大集合对应最大间隔。
时间复杂度为O(N *α(N))其中α(N)为逆Ackermann函数。
编辑:实际上,Disjoint-set对于这个简单的任务来说太强大了。 Grigor Gevorgyan的解决方案不使用它。所以它更简单,更有效。
答案 2 :(得分:5)
你可以用空间来换取线性时间。
列表中的第一步是线性的。最后一个是A的大小是线性的,如果你只有一些相距很远的值,它可能相对于你的列表很大。但是,既然你正在处理整数,那么A就是有限的。
答案 3 :(得分:3)
1个想法:好吧,我认为无论如何你必须对列表进行排序,但你不能使用合并或快速排序。但是如果你有记忆,你可以使用counting sort中的想法来表示整数。
所以你可以创建0和1的数组,从0到最大int值,如果你有值,则用1填充它,然后找到最大的连续数组
2想法:创建值词典,找到最小值和最大值 - 所有O(N)操作:
dict = {1: 1, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 10: 10}
min = 1
max = 10
然后,转到i in range(min, max)
并找到最长的连续子集
>>> d = [1, 3, 5, 7, 4, 6, 10]
>>> s = set(d)
>>> mind = min(d)
>>> maxd = max(d)
>>> a, b, j = 0, 0, 0
>>> for i in range(mind, maxd):
if i not in s:
if (b - a) < (i - j - 1):
a, b = j, i - 1
j = i + 1
>>> a, b
(3, 7)
但对于像[1, 9000, 100000]
编辑:基于Grigor Gevorgyan的超级答案,这里是Python中O(N)字典解决方案的代码(我只是喜欢它简单!!!)
l = [1, 3, 5, 7, 4, 6, 10]
d = {x:None for x in l}
print d
for (k, v) in d.iteritems():
if v is not None: continue
a, b = d.get(k - 1), d.get(k + 1)
if a is not None and b is not None: d[k], d[a], d[b] = k, b, a
elif a is not None: d[a], d[k] = k, a
elif b is not None: d[b], d[k] = k, b
else: d[k] = k
print d
m = max(d, key=lambda x: d[x] - x)
print m, d[m]
输出:
{1: None, 3: None, 4: None, 5: None, 6: None, 7: None, 10: None}
{1: 1, 3: None, 4: None, 5: None, 6: None, 7: None, 10: None}
{1: 1, 3: 3, 4: None, 5: None, 6: None, 7: None, 10: None}
{1: 1, 3: 4, 4: 3, 5: None, 6: None, 7: None, 10: None}
{1: 1, 3: 5, 4: 3, 5: 3, 6: None, 7: None, 10: None}
{1: 1, 3: 6, 4: 3, 5: 3, 6: 3, 7: None, 10: None}
{1: 1, 3: 7, 4: 3, 5: 3, 6: 3, 7: 3, 10: None}
{1: 1, 3: 7, 4: 3, 5: 3, 6: 3, 7: 3, 10: 10}
3 7
答案 4 :(得分:2)
我使用HashSet
制作了一个非常简单的解决方案。由于contains
和remove
是O(1)操作,您可以简单地从随机设置项创建一个新间隔,然后“展开”它的间隔,直到您发现其完整大小,从集合中删除项目当你走的时候。删除是关键,因为这是阻止您“重复”任何间隔的原因。
这可能有助于以这种方式思考 - 列表具有K个区间,其大小加起来为N.然后,您的任务是发现这些区间是什么,而不重复任何间隔或项目。这就是HashSet非常适合这项工作的原因 - 您可以在扩展间隔时有效地从集合中删除项目。然后,您需要做的就是跟踪最大间隔。
HashSet
i = interval.start-1
i
,但请从集合中删除i
并减少i
和interval.start
interval.end
向上扩展)以下是Java中的解决方案:
public class BiggestInterval {
static class Interval {
int start;
int end;
public Interval(int base) {
this(base,base);
}
public Interval(int start, int end) {
this.start = start;
this.end = end;
}
public int size() {
return 1 + end - start;
}
@Override
public String toString() {
return "[" + start + "," + end + "]";
}
}
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(biggestInterval(Arrays.asList(1,3,5,7,4,6,10)));
}
public static Interval biggestInterval(List<Integer> list) {
HashSet<Integer> set = new HashSet<Integer>(list);
Interval largest = null;
while(set.size() > 0) {
Integer item = set.iterator().next();
set.remove(item);
Interval interval = new Interval(item);
while(set.remove(interval.start-1)) {
interval.start--;
}
while(set.remove(interval.end+1)) {
interval.end++;
}
if (largest == null || interval.size() > largest.size()) {
largest = interval;
}
}
return largest;
}
}
答案 5 :(得分:1)
考虑到使用平均O(1)哈希表构建的字典,这将是线性的。
L = [1,3,5,7,4,6,10]
a_to_b = {}
b_to_a = {}
for i in L:
if i+1 in a_to_b and i-1 in b_to_a:
new_a = b_to_a[i-1]
new_b = a_to_b[i+1]
a_to_b[new_a] = new_b
b_to_a[new_b] = new_a
continue
if i+1 in a_to_b:
a_to_b[i] = a_to_b[i+1]
b_to_a[a_to_b[i]] = i
if i-1 in b_to_a:
b_to_a[i] = b_to_a[i-1]
a_to_b[b_to_a[i]] = i
if not (i+1 in a_to_b or i-1 in b_to_a):
a_to_b[i] = i
b_to_a[i] = i
max_a_b = max_a = max_b = 0
for a,b in a_to_b.iteritems():
if b-a > max_a_b:
max_a = a
max_b = b
max_a_b = b-a
print max_a, max_b
答案 6 :(得分:1)
这是一个类似于Grigor的解决方案。两个主要区别是此解决方案存储顺序集的长度而不是其他索引,这消除了对最后一个散列集迭代的需要。
迭代数组
通过查找和更新相邻的设置端点来构建哈希映射:
键 - 数组值
值 - 当密钥是顺序集的端点时,存储该集的长度。否则,保持它真实,所以你只考虑一次。
如果当前设置的大小最长,请更新最长设置大小和最长设置开始。
为清晰起见,这是一个JavaScript实现,以及fiddle以查看它的实际效果:
var array = [1,3,5,7,4,6,10];
//Make a hash of the numbers - O(n) assuming O(1) insertion
var longestSetStart;
var longestSetSize = 0;
var objArray = {};
for(var i = 0; i < array.length; i++){
var num = array[i];
if(!objArray[num]){//Only consider numbers once
objArray[num] = 1;//Initialize to 1 item in the set by default
//Get the updated start and end of the current set
var currentSetStart = num;//Starting index of the current set
var currentSetEnd = num;//Ending index of the current set
//Get the updated start of the set
var leftSetSize = objArray[num - 1];
if(leftSetSize){
currentSetStart = num - leftSetSize;
}
//Get the updated end of the set
var rightSetSize = objArray[num + 1];
if(rightSetSize){
currentSetEnd = num + rightSetSize;
}
//Update the endpoints
var currentSetSize = currentSetEnd - currentSetStart + 1;
objArray[currentSetStart] = currentSetSize;
objArray[currentSetEnd] = currentSetSize;
//Update if longest set
if(currentSetSize > longestSetSize){
longestSetSize = currentSetSize;
longestSetStart = currentSetStart;
}
}
}
var longestSetEnd = longestSetStart + longestSetSize - 1;
答案 7 :(得分:0)
诀窍是将项目视为一组而不是列表。这允许您识别位于连续范围的开头或结尾的项目,因为一个集合允许您检查是否存在item-1或item + 1。这样,您就可以在线性时间和空间中解决问题。
<强>伪码:强>
C#代码:
static Tuple<int, int> FindLargestContiguousRange(this IEnumerable<int> items) {
var itemSet = new HashSet<int>(items);
// find contiguous ranges by identifying their starts and scanning for ends
var ranges = from item in itemSet
// is the item at the start of a contiguous range?
where !itemSet.Contains(item-1)
// find the end by scanning upward as long as we stay in the set
let end = Enumerable.Range(item, itemSet.Count)
.TakeWhile(itemSet.Contains)
.Last()
// represent the contiguous range as a tuple
select Tuple.Create(item, end);
// return the widest contiguous range that was found
return ranges.MaxBy(e => e.Item2 - e.Item1);
}
注意:MaxBy来自MoreLinq
<强>测试强>
小理智检查:
new[] {3,6,4,1,8,5}.FindLargestContiguousRange().Dump();
// prints (3, 6)
大连续列表:
var zeroToTenMillion = Enumerable.Range(0, (int)Math.Pow(10, 7)+1);
zeroToTenMillion.FindLargestContiguousRange().Dump();
// prints (0, 10000000) after ~1 seconds
大碎片列表:
var tenMillionEvens = Enumerable.Range(0, (int)Math.Pow(10, 7)).Select(e => e*2);
var evensWithAFewOdds = tenMillionEvens.Concat(new[] {501, 503, 505});
evensWithAFewOdds.FindLargestContiguousRange().Dump();
// prints (500, 506) after ~3 seconds
<强>复杂性强>
该算法需要O(N)时间和O(N)空间,其中N是列表中项目的数量,假设设置操作是恒定时间。
请注意,如果将集合作为输入,而不是由算法构建,我们只需要O(1)空间。
(有些评论说这是二次时间。我认为他们假设所有项目,而不仅仅是范围开始时的项目,触发扫描。如果算法以这种方式工作,那确实是二次的。)
答案 8 :(得分:-1)
我想我会将它们分类为连续整数列表(假设每个数字只能出现一次)
取第一个号码
如果数字1比现有列表中的数字低1或高1?
是:预先/后发现有列表
否:创建一个以当前号码
开头的新列表如果有更多数字,请返回顶部
显示最长的列表
答案 9 :(得分:-1)
免责声明:由于该解决方案基于哈希表,因此可以预期运行时间,而非最坏情况。
这个O(n)解决方案取决于唯一的整数。如果它们不是唯一的,请使用O(1)插入和成员资格查找创建一个哈希集,并在浏览列表时跳过已遇到的数字。
创建一个O(1)查找/插入散列图,其中值是范围的开头,键是适合这些范围末尾的数字。对于值v和密钥k,这意味着从v开始并以k-1结尾的范围位于密钥k处。
浏览数字列表。对于每个数字n,检查地图在关键字n处是否具有值v。这对应于存在从v开始的范围,其将允许结尾处的n。如果有,请将v移至键n + 1并删除键n处的条目。如果没有任何范围,请在键n + 1处插入n。
由于数字是唯一的,因此最后没有一个范围重叠,但可能存在一些连续的范围。运行地图的键/值对。对于每个密钥k和值v,如果映射在密钥k1 = v处具有值v1,则意味着存在从v1到k-1的范围。在k处插入v1,并删除条目k1 / v1。
浏览地图的k / v条目,找到大小为k-v的最大范围[v,k-1],使用最大值。
对于你的例子:
setup:
l = [1,3,5,7,4,6,10]
m = {}
iteration:
process 1 : m = {2->1}
process 3 : m = {2->1, 4->3}
process 5 : m = {2->1, 4->3, 6->5}
process 7 : m = {2->1, 4->3, 6->5, 8->7}
process 4 : m = {2->1, 5->3, 6->5, 8->7}
process 6 : m = {2->1, 5->3, 7->5, 8->7}
process 10 : m = {2->1, 5->3, 7->5, 8->7, 11->10}
concatenation of contiguous ranges:
initial: m = {2->1, 5->3, 7->5, 8->7, 11->10}
first concatenation: m = {2->1, 7->3, 8->7, 11->10}, k=7, v=5, k1=5, v1=3
second concatenation: m = {2->1, 8->3, 11->10}, k=8, v=7, k1=7, v1=3
result:
largest range : [3,7] of size 5