子数组问题——动态规划

子数组问题——动态规划
个人主页:敲上瘾-ZEEKLOG博客

动态规划基础dp:基础dp——动态规划-ZEEKLOG博客多状态dp:多状态dp——动态规划-ZEEKLOG博客

目录

一、解题技巧

二、最大子数组和

三、乘积最大子数组

四、最长湍流子数组

五、单词拆分


一、解题技巧

区分子数组(子串)与子序列:

  • 子数组(子串):在数列中的一段连续的元素组成的新数列,中间不可间断。
  • 子序列:在数列中从左往右依次挑选出元素组成的新数列,或者说在数列中随意删除一些元素后,剩下的元素组成的新数列。

用动态规划做子数组类的题时,对于状态表示我们可以直接设:

  • dp[i]为以i元素结尾的子数组的... ... 

        后面就根据具体的题目要求填写,可能是子数组的和或者子数组的积等等,无论如何都可以以i元素结尾的子数组为研究对象去思考问题,如果解决不了就尝试增加状态,但研究对象不要改变。如果还解决不了那么再考虑改变或增加研究对象。

二、最大子数组和

状态表示

如上技巧所述,我们直接设状态转移方程:

  • dp[i]表示:以i位置结尾的子数组的最大和。

接下来只需要去尝试是否能写出正确的状态转移方程,如果能那么状态表示就是对的。

状态转移方程:

以i位置结尾的子数组我们可以分为两种:

  1. nums[i]单独构成一个子数组
  2. nums[i]和以i-1结尾的最大和子数组组合成的子数组。

        那么这个以i-1结尾的最大子数组的值就是一个重复子问题,我们假设在前面已经计算过了,即dp[i-1],然后需要注意两种情况只能取一种。那么:

  • dp[i] = max(nums[i]+dp[i-1],nums[i])

初始化

初始化的目的主要有两个:

  • 保证填表的时候不越界。
  • 保证填表的正确性。

        因为这里有i-1,所以如果从0开始填表可能会越界,通常有两种解决方案:

        方法一:把dp[0]初始化(即dp[0]=nums[0]),然后从dp[1]位置开始填表(即从nums[1]位置开始记录)。

        方法二:开辟一个n+1的空间(n=nums.size()),让dp[0]=0(需要根据具体情况具体分析),然后从dp[1]位置开始填写,而dp[1]记录的是nums[0]的情况,也就是错开一位进行记录,所以需要注意映射关系。

        这题看似方法一更简洁,但对于其他题可能需要做更复杂的边界判断。所以在做动规题时更推荐使用方法二来解决边界问题。

填表顺序

        从左往右。

返回值

        dp表中的最大值。

代码示例:

class Solution { public: int maxSubArray(vector<int>& nums) { int n=nums.size(),ret=INT_MIN; vector<int> dp(n+1); for(int i=1;i<n+1;i++) { dp[i]=max(nums[i-1],nums[i-1]+dp[i-1]); ret=max(ret,dp[i]); } return ret; } };

三、乘积最大子数组

状态表示

同样的我们假设状态表示为:

        dp[i]表示:以i位置结尾的子数组的最大乘积。

        那么状态转移方程dp[i]=max(dp[i-1]*nums[i],nums[i]),我们想一想这样对吗?比如dp[i-1]*nums[i],nums[i]乘以一个最大积的子数组就是最大吗?

        如果nums是一个负数不就变成最小乘积了吗,反之nums[i]小于0时乘以一个最小的数才能成为最大积。

        所以当nums[i]小于0时我们需要知道以i-1结尾的子数组的最小积。

所以状态表示为:

  • f[i]表示:以i位置结尾的子数组的最大乘积。
  • g[i]表示:以i位置结尾的子数组的最小乘积。

状态转移方程

  • nums[i]>=0:
    • f[i] = max(f[i-1]*nums[i],nums[i])
    • g[i] = min(g[i-1]*nums[i],nums[i])
  • nums[i] < 0:
    • f[i] = max(g[i-1]*nums[i],nums[i])
    • g[i] = min(f[i-1]*nums[i],nums[i])        

或:

  • f[i]=max(nums[i],max(nums[i]*f[i-1],nums[i]*g[i-1]));
  • g[i]=min(nums[i],min(nums[i]*f[i-1],nums[i]*g[i-1]));

初始化

        与上一题相同,为防止越界我们给两个dp表都多开辟一个空间,映射关系错开一位。

        然后把两个dp表都初始化为1,因为这里是乘法,如果使用默认的0值那么这个结果都是0。

注:dp[0]是我们为防止越界添加上的虚拟位置,它的值需要使得后面的填表正确。

填表顺序

        从左往右,f表和g表一起填。

返回值

        f表中的最大值。

代码示例:

class Solution { public: int maxProduct(vector<int>& nums) { int n=nums.size(), ret=INT_MIN;; vector<int> f(n+1,1),g(n+1,1); for(int i=1;i<=n;i++) { f[i]=max(nums[i-1],max(nums[i-1]*f[i-1],nums[i-1]*g[i-1])); g[i]=min(nums[i-1],min(nums[i-1]*f[i-1],nums[i-1]*g[i-1])); ret=max(ret,f[i]); } return ret; } };

四、最长湍流子数组

题目的核心就一句话:比较符号在子数组中的每个相邻元素对之间翻转。

然后找到满足这样的条件的最长子数组。

状态表示

假设状态表示为:

        dp[i]表示:以i结尾的最长湍流子数组。

我们把数据的大小波动抽象成一条折线,如下把示例1化为折线图:

结果取该段:

        也就是子数组要满足前一个元素是上升趋势那么下一个元素必须是下降,如果前一个元素是下降趋势那么下一个元素必须是上升。

我们在做状态转移方程中主要是考虑两种情况,

  1. nums[i]单独构成一个子数组
  2. nums[i]接到前一个元素结尾构成的子数组中。

第2种情况又需要分情况讨论,

  • nums[i] < nums[i-1]:只有前面的子数组最终状态是呈现上升趋势时nums[i]才能接上。
  • nums[i] > nums[i-1]:只有前面的子数组最终状态是呈现下降趋势时nums[i]才能接上。
  • nums[i]==nums[i-1]:不能接入前面子数组。

所以我们需要把状态转移细分为两种状态:

  • f[i]表示:以i结尾并且最后一个元素呈上升趋势的最长湍流子数组的长度。
  • g[i]表示:以i结尾并且最后一个元素呈下降趋势的最长湍流子数组的长度。

状态转移方程

  • nums[i] < nums[i-1]:
    • f[i]=1
    • g[i]=f[i-1]+1
  • nums[i] > nums[i-1]:
    • f[i]=g[i-1]+1
    • g[i]=1
  • nums[i]==nums[i-1]:
    • f[i]=1
    • g[i]=1

        因为任意一个子数组,最小的长度都是1,所以可以把两个dp表都初始化为1,那么状态转移方程可简化为:

  • nums[i] < nums[i-1]:g[i]+=f[i-1]
  • nums[i] > nums[i-1]:f[i]+=g[i-1]

初始化

        为防止越界我们给两个dp表都多开辟一个空间,映射关系错开一位。

        然后把两个dp表都初始化为1。

填表顺序

        从左往右,f表和g表同时填写。

返回值

        f表和g表中的最大那个元素

代码示例:

class Solution { public: int maxTurbulenceSize(vector<int>& arr) { int n=arr.size(),ret=1; vector<int> f(n,1),g(n,1); for(int i=1;i<n;i++) { if(arr[i]<arr[i-1]) g[i]+=f[i-1]; if(arr[i]>arr[i-1]) f[i]+=g[i-1]; ret=max(ret,max(f[i],g[i])); } return ret; } };

五、单词拆分

状态表示

根据经验直接设状态表示:

  • dp[i]表示:从0到i位置结尾的字符串是否能被字典中的单词表示(bool类型)。

状态转移方程

        因为在填写i时以前的每个子串是否能由字典表示已经知道,储存在dp表中。那么我们只需要找到任意一个j(0<=j<i)使得dp[j]=true,并且子字符串[j+1,i]能用字典表示,那么dp[i]=true,否则dp[i]=false。

所以状态转移方程:

  • dp[i] = (dp[i-1]&&s[i,i]能用字典表示) || (dp[i-2]&&s[i-1,i]能用字典表示) || ... ... ||(dp[0]&&s[1,i]能用字典表示)

注:s[i-1,i]表示字符串中i-1到i这个子串。

初始化

        为了让第一个字符元素也讨论进来,我们创建n+1的dp表,并把dp[0]初始化为true。

填表顺序

        从左往右

返回值

        return dp[n]

代码示例:

class Solution { public: bool wordBreak(string s, vector<string>& wordDict) { int n=s.size(); unordered_set<string> st(wordDict.begin(),wordDict.end()); vector<bool> dp(n+1); dp[0]=true; for(int i=1;i<=n;i++) { for(int j=0;j<i;j++) { if(dp[j]==false) continue; if(st.count(string(s.begin()+j,s.begin()+i))) { dp[i]=true; break; } } } return dp[n]; } };

好题推荐:

非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!

Read more

C++ vector容器底层深度剖析与模拟实现

C++ vector容器底层深度剖析与模拟实现

🔥近津薪荼:个人主页 🎬个人专栏:《c语言基础知识详解》《c++基础知识详解》 ✨每个优秀的人, 都有一段沉默的时光, ❄️那段时光是付出了很多努力, 却得不到结果的日子,我们把它叫做扎根, ⭐️祝您也祝我早日破土而出,巨木参天。 简介:本文主要以手打代码的方式来实现vector的各接口功能,带大家深入了解vector的底层原理~ 目录 1 模板的使用说明 2 vector深度剖析及模拟实现 2.1 vector的成员变量 2.2 构造函数 2.2.1 指定大小和初始值的构造函数 2.2.2 迭代器范围构造函数 2.2.3 拷贝构造函数(现代写法) 2.3 赋值运算符重载 2.4 容量相关操作 2.4.1 reserve

By Ne0inhk
最近试了下Javashop 商城系统:我愿称之为企业级电商解决方案的标杆之选

最近试了下Javashop 商城系统:我愿称之为企业级电商解决方案的标杆之选

目录 * 最近试了下Javashop 商城系统:我愿称之为企业级电商解决方案的标杆之选 * 一、产品实力:历经市场验证的成熟架构 * 1.100% 开源,灵活可控 * 2.高性能架构,从容稳定应对流量洪峰 * 3.全场景适配,覆盖业务边界 * 二、技术专业度:顶尖团队的匠心之作 * 1.前沿技术栈,保障系统生命力 * 2.高扩展性设计,拥抱业务变化 * 三、服务体验:从需求到落地的全周期护航 * 1.专业团队,精准匹配需求 * 2.重信守诺,交付零风险 * 3.贴心售后,问题响应快人一步 * 四、行业标杆案例:与头部企业共成长 * 结语:选择 Javashop,就是选择长期价值 最近试了下Javashop 商城系统:我愿称之为企业级电商解决方案的标杆之选 Javashop 是一款始于

By Ne0inhk
飞算Java在线学生成绩综合统计分析系统的设计与实现

飞算Java在线学生成绩综合统计分析系统的设计与实现

目录 * 引言 * 技术栈 * 一.需求分析与规划 * 功能需求 * 核心模块 * 技术选型 * 二.环境准备 * 1. 下载IntelliJ IDEA * 2. 安装IntelliJ IDEA * 3. 安装飞算JavaAI插件 * 4. 登录飞算JavaAI * 三.模块设计与编码 * 1. 飞算JavaAI生成基础模块 * 2. 核心代码展示 * entity包下实体类示例 * `Student.java`(学生实体) * `Score.java`(成绩实体) * dto包下数据传输对象示例 * `ScoreAddDTO.java`(成绩录入请求DTO) * `StudentRankQueryDTO.java`(个人排名查询DTO) * vo包下视图对象示例 * `StudentRankVO.java`(个人排名返回VO) * mapper包下数据访问接口示例 * `ScoreMappe

By Ne0inhk
Spring Boot 4 重磅特性解析:Java 开发者必看的 6 大升级,附实战案例!

Spring Boot 4 重磅特性解析:Java 开发者必看的 6 大升级,附实战案例!

Java 生态又迎大更新!基于 Spring Framework 7 构建的 Spring Boot 4,不仅强制拥抱 Java 21+,更在性能、云原生、开发效率上带来颠覆性变化。今天用「特性 + 案例 + 效果」的方式,带你吃透这些能直接落地的新能力👇 📝 Powered by Moshow 郑锴 | 更多技术干货:https://zhengkai.blog.ZEEKLOG.net/ 🔹 一、基础环境大升级:Java 21 成刚需,旧组件集体焕新 Spring Boot 4 彻底告别 Java 17 及以下版本,全面适配 Java 21 LTS 的新特性,

By Ne0inhk