对于子数组问题的动态规划

前言

先讲讲我对于这个问题的理解吧

当谈到解决子数组问题时,动态规划(DP)是一个强大的工具,它在处理各种算法挑战时发挥着重要作用。动态规划是一种思想,它通过将问题分解成更小的子问题并以一种递归的方式解决它们,然后利用这些解决方案来构建原始问题的解。在动态规划中,我们经常会遇到两种状态:一种是单独成一段,另一种是以 i 结尾的子数组。

通过枚举和动态规划,我们可以有效地解决子数组问题。枚举法需要考虑所有可能的子数组组合,然后比较它们以找到最优解。这种方法通常需要较多的时间和空间,因为它需要枚举所有可能性。而动态规划则更加智能化,它通过保存历史记录来避免不必要的重复计算。这样,下次遍历时,我们可以利用之前的计算结果,从而大大提高了效率。

动态规划的一个常见技巧是前缀和,它可以帮助我们快速求出数组中任意子数组的和。前缀和的核心思想是将原始数组中每个位置的值累加起来,形成一个新的数组,然后利用这个新数组来快速计算子数组的和。这种方法在处理求子数组和的问题时非常实用,因为它将复杂度降低到了单一状态的动态规划。

另外,预处理也是动态规划中常用的技巧之一。通过将经常使用的数据存储起来,我们可以在需要时快速获取,从而减少计算时间。预处理的思想是在问题出现之前就对数据进行处理,以便在需要时能够迅速获取所需的信息。

综上所述,动态规划是一种强大的解决子数组问题的方法,通过合理利用枚举、动态规划、前缀和和预处理等技巧,我们可以高效地解决各种复杂的算法挑战,为问题提供简单明了的解决方案。

这是我记得笔记 

 

我准备了五道例题都是这些解决方案

1.求最大子数组的和

. - 力扣(LeetCode)

思路分析:这一题主要是使用动态规划,也可以使用前缀和,动态规划也是求子数组的普遍思路,有两种状态,1自己组成子数组 和 前面的组成子数组,所以状态转移方程也就是Max(nums[i],dp[i - 1] + nums[i])

 代码实现

  public int maxSubArray(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n + 1];//以i位置为结尾的最大子数组和 (多状态 前面i - 1的子数组要  和 不要)
        // 初始化 (因为存在负数)
        dp[0] = -0x3f3f3f3f;

        //前面子数组都是以i - 1位置为结尾 或者 i位置自己构成一个数组
        for (int i = 1; i <= nums.length; i++) {
            dp[i] = Math.max(nums[i - 1], dp[i - 1] + nums[i - 1]);
        }
        int max = -0x3f3f3f3f;
        for (int i = 0; i < dp.length; i++) {
            max = Math.max(dp[i], max);
        }
        return max;
    }

2.求最大环形子数组

. - 力扣(LeetCode)

思路分析:

中间的是连续的所以求内部最小子数组和就好了, 或者中间成最大子数组和
//f[]表示以i位置为结尾的所有子数组中的最大值  //g[]表示以i位置为结尾的所有子数组中的最小值
//g[]就是为了处理边界。他通过计算中间部分的最小值来结算环的最大值

public int maxSubarraySumCircular(int[] nums) {
        int sum = 0;//用来处理最小值
        int n = nums.length;
        //1.状态表示
        int[] f = new int[n + 1],g = new int[n + 1];
        //2.状态转移方程    自己组成子数组  和  自己加上以 i-1位置结尾 的最大子数组
        //3.初始化 = 0 即可
        for (int i = 1; i <= n; i++) {
            f[i] = Math.max(f[i - 1] + nums[i - 1], nums[i - 1]);
            g[i] = Math.min(g[i - 1] + nums[i - 1], nums[i - 1]);
            sum += nums[i - 1];
        }
        int maxF = -0x3f3f3f3f;//统计结果
        int minG = 0x3f3f3f3f;//统计结果
        //可以和上面统一合并,一个循环就够了
        for (int i = 1; i <= n; i++) {
            maxF = Math.max(maxF,f[i]);
            minG = Math.min(minG,g[i]);
        }
        //为了防止全是负数返回0,所以sum - minG要和0做判断
        //因为  -8 - (-8) = 0;全是负数sum = -8 minG = -8 所以要返回maxF
        return Math.max(maxF,sum - minG == 0 ? maxF : sum - minG);
    }

3.和为k的子数组个数 

. - 力扣(LeetCode)

 思路分析:

//解法 动态规划  +  hash表   k == pre[i](i位置的前缀和) - pre[j - 1] //此时 [j,i]的子数组为k
 public int subarraySum(int[] nums, int k) {
        int count = 0;//统计出现了多少次
        int n = nums.length;
        HashMap < Integer, Integer > hash = new HashMap<>();//当词典使用,存储所有前缀和
        hash.put(0,1);//记录0出现了1次,防止前缀和单独构成答案
        //1.状态表示   以i位置为结尾的区间和
        int[] pre = new int[n + 1];
        //2.状态转移方程  pre[i] = pre[i - 1] + nums[i]
        //3.初始化  防止j - 1 越界 pre[0] = 0
        for (int i = 1; i <= n; i++) {
            pre[i] = pre[i - 1] + nums[i - 1];//下标映射,因为我的pre[0]是虚拟节点
            if (hash.containsKey(pre[i] - k)){
                count += hash.get(pre[i] - k);
            }
            hash.put(pre[i],hash.getOrDefault(pre[i],0) + 1);//键为前缀和的值 ,值为出现的次数
        }
        return count;
    }

 滚动数组优化形成前缀和

//因为上述我们只使用了,pre[i - 1] 和 pre[i] 这两种状态,所以可以使用滚动数组进行优化,设置两个变量即可
    //也就是我们熟知的前缀和
    public int subarraySum1(int[] nums, int k) {
        int count = 0;//统计出现了多少次
        int n = nums.length;
        HashMap < Integer, Integer > hash = new HashMap<>();//当词典使用,存储所有前缀和
        int pre = 0;
        hash.put(0,1);//记录0出现了1次,防止前缀和单独构成答案
        for (int i = 0; i < n; i++) {
            pre += nums[i];
            if (hash.containsKey(pre - k)){
                count += hash.get(pre - k);
            }
            hash.put(pre,hash.getOrDefault(pre,0) + 1);//键为前缀和的值 ,值为出现的次数
        }
        return count;
    }

4.乘积为k的最大子数组

 

. - 力扣(LeetCode)

思路分析:注释都有明确标注状态表示和转移方程

 public int maxProduct(int[] nums) {
        int n = nums.length;
        //1.定义状态表示
        int[] f = new int[n + 1];//以i位置为结尾  所有子数组中 乘积的最大值   遇到正数我要你
        int[] g = new int[n + 1];//以i位置为结尾  所有子数组中 乘积的最小值   遇到负数我要你
        //2.状态转移方程  遇到正数我要最大值(f[i - 1])    遇到负数我要最小值(g[i - 1])
        //3.初始化  防止i - 1越界但不可保存0,因为初始化的初衷就是保证后续的位置不受影响
        f[0] = g[0] = 1;//注意多次赋值是从右往左进行的
        int ret = -0x3f3f3f3f;
        for (int i = 1; i <= n; i++) {
            if (nums[i - 1] > 0){
                f[i] = Math.max(f[i - 1] * nums[i - 1],nums[i - 1]);
                g[i] = Math.min(g[i - 1] * nums[i - 1],nums[i - 1]);
            }else {
                f[i] = Math.max(g[i - 1] * nums[i - 1],nums[i - 1]);
                g[i] = Math.min(f[i - 1] * nums[i - 1],nums[i - 1]);
            }
            ret = Math.max(ret,f[i]);
        }
        return ret;
    }

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

. - 力扣(LeetCode)

 public int getMaxLen(int[] nums) {
        int n = nums.length;
        int ret = 0;//统计
        //1.状态表示
        int[] f = new int[n + 1];//以i位置为结尾中的 所有子数组中的 乘积为正数的最大长度
        int[] g = new int[n + 1];//以i位置为结尾中的 所有子数组中的 乘积为负数的最大长度
        //2.状态转移方程  f[i]如果i位置为正数为 f[i - 1] + 1    负数 g[i - 1] + 1
        //              g[i]同理正数g[i - 1] + 1   负数 f[i - 1] + 1
        //3.初始化 默认长度为0不影响后续结果
        for (int i = 1; i <= n; i++) {
            if (nums[i - 1] > 0){
                f[i] = f[i - 1] + 1;
                //当最后一个元素为正数的时候,并且g[i - 1] = 0表示前面没有负数,所以不可能组成负数
                g[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;
            }else if (nums[i - 1] < 0){
                //当最后一个元素为负数的时候,并且g[i - 1] = 0表示前面没有负数,所以不可能组成正数
                f[i] = g[i - 1] == 0 ? 0 : g[i - 1]  + 1;
                g[i] = f[i - 1] + 1;
            }else {//处理为0的情况
                f[i] = 0;
                g[i] = 0;
            }
            ret = Math.max(ret,f[i]);
        }
        return ret;
    }

总结

当解决子数组问题时,动态规划是一个强大而智能的工具。通过将问题分解成更小的子问题并以递归的方式解决它们,动态规划可以高效地找到原始问题的解。在动态规划中,我们常常会遇到两种状态:一种是单独成一段,另一种是以 i 结尾的子数组。

枚举和动态规划是解决子数组问题的两种主要方法。枚举法需要考虑所有可能的子数组组合,然后比较它们以找到最优解。而动态规划则通过保存历史记录来避免不必要的重复计算,从而提高效率。

在动态规划中,常用的技巧包括前缀和和预处理。前缀和可以帮助我们快速求出数组中任意子数组的和,而预处理则可以在问题出现之前就对数据进行处理,以提高计算效率。

综上所述,动态规划是解决子数组问题的一种强大工具,通过合理利用枚举、动态规划、前缀和和预处理等技巧,我们可以高效地解决各种复杂的算法挑战,为问题提供简单明了的解决方案。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【华为】IPSec VPN手动配置

【华为】IPSec VPN手动配置 拓扑配置ISP - 2AR1NAT - Easy IPIPSec VPN AR3NATIPsec VPN PC检验 配置文档AR1AR2 拓扑 配置 配置步骤 1、配置IP地址&#xff0c;ISP 路由器用 Lo0 模拟互联网 2、漳州和福州两个出口路由器配置默认路由指向ISP路由器 3、进行 IPsec VPN配置&…

Redission分布式锁 watch dog 看门狗机制

为了避免Redis实现的分布式锁超时&#xff0c;Redisson中引入了watch dog的机制&#xff0c;他可以帮助我们在Redisson实例被关闭前&#xff0c;不断的延长锁的有效期。 自动续租&#xff1a;当一个Redisson客户端实例获取到一个分布式锁时&#xff0c;如果没有指定锁的超时时…

笔记86:关于【#ifndef + #define + #endif】的用法

当你在编写一个头文件&#xff08;例如 pid_controller.h&#xff09;时&#xff0c;你可能会在多个源文件中包含它&#xff0c;以便在这些源文件中使用该头文件定义的函数、类或其他声明。如果你在多个源文件中都包含了同一个头文件&#xff0c;那么当你将整个工程统一编译&am…

银行卡实名认证API接口快速对接

银行卡实名认证API接口又叫银行卡核验类API接口、银行卡验证类API接口、银联核验类API接口,根据入参字段不同&#xff0c;分银行卡二要素验证API接口&#xff0c;银行卡三要素验证API接口&#xff0c;银行卡四要素验证API接口。其中&#xff0c;银行卡二要素验证API接口是验证开…

锂电池SOH估计 | Matlab实现基于ALO-SVR模型的锂电池SOH估计

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 锂电池SOH估计 | Matlab实现基于ALO-SVR模型的锂电池SOH估计 蚁狮优化支持向量机锂电池健康状态SOH估计&#xff1b; 具体流程如下&#xff1b; 1、分析锂离子电池老化数据集&#xff0c;从中选取具有代表电池性能衰减…

【自用】了解移动存储卡的基本信息

前言 本文是看B站视频做的一个简单笔记&#xff0c;方便日后自己快速回顾&#xff0c;内容主要介绍了存储卡基本参数&#xff0c;了解卡面上的数字、图标代表的含义。对于日后如何挑选判断一张存储卡的好坏、判别一张存储卡是否合格有一定帮助。 视频参考链接&#xff1a;【硬…

深入剖析Tomcat(六) Tomcat各组件的生命周期控制

Catalina中有很多组件&#xff0c;像上一章提到的四种容器&#xff0c;载入器&#xff0c;映射器等都是一种组件。每个组件在对外提供服务之前都需要有个启动过程&#xff1b;组件在销毁之前&#xff0c;也需要有个关闭过程&#xff1b;例如servlet容器关闭时&#xff0c;需要调…

OpenNJet应用引擎——云原生时代的Web服务新选择

文章目录 OpenNJet应用引擎——云原生时代的Web服务新选择引言&#xff1a;数字化转型的推动力&#xff1a;OpenNJet应用引擎为什么选择OpenNJet&#xff1f; OpenNJet的核心优势1. 云原生功能增强2. 安全加固3. 代码重构与性能优化4. 动态加载机制5. 多样化的产品形态6. 易于集…

产业空间集聚DO指数计算

1.前言 创始人 :Duranton and Overman&#xff08;2005&#xff09; 目前应用较多的产业集聚度量指数主要基于两类&#xff0c;一是根据不同空间地理单元中产业经济规模的均衡性进行构造&#xff0c;如空间基尼系数与EG指数&#xff1b;二是基于微观企业地理位置信息形成的产业…

嵌入式系统应用-拓展-FLASH之操作 SFUD (Serial Flash Universal Driver)之KEIL应用

这里已经假设SFUD代码已经移植到工程下面成功了&#xff0c;如果读者对SFUD移植还不了解。可以参考笔者这篇文章&#xff1a;SFUD (Serial Flash Universal Driver)之KEIL移植 这里主要介绍测试和应用 1 硬件设计 这里采用windbond 的W25Q32这款芯片用于SFUD测试。 W25Q32是…

LLM⊗KG范式下的知识图谱问答实现框架思想阅读

分享一张有趣的图&#xff0c;意思是在分类场景下&#xff0c;使用大模型和fasttext的效果&#xff0c;评论也很逗。 这其实背后的逻辑是&#xff0c;在类别众多的分类场景下&#xff0c;尤其是在标注数据量不缺的情况下&#xff0c;大模型的收益是否能够比有监督模型的收益更多…

[渗透利器]全能工具=信息收集->漏洞扫描->EXP调用

前言 hxd开发的工具&#xff0c;大致模块有&#xff08;信息收集&#xff0c;漏洞扫描&#xff0c;暴力破解&#xff0c;POC/EXP&#xff0c;常用编码&#xff09; 工具使用 下载后解压 安装环境 pip install -r requirements.txt 注意&#xff0c;该工具继承了两种不同的使…

HTML_CSS学习:定位

一、相对定位 相关代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>相对定位</title><style>.outer{width: 500px;background-color: #999ff0;border: 1px solid #000;p…

OpenHarmony实战开发-上传文件

Web组件支持前端页面选择文件上传功能&#xff0c;应用开发者可以使用onShowFileSelector()接口来处理前端页面文件上传的请求。 下面的示例中&#xff0c;当用户在前端页面点击文件上传按钮&#xff0c;应用侧在onShowFileSelector()接口中收到文件上传请求&#xff0c;在此接…

不考408的985,不想考408的有福了!吉林大学计算机考研考情分析

吉林大学&#xff08;Jilin University&#xff09;简称吉大&#xff0c;位于吉林长春&#xff0c;始建于1946年&#xff0c;是中华人民共和国教育部直属的综合性全国重点大学&#xff0c;国家“双一流”、“211工程”、“985工程”、“2011计划”重点建设的著名学府&#xff0…

我是如何带团队从0到1做了AI中台

经历心得 我从18年初就开始带这小团队开始做项目&#xff0c;比如最初的数字广东的协同办公项目&#xff0c;以及粤信签小程序等&#xff0c;所以&#xff0c;在团队管理&#xff0c;人员安排&#xff0c;工作分工&#xff0c;项目拆解等方面都有一定的经验。 19年中旬&#…

基于TL431和CSA的恒压与负压输出

Hello uu们,51去那里玩了呀?该收心回来上班了,嘿嘿! 为什么会有这个命题,因为我的手头只有这些东西如何去实现呢?让我们一起来看电路图吧.电路图如下图1所示 图1:CSA恒压输出电路 图1中,R1给U2提供偏置,Q1给R1提供电流,当U1-VOUT输出大于2.5V时候,U2内部的三极管CE导通,使得…

Kalign 3:大型数据集的多序列比对

之前一直用的是muscle&#xff0c;看到一个文章使用了Kalign&#xff0c;尝试一下吧 安装 wget -c https://github.com/TimoLassmann/kalign/archive/refs/tags/v3.4.0.tar.gz tar -zxvf v3.4.0.tar.gz cd kalign-3.4.0 mkdir build cd build cmake .. make make test su…

JVM之内存分配的详细解析

内存分配 两种方式 不分配内存的对象无法进行其他操作&#xff0c;JVM 为对象分配内存的过程&#xff1a;首先计算对象占用空间大小&#xff0c;接着在堆中划分一块内存给新对象 如果内存规整&#xff0c;使用指针碰撞&#xff08;Bump The Pointer&#xff09;。所有用过的内…

图片四张的时候两个一排 图片三张 五张的时候三个一排 css 如何实现

实现的效果如下图 1、html <view v-if"item.photo_list && item.photo_list.length ! 0" :class"getImageClass(item.photo_list.length)"><view v-for"(j,ind) in item.photo_list" :key"photoind" class"imag…
最新文章