当前位置: 首页 > news >正文

动态规划算法的欢乐密码(五):子数组系列(上)

专栏:算法的魔法世界

个人主页:手握风云

目录

一、例题讲解

1.1. 最大子数组和

1.2. 环形子数组的最大和

1.3. 乘积最大子数组

1.4. 乘积为正数的最长子数组长度


一、例题讲解

1.1. 最大子数组和

        在给定的数组中,我们需要找到一个连续的子数组,这个子数组中所有元素的和应该是所有可能的连续子数组中最大的。

        我们以i位置为结尾,向前截取子数组,找出最大和,那么状态表示即为以i位置结尾,所有子数组的最大和。dp[i]可以分为两种情况:当子数组长度为1,最大和为nums[i];当子数组长度大于1,nums[i]也得算上,和前面子数组的最大和,而前面的子数组又是以i-1位置为结尾,所以dp[i] = max(dp[i - 1] + nums[i - 1], nums[i - 1])。初始化的时候我们可以在前面加上虚拟头节点保证不越界,同时为了保证填表正确,可将dp[0]初始化为0。填表顺序从左到右。返回值,dp表中的最大值。

class Solution {public int maxSubArray(int[] nums) {int n = nums.length, ret = Integer.MIN_VALUE;int[] dp = new int[n + 1];for (int i = 1; i <= n; i++) {dp[i] = Math.max(dp[i - 1] + nums[i - 1], nums[i - 1]);ret = Math.max(dp[i], ret);}return ret;}
}

1.2. 环形子数组的最大和

        题目要求给定长度为`n`的“环形整数数组”nums,需找出“其中非空、且不重复使用原数组元素”的子数组的最大可能和,最终返回该最大值。核心特点:数组“环形”指末端与开头相连(子数组可同时包含原数组末尾和开头元素,如`nums=[5,-3,5]`的子数组`[5,5]`),但子数组不能重复取用原数组同一位置的元素;需覆盖两种子数组情况——不跨数组边界(如`[3]`)、跨数组边界(如`[5,5]`),取所有合法子数组的最大和。

        这道题可以分为两种情况,第一种情况:最终结果不在环上,相当于就是在求子数组的最大和。第二种情况:最终结果在环上,要想使子数组和最大,可以让环外部分的子数组和最小。

        那么本题的状态表示可以分为两种情况:f[i]表示以i位置为结尾所有子数组的最大和,g[i]表示以i位置为结尾所有子数组的最小和。参照上一题状态转移方程的推导,f[i] = max(f[i - 1] + nums[i], nums[i]),g[i] = min(g[i - 1] + nums[i], nums[i])。填表的时候可以在dp表前面引入虚拟头节点,当i = 1时,子数组最大和是它本身,那么f[0]和g[0]都可以初始化为0。填表顺序,从左到右。返回值,f需要返回里面的最大值。如果g里面全是负数,那么子数组最小和为本身,sum-sum(nums)=0,那么我们只需返回g里面的最小值;有正有负,sum-min(g)。

        完整代码实现:

class Solution {public int maxSubarraySumCircular(int[] nums) {int n = nums.length, sum = 0;for (int i = 0; i < nums.length; i++) {sum += nums[i];}int fMax = Integer.MIN_VALUE, gMin = Integer.MAX_VALUE;int[] f = new int[n + 1];int[] g = new int[n + 1];for (int i = 1; i < (n + 1); i++) {f[i] = Math.max(f[i - 1] + nums[i - 1], nums[i - 1]);fMax = Math.max(f[i], fMax);g[i] = Math.min(g[i - 1] + nums[i - 1], nums[i - 1]);gMin = Math.min(g[i], gMin);}return sum == gMin ? fMax : Math.max(fMax, sum - gMin);}
}

1.3. 乘积最大子数组

        给定整数数组 nums,需找出数组中非空且连续的子数组,使得该子数组的乘积在所有可能的连续子数组中最大,最终返回这个最大乘积(题目保证结果为 32 位整数)。

        根据题目要求,本题的状态转移表示为:以i位置为结尾,所有子数组的最大乘积。如果子数组长度为1,dp[i] = nums[i];如果子数组长度大于1,dp[i] = nums[i] * dp[i - 1]。这里需要判断dp[i - 1]与nums[i]正负情况。所以我们可以直接使用两个dp表f、g。f[i]表示以i位置为结尾,所有子数组的最大乘积;g[i]表示以i位置为结尾,所有子数组的最小乘积。如果nums[i] > 0,f[i] = max(nums[i], f[i - 1] * nums[i]);如果nums[i] < 0,f[i] = max(nums[i], g[i - 1] * nums[i])。我们不需要利用if-else判断,只需3个值取最大值。同理,g表的状态转移方程,min(nums[i], g[i - 1] * nums[i], f[i - 1] * nums[i] )。初始化的时候,因为是乘积,我们可以将2个dp表的第一个值初始化为1,同时保证下标的映射关系。填表顺序,从左到右两个表依次填写。返回值:返回f表里的最大值。

        完整代码实现:

class Solution {public int maxProduct(int[] nums) {int n = nums.length, ret = Integer.MIN_VALUE;int[] f = new int[n + 1];int[] g = new int[n + 1];f[0] = g[0] = 1;for (int i = 1; i <= n; i++) {f[i] = Math.max(nums[i - 1], Math.max(f[i - 1] * nums[i - 1], g[i - 1] * nums[i - 1]));ret = Math.max(ret, f[i]);g[i] = Math.min(nums[i - 1], Math.min(f[i - 1] * nums[i - 1], g[i - 1] * nums[i - 1]));}return ret;}
}

1.4. 乘积为正数的最长子数组长度

        给定整数数组nums,需找出其中乘积为正数的最长连续子数组,最终返回该子数组的长度。

        根据题目要求,本题的状态表示dp[i]为:以i位置为结尾,所有子数组乘积为正数的最大长度。当子数组长度为1时,如果nums[i]>0,dp[i]=1,如果nums[i]<0,dp[i]=0;当子数组长度大于1时,如果nums[i]>0,dp[i]=f[i]+1,如果nums[i]<0,要想乘积为正,那么就要使前面子数组的乘积<0,我们就会发现一个状态转移方程。

        f[i]以i位置为结尾,所有子数组乘积为正数的最大长度;g[i]以i位置为结尾,所有子数组乘积为负数的最大长度。如果g[i-1]==0,同时nums[i]<0,那么此时的f[i]应该=0,因为无法凑出乘积为正数。g[i-1]<0时,f[i]=g[i-1]+1。当nums[i]>0时,f[i]=f[i-1]+1;当nums[i]<0时,f[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1。同样g表的状态转移方程也一样,当nums[i]>0时,g[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;当nums[i]<0时,g[i] = f[i - 1] + 1。填表顺序,两个表从左到右同时填写。返回值:f表里面的最大值。

        完整代码实现:

class Solution {public int getMaxLen(int[] nums) {int n = nums.length, ret = -1;int[] f = new int[n + 1];int[] g = new int[n + 1];f[0] = g[0] = 0;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;}ret = Math.max(ret, f[i]);}return ret;}
}

文章转载自:

http://hB3ZmRCC.jxLtk.cn
http://3SQXU3ye.jxLtk.cn
http://RM96G1Tv.jxLtk.cn
http://h9LUXFFn.jxLtk.cn
http://Wh43CZrB.jxLtk.cn
http://GMvhmSwM.jxLtk.cn
http://mKRgTVuH.jxLtk.cn
http://9NMF78AD.jxLtk.cn
http://Czj543pu.jxLtk.cn
http://ztrcyX6o.jxLtk.cn
http://xVyfUlGF.jxLtk.cn
http://1k06S3Kp.jxLtk.cn
http://L7plFV2N.jxLtk.cn
http://I3s7Og94.jxLtk.cn
http://ORHv2dvg.jxLtk.cn
http://8KiQWOIp.jxLtk.cn
http://pdJcfKto.jxLtk.cn
http://MWbo1qF9.jxLtk.cn
http://OGBeneuy.jxLtk.cn
http://xAtVAtV9.jxLtk.cn
http://X9WFQbvs.jxLtk.cn
http://8IUCFcPf.jxLtk.cn
http://Tyw9LXYV.jxLtk.cn
http://JtEOQiO4.jxLtk.cn
http://KWotIAYl.jxLtk.cn
http://loUamNku.jxLtk.cn
http://dxOJuqTI.jxLtk.cn
http://16mr8GT6.jxLtk.cn
http://70k5m0n4.jxLtk.cn
http://qVdGiT8W.jxLtk.cn
http://www.dtcms.com/a/378425.html

相关文章:

  • 【国内电子数据取证厂商龙信科技】浅析文件头和文件尾和隐写
  • Gradio全解11——Streaming:流式传输的视频应用(8)——Gemini Live API:实时音视频连接
  • [特殊字符] 玩转 Python 命令行参数:从 `-m` 到 `argparse` 的全攻略
  • [免费]基于Python的Django医院管理系统【论文+源码+SQL脚本】
  • 【音视频】Android NDK 与.so库适配
  • 认识鸿蒙——它不是“安卓换皮”
  • YOLO11目标检测运行推理简约GUI界面
  • 如何在 VSCode 中设置默认浏览器为 Chrome 或 Firefox
  • VSCode设置:解决找不到文件的问题
  • rabbitmq的安装
  • 从拓扑排序看有向图的应用
  • 谷歌浏览器
  • openCV 角点检测与 SIFT 特征提取:原理与实战解析
  • 使用Samba网络磁盘作为MacOS时间机器的远程备份磁盘
  • YOLO + OpenPLC + ARMxy:工业智能化视觉识别、边缘计算、工业控制的“三位一体”解决方案
  • 超声波风向传感器:以科技之翼,捕捉风的每一次呼吸
  • 操作【GM3568JHF】FPGA+ARM异构开发板 使用指南:TF-Card
  • NineData云原生智能数据管理平台新功能发布|2025年8月版
  • 行业学习【电商】:直播电商的去头部化、矩阵号?
  • Kimi-Researcher:月之暗面推出的深度研究AI智能体
  • 西嘎嘎学习 - C++ 继承 - Day 10
  • 图像直方图,直方图均衡化和掩膜
  • react reducx的使用
  • 基于STM32设计的智慧路灯(华为云IOT)_281
  • 智慧水库综合管理系统平台御控物联网解决方案
  • react基础篇
  • 子数组最大累加和dp问题I(保姆级!)
  • Win10和Win11打开IE浏览器
  • 解锁Python超能力:面向对象编程之类继承完全指南
  • 【openGLES】纹理