【问题标题】:Returning indicies for the values producing the max difference返回产生最大差异的值的索引
【发布时间】:2020-02-12 08:54:00
【问题描述】:

我有一个递归函数,它可以找到任意两个整数之间的最大差,前提是第二个索引处的值高于第一个索引处的值:

def func(arr):
    if len(arr) <= 1:
        return 0;

    left  = arr[ : (len(arr) // 2)]
    right = arr[(len(arr) // 2) : ]

    leftBest  = func(left)
    rightBest = func(right)

    crossBest = max(right) - min(left)

    return max(leftBest, rightBest, crossBest)

如果给定列表[12, 9, 18, 3],那么我将计算任意两个元素ij 之间的最大差异,这样j &gt; ij 的元素减去i 的元素是最大的差异。

在这种情况下,我们有j = 2, i = 1,代表18 - 9,最大的区别是9

我当前的算法返回9,但我希望它返回索引(1, 2)。我该如何修改这种分而治之的方法来做到这一点?

【问题讨论】:

  • 如果您在浏览器中搜索“最佳股票买入卖出”,您会找到比我们在此处管理的更能解释这一点的参考资料。确保检查无限制购买和单次销售。这是一个常见的家庭作业问题。
  • 返回索引就是这样:返回每个索引,而不是值。如果您在定位值时没有保留索引,则使用index 检索它。

标签: python algorithm list divide-and-conquer


【解决方案1】:

我不知道你是否被一个递归的divide'n'conquer 实现所束缚。如果不是,这里有一个迭代的线性解决方案:

def func(arr):
    min_i = lower_i = 0
    max_dif = upper_i = None
    for i in range(1, len(arr)):
        if max_dif is None or max_dif < arr[i] - arr[min_i]:
            max_dif = arr[i] - arr[min_i]
            lower_i = min_i
            upper_i = i
        if arr[i] < arr[min_i]:
            min_i = i
    return lower_i, upper_i        

【讨论】:

  • 不错。不回答直接问题,但线性时间解决方案仍然很棒 :-) 我希望我今天下午有时间制定线性方法,但是唉。
【解决方案2】:

不返回最大差值,而是返回索引和最大差值。因为您将切片列表传递给递归调用,所以您必须调整从递归 rightBest 调用返回的索引(leftBest 调用始终以从“当前”0 运行到中点的列表开始)。

min()max() 与改变最大值选取方式的键一起使用。要在列表中选取最大值的索引,请使用range() 生成索引,然后使用arr.__getitem__ 将这些索引映射回列表:

def func(arr):    
    if len(arr) <= 1:
        return 0, 0, 0

    mid = len(arr) // 2
    left = arr[:mid]
    right = arr[mid:]

    leftBest  = func(left)
    ri, rj, rm = func(right)
    rightBest = (ri + mid, rj + mid, rm)

    key = arr.__getitem__
    ci = min(range(0, mid), key=key)
    cj = max(range(mid, len(arr)), key=key)
    crossBest = (ci, cj, arr[cj] - arr[ci])

    return max(leftBest, rightBest, crossBest, key=lambda ijm: ijm[2])

这将返回 2 个索引以及差异:

>>> func([12, 9, 18, 3])
(1, 2, 9)

您必须对结果进行切片才能获得 ij 值:

>>> func([12, 9, 18, 3])[:2]
(1, 2)

我什至不会分割arr 列表,因为这里真的不需要创建副本。只需将startstop 索引传递给递归函数即可替换0len(arr)。我还会使用嵌套函数来完成递归工作;外部函数可用于仅提取 (i, j) 对:

def best_difference_indices(arr):
    arr_get = arr.__getitem__
    max_get = lambda ijm: ijm[2]

    def _func(start, stop):
        if stop - start <= 1:
            return start, start, 0

        mid = (stop - start) // 2 + start
        left_best = _func(start, mid)
        right_best = _func(mid, stop)

        ci = min(range(start, mid), key=arr_get)
        cj = max(range(mid, stop), key=arr_get)
        cross_best = (ci, cj, arr[cj] - arr[ci])

        return max(left_best, right_best, cross_best, key=max_get)

    i, j, _ = _func(0, len(arr))
    return i, j

不必切片使这更快;输入 1000 个元素,第二个版本需要

对于 100 万个元素,分别变为 3.7 秒和 4.22 秒。

【讨论】:

    【解决方案3】:

    试试这个:

    In [1] arr = [12, 9, 18, 3]
    
    In [2]: diff = 0                                                                                                                                    
    
    In [3]: for i in range(len(arr)): 
        ...:     for j in range(i): 
        ...:         tmp = arr[i] - arr[j] 
        ...:         if tmp > diff: 
        ...:             diff = tmp 
        ...:             result = j, i 
        ...:              
        ...:          
        ...:                                                                                                                                             
    
    In [4]: result                                                                                                                                      
    Out[4]: (1, 2)
    

    【讨论】:

    • 这是一个效率较低的解决方案,OP 有一个 O(N log N) 的方法,你的是 O(N^2)。因此,对于 1000 个元素的输入,他们需要大约 7000 步,而你的则接近 100 万 步(是的,实数是 triangle number,但我们正在谈论渐近行为)。
    • OP 算法的更正版本需要 3.6 毫秒处理 1000 个项目,我的无切片版本需要不到 3 毫秒。您的版本需要 47 毫秒。 100万件,时间是4.2秒,3.7秒,我还在等你们版本的时间。会有点,因为要多25000倍以上,不知道要不要等一天以上!
    猜你喜欢
    • 1970-01-01
    • 2012-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-26
    • 2020-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多