算法沉淀——动态规划篇(子数组系列问题(上))

news2024/11/25 22:32:56

算法沉淀——动态规划篇(子数组系列问题(上))

  • 前言
  • 一、最大子数组和
  • 二、环形子数组的最大和
  • 三、乘积最大子数组
  • 四、乘积为正数的最长子数组长度

前言

几乎所有的动态规划问题大致可分为以下5个步骤,后续所有问题分析都将基于此

  • 1.、状态表示:通常状态表示分为基本分为以下两种,其中更是以第一种为甚。

    • 以i为结尾,dp[i] 表示什么,通常为代求问题(具体依题目而定)
    • 以i为开始,dp[i]表示什么,通常为代求问题(具体依题目而定)
  • 2、状态转移方程
    *以上述的dp[i]意义为以i位置为分界, 通过最近一步来分析和划分问题,由此来得到一个有关dp[i]的状态转移方程。

  • 3、dp表创建,初始化

    • 动态规划问题中,如果直接使用状态转移方程通常会伴随着越界访问等风险,所以一般需要初始化。而初始化最重要的两个注意事项便是:保证后续结果正确,不受初始值影响;下标的映射关系
    • 初始化一般分为以下两种:
      • 直接初始化开头的几个值。
      • 一维空间大小+1,下标从1开始;二维增加一行/一列
  • 4、填dp表、填表顺序:根据状态转移方程来确定填表顺序。

  • 5、确定返回值

一、最大子数组和

【题目链接】:53. 最大子数组和
【题目】:

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

【示例】:

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

【分析】:
 我们可以定义dp[i]表示以i为结尾,最大连续子数组和。此时连续子数组长度分为1和大于1,分别对应如下情况:

  1. 如果dp[i-1]大于0,此时最大连续子数组和为dp[i-1] + nums[i]。长度大于1。
  2. 如果dp[i-1] <= 0,此时最大的连续子数组和就是nums[i]本身。长度为1.

 所以我们可以得到状态转移方程为:dp[i] = max(nums[i - 1], dp[i - 1] + nums[i - 1])

 但显然当i为0时,状态转移方程不适用。这里我们给出的方法时dp表空间额外增加1。同时为了保证在使用状态转移方程,新增空间对后续填表结果不产生印象。我们将dp[0]初始化为0即可。

【代码编写】:

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;
    }
};

二、环形子数组的最大和

【题目链接】:918. 环形子数组的最大和
【题目】:

 给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 。
 环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。
&emsp子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], …, nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 。

【示例】:

输入:nums = [5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10
提示:
n == nums.length
1 <= n <= 3 * 104
-3 * 104 <= nums[i] <= 3 * 104

【分析】:
 显然如果我们"死板"按照题目所说的环形数组进行分析,会感到无从下手。所以我们可以换一种思路:我们对数组被偷元素是否包含首尾,进行分类讨论。从而将环形数组问题转化称求普通子数组的最大和问题!

分类情况:

  1. 首位元素都被选,此时我们将原问题转化成求普通数组中子数组的最小和。最后用整个数组的和减去最小和,间接求出该情况最大值。
    在这里插入图片描述

  2. 首位元素不全都被选。此时原问题转化成普通数组中子数组的最大和。
    在这里插入图片描述

如何求普通数组最大子数组和、最小子数组和:

 这里我们可以定义fi]表示以i位置为结尾的子数组最大和,g[i]表示以i位置为结尾的子数组最小和。

状态转移方程推导:

  1. 以i位置为结尾的子数组最大和分两种情况:如果f[i-1] >0, 此时最大子数组和为f[i-1] + nums[i]。否则最大子数组和为nums[i]本身。所以状态转移方程为:f[i] = max(nums[i], dp[i-1] + nums[i])
  2. 同理,以i位置为结尾的子数组最小和分两种情况:如果f[i-1] <0, 此时最小子数组和为f[i-1] + nums[i]。否则最小子数组和为nums[i]本身。所以状态转移方程为:g[i] = min(g[i-1]+ nums[i], nums[i])

细节处理:
 显然不管是求f[i]还是g[i],当i为0时,状态转移方程失效。这里各位办法是给f、g都额外新增一个空间。最后从左往右依次填表即可。
如果数组中元素全为负数时,此时子数组和最大值一定为负数。但此时我们发现g[i]最终求得的子数组最小和就是整个数组和,此时数组和减子数组最小和的最终结果为0。需要单独处理
在这里插入图片描述

【代码编写】:

class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        //数组和
        int sum = 0;
        for(auto x : nums)
        {
            sum += x;
        }
        int n = nums.size();
        vector<int> f(n + 1);//普通数组最大连续子数组和
        auto g = f;//普通数组最小连续子数组和
        int fmax = INT_MIN, gmin = INT_MAX;//分别统计f中最大值,g中最小值
        for(int i = 1; i <= n; i++)
        {
            f[i] = max(nums[i - 1], f[i - 1] + nums[i - 1]);
            fmax = max(fmax, f[i]);

            g[i] = min(nums[i - 1], g[i - 1] + nums[i - 1]);
            gmin = min(gmin, g[i]);
        }
        return sum == gmin ? fmax : max(fmax, sum - gmin);//判断数组元素是否全为负数
    }
};

三、乘积最大子数组

【题目链接】:152. 乘积最大子数组
【题目】:

 给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
&emsp测试用例的答案是一个 32-位 整数。

【示例】:

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

【分析】:
 我们可以定义f[i]表示以i为结尾的最大子数组乘积,g[i]表示以i为结尾的最小子数组乘积。
状态转移方程推导

  1. 以i为结尾的子数组长度可能为1,或大于1。对于大于1的情况,如果nums[i]>0,此时最大子数组乘积为g[i-1]*nums[i];如果nums[i]<0,此时最大子数组乘积为g[i-1]*nums[i]。我们仅需直接求得3种情况的最大值即可,即状态转移方程为: f[i] = max(nums[i - 1], max(f[i - 1] * nums[i - 1], g[i - 1] * nums[i - 1]))
    在这里插入图片描述

  2. 同理对于g[i]来说,也存在如下几种情况。最终可得状态转移方程为g[i] = min(nums[i - 1], min(g[i - 1] * nums[i - 1], f[i - 1] * nums[i - 1]));

在这里插入图片描述

细节处理:
 当i=0时,f、g的状态转移方程不适用。我们可以为f、g额外增加1个空间。同时为了防止新增结果对后续填表造成影响,我们将g[0]、f[0]初始化为1。

【代码编写】

public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n + 1);//f[i]表示以i为结尾的子数组的最大乘积
        auto g = f;//g[i]表示以i为结尾的子数组的最小乘积
        int ret = INT_MIN;
        f[0] = 1, g[0] = 1;
        for(int i = 1; i <= n; i++)
        {
            f[i] = max(nums[i - 1], max(f[i - 1] * nums[i - 1], g[i - 1] * nums[i - 1]));
            g[i] = min(nums[i - 1], min(g[i - 1] * nums[i - 1], f[i - 1] * nums[i - 1]));
            ret = max(ret, f[i]);
        }
        return ret;
    }
};

四、乘积为正数的最长子数组长度

【题目链接】:1567. 乘积为正数的最长子数组长度
【题目】:

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

【示例】:

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

【分析】:
 我们定义f[i]表示以i为结尾,乘积为正的子数组最长长度;g[i]表示以i为结尾,乘积为负的子数组最长长度。

状态转移方程推导:
 我们可以从最后一个元素nums[i[入手,此时分为以下几种情况:

  1. 最长子数组长度可能为1,或大于1,具体如下:

在这里插入图片描述
将上述情况仅需合并后,可得:
在这里插入图片描述

  1. 同理,g[i]的情况如下:
    在这里插入图片描述
    合并后,可得:
    在这里插入图片描述

细节处理:
 显然当i为0时,f、g的状态转移方程都失效。所以我们给f、g额外增加一个空间。为了保证新增空间对后续填表结果不造成印象,我们将f[0]、g[0]初始化为0。最后从左往右填表即可。

【代码编写】:

class Solution {
public:
    int getMaxLen(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n + 1);//f[i]: 以i为结尾的,乘积为正的最长子数组长度
        auto g = f;//g[i]: 以i为结尾的,乘积为负的最长子数组长度
        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/1565563.html

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

相关文章

机台数据传输共享存在哪些问题?机台数据管控怎么做?

一些金融机构、大型制造业以及晶圆制造厂里面&#xff0c;都会存在大量的机台设备&#xff0c;这些机台会产⽣庞⼤⽽属性不同的数据&#xff0c;这些数据需要定期的进行采集和利用。机台数据在传输分享过程中&#xff0c;会面临各种问题和调整&#xff0c;所以需要做好机台数据…

前端 - 基础 表单标签 - 表单元素 input - type 属性 ( 单选按钮和复选按钮 )

input 标签 type 属性 &#xff0c;上一篇讲了 输入框 和 密码框 这节看看 单选按钮 和 复选 按钮 目录 单选按钮 &#xff1a; 复选按钮 # 看上图就可以看到 单选按钮 -- radio 和 复选 按钮 -- checkbox 单选按钮 &#xff1a; 所谓单选按钮就是 有时…

设计灵活可扩展的文件系统适配器系统

介绍 文件系统适配器是一个用于抽象不同存储类型之间差异的接口&#xff0c;它提供了统一的方式来访问和操作文件系统中的数据。无论是本地文件系统、云存储还是其他类型的存储&#xff0c;文件系统适配器都能够提供一致的操作接口&#xff0c;使得应用程序可以更容易地与不同…

事件队列事件循环(EventLoop) 宏任务 微任务详解 面试题

事件队列 事件循环 EventLoop 宏任务 微任务详解 一、概念二、宏任务&#xff08;多个&#xff09;、微任务&#xff08;1个&#xff09;三、Promise 的构造函数四、process.nextTick在事件循环中的处理五、vue nextTick原理 一、概念 event: 事件 loop: 循环&#xff0c;循环…

使用Bitmaps位图实现Redis签到

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Redis提供了Bitmaps这个“数据类型”可以实现对位的操作: (1) Bitmaps…

整顿编剧市场:程序员提交测试流程的最佳实践

讲动人的故事,写懂人的代码 最近,一部去年推出的国产电视剧在IT圈子里引起了轰动。 引起关注的原因,并非剧中程序员的外形出众,而是她提交代码测试的方式——将写有代码的纸张放入文件夹,然后递给了对面的测试人员。如图1所示。 图1 程序员将写有代码的纸张放入文件夹,并…

Python字符串操作方法一览表

字符串操作 你患得患失太在意从前又太担心将来&#xff0c;有句话说的好昨天是段历史&#xff0c;明天是个谜团而今天是天赐的礼物 像珍惜礼物那样珍惜今天。—— 龟大仙《功夫熊猫3》 1.字符串连接 例子&#xff1a; str1 "Hello" str2 "World" resul…

算法学习17:背包问题(动态规划)

算法学习17&#xff1a;背包问题&#xff08;动态规划&#xff09; 文章目录 算法学习17&#xff1a;背包问题&#xff08;动态规划&#xff09;前言一、01背包问题&#xff1a;1.朴素版&#xff1a;&#xff08;二维&#xff09;2.优化版&#xff1a;&#xff08;一维&#xf…

DeepBook通过NFT空投预告Token发布

是Sui的第一个原生流动性层&#xff0c;正在推出自己的原生token $DEEP&#xff0c;巩固其作为Sui网络关键金融基础设施的地位。DEEP旨在为在DeFi中提供整体流动性的机构和机构交易者使用DeepBook。DeepBook和DEEP的结合为DeFi应用提供了首要的Web3流动性来源。 DEEP token的关…

人事管理系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)请假加班招聘考勤

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读300套最新项目持续更新中..... 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含ja…

分区合并风险大,数据恢复有妙招

分区合并&#xff0c;是计算机磁盘管理中的一个常见操作&#xff0c;旨在通过整合相邻的分区来创建一个更大的逻辑分区&#xff0c;从而更有效地利用磁盘空间。这种操作看似简单&#xff0c;但实则蕴含着不小的风险。一旦操作不当或遇到意外情况&#xff0c;很可能导致数据丢失…

混沌加密:探索信息安全的前沿技术

在当今数字化时代&#xff0c;信息安全已成为全球关注的焦点。随着网络攻击手段的日益复杂&#xff0c;传统的加密技术面临着严峻的挑战。为了应对这些挑战&#xff0c;科学家们一直在探索更为先进的加密技术&#xff0c;混沌加密便是其中之一。本文将对混沌加密的原理、特点以…

开源项目advcpmv实现cp/mv显示进度条 —— 筑梦之路

项目地址&#xff1a;https://github.com/jarun/advcpmv 1. 查看当前系统上的包版本 rpm -qa | grep -w coreutils 2. 下载8.32版本的源码包 wget http://ftp.gnu.org/gnu/coreutils/coreutils-8.32.tar.xz 3. 下载对应版本的补丁包 wget https://github.com/jarun/advcpm…

Incus:新一代容器与虚拟机编排管理引擎

Incus是什么&#xff1f; Incus是一个用于编排管理应用型容器、系统型容器及虚拟机实例的管理工具。它是对 Canonical LXD 的继承与发展&#xff0c;引入了更多的存储驱动支持。 Incus项目的产品地址&#xff1a;Linux Containers - Incus - Introduction 在 LXC-Incus 项目…

Runes 生态一周要览 ▣ 2024.3.25-3.31|Runes 协议更新 BTC 减半在即

Runes 生态大事摘要 1、Casey 发布了 Runes 协议文档 RUNES HAVE DOCS&#xff0c;Github 代码库更新到 ord 0.17.0 版本&#xff0c;Casey 表示符文是一个“严肃”的代币协议。 2、Casey 公布了第一个硬编码的创世符文「UNCOMMONGOODS」 3、4月7日香港沙龙&#xff5c;聚焦「…

【Algorithms 4】算法(第4版)学习笔记 23 - 5.4 正则表达式

文章目录 前言参考目录学习笔记1&#xff1a;正则表达式1.1&#xff1a;表示1.2&#xff1a;快捷表示2&#xff1a;正则表达式与非确定有限状态自动机 REs and NFAs2.1&#xff1a;二元性2.2&#xff1a;模式匹配实现2.3&#xff1a;非确定有限状态自动机 Nondeterministic fin…

mysql锁表问题

问题描述 偶尔应用日志会打印锁表超时回滚 org.springframework.dao.CannotAcquireLockException: ### Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transactionmysql锁…

Python基础之pandas:文件读取与数据处理

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、文件读取1.以pd.read_csv()为例&#xff1a;2.数据查看 二、数据离散化、排序1.pd.cut()离散化&#xff0c;以按范围加标签为例2. pd.qcut()实现离散化3.排序4.…

原型链污染攻击也称JavaScript Prototype 污染攻击

JavaScript数据类型 let和var关键字的区别 使用var或let关键字可以定义变量 let和var的区别如下&#xff1a; var是全局作用域&#xff0c;let 只在当前代码块内有效 当在代码块外访问let声明的变量时会报错 var有变量提升&#xff0c;let没有变量提升 let必须先声明…

一文秒解四大经典限流算法

阅读前提&#xff1a;没有最好的算法&#xff0c;只有最适合的算法&#xff01; 限流算法&#xff1a; 固定窗口限流算法 滑动窗口限流算法 漏桶限流算法 令牌桶限流算法 固定窗口限流算法 介绍 固定窗口限流算法&#xff08;Fixed Window Rate Limiting Algorithm&#…