原题:https://www.nowcoder.com/study/live/718/3/25
暴力探索
动态规划的基础是枚举。这道题我们想要枚举出所有的可能。
以这个数组为例:-2.5 4 0 3 0.5 8 -1,每个枚举所有的子数组:
-2.5 | ||||||
4 | ||||||
-2.5 | 4 | |||||
0 | ||||||
4 | 0 | |||||
-2.5 | 4 | 0 | ||||
3 | ||||||
0 | 3 | |||||
4 | 0 | 3 | ||||
-2.5 | 4 | 0 | 3 | |||
0.5 | ||||||
3 | 0.5 | |||||
0 | 3 | 0.5 | ||||
4 | 0 | 3 | 0.5 | |||
-2.5 | 4 | 0 | 3 | 0.5 | ||
8 | ||||||
0.5 | 8 | |||||
3 | 0.5 | 8 | ||||
0 | 3 | 0.5 | 8 | |||
4 | 0 | 3 | 0.5 | 8 | ||
-2.5 | 4 | 0 | 3 | 0.5 | 8 | |
-1 | ||||||
8 | -1 | |||||
0.5 | 8 | -1 | ||||
3 | 0.5 | 8 | -1 | |||
0 | 3 | 0.5 | 8 | -1 | ||
4 | 0 | 3 | 0.5 | 8 | -1 | |
-2.5 | 4 | 0 | 3 | 0.5 | 8 | -1 |
如果是暴力求解,我们把这些子数组对应的值求一遍,然后找其中的最大值就可以了。
但现在是动态规划,因此我们得另外找找思路。
动态规划
我们发现,以 arr[i] 结尾的子数组对应的最大累积乘,其实并不需要算那么多子数组,只要算两个有代表的子数组。且根据 arr[i] 的正负,分为三种情况:
-
arr[i] > 0,dpPos[i] = max(dpPos[i - 1] * arr[i], arr[i])
-
arr[i] < 0, dpPos[i] = max(dpNeg[i - 1] * arr[i], arr[i])
-
arr[i] == 0, dpPos[i] = 0
其中 arr[i] 表示原数组中第 i 个值,dpPos[i] 表示以 i 位置结尾的子数组的最大累积乘正值,dpNeg[i] 表示以 i 位置结尾的子数组的最大累积乘负值。
我们在计算 dpPos[i] 中的时候发现,为了算 dpPos[i],不仅要算 dpPos[i - 1],还必须把 dpNeg[i - 1] 也算出来。
那么,就一起算吧。
-
arr[i] > 0, dpNeg[i] = dpNeg * arr[i];
-
arr[i] == 0, dpNeg = 0;
-
arr[i] < 0, dpNeg = min(arr[i], dpPos[i - 1] * arr[i])
dpPos[i] 和 dpNeg[i] 这两个值极大地减少了子数组的个数,同时完成了状态的确定与转移。
完整代码
dpPos[i] 和 dpNeg[i] 只依赖于 i - 1 位置上的值,因此可以将一维数组压缩成一个变量。注意,需让 dpPos 和 dpNeg 同时更新,dpNeg 可能会依赖于不正确的 dpPos。
写代码的时候注意浮点计算,double 精确度更高一些。
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(new BufferedInputStream(System.in));
int n = scanner.nextInt();
double[] arr = new double[n]; // 有些数拿 float 不太好表示,拿 double 精确度更高一些
for(int i = 0; i < arr.length; i++) {
arr[i] = scanner.nextDouble();
}
double dpPos = 0;
double dpNeg = 0;
if(arr[0] > 0) {
dpPos = arr[0];
} else if(arr[0] < 0) {
dpNeg = arr[0];
}
double max = dpPos;
for(int i = 1; i < arr.length; i++) {
// 计算以 i 位置结尾的 dpPos 和 dpNeg
double curPos, curNeg; // 一定要另开两个变量,以确保同步更新。
if(arr[i] > 0) {
curPos = Math.max(dpPos * arr[i], arr[i]);
curNeg = dpNeg * arr[i];
} else if(arr[i] == 0) {
curPos = 0;
curNeg = 0;
} else {
curPos = dpNeg * arr[i];
curNeg = Math.min(arr[i], dpPos * arr[i]); // 若上一步为 dpPos = dpNeg * arr[i],则会计算出不正确的值
}
dpPos = curPos;
dpNeg = curNeg;
// 更新最大值
if(dpPos - max > 1e-6) {
max = dpPos;
}
}
System.out.printf("%.2f", max);
}
}