以线性时间获得列表中的第二大数字

boi*_*ert 37 python performance

我正在学习Python,并且处理列表的简单方法是一种优势.有时它是,但看看这个:

>>> numbers = [20,67,3,2.6,7,74,2.8,90.8,52.8,4,3,2,5,7]
>>> numbers.remove(max(numbers))
>>> max(numbers)
74
Run Code Online (Sandbox Code Playgroud)

从列表中获取第二大数字的一种非常简单,快捷的方法.除了简单列表处理有助于编写两次遍历列表的程序,找到最大的然后是第二大的.它也具有破坏性 - 如果我想保留原始数据,我需要两份数据.我们需要:

>>> numbers = [20,67,3,2.6,7,74,2.8,90.8,52.8,4,3,2,5,7]
>>> if numbers[0]>numbers[1]):
...    m, m2 = numbers[0], numbers[1]
... else:
...    m, m2 = numbers[1], numbers[0]
...
>>> for x in numbers[2:]:
...    if x>m2:
...       if x>m:
...          m2, m = m, x
...       else:
...          m2 = x
...
>>> m2
74
Run Code Online (Sandbox Code Playgroud)

它只运行一次列表,但不像以前的解决方案那样简洁明了.

那么:在这样的情况下,有没有办法让两者都有?第一个版本的清晰度,但第二个版本的单个运行?

Jon*_*nts 50

您可以使用heapq模块:

>>> el = [20,67,3,2.6,7,74,2.8,90.8,52.8,4,3,2,5,7]
>>> import heapq
>>> heapq.nlargest(2, el)
[90.8, 74]
Run Code Online (Sandbox Code Playgroud)

从那里开始......

  • 它相当于:`sorted(iterable,reverse = True)[:n]`,仍然是`NlogN` (13认同)
  • @JonClements:但是对于大N来说,O(NlogN)仍然没有O(N)那么好,并且OP已经有一个O(N)解决方案,这是(我认为)Ashwini所指出的. (4认同)
  • @AshwiniChaudhary 功能上与此相同,是的;然而,由于实现的原因,它执行的比较较少,因此比排序和切片更有效 (3认同)
  • 从一个非常粗略的测试,在我的Mac上使用64位CPython 3.3.0,交叉在N = 1000000附近.在此之上,OP的原始代码明显更快; 在它之下,相反. (3认同)
  • @AbhishekChoudhary 你的评论是*非常*错误的,而且不仅仅是出于迂腐的技术原因。 (2认同)
  • @Zakaria我显然并没有暗示它是相同的,但是说 Nlog(N) 比 N 差得多是非常不正确的,即使 O(N) 可以是 N 的任意倍数,并且考虑到 logN 的增长速度极其缓慢, NlogN 解决方案将是首选。另外,这是对上面这个评论的回复:“但是对于大 N 来说,O(NlogN) 仍然远不如 O(N),并且 OP 已经有了 O(N) 解决方案,这就是(我认为)阿什维尼指出。 (2认同)

Thi*_*ien 25

由于@OscarLopez和我对第二大意味着什么有不同意见,我将根据我的愿景发布代码,并与提问者提供的第一个算法一致.

def second_largest(numbers):
    count = 0
    m1 = m2 = float('-inf')
    for x in numbers:
        count += 1
        if x > m2:
            if x >= m1:
                m1, m2 = x, m1            
            else:
                m2 = x
    return m2 if count >= 2 else None
Run Code Online (Sandbox Code Playgroud)

(注意:此处使用负无穷大而不是None因为None在Python 2和3中有不同的排序行为 - 请参阅Python - 查找第二个最小数字 ;检查元素数量以numbers确保在实际时不会返回负无穷大答案是未定义的.)

如果最大值出现多次,那么它也可能是第二大的.关于这种方法的另一个问题是,如果少于两个元素,它可以正常工作; 那么没有第二大.

运行相同的测试:

second_largest([20,67,3,2.6,7,74,2.8,90.8,52.8,4,3,2,5,7])
=> 74
second_largest([1,1,1,1,1,2])
=> 1
second_largest([2,2,2,2,2,1])
=> 2
second_largest([10,7,10])
=> 10
second_largest([1,1,1,1,1,1])
=> 1
second_largest([1])
=> None
second_largest([])
=> None
Run Code Online (Sandbox Code Playgroud)

更新

我重组了条件以大幅提高性能; 我的随机数测试几乎达到了100%.原因在于,在原始版本中,elif始终在下一个数字不是列表中最大的情况下进行评估.换句话说,对于列表中的几乎每个数字,进行了两次比较,而一次比较大部分就足够了 - 如果数字不大于第二大数字,则它也不大于最大值.

  • 您不应该依赖Python 2的实现细节。“无”的排序顺序是任意选择。使用`float('inf')`代替。 (2认同)

Vol*_*ity 19

你可以随时使用 sorted

>>> sorted(numbers)[-2]
74
Run Code Online (Sandbox Code Playgroud)

  • `NlogN` vs`O(N)`. (9认同)
  • 发表以下答案,但基本上除此之外:排序(设置(数字))[ - 2] (5认同)
  • @volatility是否适用于[2,3,6,6,5]? (4认同)
  • 我不明白为什么人们接受这个,不仅不是 O(N),甚至不一样,它只是排序并从末尾选择第二个,如果超过 1 个最大值,它会给出错误的结果答案是,排序后您需要另一个循环来选择实际的第二大值,这很糟糕,因为 O(N) 解决方案已经存在。 (4认同)

Ósc*_*pez 14

尝试下面的解决方案,它将O(n)存储并返回second变量中的第二个最大数字.请注意,如果所有元素numbers都相等,或者如果它numbers是空的或者它包含单个元素,则变量second最终会得到一个值None- 这是正确的,因为在那些情况下没有"第二大"元素.

注意:这会找到"第二个最大值",如果有多个值是"第一个最大值",它们将被视为相同的最大值 - 在我的定义中,在这样的列表中:[10, 7, 10]正确的答案是7.

def second_largest(numbers):
    first, second = None, None
    for n in numbers:
        if n > first:
            first, second = n, first
        elif first > n > second:
            second = n
    return second
Run Code Online (Sandbox Code Playgroud)

以下是一些测试:

second_largest([20,67,3,2.6,7,74,2.8,90.8,52.8,4,3,2,5,7])
=> 74
second_largest([1,1,1,1,1,2])
=> 1
second_largest([2,2,2,2,2,1])
=> 1
second_largest([10, 7, 10])
=> 7
second_largest([1,1,1,1,1,1])
=> None
second_largest([1])
=> None
second_largest([])
=> None
Run Code Online (Sandbox Code Playgroud)


Abh*_*rni 6

为什么要把场景复杂化?它非常简单直接

  1. 将列表转换为集合 - 删除重复项
  2. 再次将集合转换为列表 - 按升序给出列表

这是一个代码

mlist = [2, 3, 6, 6, 5]
mlist = list(set(mlist))
print mlist[-2]
Run Code Online (Sandbox Code Playgroud)

  • 问题是这些操作花费的时间(顺便说一句,重复不是我的第一个问题)。排序操作是 O(n*log(n)),但是找到最大的(没有排序)是线性的(O(n)),因为它需要一个循环。一个简单的解决方案需要两个循环:O(2n)。一个不那么幼稚的人应该走一个循环,在一次通过中寻找最大和第二大。我真正的问题 - 这里没有真正回答 - 是这样的:单遍解决方案读写复杂,换句话说,python 不太擅长使复杂的处理变得简单,就像我们喜欢说的那样。 (2认同)
  • *“将集合再次转换为列表 - 按升序给出列表”* - 需要引用... `list({400, 2, 100})` 给出 `[400, 2, 100]` 所以我不确定这是如何回答这个问题的...(也许这适用于特定的小整数,但并非总是如此) (2认同)

Sah*_*bra 6

您可以通过以下任一方式找到第二大的:

选项1:

numbers = set(numbers)
numbers.remove(max(numbers))
max(numbers)
Run Code Online (Sandbox Code Playgroud)

选项2:

sorted(set(numbers))[-2]
Run Code Online (Sandbox Code Playgroud)