使用最大总和

时间:2018-01-02 19:19:27

标签: python algorithm python-2.7 performance

我必须创建一个程序,将数字列表作为输入,并返回以具有最大总和的相同数字开头和结尾的子序列的总和(包括子序列开头和结尾的相等数字)在总和)。它还必须返回子序列的开始和结束的位置,即它们的索引+ 1。问题是我的当前代码只在列表长度不长的情况下才能顺利运行。当列表长度扩展到5000时,程序不会给出答案。

输入如下:

6
3 2 4 3 5 6

第一行是列表的长度。第二行是列表本身,列表项由空格分隔。输出将是12, 1, 4,因为你可以看到有一对相等的数字(3):第一个和第四个元素,所以它们之间的元素总和是3 + 2 + 4 + 3 = 12,他们的位置是第一和第四。

这是我的代码。

length = int(input())
mass = raw_input().split()
for i in range(length):
    mass[i]=int(mass[i])
value=-10000000000

b = 1
e = 1
for i in range(len(mass)):
    if mass[i:].count(mass[i])!=1:
      for j in range(i,len(mass)):
        if mass[j]==mass[i]:
            f = mass[i:j+1]
            if sum(f)>value:
                value = sum(f)
                b = i+1
                e = j+1
    else:
        if mass[i]>value:
            value = mass[i]
            b = i+1
            e = i+1
print value
print b,e

1 个答案:

答案 0 :(得分:2)

这应该比您当前的方法更快。

我们不是搜索mass寻找匹配数字对,而是将mass中的每个数字与其索引配对并对这些数字进行排序。然后我们可以使用groupby来查找相等数字的组。如果有相同数字的2个以上,我们使用第一个和最后一个,因为它们之间的总和最大。

from operator import itemgetter
from itertools import groupby

raw = '3 5 6 3 5 4'
mass = [int(u) for u in raw.split()]

result = []
a = sorted((u, i) for i, u in enumerate(mass))
for _, g in groupby(a, itemgetter(0)):
    g = list(g)
    if len(g) > 1:
        u, v = g[0][1], g[-1][1]
        result.append((sum(mass[u:v+1]), u+1, v+1))
print(max(result))

<强>输出

(19, 2, 5)

请注意,如果列表包含负数,则此代码 not 必须在列表中的相等元素之间给出最大总和。如果没有相同数字的组具有两个以上的成员,它仍然可以正数使用负数。如果情况并非如此,我们需要使用较慢的算法来测试一组相等数字中的每一对。

这是一个更高效的版本。我们不是使用sum函数,而是构建整个列表的累积总和列表。对于小型列表而言,这并没有太大的区别,但是当列表大小很大时,它会更快 。例如,对于10,000个元素的列表,这种方法的速度大约快10倍。为了测试它,我创建了一个随机正整数数组。

from operator import itemgetter
from itertools import groupby
from random import seed, randrange

seed(42)

def maxsum(seq):
    total = 0
    sums = [0]
    for u in seq:
        total += u
        sums.append(total)
    result = []
    a = sorted((u, i) for i, u in enumerate(seq))
    for _, g in groupby(a, itemgetter(0)):
        g = list(g)
        if len(g) > 1:
            u, v = g[0][1], g[-1][1]
            result.append((sums[v+1] - sums[u], u+1, v+1))
    return max(result)

num = 25000
hi = num // 2
mass = [randrange(1, hi) for _ in range(num)]
print(maxsum(mass))       

<强>输出

(155821402, 21, 24831)

如果您使用的是最新版本的Python,则可以使用itertools.accumulate来构建累积总和列表。这快了大约10%。

from itertools import accumulate

def maxsum(seq):
    sums = [0] + list(accumulate(seq))
    result = []
    a = sorted((u, i) for i, u in enumerate(seq))
    for _, g in groupby(a, itemgetter(0)):
        g = list(g)
        if len(g) > 1:
            u, v = g[0][1], g[-1][1]
            result.append((sums[v+1] - sums[u], u+1, v+1))
    return max(result)

这是一个更快的版本,源自Stefan Pochmann的代码,它使用了一个字典,而不是排序&amp; groupby。谢谢,斯蒂芬!

def maxsum(seq):
    total = 0
    sums = [0]
    for u in seq:
        total += u
        sums.append(total)
    where = {}
    for i, x in enumerate(seq, 1):
        where.setdefault(x, [i, i])[1] = i
    return max((sums[j] - sums[i - 1], i, j)
        for i, j in where.values())

如果列表中没有重复项(因此没有重复项绑定的子序列),则返回列表中的最大项。

以下是另外两个版本。这些可以正确处理否定项目,如果没有重复项目,则返回None。在Python 3中,可以通过将default=None传递给max来优雅地处理,但是该选项在Python 2中不可用,因此我抓住了ValueError例外情况&#39;}当你试图找到一个空的可迭代的max时引发的。

第一个版本maxsum_combo使用itertools.combinations生成一组相等数字的所有组合,然后找到给出最大总和的组合。第二个版本maxsum_kadane使用Kadane's algorithm的变体来查找组中的最大子序列。

如果原始序列中没有多个重复项,那么平均组大小很小,maxsum_combo通常更快。但如果组很大,那么maxsum_kadane maxsum_combo快。下面的代码在15000个项目的随机序列上测试这些函数,首先是几个重复的序列(因此小的平均组大小),然后是具有大量重复的序列。它验证两个版本是否提供相同的结果,然后执行timeit测试。

from __future__ import print_function
from itertools import groupby, combinations
from random import seed, randrange
from timeit import Timer

seed(42)

def maxsum_combo(seq):
    total = 0
    sums = [0]
    for u in seq:
        total += u
        sums.append(total)
    where = {}
    for i, x in enumerate(seq, 1):
        where.setdefault(x, []).append(i)
    try:
        return max((sums[j] - sums[i - 1], i, j)
            for v in where.values() for i, j in combinations(v, 2))
    except ValueError:
        return None

def maxsum_kadane(seq):
    total = 0
    sums = [0]
    for u in seq:
        total += u
        sums.append(total)
    where = {}
    for i, x in enumerate(seq, 1):
        where.setdefault(x, []).append(i)
    try:
        return max(max_sublist([(sums[j] - sums[i-1], i, j)
            for i, j in zip(v, v[1:])], k) 
                for k, v in where.items() if len(v) > 1)
    except ValueError:
        return None

# Kadane's Algorithm to find maximum sublist
# From https://en.wikipedia.org/wiki/Maximum_subarray_problem
def max_sublist(seq, k):
    max_ending_here = max_so_far = seq[0]
    for x in seq[1:]:
        y = max_ending_here[0] + x[0] - k, max_ending_here[1], x[2]
        max_ending_here = max(x, y)
        max_so_far = max(max_so_far, max_ending_here)
    return max_so_far

def test(num, hi, loops):
    print('\nnum = {0}, hi = {1}, loops = {2}'.format(num, hi, loops))
    print('Verifying...')
    for k in range(5):
        mass = [randrange(-hi // 2, hi) for _ in range(num)]
        a = maxsum_combo(mass)
        b = maxsum_kadane(mass)
        print(a, b, a==b)

    print('\nTiming...')
    for func in maxsum_combo, maxsum_kadane:
        t = Timer(lambda: func(mass))
        result = sorted(t.repeat(3, loops))
        result = ', '.join([format(u, '.5f') for u in result])
        print('{0:14} : {1}'.format(func.__name__, result))

loops = 20
num = 15000
hi = num // 4
test(num, hi, loops)

loops = 10
hi = num // 100
test(num, hi, loops)

<强>输出

num = 15000, hi = 3750, loops = 20
Verifying...
(13983131, 44, 14940) (13983131, 44, 14940) True
(13928837, 27, 14985) (13928837, 27, 14985) True
(14057416, 40, 14995) (14057416, 40, 14995) True
(13997395, 65, 14996) (13997395, 65, 14996) True
(14050007, 12, 14972) (14050007, 12, 14972) True

Timing...
maxsum_combo   : 1.72903, 1.73780, 1.81138
maxsum_kadane  : 2.17738, 2.22108, 2.22394

num = 15000, hi = 150, loops = 10
Verifying...
(553789, 21, 14996) (553789, 21, 14996) True
(550174, 1, 14992) (550174, 1, 14992) True
(551017, 13, 14991) (551017, 13, 14991) True
(554317, 2, 14986) (554317, 2, 14986) True
(558663, 15, 14988) (558663, 15, 14988) True

Timing...
maxsum_combo   : 7.29226, 7.34213, 7.36688
maxsum_kadane  : 1.07532, 1.07695, 1.10525

此代码在Python 2和Python 3上运行。上述结果是在Linux的Debian衍生版本上运行Python 2.6.6的旧32位2GHz机器上生成的。 Python 3.6.0的速度类似。

如果要包含由单个非重复数字组成的组,并且还希望将 组中的数字包含在&#34;子序列&#34;长度为1,您可以使用此版本:

def maxsum_kadane(seq):
    if not seq:
        return None
    total = 0
    sums = [0]
    for u in seq:
        total += u
        sums.append(total)
    where = {}
    for i, x in enumerate(seq, 1):
        where.setdefault(x, []).append(i)
    # Find the maximum of the single items
    m_single = max((k, v[0], v[0]) for k, v in where.items())
    # Find the maximum of the subsequences
    try:
        m_subseq = max(max_sublist([(sums[j] - sums[i-1], i, j)
                for i, j in zip(v, v[1:])], k) 
                    for k, v in where.items() if len(v) > 1)
        return max(m_single, m_subseq)
    except ValueError:
        # No subsequences
        return m_single

我还没有对它进行过广泛的测试,但它应该工作。 ;)