在 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 中,如何有效地找到列表中不一定相邻的最大连续数字集?的主要内容,如果未能解决你的问题,请参考以下文章

如何有效地找到两个列表中匹配元素的索引

如何有效地找到从每个值到下一个较低/较高值的距离?

如何有效地将具有一定周期性的列表拆分为多个列表?

Python numpy:为3个其他列的每个唯一元组有效地获取包含min值的行

有效地找到表中不存在的第一个数字?

如何有效地复制列表中的特定值