算法沉淀——动态规划之子数组、子串系列(上)(leetcode真题剖析)

news2025/2/26 19:51:10

在这里插入图片描述

算法沉淀——动态规划之子数组、子串系列

  • 01.最大子数组和
  • 02.环形子数组的最大和
  • 03.乘积最大子数组
  • 04.乘积为正数的最长子数组长度

01.最大子数组和

题目链接:https://leetcode.cn/problems/maximum-subarray/、

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入:nums = [1]
输出:1

示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

**进阶:**如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

思路

  1. 状态表示:
    • 使用「经验 + 题目要求」定义线性动态规划的状态表示。
    • 选择以「某个位置为结尾」的方式,结合「题目要求」,定义状态表示:dp[i] 表示以 i 位置元素为结尾的「所有子数组」中和的最大和。
  2. 状态转移方程:
    • 将 dp[i] 的所有可能分为两种情况:子数组的长度为 1 或子数组的长度大于 1。
    • 如果子数组长度为 1,则 dp[i] = nums[i]。
    • 如果子数组长度大于 1,则 dp[i] 应该等于以 i-1 为结尾的「所有子数组」中和的最大值再加上 nums[i],即 dp[i-1] + nums[i]。
    • 转移方程为 dp[i] = max(nums[i], dp[i-1] + nums[i])。
  3. 初始化:
    • 在最前面加上一个「辅助结点」,用于初始化。辅助结点的值要保证后续填表是正确的,因此设 dp[0] = 0。
    • 辅助结点的存在需要注意下标的映射关系。
  4. 填表顺序:
    • 根据「状态转移方程」,填表顺序为「从左往右」。
  5. 返回值:
    • 状态表 dp 表示以 i 为结尾的所有子数组的最大值,但最大子数组和的结尾是不确定的。因此,返回整个 dp 表中的最大值。

代码

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n=nums.size();
        vector<int> dp(n+1);

        int ret=INT_MIN;
        for(int i=1;i<=n;i++){
            dp[i]=max(nums[i-1],dp[i-1]+nums[i-1]);
            ret=max(ret,dp[i]);
        }

        return ret;
    }
};

02.环形子数组的最大和

题目链接:https://leetcode.cn/problems/maximum-sum-circular-subarray/

给定一个长度为 n环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和

环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n]nums[i] 的前一个元素是 nums[(i - 1 + n) % n]

子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], ..., nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n

示例 1:

输入:nums = [1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3

示例 2:

输入:nums = [5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10

示例 3:

输入:nums = [3,-2,2,-3]
输出:3
解释:从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3

提示:

  • n == nums.length
  • 1 <= n <= 3 * 104
  • -3 * 104 <= nums[i] <= 3 * 104

思路

这个问题与「最大子数组和」的区别在于需要考虑数组首尾相连的情况。结果可能有两种情况:一是在数组的内部,包括整个数组;二是在数组首尾相连的一部分上。

  1. 对于第一种情况,按照「最大子数组和」的方法得到结果,记为 fmax。
  2. 对于第二种情况,分析得出,第二种情况的最大和应等于数组总和减去最小子数组和。其中,最小子数组和表示为 gmin。
  3. 两种情况下的最大值即为所求结果。然而,需要特殊处理数组内全部为负数的情况,因为直接比较两者的最大值可能会得到 0。这种情况下,实际结果应为数组内的最大值。
  4. 对于「最小子数组和」的求解过程与「最大子数组和」相同,使用 f 表示最大和,g 表示最小和。
  5. 返回值:先找到 f 表的最大值 fmax;找到 g 表的最小值 gmin;统计所有元素的和 sum;返回 sum == gmin ? fmax : max(fmax, sum - gmin)。

代码

class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        int n=nums.size();
        vector<int> f(n+1);
        vector<int> g(n+1);

        int fmax=INT_MIN,gmin=INT_MAX,sum=0;
        for(int i=1;i<=n;++i){
            int x=nums[i-1];
            sum+=x;
            f[i]=max(x,x+f[i-1]);
            fmax=max(fmax,f[i]);
            g[i]=min(x,x+g[i-1]);
            gmin=min(gmin,g[i]);
        }

        return sum==gmin ? fmax : max(fmax,sum-gmin);
    }
};

03.乘积最大子数组

题目链接:https://leetcode.cn/problems/maximum-product-subarray/

给你一个整数数组 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 * 104
  • -10 <= nums[i] <= 10
  • nums 的任何前缀或后缀的乘积都 保证 是一个 32-位 整数

思路

这道题类似于「最大子数组和」,但需要考虑乘积而非和。定义两个状态数组 f[i] 和 g[i] 分别表示以 i 为结尾的所有子数组的最大乘积和最小乘积。

  1. 状态表示:
    • f[i] 表示以 i 为结尾的所有子数组的最大乘积。
    • g[i] 表示以 i 为结尾的所有子数组的最小乘积。
  2. 状态转移方程:
    • 对于 f[i],需要考虑三种情况:子数组长度为 1,子数组长度大于 1 且 nums[i] > 0,子数组长度大于 1 且 nums[i] < 0。
    • 综上,f[i] = max(nums[i], max(nums[i] * f[i - 1], nums[i] * g[i - 1]))
    • 对于 g[i],同样考虑三种情况。
    • 综上,g[i] = min(nums[i], min(nums[i] * f[i - 1], nums[i] * g[i - 1]))
  3. 初始化:
    • 在最前面加上一个辅助结点,并设置 f[0] = g[0] = 1
  4. 填表顺序:
    • 从左往右,两个表一起填。
  5. 返回值:
    • 返回 f 表中的最大值。

代码

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

        f[0]=g[0]=1;
        int ret=INT_MIN;
        for(int i=1;i<=n;++i){
            int x=nums[i-1],y=f[i-1]*nums[i-1],z=g[i-1]*nums[i-1];
            f[i]=max(x,max(y,z));
            g[i]=min(x,min(y,z));
            ret=max(ret,f[i]);
        }
        return ret;
    }
};

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

题目链接:https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/

定一个整数数组nums,找到其中所有元素的乘积为正的子数组的最大长度。

数组的子数组是从该数组中取出的零个或多个值的连续序列。

返回具有正积的子数组的最大长度

示例1:

输入: nums = [1,-2,-3,4]
输出: 4
解释:数组 nums 的乘积已经为 24。

示例2:

输入: nums = [0,1,-2,-3,-4]
输出: 3
解释:具有正积的最长子数组是 [1,-2,-3],其积为 6。
请注意,我们不能在子数组中包含 0,因为这会使乘积为 0,而 0 不是正数。

示例3:

输入: nums = [-1,-2,-3,0,1]
输出: 2
解释:具有正积的最长子数组是 [-1,-2] 或 [-2,-3]。

限制条件:

  • 1 <= nums.length <= 105
  • -109 <= nums[i] <= 109

思路

1. 状态表达: 定义两个动态规划数组 fg,其中:

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

2. 状态转移方程: 对于 f[i],根据当前元素 nums[i] 的值,分三种情况讨论:

  • 如果 nums[i] = 0,说明当前子数组的乘积为零,所以 f[i] = 0

  • 如果 nums[i] > 0,说明当前子数组的乘积为正数,直接找到 f[i - 1] 的值并加一,即 f[i] = f[i - 1] + 1

  • 如果

    nums[i] < 0
    

    ,需要根据

    g[i - 1]
    

    的值来判断:

    • 如果 g[i - 1] 为零,表示以 i - 1 为结尾的最长负数子数组不存在,所以 f[i] = 0
    • 如果 g[i - 1] 不为零,表示以 i - 1 为结尾的最长负数子数组存在,此时 f[i] = g[i - 1] + 1

对于 g[i],也分三种情况讨论:

  • 如果 nums[i] = 0,说明当前子数组的乘积为零,所以 g[i] = 0

  • 如果 nums[i] < 0,说明当前子数组的乘积为负数,直接找到 f[i - 1] 的值并加一,即 g[i] = f[i - 1] + 1

  • 如果

    nums[i] > 0
    

    ,需要根据

    g[i - 1]
    

    的值来判断:

    • 如果 g[i - 1] 为零,表示以 i - 1 为结尾的最长负数子数组不存在,所以 g[i] = 0
    • 如果 g[i - 1] 不为零,表示以 i - 1 为结尾的最长负数子数组存在,此时 g[i] = g[i - 1] + 1

3. 初始化: 在数组最前面加上一个辅助结点,设置 f[0] = g[0] = 0

4. 填表顺序: 从左往右遍历数组,同时填充 fg 两个动态规划数组。

5. 返回值: 返回 f 数组中的最大值,即为最终结果。

代码

class Solution {
public:
    int getMaxLen(vector<int>& nums) {
        int n=nums.size();
        vector<int> f(n+1);
        vector<int> g(n+1);
        int ret=INT_MIN;
        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=max(ret,f[i]);
        }
        return ret;
    }
};

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

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

相关文章

JavaScript异步编程

回调地狱 回调地狱是一种由于过度使用嵌套回调函数而导致的代码结构不清晰、难以理解和维护的问题。一个典型例子是嵌套多个回调函数&#xff0c;每个回调函数都作为另一个回调函数的参数。这样会导致各个部分之间高度耦合、程序结构混乱、流程难以追踪&#xff0c;每个任务只能…

【JavaScript 漫游】【023】Event 对象总结

文章简介 本篇文章为【JavaScript 漫游】专栏的第 022 篇文章&#xff0c;记录了 JavaScript 中 Event 对象的相关知识点。 Event 对象概述 事件发生以后&#xff0c;会产生一个事件对象&#xff0c;作为参数传给监听函数。浏览器原生提供一个 Event 对象&#xff0c;所有的…

设计模式(六)代理模式

相关文章设计模式系列 1.代理模式简介 代理模式介绍 代理模式也叫委托模式&#xff0c;是结构型设计模式的一种。在现实生活中我们用到类似代理模式的场景有很多&#xff0c;比如代购、代理上网、打官司等。 定义 为其他对象提供一种代理以控制这个对象的访问。 代理模式…

支付宝小程序智能客服开发文档

语雀参考文档 https://www.yuque.com/em8gt4/qw1tt1/xgz1ol 自定义客服组件&#xff08;仅专业模式支持&#xff09; <contact-button tnt-inst-id"企业编码" scene"聊天窗编码" size"咨询按钮大小" color"咨询按钮颜色" icon&qu…

DBeaver一段时间不使用,就会自动断开连接,需要刷新数据库或者断开重连解决方案 DB2

DBeaver一段时间不使用&#xff0c;就会自动断开连接&#xff0c;需要刷新数据库或者断开重连解决方案 DB2

【Flink】Flink 中的时间和窗口之窗口(Window)

1. 窗口的概念 Flink是一种流式计算引擎&#xff0c;主要是来处理无界数据流&#xff0c;数据流的数据是一直都有的&#xff0c;等待流结束输入数据获取所有的流数据在做聚合计算是不可能的。为了更方便高效的处理无界流&#xff0c;一种方式就是把无限的流数据切割成有限的数…

【hashset】【hash查找元素O(1 )时间复杂度】Leetcode 128. 最长连续序列

【hashset】【hash查找元素O&#xff08;1 &#xff09;时间复杂度】Leetcode 128. 最长连续序列 解法1 hashmap解法2 不满足题设 排序遍历 ---------------&#x1f388;&#x1f388;题目链接&#x1f388;&#x1f388;------------------- 解法1 hashmap 核心思想 利用了ha…

Vue3的8大生命周期

查看本专栏目录 关于作者 还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#x…

Vue.js+SpringBoot开发快递管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 数据中心模块2.2 快递类型模块2.3 快递区域模块2.4 快递货架模块2.5 快递档案模块 三、界面展示3.1 登录注册3.2 快递类型3.3 快递区域3.4 快递货架3.5 快递档案3.6 系统基础模块 四、免责说明 一、摘要 1.1 项目介绍 …

windows 连接 Ubuntu 失败 -- samba服务

1. windows10连接ubuntu的时候&#xff0c;提示不允许一个用户使用一个以上用户名与服务器或共享资源的多重连接&#xff0c;中断与此服务器或共享资源的所有连接&#xff0c;然后再试一次 2. 换一台同事的电脑却又可以连上&#xff0c;我之前一直能用的&#xff0c;隔一段时间…

【 C++ 】bitset位图的模拟实现

位图概念 曾经有这样一个面试题&#xff0c;如果给你40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数中。【腾讯】方法如下&#xff1a; 遍历&#xff0c;时间复杂度O(N)。排序(O(NlogN))&#xff0c;利用二…

文献速递:深度学习--深度学习方法用于帕金森病的脑电图诊断

文献速递&#xff1a;深度学习–深度学习方法用于帕金森病的脑电图诊断 01 文献速递介绍 人类大脑在出生时含有最多的神经细胞&#xff0c;也称为神经元。这些神经细胞无法像我们身体的其他细胞那样自我修复。随着年龄的增长&#xff0c;神经元逐渐死亡&#xff0c;因此变得…

袁庭新ES系列12节 | Elasticsearch高级查询操作

前言 上篇文章讲了关于Elasticsearch的基本查询操作。接下来袁老师为大家带来Elasticsearch高级查询部分相关的内容。Elasticsearch是基于JSON提供完整的查询DSL&#xff08;Domain Specific Language&#xff1a;领域特定语言&#xff09;来定义查询。因此&#xff0c;我们有…

Python实现自动检测设备连通性并发送告警到企业微信

背景&#xff1a;门禁机器使用的WiFi连接&#xff0c;因为某些原因会不定期自动断开连接&#xff0c;需要人工及时干预&#xff0c;以免影响门禁数据同步&#xff0c;故写此脚本&#xff0c;定时检测门禁网络联通性。 #首次使用要安装tcping模块 pip install tcpingfrom tcpin…

幻兽帕鲁服务器哪家便宜?阿里云腾讯云京东云华为云对比

幻兽帕鲁服务器哪家便宜&#xff1f;阿里云腾讯云京东云华为云对比&#xff0c;阿里云更便宜&#xff0c;26元1个月。游戏服务器租用多少钱一年&#xff1f;1个月游戏服务器费用多少&#xff1f;阿里云游戏服务器26元1个月、腾讯云游戏服务器32元&#xff0c;华为云26元&#x…

Android WebView访问网页+自动播放视频+自动全屏+切换横屏

一、引言 近期&#xff0c;我发现电视家、火星直播等在线看电视直播的软件都已倒闭&#xff0c;而我奶奶也再无法通过这些平台看电视了。她已六十多岁&#xff0c;快七十岁啦。这些平台的倒下对我来说其实没有多大的影响&#xff0c;但是对于文化不多的她而言&#xff0c;生活中…

常见需求:CSS 实现弧形卡片的 3 种方式

公众号&#xff1a;程序员白特&#xff0c;欢迎一起交流学习~ 原文作者&#xff1a;前端侦探 在平时开发中&#xff0c;有时候会碰到下面这种“弧形”样式&#xff0c;主要分为“内凹”和“外凸”两种类型&#xff0c;如下 该如何实现呢&#xff1f;或者想一下&#xff0c;有哪…

用 Pyinstaller 模块将 Python 程序打包成 exe 文件(全网最全面最详细,万字详述)

目录 一、打包前置知识 1.1 什么是 exe 可执行文件&#xff1f; 1.2 为什么要将 Python 程序打包为 exe 可执行文件&#xff1f; 1.3 为什么 Python 程序不能直接运行呢&#xff1f; 1.4 我们用什么来打包 Python 文件呢&#xff1f; 1.5 打包有哪几种分类呢&#xff1f…

Spring-Cloud-Gateway集成Sentinel限流

1&#xff09;gateway添加sentinel相关依赖 <spring-cloud.version>2021.0.1</spring-cloud.version> <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version><dependencies><!--gateway--><dependency><gro…

多模态表征—CLIP及中文版Chinese-CLIP:理论讲解、代码微调与论文阅读

我之前一直在使用CLIP/Chinese-CLIP&#xff0c;但并未进行过系统的疏导。这次正好可以详细解释一下。相比于CLIP模型&#xff0c;Chinese-CLIP更适合我们的应用和微调&#xff0c;因为原始的CLIP模型只支持英文&#xff0c;对于我们的中文应用来说不够友好。Chinese-CLIP很好地…