找到最大差异和python 3.x的O(n)解决方案?

Posted

技术标签:

【中文标题】找到最大差异和python 3.x的O(n)解决方案?【英文标题】:O(n) solution for finding maximum sum of differences python 3.x? 【发布时间】:2018-02-08 05:19:15 【问题描述】:

我想知道,给定一个整数列表,比如l,如果我们可以从这个列表中选择 3 个整数,比如leftmiddleright,其中middle > left, right 和@ 987654326@ 以该顺序出现在列表中(即index(left)<index(middle)<index(right)),是否存在用于查找middle - left + middle - right 最大值的O(n) 解决方案?您可能会认为不满足这些条件的列表不会出现(例如 Eric Duminil 指出的 [5, 0, 5])

目前,我能够提出我认为(大致)O(n^2) 的解决方案(如果我错了,请纠正我)。

基本上,我目前的想法是:

maximum = 0
for idx in range(1, N - 1):
    left = min(l[0: idx])
    right = min(l[idx + 1:])
    middle = l[idx]

    if left < middle and right < middle:
        new_max = middle - left + middle - right
        maximum = max(new_max, maximum)

帮助/提示将不胜感激。

谢谢!

【问题讨论】:

不太确定,但对我来说它看起来像 O(n)。 你为什么不只取最大的middle和最小的leftright 尝试 3 个不同的 lists,每个都比上一个大一倍,然后计时。您将能够看到它是 O(n) 还是 O(n^2) @Mathieu 但是我认为 min 和 max 函数是 O(n) 时间? @khelwood 哦,哇哦,我忘了添加一些东西对不起我的错! :P 【参考方案1】:

你可以遍历你的数字一次,保持一个运行最小值,并在每一步存储它,这样最后你就知道每个索引左边的最小值是多少。 这是 O(n)。

同样,您可以从右到左遍历所有数字,然后计算出每个索引右侧的最小值。也就是 O(n)。

然后您可以遍历每个可能的 middle 值,并从您之前的计算中获取 leftright 值。也就是 O(n)。

O(n) + O(n) + O(n) = O(n)。

【讨论】:

【参考方案2】:

这是一种计算最小值的方法,每个索引的左侧和右侧,在 O(n) 中:

import random

N = 10
l = [random.randrange(N) for _ in range(N)]

print(l)
# => [9, 9, 3, 4, 6, 7, 0, 0, 7, 6]

min_lefts = []
min_left = float("inf")
min_rights = [None for _ in range(N)]
min_right = float("inf")

for i in range(N):
    e = l[i]
    if e < min_left:
        min_left = e
    min_lefts.append(min_left)

print(min_lefts)
# => [9, 9, 3, 3, 3, 3, 0, 0, 0, 0]

for i in range(N-1,-1,-1):
    e = l[i]
    if e < min_right:
        min_right = e
    min_rights[i] = min_right

print(min_rights)
# => [0, 0, 0, 0, 0, 0, 0, 0, 6, 6]

您现在可以遍历l 中的每个中间元素(idx1N-2 之间),并找到2 * l[idx] - min_rights[idx] - min_lefts[idx] 的最小值。这个操作也是O(n):

print(max(2 * l[i] - min_rights[i] - min_lefts[i] for i in range(1, N-2)))

它输出:

11

这是2 * 7 - 0 - 3

【讨论】:

嗨,我能澄清一下 float("inf") 的用途吗?谢谢! @RussellNg:意思是infinity。它用作第一个最小比较的起点。任何小于float("inf")的数字【参考方案3】:

诀窍在于列表的最小值始终是解决方案的一部分(leftright)。

    找到列表的最小值,即 O(n)。现在这个最小元素将是左或右。 求 (2x-y) 的最大值,其中 idx(x) > idx(y),并且 idx(x) 找到 max(2x-y),其中 idx(x) idx(min),即检查列表的右侧部分 现在最多执行第 2 步和第 3 步,即您的左/中(或右/中)。

【讨论】:

哦,我明白了...没有意识到列表的全局最小值必须在解决方案中(facepalm),感谢您指出! @EricDuminil 但是 (left, middle, right) 的中间元素总是大于其他两个(即 5 0 5 不是有效序列,让我在原始问题中将其编辑为让它更清楚) 嗨,我刚想到这个,但是如果有多个最小值怎么办?例如。 [10, 20, 10, 30, 10] @RussellNg 那么,有几种解决方案。这里是 10(1), 30, 10(3);和 10(2)、30、10(3)。您可以从任意最小值开始,仍然可以找到其中一种解决方案。 取最大 x-y 不一定会产生最优解。例如,输入为[1, 1000, 0, 1000000, 999999],最优解为0, 1000000, 999999,但取最大x-y会产生1, 1000, 0【参考方案4】:

这里有一些时间安排!随意编辑执行计时的代码并\添加新条目。

from timeit import timeit


setup10 = '''
import numpy.random as nprnd
lst = list(nprnd.randint(1000, size=10))
'''

setup100 = '''
import numpy.random as nprnd
lst = list(nprnd.randint(1000, size=100))
'''

setup1000 = '''
import numpy.random as nprnd
lst = list(nprnd.randint(1000, size=1000))
'''

fsetup = '''

import sys

def f2(lst):
    N = len(lst)
    maximum = 0
    for idx in range(1, N - 1):
        left = min(lst[0: idx])
        right = min(lst[idx + 1:])
        middle = lst[idx]

        if left < middle and right < middle:
            new_max = middle - left + middle - right
            maximum = max(new_max, maximum)
    return maximum


def eric(lst):
    N = len(lst)
    min_lefts = []
    min_left = float("inf")
    min_rights = [None for _ in range(N)]
    min_right = float("inf")

    for i in range(N):
        e = lst[i]
        if e < min_left:
            min_left = e
        min_lefts.append(min_left)

    for i in range(N-1,-1,-1):
        e = lst[i]
        if e < min_right:
            min_right = e
        min_rights[i] = min_right

    return max(2 * lst[i] - min_rights[i] - min_lefts[i] for i in range(1, N-2))


def bpl(lst):
    res = -sys.maxsize
    a = sys.maxsize
    b = -sys.maxsize
    c = sys.maxsize

    for i, v in enumerate(lst[1:-1]):
        a = min(lst[i], a)
        c = min(lst[i + 2], c)
        b = max(lst[i], b)
        res = max(2 * b - a - c, res)
    return res


def meow(l):
    N = len(l)
    right_min = (N - 2) * [sys.maxsize]
    right_min[0] = l[N - 1]
    for i in range(3, N):
       right_min[i - 2] = min(right_min[i - 2], l[N - i + 1])
    left = l[2]
    maximum = 2*l[1] - left - right_min[N - 3]

    for idx in range(2, N - 1):
        left = min(left, l[idx-1])
        right = right_min[N - idx - 2]
        middle = l[idx]

        if left < middle and right < middle:
            new_max = middle - left + middle - right
            maximum = max(new_max, maximum)
    return maximum

'''


print('OP with 10\t:'.format(timeit(stmt="f2(lst)", setup=setup10 + fsetup, number=100)))
print('eric with 10\t:'.format(timeit(stmt="eric(lst)", setup=setup10 + fsetup, number=100)))
print('bpl with 10\t:'.format(timeit(stmt="bpl(lst)", setup=setup10 + fsetup, number=100)))
print('meow with 10\t:'.format(timeit(stmt="meow(lst)", setup=setup10 + fsetup, number=100)))
print()
print('OP with 100\t:'.format(timeit(stmt="f2(lst)", setup=setup100 + fsetup, number=100)))
print('eric with 100\t:'.format(timeit(stmt="eric(lst)", setup=setup100 + fsetup, number=100)))
print('bpl with 100\t:'.format(timeit(stmt="bpl(lst)", setup=setup100 + fsetup, number=100)))
print('meow with 10\t:'.format(timeit(stmt="meow(lst)", setup=setup100 + fsetup, number=100)))
print()
print('OP with 1000\t:'.format(timeit(stmt="f2(lst)", setup=setup1000 + fsetup, number=100)))
print('eric with 1000\t:'.format(timeit(stmt="eric(lst)", setup=setup1000 + fsetup, number=100)))
print('bpl with 1000\t:'.format(timeit(stmt="bpl(lst)", setup=setup1000 + fsetup, number=100)))
print('meow with 10\t:'.format(timeit(stmt="meow(lst)", setup=setup1000 + fsetup, number=100)))

10 elements on the list, 100 repetitions
OP      :0.00102
eric    :0.00117
bpl     :0.00141
meow    :0.00159

100 elements on the list, 100 repetitions
OP      :0.03200
eric    :0.00654
bpl     :0.01023
meow    :0.02011

1000 elements on the list, 100 repetitions
OP      :2.34821
eric    :0.06086
bpl     :0.10305
meow    :0.21190

还有一个低效的单线:

maximum = max(2*z -sum(x) for x, z in zip([[min(lst[:i+1]), min(lst[i+2:])] for i, _ in enumerate(lst[:-2])], lst[1:-1]))

【讨论】:

【参考方案5】:

可能的解决方案:

import sys
import random

random.seed(1)

l = [random.randint(0, 100) for i in range(10)]
print(l)

res = -sys.maxsize
a = sys.maxsize
b = -sys.maxsize
c = sys.maxsize

for i, v in enumerate(l[1:-1]):
    a = min(l[i], a)
    c = min(l[i + 2], c)
    b = max(l[i], b)
    res = max(2 * b - a - c, res)

print(res)

输出:

[13, 85, 77, 25, 50, 45, 65, 79, 9, 2]
155

【讨论】:

hmm 这个,会不会有错误?对于给定的示例列表,最大总和应为 155(通过选择 13、85、2)【参考方案6】:

你肯定是在正确的轨道上,你只需要摆脱那些最小的操作。所以我给你的提示是,你可以预先(以线性时间)预先计算它们,然后在循环中查找最小值,就像你已经在做的那样。

澄清一下:您必须预先计算所有imin(list[0:i])min(list[i:n])您已经拥有的部分之前。想法是将它们存储在两个数组中,例如m1m2,例如m1[i] = min(list[0:i])m2[i] = min(list[i:n])。然后,在循环中使用 m1m2

现在的挑战是在线性时间内计算m1m2,这意味着您不能使用min 函数来计算它们。如果你有m1[i],你如何使用list[i+1]计算m1[i+1]

【讨论】:

嗯,我不太清楚这到底是什么意思……你能澄清一下吗?特别是“预先计算最小值”部分,因为最小值会根据我们正在迭代的当前数字而变化,并且“在循环中查找最小值”,我不太确定这意味着什么。谢谢! 关于编辑:但是,如果我们预先计算最小值,那是否仍需要 O(n^2) 时间?就像我们必须遍历所有 i (O(n)),然后执行最小函数 (O(n)),总时间复杂度为 O(n^2) @RussellNg 是的。挑战是在线性时间内完成,而不使用内置的min 函数。如果你有m1[i],你如何计算m1[i+1]

以上是关于找到最大差异和python 3.x的O(n)解决方案?的主要内容,如果未能解决你的问题,请参考以下文章

分治算法在 O(logn) 中找到假币

51nod 1102 面积最大的矩形 && 新疆大学OJ 1387: B.HUAWEI's billboard 单调栈+拼凑段(o(n) 或 o(nlog(n))

元素中最大距离的算法

直线上的最大点数 O(N)

算法——O(n)解决无序数组排序后的相邻最大差值

最大化序列中数字之间的差异