【问题标题】:Maximum of all possible subarrays of an array数组的所有可能子数组的最大值
【发布时间】:2015-08-13 01:57:08
【问题描述】:

如何查找/存储长度为 n 的数组的所有可能的非空子数组的最大值/最小值?

如果确实查询到段树,我生成了数组的段树和每个可能的子数组,但这效率不高。我如何在 O(n) 中做到这一点?

P.S n

For eg. arr[]= { 1, 2, 3 };  // the array need not to be sorted

sub-array     min      max
{1}            1        1
{2}            2        2
{3}            3        3
{1,2}          1        2
{2,3}          2        3
{1,2,3}        1        3

【问题讨论】:

  • 数组排序了吗?
  • 请提供样本数据(即使小于真实数据)和预期结果。
  • 您在寻找算法或 C++ 代码吗?如果您正在寻找一种算法,那么语言应该无关紧要,也许应该删除 C++ 标记。
  • 取决于你想要什么,你可能有也可能没有理由期待O(n)。如果您只想要一组可能的最大值和最小值,您只需将元素填充到一个集合中,那就是O(n),另一方面,如果您想用相应的最大值/最小值枚举所有非空子数组将不得不面对这样一个事实,即子数组的数量为O(n^2),每个子数组都需要一些时间来找到最大/最小值,所以至少O(n^2),可能更多。
  • 什么是子数组? 连续条目的集合?或不?请编辑问题。

标签: algorithm array-algorithms


【解决方案1】:

我认为不可能在 O(n) 中存储所有这些值。但是很容易在 O(n) 中创建一个可以回答的结构,在 O(1) 中查询“有多少子集,其中 A[i] 是最大元素”。

朴素版本:

想想简单的策略:要知道某个 A[i] 有多少这样的子集,您可以使用一个简单的 O(n) 算法来计算数组左侧和右侧有多少元素小于 A[i]。比方说:

A = [... 10 1 1 1 5 1 1 10 ...]

这个5 up 左边有 3 个元素,右边有 2 个元素。由此我们知道有4*3=12 子数组,其中5 是最大值。 4*3 因为左边有 0..3 子数组,右边有 0..2

优化版:

这种简单的检查版本需要对每个元素进行 O(n) 次操作,所以毕竟 O(n^2)。如果我们可以一次通过 O(n) 计算所有这些长度不是很好吗?

幸运的是,有一个简单的算法。只需使用堆栈。正常遍历数组(从左到右)。将每个元素索引放入堆栈。但是在放之前,把所有值小于当前值的索引都去掉。当前索引之前的剩余索引是最近的较大元素。

要在右边找到相同的值,只需向后遍历数组即可。

这是一个示例 Python 概念验证,展示了该算法的实际应用。我还实现了 naïve 版本,因此我们可以交叉检查优化版本的结果:

from random import choice
from collections import defaultdict, deque

def make_bounds(A, fallback, arange, op):
    stack = deque()
    bound = [fallback] * len(A)
    for i in arange:
        while stack and op(A[stack[-1]], A[i]):
            stack.pop()
        if stack:
            bound[i] = stack[-1]
        stack.append(i)
    return bound

def optimized_version(A):
    T = zip(make_bounds(A, -1, xrange(len(A)), lambda x, y: x<=y), 
            make_bounds(A, len(A), reversed(xrange(len(A))), lambda x, y: x<y))

    answer = defaultdict(lambda: 0)
    for i, x in enumerate(A):
        left, right = T[i]
        answer[x] += (i-left) * (right-i)
    return dict(answer)

def naive_version(A):
    answer = defaultdict(lambda: 0)
    for i, x in enumerate(A):
        left = next((j for j in range(i-1, -1, -1) if A[j]>A[i]), -1)
        right = next((j for j in range(i+1, len(A)) if A[j]>=A[i]), len(A))
        answer[x] += (i-left) * (right-i)
    return dict(answer)


A = [choice(xrange(32)) for i in xrange(8)]    
MA1 = naive_version(A)
MA2 = optimized_version(A)

print 'Array:    ', A
print 'Naive:    ', MA1
print 'Optimized:', MA2
print 'OK:       ', MA1 == MA2

【讨论】:

  • 太好了,这就是我的发现。但问题是我看不懂python代码。
  • @Juan Lopes 但你不认为从堆栈中弹出元素也会进行 O(n) 操作,然后复杂度变为 O(n^2)。
  • @user3396302 弹出的元素不能多于进入堆栈的元素。通过算法,每个元素在每个方向上只进入堆栈一次(只有一个追加)。堆栈中只能有 O(n) 个操作。
【解决方案2】:

我认为不可能在 O(n) 时间内直接完成:您需要遍历子数组的所有元素,并且您有 n 个。除非子数组已排序。

另一方面,您可以在初始化子数组时,而不是使它们成为普通数组,而是可以构建堆,特别是当您想要找到最小堆时的最小堆和当您想要找到最大值时的最大堆。

Building a heap is a linear time operation,并且分别检索最大堆和最小堆的最大值和最小值是一个常数时间操作,因为这些元素位于堆的第一个位置。

只需使用普通数组即可轻松实现堆。

查看 Wikipedia 上关于二进制堆的这篇文章:https://en.wikipedia.org/wiki/Binary_heap

【讨论】:

    【解决方案3】:

    我不明白您所说的最大子数组到底是什么意思,所以我假设您要求以下之一

    1. 最大/最小长度的子数组或其他一些标准(在这种情况下,问题将简化为在一维数组中查找最大元素)
    2. 在一个子数组的上下文中或在整个超级数组的上下文中,所有子数组的最大元素数

    问题 1 可以通过简单地迭代您的超级数组并存储对最大元素的引用来解决。或者像nbro所说的那样建立一个堆。问题 2 也有类似的解决方案。然而,线性扫描是通过n 长度为m 的数组不会是线性的。因此,您必须保持类不变量,以便在每次操作后知道最大值/最小值。也许在一些数据结构的帮助下,比如堆。

    【讨论】:

      【解决方案4】:

      假设您的意思是连续子数组,请创建部分和数组,其中 Yi = SUM(i=0..i)Xi,因此从 1,4,2,3 创建 0,1,1+4=5 ,1+4+2=7,1+4+2+3=10。您可以在线性时间内从左到右创建它,任何连续子数组的值都是从另一个子数组中减去一个部分和,因此 4+2+3 = 1+4+2+3 - 1= 9。

      然后从左到右扫描部分和,跟踪到目前为止看到的最小值(包括初始零)。在每个点从当前值中减去这个值,并跟踪以这种方式产生的最高值。这应该为您提供总和最大的连续子数组的值,并且您也可以保留索引信息,以查找该子数组的开始和结束位置。

      要找到最小值,要么稍微改变上面的内容,要么只是反转所有数字的符号并再次执行完全相同的操作:min(a, b) = -max(-a, -b)

      【讨论】:

        【解决方案5】:

        我认为您要问的问题是找到子数组的最大值。 bleow 是可以在 O(n) 时间内完成的代码。

        int maxSumSubArr(vector<int> a)
        {
            int maxsum = *max_element(a.begin(), a.end());
            if(maxsum < 0) return maxsum;
            int sum = 0;
            for(int i = 0; i< a.size; i++)
            {
              sum += a[i];
              if(sum > maxsum)maxsum = sum;
              if(sum < 0) sum = 0;
            }
            return maxsum;
        }
        

        注意:此代码未经测试,如有问题请添加cmets。

        【讨论】:

        • int maxsum = max(a.begin(), a.end()); 无法编译(您最多使用 2 个迭代器(即 a.end())并尝试将其转换为 int)。
        • 关闭,但 OP 说“非空子数组”,所以如果总和低于 0,您不能假设总和为 0
        • @Jarod42 是的,这是一个错误,我想我已经纠正了,感谢您指出这一点。
        • 这个返回非空子数组。如果数组中的所有元素都是-ve,则返回数组中的最大数,否则下面的循环将完成这项工作。
        猜你喜欢
        • 2022-01-14
        • 2013-02-24
        • 2021-04-11
        • 2018-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多