我在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)
中的每个元素在两个方向上找到最大值?任何帮助将不胜感激。谢谢!
答案 0 :(得分:3)
Before
和After
(均不包括最高的小节)。 Before
从前到后,After
从前到后):记住您在途中找到的最高柱,从0开始。相加当前高度到结果的高度。之所以起作用,是因为一旦找到总的最大高度,Front
和Back
的所有其他高度至少低于或等于最大高度。这样一来,您可以跳过两个方向的搜索,而只需使用到目前为止已达到的最高水平。
两个步骤均为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 = -1
和current value
设置为数组的开始。
1-,如果currbar_from_left > current
则currbar_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)
非常感谢大家,您的想法有所帮助!由于我没有那么高级,所以我不能(也不是真的不知道如何使用)使用vectors
,auto
(在我看来这很神奇),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;
}
我基本上做了同样的事情,但是速度更快。我在每个元素的右侧和左侧找到了最大值,但是在读取输入时我在每个元素的左侧找到了最大值,然后通过另一个循环,我在每个元素的右侧找到了最大值。该网站有一个非常相似的解决方案,只是短了一点。