「动态规划」如何求乘积为正数的最长子数组长度?

news2024/11/19 3:28:37

1567. 乘积为正数的最长子数组长度icon-default.png?t=N7T8https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/description/

给你一个整数数组nums,请你求出乘积为正数的最长子数组的长度。一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。请你返回乘积为正数的最长子数组长度。

  1. 输入:nums = [1,-2,-3,4],输出:4,解释:数组本身乘积就是正数,值为24。
  2. 输入:nums = [0,1,-2,-3,-4],输出:3,解释:最长乘积为正数的子数组为[1,-2,-3],乘积为6。注意,我们不能把0也包括到子数组中,因为这样乘积为0,不是正数。
  3. 输入:nums = [-1,-2,-3,0,1],输出:2,解释:乘积为正数的最长子数组是[-1,-2]或者[-2,-3]。

提示:1 <= nums.length <= 10^5,-10^9 <= nums[i] <= 10^9。


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

确定状态表示:根据经验和题目要求,我们的第一反应是,用dp[i]表示:以i位置为结尾的所有子数组中,乘积为正数的最长子数组长度。然而这个问题有些耐人寻味,乘积为正数,有可能是负负得正呀!比如nums[i]是负数,那么就需要乘一个负数,才能得到正数。

所以,我们需要分类讨论:

  • 用f[i]表示:以i位置为结尾的所有子数组中,乘积为正数的最长子数组长度。
  • 用g[i]表示:以i位置为结尾的所有子数组中,乘积为负数的最长子数组长度。

推导状态转移方程:考虑f[i],如果以i位置为结尾的子数组的乘积为正数,那么:

  • 如果nums[i]是正数:
    • 如果子数组的长度为1,即子数组只包含nums[i]本身,乘积是正数,符合条件。
    • 如果子数组的长度大于1,那么以i位置为结尾的子数组中,除去nums[i]之外的其余元素的乘积是正数。而除去nums[i]之外的其余元素构成了以i - 1位置为结尾的子数组。所以,以i位置为结尾的子数组的长度,等于以i - 1位置为结尾的子数组的长度加1,并且以i - 1位置为结尾的子数组的乘积是正数,所以此时的总长度是f[i - 1] + 1。注意:如果f[i - 1] = 0,说明以i - 1位置为结尾的子数组的乘积一定不是正数,此时f[i - 1] + 1 = 1,相当于子数组中只有nums[i],符合条件。
  • 如果nums[i]是负数:
    • 如果子数组的长度为1,即子数组只包含nums[i]本身,乘积是负数,不符合条件。
    • 如果子数组的长度大于1,那么以i位置为结尾的子数组中,除去nums[i]之外的其余元素的乘积是负数。而除去nums[i]之外的其余元素构成了以i - 1位置为结尾的子数组。所以,以i位置为结尾的子数组的长度,等于以i - 1位置为结尾的子数组的长度加1,并且以i - 1位置为结尾的子数组的乘积是负数,所以此时的总长度是g[i - 1] + 1。注意:如果g[i - 1] = 0,说明以i - 1位置为结尾的子数组的乘积一定不是负数,此时g[i - 1] + 1 = 1,相当于子数组中只有nums[i],然而nums[i]是负数,乘积是负数,不符合条件,也就是说,这种情况下无法构成乘积是正数的子数组,乘积是正数的最长子数组的长度是0。
  • 如果nums[i] = 0,那么无法构成乘积是正数的子数组,乘积是正数的最长子数组的长度是0。

综上所述:如果nums[i]是正数,那么f[i] = max(1, f[i - 1] + 1),又因为f[i - 1] >= 0,所以f[i] = f[i - 1] + 1。如果nums[i]是负数,要想以i位置为结尾的子数组的乘积是正数,子数组的长度不能等于1,只能大于1,若g[i - 1] = 0,那么无法构成乘积是正数的子数组,f[i] = 0;若g[i - 1] != 0,那么f[i] = g[i - 1] + 1。如果num[i] = 0,那么f[i] = 0。

考虑g[i],如果以i位置为结尾的子数组的乘积为负数,那么:

  • 如果nums[i]是负数:
    • 如果子数组的长度为1,即子数组只包含nums[i]本身,乘积是负数,符合条件。
    • 如果子数组的长度大于1,那么以i位置为结尾的子数组中,除去nums[i]之外的其余元素的乘积是正数。而除去nums[i]之外的其余元素构成了以i - 1位置为结尾的子数组。所以,以i位置为结尾的子数组的长度,等于以i - 1位置为结尾的子数组的长度加1,并且以i - 1位置为结尾的子数组的乘积是正数,所以此时的总长度是f[i - 1] + 1。注意:如果f[i - 1] = 0,说明以i - 1位置为结尾的子数组的乘积一定不是正数,此时f[i - 1] + 1 = 1,相当于子数组中只有nums[i],符合条件。
  • 如果nums[i]是正数:
    • 如果子数组的长度为1,即子数组只包含nums[i]本身,乘积是正数,不符合条件。
    • 如果子数组的长度大于1,那么以i位置为结尾的子数组中,除去nums[i]之外的其余元素的乘积是负数。而除去nums[i]之外的其余元素构成了以i - 1位置为结尾的子数组。所以,以i位置为结尾的子数组的长度,等于以i - 1位置为结尾的子数组的长度加1,并且以i - 1位置为结尾的子数组的乘积是负数,所以此时的总长度是g[i - 1] + 1。注意:如果g[i - 1] = 0,说明以i - 1位置为结尾的子数组的乘积一定不是负数,此时g[i - 1] + 1 = 1,相当于子数组中只有nums[i],然而nums[i]是正数,乘积是正数,不符合条件,也就是说,这种情况下无法构成乘积是正数的子数组,乘积是负数的最长子数组的长度是0。
  • 如果nums[i] = 0,那么无法构成乘积是负数的子数组,乘积是负数的最长子数组的长度是0。

综上所述:如果nums[i]是负数,那么g[i] = max(1, f[i - 1] + 1),又因为f[i - 1] >= 0,所以g[i] = f[i - 1] + 1。如果nums[i]是正数,要想以i位置为结尾的子数组的乘积是负数,子数组的长度不能等于1,只能大于1,若g[i - 1] = 0,那么无法构成乘积是负数的子数组,g[i] = 0;若g[i - 1] != 0,那么g[i] = g[i - 1] + 1。如果num[i] = 0,那么g[i] = 0。

再总结一下:如果nums[i]是正数,那么f[i] = f[i - 1] + 1,g[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;如果nums[i]是负数,那么f[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1,g[i] = f[i - 1] + 1;如果nums[i] = 0,那么f[i] = g[i] = 0。

初始化:根据状态转移方程,我们需要初始化f[0]和g[0]的值,防止越界。我们可以在f和g的最前面加上一个辅助结点。想象一下,如果在nums的最前面加上一个0,并不会影响结果,因为0不会出现在乘积为正数或负数的子数组中。由于nums的最前面加上一个0,根据状态表示,f[0]和g[0]就应该初始化为0。也就是说,辅助结点应该初始化为0

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

返回值:根据状态表示和题目要求,由于我们并不确定子数组的结束位置,所以要返回f表中除了辅助结点之外的最大值

细节问题:由于添加了一个辅助结点,所以f表和g表比nums多了一个元素,若nums有n个元素,f表和g表的规模就是1 x (n + 1),且f表和g表的i位置对应nums[i - 1]。

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

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

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

        // 填表
        for (int i = 1; i <= n; i++) {
            if (nums[i - 1] > 0) {
                f[i] = f[i - 1] + 1;
                g[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;
            } else if (nums[i - 1] < 0) {
                f[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;
                g[i] = f[i - 1] + 1;
            }
        }

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

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

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

相关文章

2024年GIS专业就业现状和解决办法

GIS专业发展历史 我国从20世纪80年代初引进和研究地理信息系统(GIS) 以来&#xff0c;经过30年的飞速发展&#xff0c;地理信息已成为信息时代重要的组成部分之一&#xff0c;被广泛应用于多个领域的建模和决策支持。 在国家数字化政策的加持下&#xff0c;GIS更成为新基建下…

敏感信息加密操作,让开发的系统更加的安全可靠!!

敏感信息加密操作&#xff0c;让开发的系统更加的安全可靠&#xff01;&#xff01;Jasypt&#xff08;Java Simplified Encryption&#xff09;是一个开源的Java库&#xff0c;用于简化加密操作。https://mp.weixin.qq.com/s/sPBV8Ej46YJsElImodRjAQ

反射的原理和操作

反射是框架设计的灵魂 &#xff08;使用的前提条件&#xff1a;必须先得到代表的字节码的Class&#xff0c;Class类用于表示.class文件&#xff08;字节码&#xff09;&#xff09; 在Java中&#xff0c;反射是指在运行时动态地获取、检查和操作类、对象、方法和属性的能力。J…

第2章 Rust初体验3/8:使用Result进行错误处理:编译时错误检查增强代码安全性:猜骰子冷热游戏

讲动人的故事,写懂人的代码 2.3.9 类型的关联函数:简化对象创建和初始化 席双嘉:“那个String::new(),毫无疑问,它确实像C++中的静态成员函数。” 贾克强:“哈哈!是的,两个冒号确实让人联想到一些东西,对吧?” “这其实是Rust中的关联函数(associated function,详…

第四集《唯识与净土》

请大家打开讲义第十面。 我们讲到念佛的功德有两个&#xff1a;第一个是“现世的安乐”&#xff0c;第二个是“来世究竟的解脱”。 这个净土法门的特色&#xff0c;是一种本尊相应法。 也就是说&#xff0c;我们在整个修行破恶生善的过程当中&#xff0c;主要是要仰仗本尊的…

设计师必看|精选免费icon生成网站推荐

在平面设计过程中&#xff0c;如何收集icon素材&#xff1f;(opens in a new tab or window)&#xff1f; 在这里我们为您准备了三个免费、易用的icon生成网站&#xff0c;需要icon材料的同学&#xff0c;记得点赞收藏。 01∣即时设计 作为国内领先的在线设计平台&#xff0…

创建第一个Springboot项目(环境准备、环境存在的问题、启动时存在的问题、启动的方式)

一、环境准备 专业版创建springboot&#xff0c;直接有一个选项可以选择 社区版&#xff0c;需要下载一个spring的插件 不要直接点 install 因为这个插件是付费的&#xff0c;直接点安装只有30天使用期限 在里面找免费版本的下载 然后安装 安装完成后&#xff0c;这个插件名会变…

作为程序员,科班计算机比起非科班有很大优势吗?

在当今这个快速变化的时代&#xff0c;个人的能力与优势成为了职场竞争中的关键因素。在众多的职业选择中&#xff0c;程序员因其独特的技能和市场需求而备受青睐。 然而&#xff0c;并非所有人在18岁就决定要进入计算机行业&#xff0c;许多人都是在大学毕业之后由于种种原因…

一步步教你用Python Selenium抓取动态网页任意行数据

引言 在现代网络中&#xff0c;动态网页越来越普遍&#xff0c;这使得数据抓取变得更具挑战性。传统的静态网页抓取方法在处理动态内容时往往力不从心。本文将详细介绍如何使用Python Selenium抓取动态网页中的任意行数据&#xff0c;并结合代理IP技术以提高抓取的成功率和效率…

PostgreSQL源码分析——缓冲区管理器

这里我们分析一下PG数据库缓冲区的代码。缓冲区是十分重要的&#xff0c;对数据库的性能和稳定性有着直接的影响。缓冲区是数据库SQL计算层与外部存储&#xff08;磁盘&#xff09;交互的关键。数据页的落盘与读取&#xff0c;都要经过缓冲区。 README src/backend/storage/…

本地部署AI模型-phi3

What&#xff1a; Phi-3-Mini被认为是Microsoft计划发布的三款小型机型中的首款。据报道&#xff0c;在语言、推理、编码和数学等领域&#xff0c;它在各种基准测试中的表现优于相同大小和下一个尺寸的模型。 从本质上讲&#xff0c;语言模型是 ChatGPT、Claude、Gemini 等 AI…

WPF/C#:显示分组数据的两种方式

前言 本文介绍自己在遇到WPF对数据进行分组显示的需求时&#xff0c;可以选择的两种方案。一种方案基于ICollectionView&#xff0c;另一种方案基于IGrouping。 基于ICollectionView实现 相关cs代码&#xff1a; [ObservableProperty] private ObservableCollection<Peo…

变声方法大公开,变女声很自然的3款变声器推荐,值得收藏

将男声变成女声并且要很自然的变声器有吗&#xff1f;很多喜欢玩游戏的小伙伴们在进行游戏连麦时&#xff0c;可能为了增加趣味&#xff0c;想要试试变成女声来交流&#xff0c;或者喜欢视频剪辑创作的小伙伴们在进行视频配音时&#xff0c;不想用自己的声音出镜&#xff0c;需…

WGCLOUD的web ssh提示websocket服务连接已断开

这个问题一般是server主机没有开放端口9998&#xff0c;因为9998是web ssh的端口&#xff0c;需要开放 我们只要在防火墙&#xff0c;或者安全软件&#xff0c;把这个端口开放了就可以了

STM32HAL库--NVIC和EXTI

1. 外部中断实验 1.1 NVIC和EXTI简介 1.1.1 NVIC简介 NVIC 即嵌套向量中断控制器&#xff0c;全称 Nested vectored interrupt controller。是ARM Cortex-M处理器中用于管理中断的重要组件。负责处理中断请求&#xff0c;分配优先级&#xff0c;并协调中断的触发和响应。 它是…

Scikit-Learn支持向量机回归

Scikit-Learn支持向量机回归 1、支持向量机回归1.1、最大间隔与SVM的分类1.2、软间隔最大化1.3、支持向量机回归1.4、支持向量机回归的优缺点2、Scikit-Learn支持向量机回归2.1、Scikit-Learn支持向量机回归API2.2、支持向量机回归初体验2.3、支持向量机回归实践(加州房价预测…

氮化铝与氧化铍用于大功率电阻器产品

在过去的几十年里&#xff0c;氧化铍&#xff08;BeO&#xff09;一直是用于高功率应用的射频电阻器和端接的主要基板材料。虽然BeO非常适合电子行业的大功率应用&#xff0c;但其粉尘颗粒是有毒的;如果吸入BeO颗粒&#xff0c;它们可能会导致铍中毒&#xff0c;即肺部炎症。由…

202406最新manjaro安装sogou输入法解决方案(采用aur本地package+sogo deb包解决方案)

本地执行安装方法 1.拉取源码 git clone https://gitee.com/liushuai05/fcitx-sogoupinyin.git cd fcitx-sogoupinyin 2.获取sogo下载地址并替换到源码中 - 下载地址&#xff1a;https://pinyin.sogou.com/linux/ - 点击立即下载->x86_64->下载&#xff0c;然后右键复制…

vue+echarts实现tooltip轮播

效果图如下&#xff1a; 实现步骤如下&#xff1a; 定义一个定时器 timer:null, len: 0,页面一加载就清空定时器&#xff0c;此操作是为了防止重复加载时会设置多个定时器在setOption后设置定时器 this.myChart.clear() this.myChart.setOption(option); this.autoShowTool…

AbMole带你探索颅内压力与肌肉生长的联系:一项突破性研究

在生物医学领域&#xff0c;颅内压力&#xff08;ICP&#xff09;的调控机制一直是研究的热点。最近&#xff0c;一项发表在《PLOS ONE》上的研究为我们揭示了颅内压力与后颅窝肌肉生长之间的潜在联系&#xff0c;为我们理解某些慢性头痛的成因提供了新的视角。 颅内压力的异常…