一、问题分析
-
目标:
在给定长度为n的数组中(数组元素来自特定集合),找到一段连续区间使得其元素乘积最大,并按照规则输出该区间的起始位置x和结束位置y(优先选择x更小的区间,若x相同则选y更小的区间)。 -
关键要点:
- 在计算过程中需要处理
0的情况。 - 需要确定最大乘积的区间。
- 值相同情况下,选择
x更小的区间,若x相同则选y更小的区间 - 乘积可能会发生越界
- 在计算过程中需要处理
二、解题思路
动态规划思想
主要的是选择区间和优化乘积越界问题
基本思路是通过利用对数的性质,将乘法运算转化为加法运算(因为log(a * b) = log(a) + log(b)),以此来更方便地计算连续区间元素的乘积情况,进而找出乘积最大的区间,并返回该区间的起始位置和结束位置。
代码详细分析
public static int[] solution(int n, int[] arr) {
// Edit your code here
double currentLogProduct = 0.0;
double maxLogProduct = Double.NEGATIVE_INFINITY;
int[] maxInterval = new int[2];
maxInterval[0] = 1;
maxInterval[1] = 1;
int start = 1; // 区间起始位置
for (int i = 0; i < n; i++) {
if (arr[i] != 0) {
currentLogProduct += Math.log(arr[i]);
} else {
// 处理当前区间的乘积
if (currentLogProduct > maxLogProduct) {
maxLogProduct = currentLogProduct;
maxInterval[0] = start;
int j = i - 1;
while (j >= 0 && arr[j] == 1) {
j--;
}
maxInterval[1] = j + 1;
}
// 重置当前乘积和起始位置
currentLogProduct = 0.0;
start = i + 2; // 下一个区间的起始位置
}
}
// 处理最后一个区间的乘积
if (currentLogProduct > maxLogProduct) {
maxLogProduct = currentLogProduct;
maxInterval[0] = start;
int j = n - 1;
while (arr[j] == 1) {
j--;
}
maxInterval[1] = j + 1;
}
return maxInterval;
}
- 变量定义部分:
currentLogProduct:用于记录当前正在考虑的连续区间内元素对数乘积的累加值。每当遇到非零元素时,就将其对数累加到这个变量上,以此来模拟区间元素的乘积情况(通过对数形式)。maxLogProduct:记录已经遍历过的所有区间中最大的对数乘积值,初始化为负无穷大,确保在后续比较中,任何有效的区间对数乘积都能比它大从而更新它。maxInterval:长度为 2 的整数数组,用于存储最终找到的最大乘积区间的起始位置和结束位置,初始化为[1, 1],表示默认的区间起始和结束都是 1(后续会根据实际情况更新)。start:记录当前正在考虑的区间的起始位置,初始化为 1,对应题目中数组位置从 1 开始计数的要求。
- 非零元素处理逻辑(循环体内部) :
当数组中当前元素arr[i]不为 0 时,将该元素的自然对数值累加到currentLogProduct变量上,这样通过对数的累加就等效于对原区间元素进行乘积运算(利用对数性质)。
- 零元素处理逻辑(循环体内部) :
-
当遇到数组元素为 0 时,意味着当前正在考虑的区间结束了,需要进行如下操作:
-
首先,判断当前区间(截止到这个 0 元素之前)的对数乘积
currentLogProduct是否大于已记录的最大对数乘积maxLogProduct。如果是,则更新maxLogProduct为当前区间的对数乘积值,同时更新maxInterval数组,将起始位置设为start(当前区间的起始位置),结束位置通过一个内层while循环来确定。这个while循环是从当前 0 元素的前一个位置i - 1开始往前找,只要遇到元素为 1 就继续往前,直到找到不为 1 的元素(因为 1 乘任何数都不改变乘积大小,所以要跳过连续的 1 来确定真正有效的区间结束位置),最终将结束位置设为找到的位置j + 1。 -
接着,重置
currentLogProduct为 0.0,表示开始考虑下一个区间的乘积了,同时将start更新为i + 2,即下一个区间的起始位置(跳过当前的 0 元素以及它后面可能的连续 1 元素)。
- 处理最后一个区间的乘积部分(循环结束后) :
在遍历完整个数组后,还需要单独处理最后一个区间(因为最后一个区间结束时不会再遇到 0 元素来触发上述的区间处理逻辑了)。同样判断最后一个区间的对数乘积currentLogProduct是否大于已记录的最大对数乘积maxLogProduct,如果是,则更新maxLogProduct以及maxInterval数组的起始和结束位置,结束位置的确定方式和前面遇到 0 元素时处理最后一个非零元素区间的方式类似,通过从数组末尾往前找,跳过连续的 1 元素来确定真正的区间结束位置。
最后,将记录着最大乘积区间起始位置和结束位置的maxInterval数组作为结果返回,供调用者获取找到的最大乘积区间信息。