简体   繁体   English

O(n)求解最大差值和python 3.x的解决方案?

[英]O(n) solution for finding maximum sum of differences python 3.x?

I was wondering, given a list of integers, say l , and if we are allowed to select 3 integers from this list, say left , middle , right , where middle > left, right and left, middle, right appear in that order in the list (ie. index(left)<index(middle)<index(right) ), does there exist an O(n) solution for finding the maximum of middle - left + middle - right ? 我想知道,给定一个整数列表,比如l ,如果我们被允许从这个列表中选择3个整数,说leftmiddlerightmiddle > left, rightleft, middle, right出现在列表(即index(left)<index(middle)<index(right) ),是否存在O(n)解决方案,用于查找middle - left + middle - right的最大值? You may suppose that lists that do not satisfy these conditions do not appear (eg. [5, 0, 5] as pointed out by Eric Duminil) 您可能会认为不符合这些条件的列表不会出现(例如[5,0,5],如Eric Duminil所指出的那样)

Currently, I am able to come up with what I believe is (roughly) an O(n^2) solution (correct me if I am wrong). 目前,我能够提出我认为(大致) O(n^2)解决方案(如果我错了,请纠正我)。

Essentially, my current idea is to do: 从本质上讲,我目前的想法是:

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)

Help/hints would be greatly appreciated. 帮助/提示将不胜感激。

Thanks! 谢谢!

You can run through your numbers once, keeping a running minimum value , and storing it at each step, so that at the end you know what the minimum value is to the left of each index. 您可以运行一次数字,保持运行的最小值 ,并将其存储在每一步,以便最后知道每个索引左侧的最小值。 That's O(n). 那是O(n)。

Similarly, you can run through all your numbers once from right to left, and work out what the minimum value is to the right of each index. 同样,您可以从右到左遍历所有数字,并计算出每个索引右侧的最小值。 That is O(n). 那是O(n)。

Then you can run through each possible middle value, and take the left and right values from your earlier computations. 然后你就可以通过每个可能的运行middle值,并采取leftright从早期的计算值。 That is O(n). 那是O(n)。

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

The trick is that minimum value of the list is always the part of solution (either left , or right ). 诀窍是列表的最小值始终是解决方案的一部分( )。

  1. Find minimum of the list, which is O(n). 找到列表的最小值,即O(n)。 Now this minimum element will be either left or right. 现在这个最小元素将是左或右。
  2. Find maximum of (2x-y), where idx(x) > idx(y), and idx(x) < idx(min), that is check the left part of the list 找到最大值(2x-y),其中idx(x)> idx(y)和idx(x)<idx(min),即检查列表的左侧部分
  3. Find max(2x-y), where idx(x) < idx(y), and idx(x) > idx(min), that is check the right part of the list 找到max(2x-y),其中idx(x)<idx(y)和idx(x)> idx(min),即检查列表的右侧部分
  4. Now take maximum of the steps 2 and 3, which is your left/middle (or right/middle). 现在最多采取步骤2和3,即左/中(或右/中)。

Here's a way to calculate the minimums, left and right of every index, in O(n): 这是一种计算每个索引的最小值,左和右的方法,在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]

You can now iterate over every middle element in l ( idx between 1 and N-2 ), and find the minimum of 2 * l[idx] - min_rights[idx] - min_lefts[idx] . 您现在可以迭代l每个中间元素( idx1N-2 ),并找到最小值2 * l[idx] - min_rights[idx] - min_lefts[idx] This operation is also O(n): 这个操作也是O(n):

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

It outputs : 它输出:

11

which is 2 * 7 - 0 - 3 . 这是2 * 7 - 0 - 3

Here are some timings! 这是一些时间! Feel free to edit the code that does the timing and\\add new entries. 随意编辑执行计时和\\添加新条目的代码。

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

And as a bonus an inefficient one-liner: 作为一个低效率的单线程奖励:

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]))

possible solution: 解决方案:

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)

output: 输出:

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

You are definitely on the right track, you just have to get rid of those min operations. 你肯定是在正确的轨道上,你只需要摆脱那些最小的操作。 So my hint for you is that you can pre-compute them (in linear time) beforehand, then look up the min in the loop, as you are already doing. 所以我的暗示是你可以预先计算它们(在线性时间内),然后在循环中查找min,就像你已经在做的那样。

To clarify: you have to pre-compute min(list[0:i]) and min(list[i:n]) for all i 's, before the part you already have. 澄清一下:你必须你已经拥有的部分之前为所有i预先计算min(list[0:i])min(list[i:n]) The idea is to store them in two arrays, say m1 and m2 , such that m1[i] = min(list[0:i]) and m2[i] = min(list[i:n]) . 我们的想法是将它们存储在两个数组中,比如m1m2 ,这样m1[i] = min(list[0:i])m2[i] = min(list[i:n]) Then, use m1 and m2 in your loop 然后,在循环中使用m1m2

The challenge now is to compute m1 and m2 in linear time, meaning that you are not allowed to use the min function to compute them. 现在的挑战是在线性时间内计算m1m2 ,这意味着不允许使用min函数来计算它们。 If you have m1[i] , how can you compute m1[i+1] using list[i+1] ? 如果你有m1[i] ,怎么用list[i+1]计算m1[i+1] list[i+1]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM