问题描述
小R手上有一个长度为 n 的数组 (n > 0),数组中的元素分别来自集合 [0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]。小R想从这个数组中选取一段连续的区间,得到可能的最大乘积。
你需要帮助小R找到最大乘积的区间,并输出这个区间的起始位置 x 和结束位置 y (x ≤ y)。如果存在多个区间乘积相同的情况,优先选择 x 更小的区间;如果 x 相同,选择 y 更小的区间。
注意:数组的起始位置为 1,结束位置为 n。
测试样例
样例1:输入:n = 5, arr = [1, 2, 4, 0, 8]
输出:[1, 3]
样例2:输入:n = 7, arr = [1, 2, 4, 8, 0, 256, 0]
输出:[6, 6]
样例3:输入:n = 8, arr = [1, 2, 4, 8, 0, 256, 512, 0]
输出:[6, 7]
解题思路
暴力做法是,记录当前的最大乘积以及对应的区间。对于每一个可能的区间起点 i,我们尝试扩展区间直到遇到 0 为止。在遍历过程中,不断更新最大乘积以及对应的区间。但由于直接计算乘积,可能会导致以下问题:
- 如果数组中的元素较大或区间较长,直接计算乘积可能会超出
long long类型的表示范围(从约为),导致 整数溢出。 - 整数溢出会导致计算结果不准确,进而导致算法输出错误的区间。
使用对数避免整数溢出
由于直接计算子区间的乘积可能导致溢出(例如,当数组元素很大或区间很长时),代码通过对数组元素取对数,将乘法转化为加法: 使用 current_log_sum 来维护当前子区间的对数和。
处理0的特殊情况
当数组元素为 0 时,子区间的乘积无论如何都会是 0,因此将 current_log_sum 重置为 0,并将区间的起点更新到下一位置。
滑动窗口动态更新最大值
代码维护了 res_log(全局最大对数和),以及对应的区间起点和终点(l 和 r)。如果当前子区间的对数和大于 res_log,则更新最大值及其区间。如果当前对数和等于最大值,则按照题目要求更新区间起点和终点,优先选择起点更小的区间;若起点相同,则选择终点更小的区间。
- 初始化
- 使用
double类型的变量来存储对数和,例如current_log_sum和res_log。current_log_sum包含了所有以当前右端点为终点的子数组的累积乘积。 - 将
res_log初始化为非常小的负数,例如-1e20。
- 遍历数组
- 对于每个元素,如果值为 0,则重置
current_log_sum并更新起始位置。 - 如果值不为 0,则计算其对数,并累加到
current_log_sum。
- 更新最大值和区间
- 比较
current_log_sum和res_log,如果更大则更新最大值和对应的区间。 - 按题目要求,处理起始位置和结束位置相同的情况(多个区间的乘积相同时,优先选择起点更小的区间,起点相同时选择终点更小的区间)。
最终代码
vector<int> solution(int n, vector<int> arr) {
double res_log = -1e20; // 初始化为很小的数
int l = 1, r = 1;
int start = 0;
double current_log_sum = 0;
for (int i = 0; i < n; i++) {
if (arr[i] == 0) {
current_log_sum = 0;
start = i + 1;
continue;
}
current_log_sum += log(arr[i]);
if (current_log_sum > res_log) {
res_log = current_log_sum;
l = start + 1;
r = i + 1;
} else if (current_log_sum == res_log) {
if (start + 1 < l) {
l = start + 1;
r = i + 1;
} else if (start + 1 == l && i + 1 < r) {
r = i + 1;
}
}
}
return {l, r};
}