如何在数组的左侧和右侧找到最大元素?

时间:2019-06-18 09:41:49

标签: c++

我在C ++中有一个可以解决(但确实解决)的家庭作业问题,但是不够快。

所以问题是这样的:在平台上,有n条宽度和高度相等的条。开始下雨了。找出适合条形之间的水量(非常糟糕,我知道,最好看一下例子)。例子:

n = 6
bar lengths = {3, 0, 0, 2, 0, 4}
Answer would be = 10

水的立方体会“填满”条之间的空白,我需要找到立方体的数目:

说明:

另一个例子:

n = 12
bar lengths = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1}
Answer = 6

我尝试过的事情:

对于阵列中的每个点,我都找到了它左侧和右侧的最大高度条,然后用左侧的最大值和右侧的最小值之间的最小值“填充”此点。 减去当前位置条的高度:

#include <iostream>

using namespace std;

int main() {
    int n, a[100001], i, j, volume=0, max_left, max_right;
    cin >> n;

    // Input the array

    for (i=0; i<n; i++) {
        cin >> a[i];
    }

    // For each element (except the first and last)

    for (i=1; i<(n-1); i++) {

        max_left = max_right = a[i];

        // Find the maximum to the left of it and to the right of it

        for (j=0; j<i; j++) {
            if (a[j] > max_left) {
                max_left = a[j];
            }
        }

        for (j=(i+1); j<n; j++) {
            if (a[j] > max_right) {
                max_right = a[j];
            }
        }

        // The quantity of water that fits on this spot is equal to
        // the minimum between the maxes, minus the height of the
        // bar in this spot

        volume += (min(max_left, max_right) - a[i]);
    }
    cout << volume;
    return 0;
}

解决方案很好,我得到了正确的结果。但是速度是一个问题。我相信,如果我没有记错的话,此解决方案的复杂度为O(n^2)。现在必须在O(n)中解决问题。问题是:如何为O(n)中的每个元素在两个方向上找到最大值?任何帮助将不胜感激。谢谢!

5 个答案:

答案 0 :(得分:3)

  1. 在完整列表中找到最高的栏。这给出了子范围BeforeAfter(均不包括最高的小节)。
  2. 遍历两个子范围(Before从前到后,After从前到后):记住您在途中找到的最高柱,从0开始。相加当前高度到结果的高度。
  3. 同时添加两个结果。

之所以起作用,是因为一旦找到总的最大高度,FrontBack的所有其他高度至少低于或等于最大高度。这样一来,您可以跳过两个方向的搜索,而只需使用到目前为止已达到的最高水平。

两个步骤均为O(n)。这是一个示例:

#include <algorithm>
#include <cassert>
#include <iostream>
#include <numeric>
#include <vector>

template <typename First, typename Last>
int calcRange(First begin, Last end) {
  int max{0};
  int result{0};
  for (auto it = begin; it != end; ++it) {
    const auto current = *it;
    result += std::max(0, max - current);
    max = std::max(max, current);
  }
  return result;
}

int calc(const std::vector<int>& bars) {
  if (bars.size() <= 1) return 0;

  // find max = O(n)
  const auto maxIt = std::max_element(bars.cbegin(), bars.cend());
  assert(maxIt != bars.cend());

  // calculate left and right = O(n)
  const auto l = calcRange(bars.cbegin(), maxIt);
  const auto r = calcRange(bars.crbegin(), std::make_reverse_iterator(maxIt));

  return l + r;
}

int main() {
  std::cout << calc({3, 0, 0, 2, 0, 4}) << std::endl;
  std::cout << calc({0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1}) << std::endl;
}

答案 1 :(得分:1)

我不得不说,我真的很喜欢这个问题。

这可能使您对如何解决此问题有所了解。基本上,您正在寻找最左边和最右边的条形高度。然后,您将水位提高到两者中的最小值,并计算为此所需的水量。然后,您可以缩小条形数组并重复该过程。

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <numeric>

int main() {
    std::vector<int> bars{ 3, 0, 0, 2, 0, 4 };
    int waterCounter = 0;
    int waterLevel = 0;
    auto leftIter = bars.begin();
    auto rightIter = bars.end() - 1;
    while (true)
    {
        if (leftIter == rightIter) break;
        auto newWaterLevel = std::min(*leftIter, *rightIter);
        if (newWaterLevel > waterLevel)
        {
            auto newRight = std::next(rightIter);
            auto size=std::distance(leftIter, newRight);

            auto waterInGaps = 0;
            for (auto iter=leftIter; iter!=newRight; iter++ )
            {
                waterInGaps += *iter > newWaterLevel ? 0 : newWaterLevel-*iter;
                *iter = *iter>newWaterLevel?*iter:newWaterLevel;
            }
            waterCounter += waterInGaps;
        }
        while (leftIter!=rightIter)
        {
            if (*leftIter > newWaterLevel) break;
            std::advance(leftIter, 1);      
        }
        while (rightIter!=leftIter)
        {
            if (*rightIter > newWaterLevel) break;
            std::advance(rightIter, -1);
        }
        waterLevel = newWaterLevel;
    }
    std::cout << waterCounter << std::endl;
    return EXIT_SUCCESS;
}

答案 2 :(得分:0)

我首先会问一些问题,这将使我接近解决方案。这样想,如果您向上面的结构中倒水,则空点只会堆积在“最高条”的右侧或左侧。了解了这一点之后,现在从左到最高的栏执行以下操作,将currbar_from_left = -1current value设置为数组的开始。

1-,如果currbar_from_left > currentcurrbar_from_left = current

  • 其他ans = ans + (currbar - currbar_from_left)-将差值添加到答案中,因为我们明确地知道,与我们的锚定点(最高条形)相比,该值会累积

2-现在再次进行遍历,这次遍历是从数组的右边到最高点。 currbar_from_right = -1,当前设置为数组中的最后一个值

  • 如果currbar_from_right > current then currbar_from_right = current
  • 其他ans = ans + (currbar - curbar_from_right)

3-现在,ans是空立方体的总数。

您可以将第1步和第2步按原样组合成一个循环,但是上述算法在理解上更为清晰。

以下代码对此进行了说明:

int n, a[100001], i, j, volume = 0, max_left, max_right;
    cin >> n;

    // Input the array
    for (i = 0; i<n; i++) {
        cin >> a[i];
    }

    // Maximum height in the array
    int maximum_bar = -1, maximum_index = -1;
    for (i = 0; i < n; i++) {
        if (a[i] > maximum_bar)
        {
            maximum_bar = a[i];

            // where did i see it?
            maximum_index = i;
        }
    }

    //Left search, to the maximum_bar
    int left = -1;
    for (i = 0; i < maximum_index; i++) {
        if (a[i] > left) left = a[i];
        else volume = volume + (left - a[i]);
    }


    //Right search, to the maximum_bar
    int right = -1;
    for (i = n - 1; i >= maximum_index; i--) {
        if (a[i] > right) right = a[i];
        else volume = volume + (right - a[i]);
    }


    cout << volume;
    return 0;

答案 3 :(得分:0)

让我们尝试另一种方法。除了在数组末尾的特殊情况外,我们可以从左到右进行一次遍历。

考虑我们要从左到右进行迭代,跟踪当前的水位。这些情况都可能发生:

  • 我们到达的柱子高于水位;
  • 我们到达的柱子低于水位;
  • 我们到达了一个与水位高度相同的列;

第一个定义新的水位。对于另外两个,请注意,要计算块数,我们要做的就是将water_level - height添加到当前总数中。

这只会在我们到达终点时引起问题。考虑以下输入,例如:

{2, 0, 0, 3, 0, 0}

请注意,最后两个项目的水位应为0,但我们刚刚将其设置为3!要解决此问题,我们只需丢弃最后一次迭代的结果(从3到末尾),然后从末尾到该点进行反向迭代。

如果这听起来有些棘手,您将看到实现实际上非常简单。以下是我的想法:它使用一些递归来简化操作,并且可以在任何一对迭代器上使用:

#include <iterator> // std::reverse_iterator

template<typename It>
int pass(It begin, It end) {
    if (end - begin <= 1) return 0;
    int result = 0;
    auto it = begin;
    while(++it != end) {
        // We keep track of the current water level by simply querying the begin iterator.
        if (*it <= *begin) {
            result += *begin - *it;
        } else {
            // We need to define a new water level. Let's just do a new pass, with the begin pointing to the new column.
            return result + pass(it, end);
        }
    }
    // If we got here, it means we reached the end. We should discard the result, and do a reverse pass instead
    return pass(std::reverse_iterator(end), std::reverse_iterator(begin));
}

像这样使用它:

int main() {
    std::vector<int> v{0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};
    std::cout << pass(v.begin(), v.end());
}
// Output: 6

答案 4 :(得分:0)

非常感谢大家,您的想法有所帮助!由于我没有那么高级,所以我不能(也不是真的不知道如何使用)使用vectorsauto(在我看来这很神奇),templates等东西。如果仍然有人感兴趣,这是我使用的代码,我在网站上获得了100分:

#include <iostream>

using namespace std;

int main()
{
    int n, a[100001], left_max[100001], right_max[100001];
    int i, max_to_right, max_to_left, volume=0;

    cin >> n;

    for (i=0; i<n; i++) {

        // Input the array

        cin >> a[i];

        // Directly find the "maximum to the left" of each element

        if (i == 0) {
            left_max[i] = max_to_left = a[i];
        }
        else {
            if (a[i] > max_to_left) {
                max_to_left = a[i];
            }
            left_max[i] = max_to_left;
        }
    }

   // Not the only thing left is to find the "maximum to the right" of each element

    for (i=(n-1); i>=0; i--) {
        if (i == (n-1)) {
            right_max[i] = max_to_right = a[i];
        }
        else {
            if (a[i] > max_to_right) {
                max_to_right = a[i];
            }
            right_max[i] = max_to_right;
        }

        // No need to have another loop afterwards, add to volume as we go

        if (i>0 && i<(n-1)) {
            volume += (min(left_max[i], right_max[i]) - a[i]);
        }

    }

    cout << volume;

    return 0;
}

我基本上做了同样的事情,但是速度更快。我在每个元素的右侧和左侧找到了最大值,但是在读取输入时我在每个元素的左侧找到了最大值,然后通过另一个循环,我在每个元素的右侧找到了最大值。该网站有一个非常相似的解决方案,只是短了一点。