【问题标题】:Better python logic that prevent time out when comparing arrays in nested loops更好的 Python 逻辑,可防止在嵌套循环中比较数组时超时
【发布时间】:2015-07-17 01:38:27
【问题描述】:

我试图解决一个编程挑战,而我编写的程序正确地解决了这个问题的小测试数据。但是当他们针对更大的数据集运行它时,我的程序在某些情况下会超时。我主要是一个自学成才的程序员,如果有比我的逻辑更好的算法/实现,你们能告诉我。谢谢。

问题

给定一个整数数组a,返回任意整数的最大差值 一对数字,使得该对中较大的整数出现在 比较小的整数更高的索引(在数组中)。 Return -1 如果你 找不到满足此条件的对。

我的 Python 函数

def maxDifference( a):
    diff=0
    find=0
    leng = len(a)
    for x in range(0,leng-1):
        for y in range(x+1,leng):
            if(a[y]-a[x]>=diff):
                  diff=a[y]-a[x]
                  find=1
    if find==1:
        return diff
    else:
        return -1

约束:

1 <= N <= 1,000,000
-1,000,000 <= a[i] <= 1,000,000 i belongs to [1,N]

示例输入:

Array { 2,3,10,2,4,8,1}

样本输出:

8

【问题讨论】:

  • 有嵌套的 for 循环,所以它是 O(n^2),任何在线法官都不会轻易接受。
  • 这里有一个提示:你可以在线性时间内找到每个索引之前的最大值。您可以在线性时间内找到每个索引之后的最小值。一旦你有了这两个列表,你怎么能避免内部循环,使你的整个算法线性时间而不是二次?
  • 同时,您声称此代码提供了正确的输出,除了对一些大型数据集超时......但它实际上引发了SyntaxError,如果您修复它,它会为您的样本返回 -1输入,而不是 8。
  • 嵌套循环,其中两个循环都遍历所有 N 值,本质上意味着二次时间。当二次时间是不可避免的(或者对于问题域来说足够好),嵌套循环通常是最易读的写东西的方式(尽管并非总是如此;有时for x, y in product(…) 或使用 NumPy 数组或类似的更好......)。但是当存在线性或对数线性解决方案时,您可能必须采用不同的结构。
  • 同时,您的固定版本仍然有相同的 SyntaxError;我没有检查你是否修复了其他任何东西。

标签: python arrays python-2.7


【解决方案1】:

好吧...既然您只关心在最小数字之后找到最大数字,只要差异是迄今为止最大的,就没有理由在切片上进行多次传递或使用max()数组:

def f1(a):
    smallest = a[0]
    result = 0
    for b in a:
        if b < smallest: 
            smallest = b
        if b - smallest > result:
            result = b - smallest

    return result if result > 0 else -1

感谢@Matthew 提供测试代码 :) 即使在大型集上,这也非常快:

The maximum difference is 99613 99613 99613
Time taken by Sojan's method: 0.0480000972748
Time taken by @Matthews's method: 0.0130000114441
Time taken by @GCord's method: 0.000999927520752

【讨论】:

  • [5, 4, 3, 2, 1] 等情况需要返回 -1 而不是 0
【解决方案2】:

你的程序耗时太长的原因是你的嵌套循环本质上意味着二次时间。

外循环通过N-1 索引。内部循环每次都经过不同数量的索引,但平均值显然是 (N-1)/2 向上取整。所以,通过内循环的总次数是(N-1) * (N-1)/2,也就是O(N^2)。对于最大 N=1000000,这意味着 499999000001 次迭代。这将需要很长时间。

诀窍是在线性时间内找到一种方法。

这里有一个解决方案(作为一个模糊的描述,而不是实际的代码,所以当他们面临与你相同的测试时,不能只是复制和粘贴它):

  • 列出每个索引之前的最小值。每一个都只是min(smallest_values[-1], arr[i]),显然你可以通过N 的步骤来做到这一点。
  • 列出每个索引后的最大值。最简单的方法是反转列表,执行与上面完全相同的循环(但使用max 而不是min),然后再次反转。 (当然,反转列表需要 N 步骤。)
  • 现在,对于列表中的每个元素,您只需与smallest_values[i]largest_values[i] 进行比较,而不是与其他所有元素进行比较。由于您只对每个 N 值进行 2 次比较,因此这需要 2N 时间。

所以,即使是懒惰和幼稚,总共有N + 3N + 2N 步,也就是O(N)。如果N=1000000,则意味着 6000000 步,比 499999000001 快很多。

您可以清楚地看到如何删除两个反向,以及如何跳过第一个和最后一个比较。如果您很聪明,您可以了解如何将整个 largest_values 完全排除在外。最终,我认为您可以将其降低到2N - 3 步或 1999997。但这只是一个小的持续改进;远没有解决基本算法问题重要。您可能会获得比 3 倍(可能是 20 倍)更大的改进,只需在 PyPy 而不是 CPython 中运行幼稚代码,或转换为 NumPy 即可减少工作量——但您不会获得 83333x 的任何改进除了改变算法之外的方式。

【讨论】:

  • 感谢您花时间如此清楚地回答所有问题。我正在考虑使用其他一些数据结构或程序改进是解决方案。但明白在处理这样的问题时,大流程是我的算法。会注意所有提到的点。
【解决方案3】:

这是一个线性时间解决方案。它跟踪列表的每个索引之前的最小值。这些最小值存储在列表min_lst 中。最后,将原始列表和最小列表的对应元素之间的差异计算为另一个差异列表,通过压缩两者。此差异列表中的最大值应该是要求的答案。

def get_max_diff(lst):
    min_lst = []
    running_min = lst[0]
    for item in lst:
        if item < running_min:
            running_min = item
        min_lst.append(running_min)
    val = max(x-y for (x, y) in zip(lst, min_lst))
    if not val:
        return -1
    return val

>>> get_max_diff([5, 6, 2, 12, 8, 15])
13
>>> get_max_diff([2, 3, 10, 2, 4, 8, 1])
8
>>> get_max_diff([5, 4, 3, 2, 1])
-1

【讨论】:

  • 我不认为if not val 是你想要的。也许if val &lt;= 0? (例如,考虑[8, 6, 4, 2]。)
  • val &lt;= 0 可以使用,但不应出现 val 为负数的情况。因此,它可以达到的最小值为 0,就像您提到的 [8, 6, 4, 2] 案例中一样。我错过了什么吗?
  • 啊,我明白了,您将每个项目本身都包含在自己的运行分钟中……是的,我认为这可以解决它。
【解决方案4】:

好吧,我想既然遇到相同问题的人可以复制你的代码并运行它,我不会因为他们复制一些更优化的代码而失眠:

import time
import random

def max_difference1(a):
    # your function

def max_difference2(a):
    diff = 0

    for i in range(0, len(a)-1):
        curr_diff = max(a[i+1:]) - a[i]
        diff = max(curr_diff, diff)

    return diff if diff != 0 else -1

my_randoms = random.sample(range(100000), 1000)

t01 = time.time()
max_dif1 = max_difference1(my_randoms)
dt1 = time.time() - t01

t02 = time.time()
max_dif2 = max_difference2(my_randoms)
dt2 = time.time() - t02

print("The maximum difference is", max_dif1)
print("Time taken by your method:", dt1)
print("Time taken by my method:", dt2)
print("My method is", dt1/dt2, "times faster.")

The maximum difference is 99895
Time taken by your method: 0.5533690452575684
Time taken by my method: 0.08005285263061523
My method is 6.912546237558299 times faster.

类似于@abarnert 所说的(他总是在我发誓的这些事情上狙击我),你不想循环遍历列表两次。您可以利用这样一个事实,即您知道较大的值必须在较小的值之前。您还可以利用除了最大数字之外您不关心任何内容的事实,即在列表[1,3,8,5,9] 中,最大差异为 8 (9-1) 而您不关心 3、8、和 5 在那里。因此:max(a[i+1:]) - a[i] 是给定索引的最大差异。

然后将其与diff 进行比较,并将两者中较大的一个与max 进行比较,因为调用默认的内置python 函数比if curr_diff &gt; diff: diff = curr_diff(或等效)要快一些。

return 行只是您的(固定)行在 1 行而不是 4 行


如您所见,在 1000 个样本中,此方法的速度快了约 6 倍(注意:使用了 python 3.4,但在 python 2.x 上没有任何问题)

【讨论】:

    【解决方案5】:

    我认为预期的答案

    1、2、4、2、3、8、5、6、10

    将是 8 - 2 = 6,但 Saksham Varma code 将返回 10 - 1 = 9。

    它的max(arr) - min(arr)

    出现下跌时我们是否必须重新设置最小值

    。 IE; 4 -&gt; 2 将重置 current_smallest = 2 并继续使用值 '2' 进行差异计算。

    def f2(a):
        current_smallest = a[0]
        large_diff = 0
        for i in range(1, len(a)):
    
            # Identify the dip
            if a[i] < a[i-1]:
                current_smallest = a[i]
    
            if a[i] - current_smallest > large_diff:
                large_diff = a[i] - current_smallest
    

    【讨论】:

      猜你喜欢
      • 2015-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多