动态规划之 0-1背包 完全背包 基础题

第一轮面试准备到第26题

一  解题步骤

对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

二  基础题模板

递推公式,要站dp【i】的角度去想,想象dp【i】是由哪些前面的状态推倒而来的。

. - 力扣(LeetCode)

class Solution {
    public int fib(int n) {
        // 边界处理
        if(n <= 1){
            return n;
        }

        //1 确定数组含义
        // 下标是指第几个数
        // dp[i] 指第i个数对应的值

        //2 确定递推公式
        // dp[i] = dp[i - 1] + dp[i - 2]
        
        //3 初始dp数组
        int dp[] = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;

        //4 确定遍历顺序
        for(int i = 2; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];

    }
}

三  背包类型题目总览

01背包:每件物品只能用一次

完全背包:每件物品无限用

四  0-1背包题模板

1  二维dp数组模板

遍历顺序以及循环顺序无要求

46. 携带研究材料(第六期模拟笔试) (kamacoder.com)

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int bagweight = scanner.nextInt();

        int[] weight = new int[n];
        int[] value = new int[n];

        for (int i = 0; i < n; ++i) {
            weight[i] = scanner.nextInt();
        }
        for (int j = 0; j < n; ++j) {
            value[j] = scanner.nextInt();
        }

        int[][] dp = new int[n][bagweight + 1];

        for (int j = weight[0]; j <= bagweight; j++) {
            dp[0][j] = value[0];
        }

        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= bagweight; j++) {
                if (j < weight[i]) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
                }
            }
        }

        System.out.println(dp[n - 1][bagweight]);
    }
}

2  一维dp数组模板

两次循环时,必须先循环遍历物品。再倒序遍历空间

46. 携带研究材料(第六期模拟笔试) (kamacoder.com)

import java.util.*;

public class Main{

    public static void main (String[] args) {
        // 录取 M 代表研究材料的种类, N,代表小明的行李空间。
        Scanner input = new Scanner(System.in);
        int m = input.nextInt();
        int n = input.nextInt();

        //  M 个正整数,代表每种研究材料的所占空间。 
        int weight[] = new int[m];
        for(int i = 0; i < m; i++){
            weight[i] = input.nextInt();
        }
        //  M 个正整数,代表每种研究材料的价值。
        int value[] = new int[m];
        for(int i = 0; i < m; i++){
            value[i] = input.nextInt();
        }

        //初始化dp数组
        int dp[] =  new int[n + 1];
        for(int i = 0; i <= n; i++){
            dp[i] = 0;
        }

        for(int i = 0; i < m; i++){
            for(int j = n; j >= weight[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }

        System.out.println(dp[n]);
    }
}

3  是否能装满一定容量背包类型

416. 分割等和子集 - 力扣(LeetCode)

class Solution {
    public boolean canPartition(int[] nums) {
        if(nums == null || nums.length == 0){
            return false;
        }
        //遍历数组计算总结果
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        //如果结果是奇数,那么无法分割
        if(sum % 2 != 0){
            return false;
        }
        int target = sum / 2;

        //初始dp数组
        int dp[][] = new int[nums.length][target + 1];
        //第一行
        for(int i = nums[0]; i <= target; i++){
            dp[0][i] = nums[0];
        }
        //第一列
        for(int j = 0; j < nums.length; j++){
            dp[j][0] = 0;
        }

        for(int i = 1; i < nums.length; i++){
            for(int j = 0; j <= target; j++){
                if(j < nums[i]){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
                }
            }
        }
        return dp[nums.length - 1][target] == target;
        
    }
}

/*
 //边界判断
        if(nums == null || nums.length == 0){
            return false;
        }
        //遍历数组计算总结果
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        //如果结果是奇数,那么无法分割
        if(sum % 2 != 0){
            return false;
        }
        int target = sum / 2;

        //初始01背包
        //明确含义:dp[i] 代表 从0到i 的最大价值,也就是最大的和 i是第几个数,i也是数字i的重量
        int dp[] = new int[target + 1];
        for(int i = 0; i <= target; i++){
            dp[i] = 0;
        }

        for(int i = 0; i < nums.length; i++){
            for(int j = target; j >= nums[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }

        return dp[target] == target;
        */

4  一定容量尽可能装满背包类型

1049. 最后一块石头的重量 II - 力扣(LeetCode)

class Solution {
    public int lastStoneWeightII(int[] stones) {
        //处理边界
        if(stones.length == 0 || stones == null){
            return 0;
        }

        int n = stones.length;

        //计算总和
        int sum = 0;
        for(int i = 0; i < n; i++){
            sum += stones[i];
        }

        //一半
        int target = sum / 2;

        //初始dp数组
        int dp[] = new int[target + 1];

        for(int i = 0; i < n; i++){
            for(int j = target; j >= stones[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }

        return sum - 2 * dp[target];
    }
}

5  装满背包有多少种方法类型

. - 力扣(LeetCode)

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        // 数组的和
        int sum = 0;
        for(int num : nums){
            sum += num;
        }

        // 背包容量
        // 求背包容量公式:int bagsize = (sum + target) / 2;
        // 要先判断是否有效
        if(Math.abs(target) > sum){
            return 0;
        }
        if((sum + target) % 2 != 0){
            return 0;
        }

        // 背包容量
        int bagsize = (sum + target) / 2;

        // 定义dp数组
        int dp[] = new int[bagsize + 1];
        dp[0] = 1;

        for(int i = 0; i < nums.length; i++){
            for(int j = bagsize; j >= nums[i]; j--){
                dp[j] += dp[j - nums[i]];
            }
        }

        return dp[bagsize];

    }
}

6  两个维度装满背包类型

474. 一和零 - 力扣(LeetCode)

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        // 定义dp数组,两个维度
        int dp[][] = new int[m + 1][n + 1];
        int zeroCount = 0;
        int oneCount = 0;

        //遍历每一个元素
        for(String s : strs){
            zeroCount = 0;
            oneCount = 0;

            // 记录0和1的数量
            for(char c : s.toCharArray()){
                if(c == '0'){
                    zeroCount++;
                }else{
                    oneCount++;
                }
            }

            for(int i = m; i >= zeroCount; i--){
                for(int j = n; j >= oneCount; j--){
                    dp[i][j] = Math.max(dp[i][j], dp[i - zeroCount][j - oneCount] + 1);
                }
            }
        }

        return dp[m][n];


    }
}

五  完全背包模板

52. 携带研究材料(第七期模拟笔试) (kamacoder.com)

用一维数组和0-1背包模板区别:1  完全背包在两层遍历的时候,顺序没有要求

                                  2  第二层遍历,是顺序遍历不是倒叙遍历 

import java.util.*;

public class Main{
    
    public static void main (String[] args) {
        Scanner input = new Scanner(System.in);
        
        //种类和行李空间
        int n = input.nextInt();
        int v = input.nextInt();
        
        int value[] = new int[n];
        int weight[] = new int[n];
        
        // 记录种类和对应的空间
        for(int i = 0; i < n; i++){
            int wi = input.nextInt();
            int vi = input.nextInt();
            
            value[i] = vi;
            weight[i] = wi;
        }
        
        int dp[] = new int[v + 1];
        
        for(int i = 0; i < n; i++){
            for(int j = weight[i]; j <= v; j++){
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        
        System.out.println(dp[v]);
        
        
    }
}

1 求装满一定容量背包的组合数类型

如果求组合数就是外层for循环遍历物品,内层for遍历背包

518. 零钱兑换 II - 力扣(LeetCode)

class Solution {
    public int change(int amount, int[] coins) {

        // 记录数组的长度
        int len = coins.length;
        // 背包容量
        int bigSize = amount;

        // 初始化dp数组
        int dp[] = new int[bigSize + 1];
        dp[0] = 1;

        for(int i = 0; i < coins.length; i++){
            for(int j = coins[i]; j <= bigSize; j++){
                dp[j] += dp[j - coins[i]];
            }
        }

        return dp[bigSize];



    }
}

2  求装满一定容量背包的排列数类型

377. 组合总和 Ⅳ - 力扣(LeetCode)

如果求排列数就是外层for遍历背包,内层for循环遍历物品

如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!

class Solution {
    public int combinationSum4(int[] nums, int target) {

        int len = nums.length;

        int dp[] = new int[target + 1];
        dp[0] = 1;

        for(int i = 0; i <= target; i++){
            for(int j = 0; j < nums.length; j++){
                if(i >= nums[j]){
                    dp[i] += dp[i - nums[j]];
                }
            }
        }

        return dp[target];



    }
}

六  普通题概览

斐波那契数

509. 斐波那契数 - 力扣(LeetCode)

class Solution {
    public int fib(int n) {
        // 边界处理
        if(n <= 1){
            return n;
        }

        //1 确定数组含义
        // 下标是指第几个数
        // dp[i] 指第i个数对应的值

        //2 确定递推公式
        // dp[i] = dp[i - 1] + dp[i - 2]
        
        //3 初始dp数组
        int dp[] = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;

        //4 确定遍历顺序
        for(int i = 2; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];

    }
}

爬楼梯

70. 爬楼梯 - 力扣(LeetCode)

class Solution {
    public int climbStairs(int n) {
        // 边界处理
        if(n <= 3){
            return n;
        }

        //1 确定数组的含义
        //i 代表第几层楼梯
        //dp[i] 代表有多少种方法

        //2 初始化数组
        int dp[] = new int[n + 1];
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 3;
        dp[4] = 5;

        //3 定义dp公式  
        //dp[i] = dp[i - 1] + dp[i - 2];

        //4 从左到右遍历
        for(int i = 4; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        } 
        //5 打印第n街楼梯可以到达的方法数
        return dp[n];


    }
}

使用最小花费爬楼梯

746. 使用最小花费爬楼梯 - 力扣(LeetCode)

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        //1 处理边界
        // if(cost.length <= 1){
        //     return 0;
        // }

        //2 初始dp数组
        int dp[] = new int[cost.length + 1];
        dp[0] = 0;
        dp[1] = 0;

        //3 确定递推公式
        // dp[i] = Math.min(dp[i - 1] + cost[i], dp[i - 2] + cost[i]);

        //4 遍历
        for(int i = 2; i <= cost.length; i++){
            dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }

        //输出
        return dp[cost.length];


    }
}

不同路径

62. 不同路径 - 力扣(LeetCode)

class Solution {
    public int uniquePaths(int m, int n) {
        //1 初始化数组
        int dp[][] = new int[m + 1][n + 1];
        for(int i = 0; i <= m; i++){
            dp[i][0] = 1;
        }
        for(int j = 0; j <= n; j++){
            dp[0][j] = 1;
        }

        //遍历
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }

        //输出
        return dp[m - 1][n - 1];



    }
}

不同路径 II

63. 不同路径 II - 力扣(LeetCode)

class Solution {
    // 计算给定障碍物网格中从左上角到右下角的唯一路径数
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        // 获取网格的行数和列数
        int x = obstacleGrid.length; // 行数
        int y = obstacleGrid[0].length; // 列数

        // 初始化动态规划数组,多添加一行和一列是为了处理边界情况
        int dp[][] = new int[x + 1][y + 1];
        
        // 初始化第一行,如果遇到障碍物,则该行后续位置的路径数为0
        for(int i = 0; i < x; i++){
            if(obstacleGrid[i][0] == 1){ // 如果第一列有障碍物
                break; // 则该行后续位置的路径数为0,直接跳出循环
            }
            dp[i][0] = 1; // 如果没有障碍物,则路径数为1(只能从左边来)
        }
        
        // 初始化第一列,如果遇到障碍物,则该列后续位置的路径数为0
        for(int i = 0; i < y; i++){
            if(obstacleGrid[0][i] == 1){ // 如果第一行有障碍物
                break; // 则该列后续位置的路径数为0,直接跳出循环
            }
            dp[0][i] = 1; // 如果没有障碍物,则路径数为1(只能从上边来)
        }

        // 遍历网格,从第二行第二列开始(因为第一行和第一列已经初始化)
        for(int i = 1; i < x; i++){
            for(int j = 1; j < y; j++){
                // 如果当前位置没有障碍物
                if(obstacleGrid[i][j] == 0){
                    // 则当前位置的路径数为左边和上边路径数之和
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                }
                // 如果当前位置有障碍物,则路径数为0
            }
        }
        
        // 返回右下角的路径数,即从左上角到右下角的唯一路径数
        return dp[x - 1][y - 1];
    }
}

整数拆分(难)

343. 整数拆分 - 力扣(LeetCode)

class Solution {
    /**
     * 将一个正整数 n 拆分成 k 个正整数的和,使得这些整数的乘积最大。
     * @param n 需要拆分的正整数
     * @return 最大乘积
     */
    public int integerBreak(int n) {
        // 初始化动态规划数组,长度为 n+1,因为问题中 n 从 0 开始
        int dp[] = new int[n + 1];
        
        // 特殊情况处理
        dp[0] = 0; // 如果 n 为 0,没有数字可以拆分,乘积为 0
        dp[1] = 0; // 如果 n 为 1,乘积为 0,因为没有正整数可以拆分
        dp[2] = 1; // 如果 n 为 2,最优拆分是 1+1,乘积为 1

        // 从 3 开始遍历,因为 0、1、2 已经处理
        for(int i = 3; i <= n; i++){
            // 尝试所有可能的拆分方式
            for(int j = 1; j <= i - j; j++){ // j 表示拆分的第一个数,i-j 表示第二个数
                // 更新 dp[i] 的值,取当前值和拆分后乘积的最大值
                // 比较直接乘以 (i - j) 和拆分 (i - j) 的两种情况
                dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
            }
        }

        // 返回 n 的最大乘积
        return dp[n];
    }
}

不同的二叉搜索树(难)

二叉搜索树的性质是  左中右  对应的数值是  小中大  所以才有了dp的推导式

96. 不同的二叉搜索树 - 力扣(LeetCode)

class Solution {
    /**
     * 计算给定数量的节点可以形成的不同二叉搜索树(BST)的数量。
     * @param n 节点的数量
     * @return 不同二叉搜索树的数量
     */
    public int numTrees(int n) {
        // 初始化动态规划数组,长度为 n+1,因为问题中 n 从 0 开始
        int dp[] = new int[n + 1];
        
        // 特殊情况处理
        dp[0] = 1; // 如果没有节点,只有一种空树的情况
        dp[1] = 1; // 如果有一个节点,只有一种BST,即单个节点本身

        // 从 2 开始遍历,因为 0 和 1 已经处理
        for(int i = 2; i <= n; i++){
            // 对于每个节点数 i,尝试所有可能的根节点
            for(int j = 1; j <= i; j++){
                // 计算以 j 为根的二叉搜索树的数量
                // 左子树有 j-1 个节点,右子树有 i-j 个节点
                // 根据组合数的性质,dp[i] += dp[j - 1] * dp[i - j]
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }

        // 返回 n 个节点可以形成的不同二叉搜索树的数量
        return dp[n];
    }
}

按规则计算统计结果

LCR 191. 按规则计算统计结果 - 力扣(LeetCode)

class Solution {
    /**
     * 计算数组中每个元素的左乘积和右乘积,并将结果存储在返回数组中。
     *
     * @param arrayA 输入的整数数组
     * @return 一个新数组,其中每个元素是原数组对应位置元素的左乘积和右乘积的乘积
     */
    public int[] statisticalResult(int[] arrayA) {
        // 检查输入数组是否为空或null,如果是,则直接返回原数组
        if (arrayA == null || arrayA.length == 0) {
            return arrayA;
        }

        // 获取输入数组的长度
        int len = arrayA.length;

        // 初始化两个数组,分别用于存储每个元素的左乘积和右乘积
        int left[] = new int[len];
        int right[] = new int[len];

        // 第一个元素的左乘积为1(因为没有更左边的元素)
        left[0] = 1;

        // 最后一个元素的右乘积为1(因为没有更右边的元素)
        right[len - 1] = 1;

        // 计算每个元素的左乘积
        for (int i = 1; i < len; i++) {
            // 左乘积是当前元素和前一个元素左乘积的乘积
            left[i] = left[i - 1] * arrayA[i - 1];
        }

        // 计算每个元素的右乘积
        for (int i = len - 2; i >= 0; i--) {
            // 右乘积是当前元素和后一个元素右乘积的乘积
            right[i] = right[i + 1] * arrayA[i + 1];
        }

        // 初始化返回数组,用于存储每个元素的左乘积和右乘积的乘积
        int ret[] = new int[len];

        // 计算每个元素的左乘积和右乘积的乘积,并存储在返回数组中
        for (int i = 0; i < len; i++) {
            ret[i] = right[i] * left[i];
        }

        // 返回计算结果
        return ret;
    }
}

单词拆分

import java.util.HashSet;
import java.util.List;
import java.util.Set;

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        // 将单词字典转换为一个集合,便于快速查找
        Set<String> set = new HashSet<>(wordDict);

        // 获取字符串 s 的长度
        int n = s.length();

        // 创建一个布尔类型的动态规划数组 dp,长度为 n + 1
        // dp[i] 表示字符串 s 的前 i 个字符是否可以被拆分成字典中的单词
        boolean[] dp = new boolean[n + 1];

        // 初始化 dp[0] 为 true,因为空字符串可以被拆分(不需要拆分)
        dp[0] = true;

        // 外层循环:遍历字符串 s 的所有可能的子串长度
        for (int i = 1; i <= n; i++) {
            // 内层循环:对于长度为 i 的子串,尝试所有可能的拆分点 j
            for (int j = 0; j < i; j++) {
                // 检查两个条件:
                // 1. dp[j] 为 true,即前 j 个字符可以被拆分
                // 2. 从 j 到 i 的子串是否在字典中
                if (dp[j] && set.contains(s.substring(j, i))) {
                    // 如果两个条件都满足,说明前 i 个字符可以被拆分
                    dp[i] = true;
                    // 找到一个有效的拆分点后,提前结束内层循环
                    break;
                }
            }
        }

        // 返回 dp[n],表示整个字符串 s 是否可以被拆分
        return dp[n];
    }
}

三角形最小路径和

import java.util.List;

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int n = triangle.size();
        if (n == 0)
            return 0;
        // dp[i][j] := triangle[0..i] 到达第 i 层第 j 个元素的最小路径和
        int[][] dp = new int[n][n];
        dp[0][0] = triangle.get(0).get(0);

        // 自顶向下填表
        for (int i = 1; i < n; i++) {
            // 每层第一个只能从上一层第一个下来
            dp[i][0] = dp[i - 1][0] + triangle.get(i).get(0);
            // 中间位置可从 dp[i-1][j-1] 或 dp[i-1][j] 转移
            for (int j = 1; j < i; j++) {
                dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j])
                        + triangle.get(i).get(j);
            }
            // 每层最后一个只能从上一层最后一个转移
            dp[i][i] = dp[i - 1][i - 1] + triangle.get(i).get(i);
        }

        // 在最后一层取最小
        int ans = dp[n - 1][0];
        for (int j = 1; j < n; j++) {
            ans = Math.min(ans, dp[n - 1][j]);
        }
        return ans;
    }
}

最大正方形

class Solution {
    public int maximalSquare(char[][] matrix) {
        // 获取矩阵的行数和列数
        int n = matrix.length; // 行数
        int m = matrix[0].length; // 列数

        // 创建一个动态规划数组 dp,dp[i][j] 表示以 (i, j) 为右下角的最大正方形的边长
        int dp[][] = new int[n][m];

        // 用于记录最大正方形的边长
        int max = 0;

        // 遍历矩阵的每个元素
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                // 如果当前元素为 '0',则无法构成正方形,dp[i][j] 为 0
                if (matrix[i][j] == '0') {
                    dp[i][j] = 0;
                }
                // 如果当前元素为 '1'
                if (matrix[i][j] == '1') {
                    // 如果是第一行或第一列,只能构成边长为 1 的正方形
                    if (i == 0 || j == 0) {
                        dp[i][j] = 1;
                    } else {
                        // 对于其他位置,dp[i][j] 的值取决于其左、上、左上三个位置的最小值加 1
                        // 这是因为以 (i, j) 为右下角的正方形,其边长受限于这三个位置的最小正方形边长
                        dp[i][j] = Math.min(
                            dp[i - 1][j], // 上方的正方形边长
                            Math.min(dp[i][j - 1], dp[i - 1][j - 1]) // 左方和左上方的正方形边长
                        ) + 1;
                    }
                }
                // 更新最大正方形的边长
                max = Math.max(max, dp[i][j]);
            }
        }

        // 返回最大正方形的面积(边长的平方)
        return max * max;
    }
}



最大子数组和

class Solution {
    public int maxSubArray(int[] nums) {
        // 初始化前缀和变量,用于存储当前子数组的和
        int preSum = 0;
        // 初始化最大子数组和,初始值为数组的第一个元素
        int max = nums[0];

        // 遍历数组中的每个元素
        for (int x : nums) {
            // 更新前缀和:
            // 如果前缀和加上当前元素 x 的值大于 x 本身,则保留前缀和加上 x 的值;
            // 否则,直接从当前元素 x 开始新的子数组
            preSum = Math.max(x, preSum + x);

            // 更新最大子数组和:
            // 比较当前的最大子数组和与当前的前缀和,取较大值
            max = Math.max(max, preSum);
        }

        // 返回最终的最大子数组和
        return max;
    }
}

乘积最大子数组

class Solution {
    public int maxProduct(int[] nums) {
        // 获取数组的长度
        int n = nums.length;

        // 创建两个数组,分别用于存储以第 i 个元素结尾的子数组的最大乘积和最小乘积
        int maxF[] = new int[n];
        int minF[] = new int[n];

        // 初始化第一个元素的最大乘积和最小乘积
        maxF[0] = nums[0];
        minF[0] = nums[0];

        // 遍历数组,从第二个元素开始
        for (int i = 1; i < n; i++) {
            // 计算以第 i 个元素结尾的子数组的最大乘积
            // 有三种可能:
            // 1. 当前元素本身
            // 2. 前一个最大乘积与当前元素的乘积
            // 3. 前一个最小乘积与当前元素的乘积(负数乘以负数可能变成最大值)
            maxF[i] = Math.max(maxF[i - 1] * nums[i], Math.max(nums[i], minF[i - 1] * nums[i]));

            // 计算以第 i 个元素结尾的子数组的最小乘积
            // 有三种可能:
            // 1. 当前元素本身
            // 2. 前一个最小乘积与当前元素的乘积
            // 3. 前一个最大乘积与当前元素的乘积(正数乘以负数可能变成最小值)
            minF[i] = Math.min(minF[i - 1] * nums[i], Math.min(nums[i], maxF[i - 1] * nums[i]));
        }

        // 初始化结果为第一个元素的最大乘积
        int res = maxF[0];

        // 遍历 maxF 数组,找到最大值
        for (int i = 1; i < n; i++) {
            res = Math.max(res, maxF[i]);
        }

        // 返回最终结果
        return res;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值