在 python 中,如何有效地找到列表中不一定相邻的最大连续数字集?
Posted
技术标签:
【中文标题】在 python 中,如何有效地找到列表中不一定相邻的最大连续数字集?【英文标题】:In python, how does one efficiently find the largest consecutive set of numbers in a list that are not necessarily adjacent? 【发布时间】:2012-01-29 16:26:59 【问题描述】:例如,如果我有一个列表
[1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]
此算法应返回 [1,2,3,4,5,6,7,8,9,10,11]。
为了澄清,最长的列表应该向前运行。我想知道什么是算法上有效的方法(最好不是 O(n^2))?
另外,我对不在 python 中的解决方案持开放态度,因为算法才是最重要的。
谢谢。
【问题讨论】:
为什么不[1,2,3,4,5,6,7,8,9,10,11]
。我认为没有理由不包括这些数字,因为它们不必相邻。
对不起,我的错误。谢谢指正。
最长的连续序列可以从1以外的数字开始吗?
算法应该向前和向后工作吗?
向前,无需向后。
【参考方案1】:
这是一个简单的一次性 O(n) 解决方案:
s = [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11,42]
maxrun = -1
rl =
for x in s:
run = rl[x] = rl.get(x-1, 0) + 1
print x-run+1, 'to', x
if run > maxrun:
maxend, maxrun = x, run
print range(maxend-maxrun+1, maxend+1)
如果您考虑范围而不是端点和运行长度的单个变量,则逻辑可能会更加不言而喻:
rl =
best_range = xrange(0)
for x in s:
run = rl[x] = rl.get(x-1, 0) + 1
r = xrange(x-run+1, x+1)
if len(r) > len(best_range):
best_range = r
print list(best_range)
【讨论】:
@RaymondHettinger - 最后一行应该是:print range(maxend-maxrun+1, maxend+1)
?否则对于s = [1,4,2,3,5,4,9,10,11,5,6,7,8,1,3,4,5]
,我只会得到[4, 5, 6, 7, 8]
,而不是[1, 2, 3, 4, 5, 6, 7, 8]
。
@nightcracker - 你运行它并得到一个 IndexError,或者你只是在你的脑海中运行它?链式赋值从右到左工作,并且 rl.get 有一个默认值 0 传入,所以没有 IndexError 那里。并且由于 rl[1] 得到 0+1=1 的值,因此可以将其复制到 run
,同样不会出现 IndexError。尝试运行它,它确实可以正常工作。
@Paul McGuire,同意,我认为应该是 maxrun 而不是 run。
此解决方案仅适用于数据已排序。也就是说,它不会考虑 1、2、3、5、6、7、8、4。最后输入 4 不会更新 5、6、7、8 处的值。所以除非你考虑到排序复杂度,不是 O(n)。
@BrettStottlemyer 您只是对 OP 提出的问题有不同的理解。我的解释(以及其他一些受访者的解释)是正在寻找一个排序的、连续的、递增的子序列。该读数得到了 OP 要求“最长的列表应该向前运行”的支持。根据该要求,添加 4 不应更新 5、6、7 和 8 处的值。因此,您的反对票是没有根据的和不正确的。【参考方案2】:
不是那么聪明,不是 O(n),可以使用一些优化。但它有效。
def longest(seq):
result = []
for v in seq:
for l in result:
if v == l[-1] + 1:
l.append(v)
else:
result.append([v])
return max(result, key=len)
【讨论】:
其实没有*O*(n) 实现这个:-) 这是 O(n^2),我的也是。需要考虑不同的方法。 @Abhijit:有,看看 Raymond Hettingers 的。【参考方案3】:你可以使用Largest Ascending Sub-sequence Algorithm的Patience Sort实现
def LargAscSub(seq):
deck = []
for x in seq:
newDeck = [x]
i = bisect.bisect_left(deck, newDeck)
deck[i].insert(0, x) if i != len(deck) else deck.append(newDeck)
return [p[0] for p in deck]
这是测试结果
>>> LargAscSub([1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11])
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> LargAscSub([1, 2, 3, 11, 12, 13, 14])
[1, 2, 3, 11, 12, 13, 14]
>>> LargAscSub([11,12,13,14])
[11, 12, 13, 14]
复杂度的顺序是O(nlogn)
在 wiki 链接中有一个注释,他们声称您可以通过依赖 Van Emde Boas tree 来实现 O(n.loglogn)
【讨论】:
结果不是必须是连续个整数吗? @srgerg,看看上面 Serdalis 和 Chi Zeng 回复的评论问题 不是最大的上升,是最大的连续上升。【参考方案4】:使用修改后的Radix Sort 怎么样?正如 JanneKarila 指出的那样,解决方案不是 O(n)。它使用基数排序,***说Radix sort's efficiency is O(k·n) for n keys which have k or fewer digits.
这只有在您知道我们正在处理的数字范围时才有效,这将是第一步。
查看起始列表中的每个元素以找到最低的 l
和最高的 h
数字。在这种情况下,l
为 1,h
为 11。请注意,如果您由于某种原因已经知道范围,则可以跳过此步骤。
创建一个我们范围大小的结果列表,并将每个元素设置为空。
查看列表中的每个元素,如果需要,将它们添加到结果列表的适当位置。即,元素是 4,将 4 添加到结果列表的位置 4。result[element] = starting_list[element]
。您可以根据需要丢弃重复项,它们只会被覆盖。
遍历结果列表以找到最长的没有任何空值的序列。保留element_counter
以了解我们正在查看的结果列表中的哪个元素。将curr_start_element
设置为当前序列的开始元素,并保持curr_len
当前序列的长度。还要保留一个longest_start_element
和一个“longest_len”,它们将从零开始,并随着我们在列表中移动而更新。
返回从longest_start_element
开始并取longest_len
的结果列表
编辑:添加代码。经过测试和工作
#note this doesn't work with negative numbers
#it's certainly possible to write this to work with negatives
# but the code is a bit hairier
import sys
def findLongestSequence(lst):
#step 1
high = -sys.maxint - 1
for num in lst:
if num > high:
high = num
#step 2
result = [None]*(high+1)
#step 3
for num in lst:
result[num] = num
#step 4
curr_start_element = 0
curr_len = 0
longest_start_element = -1
longest_len = -1
for element_counter in range(len(result)):
if result[element_counter] == None:
if curr_len > longest_len:
longest_start_element = curr_start_element
longest_len = curr_len
curr_len = 0
curr_start_element = -1
elif curr_start_element == -1:
curr_start_element = element_counter
curr_len += 1
#just in case the last element makes the longest
if curr_len > longest_len:
longest_start_element = curr_start_element
longest_len = curr_len
#step 5
return result[longest_start_element:longest_start_element + longest_len-1]
【讨论】:
第 4 步迭代结果列表 n 次,所以这不是 O(n)。 @jknupp 不,你只需要经历一次。这与从列表中查找最大值相同。除了它在列表中找到最长的序列。假设 list =[1,2,3,null,5,6,7,8,null,10]
我看到 [1,2,3]
的长度为 3,所以我保存了起始索引。然后看到[5,6,7,8]
的长度为 4,因此更新最长的索引/长度变量。 [8]
不会改变它。一个循环,找到最长的。
O(n)中的n指的是输入列表的大小。值的范围可以大得多,并且与列表的长度无关。
@JanneKarila 我的错,你说得对。来自***Radix sort's efficiency is O(k·n) for n keys which have k or fewer digits.
【参考方案5】:
如果结果确实必须是连续升序整数的子序列,而不仅仅是升序整数,则无需记住每个完整的连续子序列,直到确定哪个是最长的,你只需要记住每个子序列的开始和结束值。所以你可以这样做:
def longestConsecutiveSequence(sequence):
# map starting values to largest ending value so far
map = collections.OrderedDict()
for i in sequence:
found = False
for k, v in map.iteritems():
if i == v:
map[k] += 1
found = True
if not found and i not in map:
map[i] = i + 1
return xrange(*max(map.iteritems(), key=lambda i: i[1] - i[0]))
如果我在原始示例日期(即[1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]
)运行它,我会得到:
>>> print list(longestConsecutiveSequence([1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
如果我在 Abhijit 的一个样本 [1,2,3,11,12,13,14]
上运行它,我会得到:
>>> print list(longestConsecutiveSequence([1,2,3,11,12,13,14]))
[11, 12, 13, 14]
很遗憾,这个算法在最坏的情况下是 O(n*n)。
【讨论】:
【参考方案6】:警告:这是骗人的方法(又名我使用 python...)
import operator as op
import itertools as it
def longestSequence(data):
longest = []
for k, g in it.groupby(enumerate(set(data)), lambda(i, y):i-y):
thisGroup = map(op.itemgetter(1), g)
if len(thisGroup) > len(longest):
longest = thisGroup
return longest
longestSequence([1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11, 15,15,16,17,25])
【讨论】:
【参考方案7】:您需要最大连续和(Optimal Substructure):
def msum2(a):
bounds, s, t, j = (0,0), -float('infinity'), 0, 0
for i in range(len(a)):
t = t + a[i]
if t > s: bounds, s = (j, i+1), t
if t < 0: t, j = 0, i+1
return (s, bounds)
这是一个动态规划的例子,是 O(N)
【讨论】:
【参考方案8】:即使序列不是从第一个元素开始,O(n) 解决方案也有效。
如果 len(A) = 0,则警告不起作用。
A = [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]
def pre_process(A):
Last =
Arrow = []
Length = []
ArgMax = 0
Max = 0
for i in xrange(len(A)):
Arrow.append(i)
Length.append(0)
if A[i] - 1 in Last:
Aux = Last[A[i] - 1]
Arrow[i] = Aux
Length[i] = Length[Aux] + 1
Last[A[i]] = i
if Length[i] > Max:
ArgMax = i
Max = Length[i]
return (Arrow,ArgMax)
(Arr,Start) = pre_process(A)
Old = Arr[Start]
ToRev = []
while 1:
ToRev.append(A[Start])
if Old == Start:
break
Start = Old
New = Arr[Start]
Old = New
ToRev.reverse()
print ToRev
欢迎使用 Python 化!!
【讨论】:
【参考方案9】:好的,这是python的另一个尝试:
def popper(l):
listHolders = []
pos = 0
while l:
appended = False
item = l.pop()
for holder in listHolders:
if item == holder[-1][0]-1:
appended = True
holder.append((item, pos))
if not appended:
pos += 1
listHolders.append([(item, pos)])
longest = []
for holder in listHolders:
try:
if (holder[0][0] < longest[-1][0]) and (holder[0][1] > longest[-1][1]):
longest.extend(holder)
except:
pass
if len(holder) > len(longest):
longest = holder
longest.reverse()
return [x[0] for x in longest]
输入和输出示例:
>>> demo = list(range(50))
>>> shuffle(demo)
>>> demo
[40, 19, 24, 5, 48, 36, 23, 43, 14, 35, 18, 21, 11, 7, 34, 16, 38, 25, 46, 27, 26, 29, 41, 8, 31, 1, 33, 2, 13, 6, 44, 22, 17,
12, 39, 9, 49, 3, 42, 37, 30, 10, 47, 20, 4, 0, 28, 32, 45, 15]
>>> popper(demo)
[1, 2, 3, 4]
>>> demo = [1,4,2,3,5,4,5,6,7,8,1,3,4,5,9,10,11]
>>> popper(demo)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>>
【讨论】:
【参考方案10】:这应该可以解决问题(并且是 O(n)):
target = 1
result = []
for x in list:
for y in result:
if y[0] == target:
y[0] += 1
result.append(x)
对于任何起始数字,这都有效:
result = []
for x in mylist:
matched = False
for y in result:
if y[0] == x:
matched = True
y[0] += 1
y.append(x)
if not matched:
result.append([x+1, x])
return max(result, key=len)[1:]
【讨论】:
这会找到第一个,而不是最大的,从1开始。[2, 3, 4, 5, 1, 2]
哇,太聪明了,谢谢。 [1, 2, 3, 11, 12, 13, 14]
怎么样?这个算法会返回[1, 2, 3]
吗?
为什么你或支持者不检查代码?你怎么能第一次订阅y
? (TypeError: 'int' object is unsubscriptable
)
第一个代码示例返回一个空列表,第二个在if y[0] == x
行上引发TypeError: 'int' object is not subscriptable
。
False
也应该大写,但我在运行它之前修复了它。以上是关于在 python 中,如何有效地找到列表中不一定相邻的最大连续数字集?的主要内容,如果未能解决你的问题,请参考以下文章