「动态规划」如何求乘积最大子数组?

152. 乘积最大子数组icon-default.png?t=N7T8https://leetcode.cn/problems/maximum-product-subarray/description/

给你一个整数数组nums,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。测试用例的答案是一个32位整数。

  1. 输入:nums = [2,3,-2,4],输出:6,解释:子数组[2,3]有最大乘积6。
  2. 输入:nums = [-2,0,-1],输出:0,解释:结果不能为2,因为[-2,-1]不是子数组。

提示:1 <= nums.length <= 2 * 10^4,-10 <= nums[i] <= 10,nums的任何前缀或后缀的乘积都保证是一个32位整数。


我们用动态规划的思想来解决这个问题。

确定状态表示:根据经验和题目要求,我们可以选择用dp[i]表示,以i位置为结尾的所有子数组中,最大的乘积。比如,dp[3]就表示:下标范围在[0, 3],[1, 3],[2, 3],[3, 3]这4个子数组中,最大的乘积。但是这样的状态表示是推不出状态转移方程的,因为被乘数和乘数的正负也会影响到乘积的大小,比如2个很小的负数相乘的结果会很大(-1000 x -1000 = 1000000)。所以,我们还需要定义一个保存最小乘积的状态表示,也就是说:

  • 用f[i]表示,以i位置为结尾的所有子数组中,最大的乘积。
  • 用g[i]表示,以i位置为结尾的所有子数组中,最小的乘积。

推导状态转移方程:考虑f[i],即以i位置为结尾的所有子数组中最大的乘积,分类讨论以下情况:

  • 如果子数组的长度为1,也就是说子数组的下标范围是[i, i],那么最大的乘积就是nums[i]本身。
  • 如果子数组的长度大于1,最大的乘积就和nums[i]的正负相关。
    • 如果nums[i]是正数,那么子数组中除了nums[i]的其他元素的乘积越大,子数组的乘积就越大(因为y = kx,k > 0时是增函数,x越大,y越大)。也就是说,此时最大的乘积就是以i - 1位置为结尾的所有子数组中的最大的乘积乘以nums[i],即f[i - 1] * nums[i]。
    • 如果nums[i]是负数,那么子数组中除了nums[i]的其他元素的乘积越小,子数组的乘积就越大(因为y = kx,k < 0时是减函数,x越小,y越大)。也就是说,此时最大的乘积就是以i - 1位置为结尾的所有子数组中的最小的乘积乘以nums[i],即g[i - 1] * nums[i]。

事实上,乘积的最大值一定是nums[i],f[i - 1] * nums[i],g[i - 1] * nums[i]这三者之一,所以f[i] = max(nums[i], f[i - 1] * nums[i], g[i - 1] * nums[i])。乘积的最小值同理,即g[i] = min(nums[i], f[i - 1] * nums[i], g[i - 1] * nums[i])

初始化:根据状态转移方程,计算f[0]和g[0]时会越界,所以要对其初始化。根据状态表示,f[0]和g[0]分别表示以0为结尾的所有子数组中,乘积的最大值和最小值,显然以0为结尾的子数组只有下标范围是[0, 0]的数组本身,所以f[0] = g[0] = nums[0]

填表顺序:根据状态转移方程,f[i]和g[i]都依赖于f[i - 1]和g[i - 1],所以应从左往右,同时填f表和g表

返回值:由于并不确定子数组结尾的下标,根据状态表示,应返回f表的最大值

细节问题:f表和g表的规模和nums相同,都是1 x n

时间复杂度:O(N),空间复杂度:O(N)。

但是以下代码会有一个测试用例通不过……

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size();

        // 创建dp表
        vector<int> f(n);
        auto g = f;

        // 初始化
        f[0] = g[0] = nums[0];

        // 填表
        for (int i = 1; i < n; i++) {
            int x = nums[i];
            int y = f[i - 1] * x;
            int z = g[i - 1] * x;

            f[i] = max(x, max(y, z));
            g[i] = min(x, min(y, z));
        }

        // 返回结果
        return *max_element(f.begin(), f.end());
    }
};

通不过的测试用例:[0,10,10,10,10,10,10,10,10,10,-10,10,10,10,10,10,10,10,10,10,0],报错:Line 17: Char 30: runtime error: signed integer overflow: 1000000000 * -10 cannot be represented in type 'int' (solution.cpp) SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior solution.cpp:17:30

溢出了?好好好,这么玩是吧。仔细想想,题目描述中说了:nums的任何前缀或后缀的乘积都保证是一个32位整数,好像没毛病?因为第一个数和最后一个数是0,那么nums的任何前缀或后缀的乘积都是0。好一个文字游戏!

我试着把int改成long long,如下:

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size();

        // 创建dp表
        vector<long long> f(n);
        auto g = f;

        // 初始化
        f[0] = g[0] = static_cast<long long>(nums[0]);

        // 填表
        for (int i = 1; i < n; i++) {
            long long x = static_cast<long long>(nums[i]);
            long long y = f[i - 1] * x;
            long long z = g[i - 1] * x;

            f[i] = max(x, max(y, z));
            g[i] = min(x, min(y, z));
        }

        // 返回结果
        return static_cast<int>(*max_element(f.begin(), f.end()));
    }
};

结果同样的测试用例:[0,10,10,10,10,10,10,10,10,10,-10,10,10,10,10,10,10,10,10,10,0],报错:Line 18: Char 36: runtime error: signed integer overflow: -1000000000000000000 * 10 cannot be represented in type 'long long' (solution.cpp) SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior solution.cpp:18:36

!!?long long还能溢出,活久见!最后改用double,就过了。这个测试用例有毒吧。

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size();

        // 创建dp表
        vector<double> f(n);
        auto g = f;

        // 初始化
        f[0] = g[0] = static_cast<double>(nums[0]);

        // 填表
        for (int i = 1; i < n; i++) {
            double x = static_cast<double>(nums[i]);
            double y = f[i - 1] * x;
            double z = g[i - 1] * x;

            f[i] = max(x, max(y, z));
            g[i] = min(x, min(y, z));
        }

        // 返回结果
        return static_cast<int>(*max_element(f.begin(), f.end()));
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/713984.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WebGL学习【焕新计划】

WebGL基础 在正式进入webgl之前&#xff0c;我想有必要简单了解一下渲染管线&#xff0c;毕竟它贯穿webgl学习的整个过程。 渲染管线流程图&#xff1a; webgl着色器简单语法&#xff1a; 在GLSL&#xff08;OpenGL Shading Language&#xff09;中&#xff0c;常见的变量类…

企业化运维(4)_tomcat

###1.配置tomcat### 可以将tomcat部署在server2主机&#xff0c;与nginx主服务器分开&#xff0c;便于进行交互存储。 下载安装jdk与tomcat&#xff0c;并开启服务&#xff0c;便可以在浏览器进行访问。 [rootserver3 ~]# rpm -ivh jdk-8u121-linux-x64.rpm [rootserver3 ~]#…

Mybatis-Plus多种批量插入方案对比

背景 六月某日上线了一个日报表任务&#xff0c;因是第一次上线&#xff0c;故需要为历史所有日期都初始化一次报表数据 在执行过程中发现新增特别的慢&#xff1a;插入十万条左右的数据&#xff0c;SQL执行耗费高达三分多钟 因很早就听闻过mybatis-plus的[伪]批量新增的问题&…

万事开头难——Java实现俄罗斯小方块【第一步】

目录 技术实现&#xff1a; 1.初始化游戏窗口&#xff1b; 1.1 什么是窗口&#xff1a; 1.2 Swing 1.3 JFrame创建窗口&#xff1a; 1.3.1创建窗口的逻辑 1.3.2.设置简单的页面 1.3.3.优化 1.3.4.设置标题 1.4 创建游戏窗口 技术实现&#xff1a; 1.初始化游戏窗口&am…

【CT】LeetCode手撕—20. 有效的括号

题目 原题连接&#xff1a;20. 有效的括号 1- 思路 模式识别 模式1&#xff1a;括号左右匹配 ——> 借助栈来实现 ——> Deque<Character> deque new LinkedList<>()模式2&#xff1a;顺序匹配 ——> 用 if 判断 具体思路 1.遇到左括号 直接入栈相应…

ARM32开发--电源管理单元

知不足而奋进 望远山而前行 目录 文章目录 前言 学习目标 学习内容 PMU 电源域 VDD/VDDA域 备份域 1.2V域 省电模式 睡眠模式 深度睡眠模式 待机模式 几种模式总结 WFI和WFE指令 案例需求 模式初始化 源码 总结 前言 在嵌入式系统中&#xff0c;有效的电池管…

【AI基础】第六步:纯天然保姆喂饭级-安装并运行qwen2-7b

整体步骤类似于 【AI基础】第五步&#xff1a;纯天然保姆喂饭级-安装并运行chatglm3-6b-CSDN博客。 此系列文章列表&#xff1a; 【AI基础】概览 【AI基础】第一步&#xff1a;安装python开发环境-windows篇_下载安装ai环境python 【AI基础】第一步&#xff1a;安装python开发环…

面试题 17.05. 字母与数字

链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题解&#xff1a;把字母看成1&#xff0c;把数字看为-1&#xff0c;将题目变为求的和为0的最长连续子数组 class Solution { public:vector<string> findLongestSubarray(vector<string>& array) …

MySQL查询练习题1.平均工资2.查询各部门的总薪水3.查询总薪水排名第二的部门4.查询姓名重复的员工信息5.查询各部门薪水大于900的男性员工的平均薪水

创建一个员工表emp&#xff0c;包含字段&#xff1a;姓名name&#xff0c;性别sex&#xff0c;部门depart&#xff0c;工资salary create table emp(name varchar(30) not null,sex varchar(30) not null,depart int not null,salary int not null); 插入数据打印为 mysql>…

Windows Server 2008 r2 IIS .NET

Windows Server 2008 r2 IIS .NET

2-2 基于matlab的变邻域

基于matlab的变邻域&#xff0c;含变惯性权重策略的自适应离散粒子群算法&#xff0c;适应函数是多式联运路径优化距离。有10城市、30城市、75城市三个案例。可直接运行。 2-2 路径规划 自适应离散粒子群算法 - 小红书 (xiaohongshu.com)

MySQL基础——多表查询和事务

目录 1多表关系 2多表查询概述 3连接查询 3.1内连接 3.2左外连接 3.3右外连接 3.4自连接 4联合查询 5子查询 5.1标量子查询(子查询结果为单个值) 5.2列子查询(子查询结果为一列) 5.3行子查询(子查询结果为一行) 5.4表子查询(子查询结果为多行多列) 6事务简介和操…

模拟电子技术基础(二)--PN结

PN结的本质 芯片都是由硅晶体制成&#xff0c;单个硅原子最外层有带有4个电子 在纯硅当中这些电子会两两形成共价键&#xff0c;此时周围形成非常稳定的八电子结构 在一个回路中&#xff0c;灯泡不亮&#xff0c;不导通&#xff0c;因为电池无法吸引其中的电子离开&#xff0c…

机器学习在医学领域中的应用|文献精析·24-06-13

小罗碎碎念 2024-06-13&#xff5c;文献精析&#xff1a;机器学习在医学领域中的应用 为了系统性地和大家梳理一下机器学习在医学领域中的应用&#xff0c;我特意去找了一篇文献&#xff0c;把其中有价值的信息筛选出来了。但是我没选的内容不代表不重要&#xff0c;感兴趣的可…

高分论文密码---大尺度空间模拟预测与数字制图

大尺度空间模拟预测和数字制图技术和不确定性分析广泛应用于高分SCI论文之中&#xff0c;号称高分论文密码。大尺度模拟技术可以从不同时空尺度阐明农业生态环境领域的内在机理和时空变化规律&#xff0c;又可以为复杂的机理过程模型大尺度模拟提供技术基础。我们将结合一些经典…

Java SE进阶必备:数组中的命令行参数详解

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

5000天后的世界

为何可以预见未来 1993年&#xff0c;在互联网的黎明时代&#xff0c;凯文凯利创办了《连线》杂志。他曾经采访过以比尔盖茨、史蒂夫乔布斯、杰夫贝佐斯为代表的一众风云创业家。《连线》杂志是全球发行的世界著名杂志&#xff0c;一直致力于报道科学技术带来的经济、社会变革…

Linux screen命令使用

文章目录 1. 前言2. screen是什么?3. screen使用场景描述3. screen常用命令4. 小结5. 参考 1. 前言 实际开发中用到的云服务器&#xff0c;如果项目使用的是python&#xff0c;需要利用项目运行一些时间较长的项目程序脚本的话&#xff0c;由于我们通过ssh连接远端服务器&…

【深度学习】TCN,An Empirical Evaluation of Generic Convolutional【二】

文章目录 膨胀卷积什么是膨胀卷积膨胀卷积公式PyTorch代码 从零开始手动实现一个1D膨胀卷积&#xff0c;不使用PyTorch的nn.Conv1d1. 基本概念2. 手动实现1D膨胀卷积 TCN结构如何使用TCN源码说明1. Chomp1d 类2. TemporalBlock 类3. TemporalConvNet 类 使用方法 膨胀卷积 什么…

计算机毕业设计hadoop+spark+hive知识图谱股票推荐系统 股票数据分析可视化大屏 股票基金爬虫 股票基金大数据 机器学习 大数据毕业设计

哈 尔 滨 理 工 大 学 毕业设计开题报告 题 目&#xff1a; 基于Spark的股票大数据分析及可视化系统 院 系&#xff1a; 计算机科学与技术学院 数据科学与大数据技术系 2023年10月 一、选题的依据…