动态规划算法(子数组专题1)

news/2025/2/25 4:05:54

动态规划算法专辑之子数组问题(1)

本专栏将从状态定义、状态转移方程、初始化、填表顺序、返回值这五大细节来详细讲述动态规划算法的解题思路及代码实现

一、什么是子数组

子数组:子数组是数组中的一个连续部分的集合,子序列可以不连续,但子数组的元素必定在原数组中是连续的

二、leetcode.cn/problems/maximum-subarray/">最大子数组和

image-20230615060939275

1.题目解析

在n个子数组中,找到和最大的那个,并返回该子数组元素的总和

2.状态定义

根据经验+题目要求,dp[i]表示:以i下标为结尾的子数组中最大的和

3.状态转移方程

这题和最长递增子序列分析相似,dp[i]同样分为长度为1和长度大于1这两种情况

image-20230615194212469

由上诉分析可得如下的状态转移方程:

image-20230615194353900

4.初始化

在之前的子序列问题中,我们是按照长度为1的情况下来对dp表进行初始化,但这题将整个dp表初始化的话,反而冗余了,只需初始化dp[0]即可(在子序列问题中,是和dp表进行比较,此题是和nums进行比较)

5.填表顺序

可以看到在dp表中,i收到i-1的影响,所以应该从左到右进行填表

6.返回值

这和之前的子序列问题也类似,返回的应该是dp表中的最大值

7.代码实现

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

        return res;
    }
};

三、leetcode.cn/problems/maximum-sum-circular-subarray/">环形子数组的最大和

image-20230615195140108

1.题目解析

本题是上面那题的变形题,将线性的数组,变为了环形,也就是说首尾可以相连,在原题上上升了一定难度

2.状态定义

根据经验+题目要求,dp[i]表示:以下标i结尾的所有环形子数组的和的最大值

状态分析:

image-20230615195923895

由上图我们可以看到,原来的状态定义只能满足下标是连续的状态,对于第二种情况处理起来十分麻烦,在数学上有一种思路:正难则反,既然算收尾相连是麻烦的,除去了首尾相连的子数组,剩下的子数组必然连续,那么就可以复用原来的状态转移方程了,要保证首尾相连最大,数组总和是确定的,所以只需保证剩余子数组和最小

由上述分析,此题因有两个不同且相互独立的子问题,因此要定义两种不同的状态:

f[i]表示:以下标i结尾的所有环形子数组的和的最大值

g[i]表示:以下标i结尾的所有环形子数组的和的最小值

3.状态转移方程

经过上述的分析,状态转移方程也应该有两个:

image-20230615201039604

4.初始化

和上题一样,初始化dp[0]就行

5.填表顺序

经过上述的变形,使得两个dp表中i都只受i-1影响,因此可以从左到右进行填表

试想,如果直接计算首尾相连的情况,还能直接从左到右填表吗?

6.返回值

能直接返回max(fmax,sum - gmin)吗(fmax是f表里的最大值,gmin同理)

很显然是不能的,当数组里的所有元素都为负数时,返回的会是0,全是负数的子数组的和可能出现0吗?

因此,当全负数时,因返回fmax

为了减少单独计算数组总和这一步骤,我们只需在更新dp表的同时计算总和,在最后判断sum和gmin是否相等(详情见代码)

7.代码实现

class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n+1,0);
        auto g = f;
        int fmax = INT_MIN;
        int gmin = INT_MAX;
        int sum = 0;
        for(int i=1;i<n+1;i++)
        {
            int x = nums[i-1];
            f[i] = max(x,f[i-1]+x);
            fmax = max(fmax,f[i]);
            g[i] = min(x,g[i-1]+x);
            gmin = min(gmin,g[i]);
            sum += x;
        }
        

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

四、总结

我们任然不要害怕状态定义,一步一步分析来,当发现无法写出状态转移方程或状态转移方程太难时,我们再重新考虑状态定义即可,没有小白可以一来就能找到最准确的状态转移方程

本文涉及到的几个小细节:对环形问题应该计算他的对立面(正难则反),返回值、初始化


http://www.niftyadmin.cn/n/437808.html

相关文章

蓝牙ble的常见概念

蓝牙广播 包组成结构 低功耗蓝牙一共有40个信道&#xff0c;频段范围从2402Mhz-2480Mhz&#xff0c;每2Mhz一个信道&#xff0c;37 38 39 是广播信道&#xff0c;其余为数据信道 一个广播信道最长37字节&#xff0c;有6字节用作蓝牙设备的MAC地址&#xff0c;我们只需要关注剩…

2、产品经理的工作内容

上一篇文章&#xff1a;1、产品经理的宏观定义_阿杰学编程的博客-CSDN博客 接下来这个章节里&#xff0c;我们有三个目标。 第一个通过案例&#xff0c;大家要了解一下产品经理的一个主要的工作内容。 第二个理解产品经理的一个重要性。 第三个我们要熟悉一下MVP的概念&…

快速部署合同管理模板:低代码实现高效率

在现代商业环境中&#xff0c;合同管理是企业日常运营中至关重要的一环。合同是企业与外部实体之间约定的法律文件&#xff0c;合够帮助企业有效管理合同的全生命周期&#xff0c;包括合同创建、审批、签署、执行和归档&#xff0c;以提高合同管理的效率和准确性。 随着企业数…

【0208】Backend向客户端发送Client authentication的底层实现(10 - 3)

文章目录 1. 完整消息发送到客户端1.1 Backend并非直接调用send()发送消息1.2 拼接消息类型(msgtype)前缀1.2.1 从tcpdump抓包文件开始分析【0206】Backend 向客户端发送身份认证请求报文(Client authentication) (10 - 1) 【0207】Backend向客户端发送Client authentica…

搞定剑桥面试数学题番外篇2:使用多线程并发“加强版”

0. 概览 我们在之前三篇博文中已经介绍了如何用多种语言&#xff08;ruby、swift、c、x64 汇编和 ARM64 汇编&#xff09;实现一道“超超超难”的剑桥数学面试题&#xff1a; 有趣的小实验&#xff1a;四种语言搞定“超超超难”剑桥面试数学题 搞定“超超超难”剑桥面试数学…

行为型设计模式05-备忘录模式

&#x1f9d1;‍&#x1f4bb;作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 备忘录模式 1、备忘录模式介绍 备忘录模式是一种行为型设计模式&#xff0c;用于在不破坏封装性的前提…

【mysql】1378. 使用唯一标识码替换员工ID

题目&#xff1a; Employees 表&#xff1a; ---------------------- | Column Name | Type | ---------------------- | id | int | | name | varchar | ---------------------- id 是这张表的主键。 这张表的每一行分别代表了某公司其中一位员工的名字和 ID 。 EmployeeUN…

CentOS GCC 离线升级 编译安装 8.3.0

从系统自带的 gcc-4.8.5 版本升级至 gcc-8.3.0 版本 目录 下载源代码&#xff1a; 下载依赖&#xff1a; 编译&#xff08;约一个小时&#xff09; 重开控制台确认是否生效 下载源代码&#xff1a; https://ftp.gnu.org/gnu/gcc/gcc-8.3.0/gcc-8.3.0.tar.gzhttps://ftp.gn…