子数组的最大累乘积 -- 暴力与动态规划

原题:https://www.nowcoder.com/study/live/718/3/25

暴力探索

动态规划的基础是枚举。这道题我们想要枚举出所有的可能。

以这个数组为例:-2.5 4 0 3 0.5 8 -1,每个枚举所有的子数组:

-2.5
4
-2.54
0
40
-2.540
3
03
403
-2.5403
0.5
30.5
030.5
4030.5
-2.54030.5
8
0.58
30.58
030.58
4030.58
-2.54030.58
-1
8-1
0.58-1
30.58-1
030.58-1
4030.58-1
-2.54030.58-1

如果是暴力求解,我们把这些子数组对应的值求一遍,然后找其中的最大值就可以了。

但现在是动态规划,因此我们得另外找找思路。

动态规划

我们发现,以 arr[i] 结尾的子数组对应的最大累积乘,其实并不需要算那么多子数组,只要算两个有代表的子数组。且根据 arr[i] 的正负,分为三种情况:

  1. arr[i] > 0,dpPos[i] = max(dpPos[i - 1] * arr[i], arr[i])

  2. arr[i] < 0, dpPos[i] = max(dpNeg[i - 1] * arr[i], arr[i])

  3. 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] 也算出来。

那么,就一起算吧。

  1. arr[i] > 0, dpNeg[i] = dpNeg * arr[i];

  2. arr[i] == 0, dpNeg = 0;

  3. 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);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值