【问题标题】:How to most efficiently increase values at a specified range in a large array and then find the largest value如何最有效地增加大数组中指定范围内的值,然后找到最大值
【发布时间】:2016-10-14 09:44:43
【问题描述】:

所以我刚刚进行了一次面试的编程测试,我认为自己是一个不错的程序员,但是我无法满足在线测试的时间限制(并且不允许使用调试器)。本质上,问题是给出一个索引范围 [low, high] 和一个值来增加这些索引,在对数组执行 M 次之后,找到我的最大值。

So if you had an array of size 5 [0, 0, 0, 0, 0]
and you were given instructions
[0, 3] 143
[2, 4] 100
and [2,2] 100
the array would be [143, 143, 343, 243, 100]

最高为 343。

我尝试了简单的解决方案,但想不出一个巧妙的算法,并认为答案必须通过一些内存复制来完成?

如何才能最快地解决这个问题?我在这里有什么遗漏吗?

谢谢

【问题讨论】:

标签: c++ algorithm bigdata


【解决方案1】:

您并不完全清楚大数组是否在开始时包含全零,或者是否给您一个带有初始值的大数组,但在这两种情况下都可以使用类似的方法:

A) 大的零数组

首先,在这种情况下,不需要实际创建大数组,或者对它做任何事情。

鉴于这些范围和值:

[0, 3] 143
[2, 4] 100
[2, 2] 100

创建一个列表,其中每个低索引存储值,每个高索引(加1)存储值的倒数:

{0, +143} {4, -143} {2, +100} {5, -100} {2, +100} {3, -100}

然后对这个列表进行排序(最好合并具有相同索引的值):

{0, +143} {2, +200} {3, -100} {4, -143} {5, -100}

然后,遍历列表,保持运行总和,并找到最大值及其开始和结束索引:

           total  
{0, +143}   143  
{2, +200}   343   <-- max  
{3, -100}   243   <-- end  
{4, -143}   100  
{5, -100}     0  

所以最大值是343,它的范围是索引2~3(所以真的只有位置2)。

该算法的复杂度与范围数 M 成线性关系,但不受大数组 N 的大小影响,因此 O(M)。

B) 具有初始值的大型数组

如果给定一个带有初始值的数组,例如:

[300, 200, 400, 600, 700]

在增加范围内的值后,任何元素仍可能具有最大值,因此最终您必须遍历数组中的每个元素以找到最大值。

但是,您可以通过创建与上述相同的列表来避免实际增加数组中的任何值,或多次迭代数组:

{0, +143} {2, +200} {3, -100} {4, -143} {5, -100}

然后遍历数组以找到最大值,同时保持附加值的总和,并将这些值添加到值中,同时与最大值进行比较:

              total
0: {0, +143}   143   value: 300 + 143 = 443  
1: no change   143   value: 200 + 143 = 343  
2: {2, +200}   343   value: 400 + 343 = 743  
3: {3, -100}   243   value: 600 + 243 = 843   <-- max  
4: {4, -143}   100   value: 700 + 100 = 800   <-- end  
5: {5, -100}     0  

所以最大值是843,它的范围是索引3~4(所以真的只有位置3)。

这个算法的复杂度与大数组N的大小成线性关系,与范围数M成线性关系,或者说O(N+M),但是假设N远大于M,这是~O (N)。

【讨论】:

  • 我实际上认为我比我更喜欢你的建议,我保留了范围和值,但你只保留值变化的地方,我认为这更简单 +1
【解决方案2】:

解决方案是确定范围重叠的位置。代码需要维护一个范围列表,并且在处理每个输入行时,必须更新该列表。假设与数组中的项目数相比,范围的数量很少,那么处理范围表会比天真地更新数组要快得多。

例如,假设数组有一千万个条目,并且给了您两条指令

[0,5000000] 50
[4000000,6000000] 100

简单的解决方案将在数组中写入 1700 万个条目(1000 万个用于初始化数组,另外 700 万个用于处理两条指令)。但是您可以立即确定最大值为 150,因为这两个范围重叠。

【讨论】:

  • 但是最大值仍然可以在任何地方,因为起始值不一定为零。 (至少我认为他们不是,否则这不是什么难题。)
  • @m69 确实如此,尽管从问题中并不清楚数组是否可以从非零值开始。
【解决方案3】:

您可以将数组条件写成矩阵的行:

[0, 3] -> [1 1 1 0 0]

[2, 4] -> [0 0 1 1 1]

[2, 2] -> [0 0 1 0 0]

然后将值写为行向量 [143 100 100] 并将其与上述矩阵相乘(左乘)。结果向量的最大值就是你要找的。​​p>

对于一般的向量矩阵乘法,有快速算法。可以在此处找到一些示例:http://web.stanford.edu/~rrwill/mat-vec-slides.pdf

如果您可以假设上述矩阵是稀疏的(合理的),那么执行乘法运算的算法非常有效。

【讨论】:

  • 添加一条指令[4,4,10000]。
  • 将这种方法与 OP 标题指定的“大数组”一起使用对我来说似乎不是很有效
  • @vu1p3n0x 为什么你的方法比我的更有效?您执行与重写数组传播的复杂性相同的操作(由于声誉低,我无法评论您的答案)
  • 我不主张重写数组,我只是为了可视化目的而包含了该步骤。我说的是只使用范围。请参阅 user3386109 的回答,了解为什么使用范围会更有效率。
【解决方案4】:

这里的关键部分是使用范围而不是数组(直到最后)。您可以做的是合并和拆分范围以保留修改列表。 (中间数组仅用于可视化范围如何重叠,您不需要为每个添加的范围修改一个数组)

从一个空范围开始(全为零)

{0, 5, 0} -> [0, 0, 0, 0, 0]

添加第一个范围

{0, 4, 0  } -> [0,   0,   0,   0,   0]
{0, 3, 143} -> [143, 143, 143]
               [143, 143, 143, 0,   0] -> {0, 2, 143}, {3, 4, 0}

所以你现在有 2 个范围

{0, 3, 143}, {4, 5, 0} -> [143, 143, 143, 0,   0  ]
{2, 4, 100}            ->           [100, 100, 100]
                          [143, 143, 243, 100, 100] -> {0, 1, 143}, {2, 2, 243}, {4, 5, 100}

现在是 3,以此类推...当您到达末尾时,您只需在范围列表中搜索最大的。

这里的棘手部分当然是知道如何合并、添加和拆分范围。如果添加范围的开头在另一个范围内,则必须将第一个范围一分为二。与添加的范围在另一个范围内结束时相同。如果添加的范围与另一个范围的整个范围重叠,则只需将范围的值添加到其中。

【讨论】:

    【解决方案5】:

    在运行指令时检测局部最大值。答案将是局部最大值的最大值。请注意,vector&lt;Instruction&gt; p 必须按start 排序。

    struct Instruction
    {
        size_t start, stop;
        value_t value;
    
        bool operator > (const Instruction& b) const
        {
            return stop > b.stop;
        }
    };
    
    template<class Ti>
    value_t do_work(const Ti&& b, const Ti&& e)
    {
        value_t result = 0;
        value_t local = 0;
    
        auto q = priority_queue<Instruction, 
                     vector<Instruction>, 
                     greater<Instruction>>();
    
        for (auto i=b; i!=e; ++i)
        {
            q.push(*i);
            if (q.top().stop < i->start)
            {
                if (local > result)result = local;
                do
                {
                    local -= q.top().value;
                    q.pop();
                } while (q.top().stop < i->start);
            }
    
            local += i->value;
        }
    
        return max(local, result);
    }
    
    int main()
    {
        vector<Instruction> p = { 
            {0,3,143}, {2,4,100}, {2,2,100}, 
            {3,5,1000}, {4,4,500} 
        };
    
        cout << do_work(begin(p),end(p)) << endl;
    
        return 0;
    }
    

    【讨论】:

    • 认真思考与因其他人完成作业而毕业的人一起工作是多么有趣。
    猜你喜欢
    • 2022-01-04
    • 1970-01-01
    • 2015-10-25
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 2020-09-28
    • 2019-11-08
    • 1970-01-01
    相关资源
    最近更新 更多