【动态规划】子数组系列(上)

news2024/12/23 13:28:27

子数组问题

在这里插入图片描述

文章目录

  • 【动态规划】子数组系列(上)
    • 1. 最大子数组和
      • 1.1 题目解析
      • 1.2 算法原理
        • 1.2.1 状态表示
        • 1.2.2 状态转移方程
        • 1.2.3 初始化
        • 1.2.4 填表顺序
        • 1.2.5 返回值
      • 1.3 代码实现
    • 2. 环形子数组的最大和
      • 2.1 题目解析
      • 2.2 算法原理
        • 2.2.1 状态表示
        • 2.2.2 状态转移方程
        • 2.2.3 初始化
        • 2.2.4 填表顺序
        • 2.2.5 返回值
      • 3. 代码实现
    • 3. 乘积最大子数组
      • 3.1 题目解析
      • 3.2 算法原理
        • 3.2.1 状态表示
        • 3.2.2 状态转移方程
        • 3.2.3 初始化
        • 3.2.4 填表顺序
        • 3.2.5 返回值
      • 3.3 编写代码
    • 4. 乘积为整数的最长子数组长度
      • 4.1 题目解析
      • 4.2 算法原理
        • 4.2.1 状态表示
        • 4.2.2 状态转移方程
        • 4.2.3 初始化
        • 4.2.4 填表顺序
        • 4.2.5 返回值
      • 4.3 代码实现

【动态规划】子数组系列(上)

1. 最大子数组和

传送门:力扣53最大子数组和

题目:

在这里插入图片描述

1.1 题目解析

在这里插入图片描述

示例:

在这里插入图片描述

1.2 算法原理

在没有学动态规划之前,我们的做法可能就是暴力得到所有子数组,求其中子数组和最大是多少~

  • 这里将以动态规划的方法去解决问题!

1.2.1 状态表示

这里的状态表示也是通过“经验 + 题目要求”

  • 题目要求:返回最大和,一维数组
    • 建立一维dp表,大小为n
    • 一维解决不了再上升二维
  • 经验:以什么为结尾 / 以什么为起点
    • 这里以…为结尾即可

得到状态表示:

  • dp[i]表示,以nums[i]为结尾的子数组中的最大和

在这里插入图片描述

1.2.2 状态转移方程

得到状态转移方程的重点就是:

  1. 理清楚逻辑

    • 本题要分为两种情况:
    1. 一个元素的子数组
    2. 大于1个元素的子数组
  2. 理所当然地把dp表已填的数据当成绝对正确的值

以i为结尾,有两种情况:

  1. 子数组长度为1:nums[i]
  2. 子数组长度大于1:
    • 那么这个子数组至少有nums[i - 1]为结尾的子数组 + nums[i]
    • 所以就是max{nums[i - 1]为结尾的子数组和} + nums[i]
    • 即dp[i - 1] + nums[i]
      在这里插入图片描述

得到状态转移方程:

dp[i] = nums[i] + max{0, dp[i - 1]};

1.2.3 初始化

对于第一个节点,需要规避一下越界问题~

  • 很明显,应该dp[0] = nums[0]

或者加虚拟节点 => 大小为(n+1),结合状态转移方程,dp[0] = 0不会影响dp表原有值

  • 注意:下标对应问题

在这里插入图片描述

1.2.4 填表顺序

从左往右填表,保证dp[i - 1]已填写

1.2.5 返回值

并不是返回最后一个节点的值哦,因为最大子数组和不一定以最后一个节点为结尾,而是求dp表的最大值

  • 不要算虚拟节点,因为这个数组全是负的的话,那么虚拟节点就为最大值了~

1.3 代码实现

class Solution {
    public int maxSubArray(int[] nums) {
        //1. 创建dp表
        //2. 初始化
        //3. 填表
        //4. 返回值
        int n = nums.length;
        int[] dp = new int[n + 1];
        int max = Integer.MIN_VALUE;
        for(int i = 1; i < n + 1; i++) {
            dp[i] = Math.max(0, dp[i - 1]) + nums[i - 1];
            max = Math.max(dp[i], max);
        }
        return max;
    }
}
  • 可以边填表边判断最大值
  • +nums[i - 1]哦,因为我们多加了个虚拟节点
    在这里插入图片描述

2. 环形子数组的最大和

传送门:力扣918

题目:

在这里插入图片描述

2.1 题目解析

在这里插入图片描述

2.2 算法原理

2.2.1 状态表示

与前一道题一致:

在这里插入图片描述

但是我们可以注意到,如果这样的话,我们在填第一个数据的时候,dp[0]是填不出来的,并且dp[i - 1]可能包括了nums[i]这个点(由于环形)

所以在状态表示的时候就要进行逻辑的分析:

  • 我们可以分为两个情况:
  1. 常规子数组
  2. 环型子数组

在这里插入图片描述

  • 而后者的求法是和常规的一样

前者则可以看成一下理解方式:

在这里插入图片描述

所以环形子数组的最大化就是紫色的最小化

  • 所以我们还需要知道“最小数组和”!
  • 这也演变成了多状态问题!

f[i]代表已i为结尾的子数组的最大和

g[i]代表已i为结尾的子数组的最小和

而这两个都不考虑“环”的存在!

2.2.2 状态转移方程

f,g表两者类似:

在这里插入图片描述

有:

f[i] = nums[i] + max{0, f[i - 1]}

g[i] = nums[i] + min{0, g[i - 1]}

2.2.3 初始化

虚拟节点法

  1. 不影响原dp值
  2. 下标对应问题

在这里插入图片描述

2.2.4 填表顺序

从左往右两个表一起填

2.2.5 返回值

在f表中找到最大值 => max{f[ ]} => 常规子数组的最大和

在g表中找到最小值 => sum - min{g[ ]} => 环形子数组的最大和

注意,还有一个细节!

  • 如果sum == min{g[ ]},那么最大和就为0了?
  • 并不是,因为环形子数组也至少要有一个元素!所以这里应该是负无穷大
  • 这样则代表的是环形子数组无可能为最大和

3. 代码实现

class Solution {
    public int maxSubarraySumCircular(int[] nums) {
        //1. 创建dp表
        //2. 初始化
        //3. 填表
        //4. 返回值
        int n = nums.length;
        int[] f = new int[n + 1];
        int[] g = new int[n + 1];
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        int sum = 0;
        for(int i = 1; i < n + 1; i++) {
            f[i] = Math.max(0, f[i - 1]) + nums[i - 1];
            g[i] = Math.min(0, g[i - 1]) + nums[i - 1];
            max = Math.max(max, f[i]);
            min = Math.min(min, g[i]);
            sum += nums[i - 1];
        }
        return sum == min ? max : Math.max(max, sum - min);
    }
}

在这里插入图片描述

  1. 下标对应!
  2. 特殊情况处理!

在这里插入图片描述

3. 乘积最大子数组

传送门:力扣152

题目:

在这里插入图片描述

3.1 题目解析

在这里插入图片描述

3.2 算法原理

3.2.1 状态表示

根据经验 + 题目要求,我们可以快速得到,一维dp表(大小为n),dp[i]代表以i为结尾的子数组的乘积最大值~

但是我们稍微想一想状态转移方程(这些步骤其实在做题过程中,是没有明显的分割的)

  • 如果nums[i]为负数,那么我们要得到dp[i],用到dp[i - 1](到i - 1的子数组的最大乘积),nums[i] * dp[i - 1]反而成为了最小值~

所以一个dp表是解决不了问题的!

刚才我们得到了最小值,那么反着看,如果我们要得到最大值,那么就需要前者的最小值

  • 则得出,我们是需要知道“乘积最小值”

故,演变成了多状态问题

f[i]代表i为结尾的子数组的乘积最大值

g[i]代表i为结尾的子数组的乘积最小值

3.2.2 状态转移方程

在这里插入图片描述

对于f表:

  1. 子数组大小为1,f[i] = nums[i];
  2. 子数组大小大于1
    1. nums[i] < 0, f[i] = nums[i] * g[i - 1]
    2. nums[i] > 0, f[i] = nums[i] * f[i - 1](由于nums为整数数组,所以没有乘积后变小的情况)
    3. nums[i] == 0, f[i] = 0

对于g表:

  1. 子数组大小为1,g[i] = nums[i];
  2. 子数组大小大于1
    1. nums[i] < 0, g[i] = nums[i] * f[i - 1]
    2. nums[i] > 0, g[i] = nums[i] * g[i - 1](由于nums为整数数组,所以没有乘积后变大的情况)
    3. nums[i] == 0, f[i] = 0

所以得到状态转移方程:

f[i] = max{nums[i], nums[i] * f[i - 1], nums[i] * g[i - 1]};

  • 0可以省去,因为有一个大于等于0的数

g[i] = min{nums[i], nums[i] * f[i - 1], nums[i] * g[i - 1]};

  • 0可以省去,因为有一个小于等于0的数

这样写就无需判断nums[i]正负~

3.2.3 初始化

虚拟节点法:

  1. 不影响原有值
  2. 下标对应问题

1乘以任何值都不会改变其值,所以两个表前添加一个1的节点即可

3.2.4 填表顺序

从左往右,两个表一起填

3.2.5 返回值

返回f表中的最大值(不包含虚拟节点)

3.3 编写代码

class Solution {
    public int maxProduct(int[] nums) {
        //1. 创建dp表
        //2. 初始化
        //3. 填表
        //4. 返回值
        int n = nums.length;
        int[] f = new int[n + 1];
        int[] g = new int[n + 1];
        f[0] = 1;
        g[0] = 1;
        int max = Integer.MIN_VALUE;
        for(int i = 1; i < n + 1; i++) {
            int number = nums[i - 1];
            f[i] = Math.max(number, Math.max(number * f[i - 1], number * g[i -1]));
            g[i] = Math.min(number, Math.min(number * f[i - 1], number * g[i -1]));
            max = Math.max(max, f[i]);
        }
        return max;
    }
}

在这里插入图片描述

  • 注意下标对应!

在这里插入图片描述

4. 乘积为整数的最长子数组长度

传送门:力扣1567

题目:

在这里插入图片描述

4.1 题目解析

在这里插入图片描述

在这里插入图片描述

4.2 算法原理

4.2.1 状态表示

根据“经验 + 题目要求”快速得到,一维dp表,大小为n,dp[i]代表“乘积正数子数组最长长度”

  • 同样的,如果只是正数的最长长度,是不能解决问题的,因为nums[i]小于0,dp[i],无法用dp[i - 1]去表示!

而nums[i]小于0,我们需要“负数”去负负得正

  • 所以我们还需要“乘积负数子数组最长长度”

所以演变成多状态问题:

f[i]代表乘积正数子数组最长长度

g[i]代表乘积负数子数组最长长度

4.2.2 状态转移方程

在这里插入图片描述

对于f表:

  1. 子数组长度为1
    1. nums[i] > 0, f[i] = 1
    2. nums[i] <= 0, f[i] = 0
  2. 子数组长度大于1
    1. nums[i] > 0, f[i] = 1 + f[i - 1](正数延续)
    2. nums[i] < 0, f[i] = 1 + g[i - 1](负负得正)
    3. nums[i] = 0, f[i] = 0

nums[i] = 0,前功尽弃~

细节:

nums[i] < 0, g[i - 1] == 0,则f[i] = 0!

对于g表:

  1. 子数组长度为1
    1. nums[i] < 0, g[i] = 1
    2. nums[i] >= 0, g[i] = 0
  2. 子数组长度大于1
    1. nums[i] > 0, g[i] = 1 + g[i - 1](负数延续)
    2. nums[i] < 0, g[i] = 1 + f[i - 1](正负得负)
    3. nums[i] = 0, g[i] = 0

nums[i] = 0,前功尽弃~

细节:

nums[i] > 0, g[i - 1] == 0,则g[i] = 0!

所以得到状态转移方程:

  1. nums[i] == 0时,f[i] = 0, g[i] = 0,java数组本身就是0~

  2. nums[i] < 0 时

    • f[i] = g[i - 1] == 0 ? 0 : 1 + g[i - 1]
    • g[i] = 1 + f[i - 1]
  3. nums[i] > 0时

    • f[i] = 1 + f[i - 1]
    • g[i] = g[i - 1] == 0 ? 0 : 1 + g[i - 1]

4.2.3 初始化

用假数据法,f表和g表前面加一个值为0的假节点

  1. 不影响原值
  2. 下标对应问题

4.2.4 填表顺序

从左往右,两个表一起填

4.2.5 返回值

f表最大值(不包含假节点)

4.3 代码实现

class Solution {
    public int getMaxLen(int[] nums) {
        //1. 创建dp表
        //2. 初始化
        //3. 填表
        //4. 返回值
        int n = nums.length;
        int[] f = new int[n + 1];
        int[] g = new int[n + 1];
        int max = 0;
        for(int i = 1; i < n + 1; i++) {
            if(nums[i - 1] > 0) {
                f[i] = 1 + f[i - 1];
                g[i] = g[i - 1] == 0 ? 0 : 1 + g[i - 1];
            }else if(nums[i - 1] < 0) {
                f[i] = g[i - 1] == 0 ? 0 : 1 + g[i - 1];
                g[i] = 1 + f[i - 1];
            }
            max = Math.max(max, f[i]);
        }
        return max;
    }
}

在这里插入图片描述

  • 注意下标对应!

在这里插入图片描述


文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭🦆

代码位置:DP05 · 游离态/马拉圈2023年7月 - 码云 - 开源中国 (gitee.com)

还有下集~


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

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

相关文章

C++2(表达式和关系运算)

目录 1.表达式基础 1.表达式基础 运算符重载&#xff0c;就是自己定义 - * / 之类的运算符怎么运算 C中的左值和右值 C语言左值在左侧&#xff0c;右值在右侧 在cpp中要复杂的多 能取到地址的表达式是左值 不能取到地址的表达式是右值 常量对象为代表的左值不能作为赋值语句的左…

【Linux】网络相关概念概述以及原理简单分析介绍

文章目录 [toc] Linux 网络概述网络发展独立模式网络互联局域网LAN 和 广域网WAN 认识 "协议"协议的分层网络协议栈OSI七层模型TCP/IP五层(四层)模型TCP/IP网络协议栈 与 操作系统 的关系 **重新以计算机的视角看待 网络协议栈 局域网内部通信原理简单介绍不同局域网…

mybatis web使用02

处理 transfer 请求的 servlet package com.wsd.web;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRe…

GAMES101 笔记 Lecture08 Shading 2(Shading, Pipeline and Texture Mapping)

目录 Specular Term(高光项)Ambient Term(环境光照项)Blinn-Phong Reflection ModelShading Frequencies(着色频率)Shade each triangle(flat shading)在每个三角形上进行着色Shade each vertex (Gouraud shading)(顶点着色)Shade each pixel (Phong shading)Defining Per-Vert…

【C++详解】——哈希

目录 unordered系列关联式容器 unordered_map unordered_map的接口说明 1.unordered_map的构造 2.unordered_map的容量 3.迭代器相关 4.unordered_map的元素访问 5. unordered_map的查询 6.unordered_map的修改操作 unordered_set 性能测试 底层结构——Hash 哈希…

copula简介

二元正态copula最为重要

MySQL - 自连接查询

1. 测试数据 创建 category 表 : CREATE TABLE category(categoryid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主题id,pid INT(10) NOT NULL COMMENT 父id,categoryName VARCHAR(50) NOT NULL COMMENT 主题名字,PRIMARY KEY(categoryid) ) ENGINEINNODB AUTO_INCREM…

cmd的学习

目录 常用的cmd命令 使用cmd的例子 常用的cmd命令 指令作用盘符名称:盘符切换dir查看当前路径下的内容tree以树形结构输出当前路径下的内容cd进入单级目录cd ..回退到上一级目录cd 目录1\目录2\...进入多级目录cd \回退到盘符目录cls清屏exit退出窗口 &#xff08;值得注意的…

Android AlertDialog setView,kotlin

Android AlertDialog setView&#xff0c;kotlin <?xml version"1.0" encoding"utf-8"?> <com.google.android.material.textfield.TextInputLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width…

MySQL数据库——主从复制和读写分离

MySQL数据库——主从复制和读写分离 一、主从复制和读写分离的相关知识1.什么是读写分离&#xff1f;2.为什么要读写分离呢&#xff1f;3.什么时候要读写分离&#xff1f;4.主从复制与读写分离5.mysql支持的复制类型6.主从复制的工作过程7.MySQL 读写分离原理8.目前较为常见的 …

前端实现俄罗斯方块游戏(内含源码)

目录 一、前言 二、功能介绍 三、页面搭建 四、样式设置 五、逻辑部分 一、前言 今天带领大家完成俄罗斯方块游戏&#xff0c;功能也比较简单&#xff0c;也是想借助这样一个简单的功能&#xff0c;然后来帮助大家了解我们JavaScript在前端中的作用&#xff0c; 后续也会带…

【服务器】ASP.Net Core(C#)创建Web站点

简单几步实现本地ASP.Net.Core web 站点结合cpolar内网穿透工具实现远程访问 1. 创建站点 *环境搭建,这边测试,使用.NET 6.0 SDK,可以点击跳转到官网下载,下载后安装即可. 安装完成后,进入到某个文件夹,打开powershell执行下面命令,创建新的 Web 应用,名称叫:aspnetcoreapp …

机器学习 day22(ReLU激活函数)

ReLU激活函数 如果想让a取更大的非负数&#xff0c;激活函数g(z)可以选用ReLU激活函数&#xff0c;他在z&#xff1c;0时取0&#xff0c;在z ≥ 0时取z 常见的激活函数 左侧的为线性激活函数&#xff0c;因为f(x) wxb&#xff0c;使用激活函数后f(x) g(z)&#xff0c;此…

综合评价算法 | Matlab实现基于TOPSIS法的综合评价算法

文章目录 效果一览文章概述研究内容源码设计参考资料效果一览 文章概述 综合评价算法 | Matlab实现基于TOPSIS法的综合评价算法 研究内容 C.L.Hwang 和 K.Yoon 于1981年首次提出 TOPSIS (Technique for Order Preference by Similarity to an Ideal Solution)。TOPSIS 法是一种…

卷积神经网络--猫狗系列【CNN】

数据集&#xff0c;这次这个是分了类的【文末分享】 各12500张&#xff1a; 两点需要注意&#xff1a; ①猫狗分类是彩色图片&#xff0c;所以是3个channel&#xff1b; ②猫狗分类的图片大小不一&#xff0c;但是CNN的输入要求是固定大小&#xff0c;所以要resize。 划分训练…

【动态规划】子数组系列(下)

子数组问题 文章目录 【动态规划】子数组系列&#xff08;下&#xff09;1. 等差数组划分1.1 题目解析1.2 算法原理1.2.1 状态表示1.2.2 状态转移方程1.2.3 初始化1.2.4 填表顺序1.2.5 返回值 1.3 代码实现 2. 最长湍流子数组2.1 题目解析2.2 算法原理2.2.1 状态表示2.2.2 状态…

初学spring5(五)使用注解开发

学习回顾&#xff1a;初学spring5&#xff08;四&#xff09;自动装配 一、使用注解开发 二、说明 在spring4之后&#xff0c;想要使用注解形式&#xff0c;必须得要引入aop的包 在配置文件当中&#xff0c;还得要引入一个context约束 <beans xmlns"http://www.sprin…

Node.js模块化加载机制

优先从缓存中加载 模块在第一次加载后会被缓存。这也意味着多次调用 require() 不会导致模块的代码被执行多次 注意:不论是内置模块、用户自定义模块、还是第三方模块&#xff0c;它们都会优先从缓存中加载&#xff0c;从而提高模块的加载效率 $就像下方图中测试 内置模块…

【软件测试】MySQL操作数据表常用sql语句(汇总)

目录&#xff1a;导读 前言 一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 数据表有哪些操作…

【JavaEE初阶】HTML

摄影分享~ 文章目录 一.第一个HTML程序1.创建一个HTML文件并运行2.在vscode中创建HTML文件并运行HTML代码的特点 二.HTML中的标签1.注释标签2.标题标签3.段落标签4.换行标签5.格式化标签6.图片标签&#xff1a;img7.超链接标签8.表格标签9.列表标签10.from标签input标签selec…