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

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()));}
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】初识集合深入剖析顺序表(Arraylist)

【数据结构】初识集合&深入剖析顺序表&#xff08;Arraylist&#xff09; 集合体系结构集合的遍历迭代器增强for遍历lambda表达式 List接口中的增删查改List的5种遍历ArrayList详解ArrayList的创建ArrayList的增删查改ArrayList的遍历ArrayList的底层原理 &#x1f680;所属…

【全栈实战】大模型自学:从入门到实战打怪升级,20W字总结(一)

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本栏讲解【全栈实战】大模型自学&#xff1a;从入门到实战打怪升级。 &#x1f514;专栏持续更新&#xff0c;适合人群&#xff1a;本科生、研究生、大模型爱好者&#xff0c;期…

JVM-GC-什么是垃圾

JVM-GC-什么是垃圾 前言 所谓垃圾其实是指&#xff0c;内存中没用的数据&#xff1b;没有任何引用指向这块内存&#xff0c;或者没有任何指针指向这块内存。没有的数据应该被清除&#xff0c;垃圾的处理其实是内存管理问题。 JVM虽然不直接遵循冯诺依曼计算机体系架构&#…

基于flask的网站如何使用https加密通信-问题记录

文章目录 项目场景&#xff1a;问题1问题描述原因分析解决步骤解决方案 问题2问题描述原因分析解决方案 参考文章 项目场景&#xff1a; 项目场景&#xff1a;基于flask的网站使用https加密通信一文中遇到的问题记录 问题1 问题描述 使用下面的命令生成自签名的SSL/TLS证书和…

Docker镜像技术剖析

目录 1、概述1.1 什么是镜像&#xff1f;1.2 联合文件系统UnionFS1.3 bootfs和rootfs1.4 镜像结构1.5 镜像的主要技术特点1.5.1 镜像分层技术1.5.2 写时复制(copy-on-write)策略1.5.3 内容寻址存储(content-addressable storage)机制1.5.4 联合挂载(union mount)技术 2.机制原理…

用PHP来调用API给自己定制一个“每日新闻”

头条新闻汇聚了互联网上的时事动态&#xff0c;提供最新新闻动态、网络热门话题和视频更新等&#xff0c;覆盖社会、政治、体育、经济、娱乐、科技等多个领域&#xff0c;并不断刷新内容。企业应用这一接口后&#xff0c;可以快速吸引更多的用户访问自己的平台。即使是非新闻类…

面向对象的三大特性与类图

1. 面向对象编程的三大特点 Object-oriented programming (OOP) is a paradigm centered around the concept of objects, which can contain data and code to manipulate that data. The three major characteristics of object-oriented programming are encapsulation, in…

天童美语:为了得体退出的那一天,你一定要好好爱孩子

父母最大的成就就是孩子可以独立&#xff0c;自己完成自己的人生。为了得体退出的那一天&#xff0c;你一定要好好爱你的小孩&#xff0c;因为每一天都在过去。当我们站在孩子成长的十字路口&#xff0c;面对那个终将到来的退出时刻&#xff0c;心中总会涌起一股难以言表的情感…

有趣的傅里叶变换与小波变换对比(Python)

不严谨的说&#xff0c;时域和频域分析就是在不同的空间看待问题的&#xff0c;不同空间所对应的原子(基函数)是不同的。你想一下时域空间的基函数是什么&#xff1f;频域空间的基函数是什么&#xff1f;一般的时-频联合域空间的基函数是什么&#xff1f;小波域空间的基函数是什…

摄影师在人工智能竞赛中与机器较量并获胜

摄影师在人工智能竞赛中与机器较量并获胜 自从生成式人工智能出现以来&#xff0c;由来已久的人机大战显然呈现出一边倒的态势。但是有一位摄影师&#xff0c;一心想证明用人眼拍摄的照片是有道理的&#xff0c;他向算法驱动的竞争对手发起了挑战&#xff0c;并取得了胜利。 迈…

课时157:脚本发布_简单脚本_功能函数

2.1.3 功能函数 学习目标 这一节&#xff0c;我们从 基础知识、简单实践、小结 三个方面来学习 基础知识 简介 需求&#xff1a;三条命令其实是一个组合&#xff0c;实现的是一个功能简单实践 实践 查看脚本内容 #!/bin/bash # 功能&#xff1a;打包代码 # 版本: v0.3 #…

代码随想录第29天|贪心算法part3

134.加油站 首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈 每个加油站的剩余量rest[i]为gas[i] - cost[i] 从0开始累加rest[i]&#xff0c;和记为curSum&#xff0c;一旦curSum小于零&#xff0c;说明[0, i]区间都不能作为起始位置 因为我们一直维护的是一个剩余量大…

HCIA11 网络安全之本地 AAA 配置实验

AAA 提供 Authentication&#xff08;认证&#xff09;、Authorization&#xff08;授权&#xff09;和 Accounting&#xff08;计费&#xff09;三种安全功能。 • 认证&#xff1a;验证用户是否可以获得网络访问权。 • 授权&#xff1a;授权用户可以使用哪些服务。 •…

机器学习中的监督学习介绍

In this post well go into the concept of supervised learning, the requirements for machines to learn, and the process of learning and enhancing prediction accuracy. 在这篇文章中&#xff0c;我们将深入探讨监督学习的概念、机器学习的要求以及学习和提高预测准确…

Kotlin 协程:从基础概念到开发实践

前言 上一篇文章 深入理解Android多线程开发:场景应用与解决方案解析 针对Android开发中的多线程应用场景和相应的解决方案做了一个梳理。 总结出了Android开发中多线程编程的几个重要点: 资源复用和优化切线程任务编排并结合示例说明了Kotlin协程在处理上述问题时的优势。 …

如何删除:systemctl status ztncui

要删除 systemctl status ztncui&#xff0c;需要卸载与该服务相关的程序&#xff0c;并停止和禁用该服务。以下是详细的步骤&#xff1a; 停止服务&#xff1a; sudo systemctl stop ztncui 禁用服务&#xff1a; sudo systemctl disable ztncui 删除服务文件&#xff1a;…

归纳贪心好题

很有趣的一道归纳贪心题目 class Solution { public:int minimumAddedCoins(vector<int>& coins, int target) {sort(coins.begin(),coins.end());int n coins.size();int s 0,i0;int res 0;while(s<target){if(i<n&&coins[i]<s1)scoins[i];els…

图像分隔和深度成像技术为什么受市场欢迎-数字孪生技术和物联网智能汽车技术的大爆发?分析一下图像技术的前生后世

图像分隔和深度成像是计算机视觉和图像处理领域的两项重要技术&#xff0c;它们各自有不同的技术基础和要点。 图像分隔技术基础&#xff1a; 机器学习和模式识别&#xff1a; 图像分隔通常依赖于机器学习算法&#xff0c;如支持向量机&#xff08;SVM&#xff09;、随机森林…

算法题--华为od机试考试(开源项目热度榜单、API集群负载统计、分月饼)

目录 开源项目热度榜单 题目描述 输入描述 输出描述 示例1 输入 输出 解析 答案 API集群负载统计 题目描述 输入描述 输出描述 示例1 输入 输出 解析 答案 分月饼 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入 输出 说明 示例3…

Python 操作Redis

本篇介绍在Python中如何使用Redis。 目录 安装类库 引入类库 操作读写 使用pipeline 封装 封装redis 操作 调用 总结 安装类库 首先通过pip list命令查看&#xff0c; 本地安装类库中是否有redis类库&#xff0c; 如果没有开始安装。 安装命令如下&#xff1a; pip…