题目描述
给定一个数组序列,需要求选出一个区间,使得该区间是所有区间中经过如下计算的值最大的一个:
区间中的最小数 * 区间所有数的和
最后程序输出经过计算后的最大值即可,不需要输出具体的区间。如给定序列 [6 2 1]则根据上述公式,可得到所有可以选定各个区间的计算值:
[6] = 6 * 6 = 36;
[2] = 2 * 2 = 4;
[1] = 1 * 1 = 1;
[6,2] = 2 * 8 = 16;
[2,1] = 1 * 3 = 3;
[6, 2, 1] = 1 * 9 = 9;
从上述计算可见选定区间[6],计算值为36, 则程序输出为36。
区间内的所有数字都在[0, 100]的范围内;
输入描述:
第一行输入数组序列个数,第二行输入数组序列。
输出描述:
输出数组经过计算后的最大值。
输入例子
3
6 2 1
输出例子
36
第一次思考
我就看到这个给的输入输出例子,心想这最大值不是可明显的嘛。就是这个数组的最大值的平方,与数组中的最小值再与数组所有元素和,这两者再取个最大值就行了噻。虽然心里想着头条的题怎么可能这么简单,但还是大胆地去提交了代码(python3.5):
def max_region(arr):
max1 = max(arr) * max(arr)
max2 = min(arr) * sum(arr)
print(max(max1, max2, min(arr)*sum(arr)))
if __name__ == '__main__':
n = int(input())
arr = [int(i) for i in input().split()]
max_region(arr)
嘿,你看这个通过率还有75%呢,我还挺高兴的
好孩子怎么能不追求100%的正确率呢?于是我想了想这个显示的错误案例。明显我程序的输出9025,即是数组中最大值95的平方。数组中最小值 与 数组所有元素之和 的乘积,确实是小于9025的。所以,25818是怎么个结果呢?
第二次思考
让我们再来认真地从头到尾看一遍题。
“ 要求选出一个区间,使得该区间是所有区间中经过如下计算的值最大的一个:区间中的最小数*区间所有数的和 ”
看清楚了吧?所以给的案例中为什么:[6] = 6 * 6 = 36 ?这不是指的数组最大值的平方,而是选取了原数组中[6]这个区间,这个区间只有一个数,所以该区间的最小数、以及区间所有数的和都是它本身,所以这个区间返回的值是36。
那么对于原数组来说,取的所有单个值的区间,肯定是最大值对应的那个单值区间乘积最大,这还是没错的。同样,对于原数组来说,取其最小值,和所有值的和,的乘积,是这个数组中最长区间的最大值,这也是没有错的。
然而,对于其他多个数组合的区间,可就不一定了。
举个例子:[24 3 44 76 83],原数组整个区间的 “区间中的最小数 * 区间所有数的和” = 3 * (24+3+44+76+83)= 690,而实际上它的子区间[44 76 83]对应的 “区间中的最小数 * 区间所有数的和” = 44 * (44+76+83)= 8932。谁大谁小就不用多说了哈 ~
所以说,我们得去遍历可能的区间,比较究竟哪个 “区间中的最小数 * 区间所有数的和” 才是真正的最大值。如果这个区间包含数组中的最小值,那肯定是整个区间的结果最大;如果这个区间的最小值比整个数组的最小值要大,那就得比较到底哪个区间会得到真正的最大值。
用递归来可以试一试。
- 首先获得原数组整个区间中的最小值;
- 计算原数组整个区间的和 与 其最小值 的乘积;
- 比较这个乘积是否大于当前的最大值,如果是,就需要更新;
- 将原数组,按照整个区间最小值对应的索引,分割成两个区间,对这两个区间,回到步骤1进行递归调用
按照这个思路的代码(python3.5)如下:
maxs = 0
def max_region(arr):
global maxs
if not arr: # 递归的结束条件
return maxs
max1 = min(arr) * sum(arr)
maxs = max(max1, maxs)
min_index = arr.index(min(arr))
# 包含min(arr)的区间,一定是整个区间中的计算结果最大,则以min(arr)对应的索引分割原数组,对得到的左右两个区间分别递归比较
return max(max_region(arr[0:min_index]), max_region(arr[min_index+1:]), maxs)
if __name__ == '__main__':
n = int(input())
arr = [int(i) for i in input().split()]
print(max_region(arr))
结果显示结果如下:
第三次思考
我们知道,一般递归会有很多重复计算,转成动态规划的话会得到优化。这道题用动态规划如何做?
这位大佬https://blog.csdn.net/alixia111/article/details/80455950的博客写出了Java的动态规划解法,但是最终通过率也只是70%。按照大佬的思路,我们需要定义2个 int[arr.length][arr.length] 的矩阵,分别计算dp_min[i][j],dp_sum[i][j]。dp_min[i][j]表示开始于 i 结束于 j 的区间最小值,dp_sum[i][j]表示开始于 i 结束于 j 的区间所有数之和。
对于序列arr中开始于i,结束于j的 区间中的最小数 * 区间所有数的和 = dpmin[i][j] * dpsum[i][j],然后遍历求积最大即可。
博客后面提到了,本题能正确通过的思路是:按照y轴升序,x轴降序排序,然后从最后一个元素开始,始终与(y后者x)最大元素做对比,如果比最大元素大,就保存到栈中,然后把最大元素更新;如果小于最大元素,那么就查看下一个元素。
这……都是个啥……
第四次思考-放弃思考
拜服牛客各大佬们 https://www.nowcoder.com/questionTerminal/e6e57ef2771541dfa2f1720e50bebc9a
LeetCode84. Largest Rectangle in Histogram 题目的变种。
主要思路是以每个数作为最小值,找到它的左边界和右边界,然后最小值乘以边界的累加和;如果是遍历每个值找其左右边界,会超时(应该只能ac70%左右);使用压栈的方法可以使得递增的序列不用重复计算其左右边界(最终用时136ms)。
分享一个看起来比较简单的、可以完全通过的C++代码(大佬写的,待我再好好想一下……):
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int main()
{
int n;
while(cin >> n)
{
int a[n];
for(int i=1; i<=n; i++)
{
cin >> a[i];
}
int Max1 = 0;
for(int i=1; i<=n; i++)
{
int Min1 = a[i];
int sum = a[i];
for(int j=i-1; j>=1; j--)
{
if(a[j] >= Min1)
{
sum += a[j];
}
else break;
}
for(int j=i+1; j<=n; j++)
{
if(a[j] >= Min1)
{
sum += a[j];
}
else break;
}
Max1 = max(Max1, sum*Min1);
}
cout << Max1 << endl;
}
return 0;
}
我枯了……