LeetCode 例题精讲 | 16 最大子数组和:子数组类问题的动态规划技巧

算法复习——动态规划篇之最大子数组问题 算法复习——动态规划篇之最大子数组问题 以下内容主要参考中国大学MOOC《算法设计与分析》,墙裂推荐希望入门算法的童鞋学习! 1. 问题背景 子数组:数组中连续的一段序列,例如X[3..7]X[3..7]X[3..7]; 子数组和:子数组中元素的求和,X[3..7]X[3..7]X[3..7]的和就是3+5−4+3+2=93+5-4+3+2=93+5−4+3+2=9; ​ 那么,问题就是如何寻找数组XXX中最大的非空子数组? 2. 问题定义 最大子数组问题(Max Continuous Subarr 阅读详情

59d5d048820f95a92a5b46be401ef15e.jpeg

本期例题:

  • LeetCode 53. Maximum Subarray Sum 最大子数组和(Easy)

  • LeetCode 718. Maximum Length of Repeated Subarray 最长公共子数组(Medium)

在前面的文章中,我们分别讲解了一维和二维动态规划问题的解题步骤与基本思路。不过,仅仅掌握基本步骤是不够的。要想熟练做出动态规划题目,还要掌握足够的解题技巧。

接下来的文章中,我会讲解动态规划问题中针对不同类型问题的小技巧。今天要讲的是关于「子数组」类题目的常见技巧。

在讲具体问题之前,我们要明确一下「子数组」和「子序列」的概念。

  • 子序列 (subsequence) 可以是不连续的。例如 "ACD" 是 "ABCDE" 的子序列;

  • 子数组 (subarray)、子串 (substring) 必须是连续的。例如 "BCD" 是 "ABCDE" 的子数组/子串。

我们前面的例题中讲过的最长公共子序列问题,关注的是「子序列」,而今天的文章我们要关注的都是「子数组」,请牢记这一点。

这篇文章的内容包括:

  • 子数组类问题的动态规划技巧

  • 「最大子数组和」问题的解法

  • 「最长公共子数组」问题的解法

最大子数组和

LeetCode 53. Maximum Subarray Sum 最大子数组和(Easy)

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

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

子问题定义的错误尝试

拿到这道「最大子数组和」问题,如果直接把动态规划的解题四步骤往上套,那么你可能首先会想到这么定义子问题:

子问题 表示「nums[0..k) 中的最大子数组和」。

662f3aab43feffe10868b2d18b062aac.jpeg
子问题定义的尝试

很不幸,这么定义子问题是行不通的。因为这么定义的话,我们无法写出子问题的递推关系!

我们用一个实际的例子 [-1, 2, 3, -2, 4, 1] ,看看每个子问题计算的结果:

  • 即 [-1] 的最大和子数组为 [-1],和为 -1;

  • 即 [-1, 2] 的最大和子数组为 [2],和为 2;

  • 即 [-1, 2, 3] 的最大和子数组为 [2, 3],和为 5;

  • 即 [-1, 2, 3, -2] 的最大和子数组为 [2, 3],和为 5;

  • 即 [-1, 2, 3, -2, 4] 的最大和子数组为 [2, 3, -2, 4],和为 7;

  • 即 [-1, 2, 3, -2, 4, 1] 的最大和子数组为 [2, 3, -2, 4, 1],和为 8。

在依次计算子问题的过程中, 到 的递推关系出现了一个断裂:

求出 [-1, 2, 3, -2] 的最大和子数组和为 [2, 3],位于数组的中部。

求 的时候,我们加入了一个新元素 4。自然地,我们想知道这个新元素能不能与 求出的最优解相结合,得到 的最优解。然而,它们结合不起来!

的最优解 [2, 3],和新元素 4 并不相邻。它们不能拼成一个连续的子数组!

dead908391395106ae6b90e4a0f411e9.jpeg
尝试构造子问题的递推关系

可见,问题出在「子数组」的限制上。子数组要求元素是连续的,那么,前一个子问题的最优解,可能在后一个子问题中用不上。子问题的递推链条就断开了。

正确的子问题定义

那么,怎么解决这个子问题「续不上」的问题呢?答案是修改子问题的定义,加一个限制条件:

子问题 表示「nums[0..k) 中,以最后一个元素结尾的最大子数组和」。

既然子数组拼接不起来,那么我们就限制子问题计算的子数组只能位于数组尾部。这样得到的最大和子数组,就一定可以跟下一个元素拼接起来了。

fa7b570184eca0159feb64be73b8d973.jpeg
子问题的新版定义

我们来看看这时候的子问题是怎么计算的:

  • 这时候求的是 [-1, 2, 3, - 2] 中位于数组尾部的最大子数组和,结果是 [2, 3, -2],和为 3。

  • 计算 的时候,直接把新元素 4 跟前面的最优解 [2, 3, -2] 拼接起来,得到最大和子数组是 [2, 3, -2, 4],和为 7。

子问题的递推关系

正确定义了子问题,就可以写出子问题的递推关系了。我们用 表示元素 nums[i],写出的子问题递推公式为:

这个递推公式的含义是,计算 nums[0..k) 的最大子数组和时,要么是把新元素 nums[k-1] 加入前一个子问题的结果(即 的含义),要么是只使用元素 nums[k-1],选择其中结果较大的一个方案。

这个递推公式可以继续化简为:

这样,我们就可以写出题解代码了:

public int maxSubArray(int[] nums) {
    // 子问题:
    // f(k) = nums[0..k) 中以 nums[k-1] 结尾的最大子数组和
 // 原问题 = max{ f(k) }, 0 <= k <= N

    // f(0) = 0
    // f(k) = max{ f(k-1), 0 } + nums[k-1]

    int N = nums.length;
    int[] dp = new int[N+1];
    dp[0] = 0;

    int res = Integer.MIN_VALUE;
    for (int k = 1; k <= N; k++) {
        dp[k] = Math.max(dp[k-1], 0) + nums[k-1];
        res = Math.max(res, dp[k]);
    }
    return res;
}

需要注意的是,我们最后不能直接返回最后一个子问题 的结果。因为我们子问题的定义变了, 只计算了以最后一个元素结尾的最大子数组和。我们需要取所有子问题结果的最大值,也就是

最终结果

限于文章篇幅,我们这里就不讨论这道题的空间优化方法以及其他解法了。以后会专门写一篇文章介绍这道题的不同解法。

最长公共子数组

理解了「最大子数组和」问题的解法,我们再来看一道类似思路的二维动态规划问题:最长公共子数组。

LeetCode 718. Maximum Length of Repeated Subarray 最长公共子数组(Medium)

给两个整数数组 s 和 t ,返回两个数组中公共的、长度最长的子数组的长度。例如:

输入:
s: [1, 2, 3, 2, 1, 5]
t: [6,3, 2, 1, 4, 7]
输出: 3
解释:
长度最长的公共子数组是 [3, 2, 1]。

「最长公共子数组」问题其实和「最长公共子序列」问题相似,只是把题目中的「子序列」换成了「子数组」。如果你对「最长公共子序列」问题不是很熟悉,可以回顾之前的文章:

LeetCode 例题精讲 | 15 最长公共子序列:二维动态规划的解法

在「最长公共子序列」问题中,我们是这样定义子问题的:

子问题 表示「s[0..i) 和 t[0..j) 的最长公共子序列」。

然而,当问题变成「子数组」以后,子数组的性质导致了原先的子问题递推关系不成立了,我们需要重新定义子问题。我们参考上一道例题的方法,给子数组加上一个位于数组尾部的限制,将子问题定义成这样:

子问题 表示「s[0..i) 和 t[0..j) 中,以最后一个元素结尾的最长公共子数组」。

8ea94ed07e6a076b8461f46918e18fd4.jpeg
最长公共子数组问题的子问题定义

注意,这里的以最后一个元素结尾指的是公共子数组既要包含 s[0..i) 的最后一个元素,也要包含 t[0..j) 的最后一个元素。

换句话说,就是从后往前看 s[0..i) 和 t[0..j) 的最后有几个元素是相同的。

7ecef85add42857d9980527093729a62.jpeg
从后往前比较相同元素

那么子问题的递推关系如何定义呢?很简单,我们还是只需要看 s[0..i) 和 t[0..j) 的最后一个元素:s[i-1] 和 t[j-1]。

  • 如果 s[i-1] == t[j-1],那么已经有一个元素是相同的,接下来是要继续往前看 s[0..i-1) 和 t[0..j-1) 的最后有几个元素是相同的。也就是 。

  • 如果 s[i-1] != t[j-1],那么最后一个元素就不相同,前面就更不用看了。也就是 。

这样我们就得到了子问题的递推公式:

那么原问题如何由子问题表示呢?是这样的:

最终结果

这样,我们就可以写出题解代码了:

public int findLength(int[] s, int[] t) {
    // 子问题:
    // f(i, j) = s[0..i) 和 t[0..j) 中以 s[i-1] 和 t[j-1] 结尾的最长公共子数组
    // 原问题 = max{ f(i, j) }, 0 <= i <= m, 0 <= j <= n

    // f(0, *) = 0
    // f(*, 0) = 0
    // f(i, j) = max:
    //           f(i-1, j-1) + 1, if s[i-1] == t[j-1]
    //           0              , if s[i-1] != t[j-1]

    int m = s.length;
    int n = t.length;
    int[][] dp = new int[m+1][n+1];

    int res = 0;
    for (int i = 0; i <= m; i++) {
        for (int j = 0; j <= n; j++) {
            if (i == 0 || j == 0) {
                dp[i][j] = 0;
            } else {
                if (s[i-1] == t[j-1]) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                } else {
                    dp[i][j] = 0;
                }
            }
            res = Math.max(res, dp[i][j]);
        }
    }
    return res;
}

总结

从以上两个例题中,我们可以看出「子数组」类问题的解题套路。我们可以在定义子问题的时候加上位于数组尾部的限制条件。这样才可以正常地推导出子问题的递推关系。

本文讲解的第二个例题「最长公共子数组」和「最长公共子序列」仅仅一个概念上的差距,子问题的定义和递推关系就有很多不同。建议大家把这两道题对照着来做,可以很容易地体会到「子数组」和「子序列」问题的不同之处。这里放上两道题的题号:

  • LeetCode 1143. Longest Common Subsequence 最长公共子序列(Medium)

  • LeetCode 718. Maximum Length of Repeated Subarray 最长公共子数组(Medium)

除了例题之外,LeetCode 上还有一些「子数组」类的题目:

  • LeetCode 152. Maximum Subarray Product 最大乘积子数组(Medium)

  • LeetCode 978. Longest Turbulent Subarray 最长波形子数组(Medium)

都是很典型的子数组类题目,可以试着练习一下。

我是 nettee,致力于分享面试算法的解题套路,让你真正掌握解题技巧,做到举一反三。我的《LeetCode 例题精讲》系列文章正在写作中,关注我的公众号,获取最新文章。

d9e5800901c70bfdd98947fc84cd5987.jpeg
最大子序列和问题及其解法 最大子序列和问题是一个经典的算法问题,其目标是在给定整数序列中找到一个连续子序列,使得子序列的和最大。在本文中,我将介绍该问题的解决方法,并提供相应的源代码。综上所述,我们介绍了最大子序列和问题的三种解法:暴力解法、动态规划解法和分治法解法。根据问题规模和要求的时间复杂度,选择适合的解法可以提高算法的效率。通过将序列分成更小的子问题并合并它们的解,可以有效地解决较大规模的问题。该方法只需要遍历序列一次,时间复杂度为O(n),其中n是序列的长度。这种暴力解法的时间复杂度为O(n^3),其中n是序列的长度。 阅读详情

相关推荐

maximum-subarray-sum-:Java中的最大子数组总和

最大子数组总和 maximum subarray sum使用Java语言中的所有复杂度来计算子数组的最大和。 1.O(n ^ 3) 2.O(n ^ 2) 3.O(n)

Coderwars:Maximum subarray sum(最大子数组和)

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Mar

a1_bc2的博客 530

生信实战|EffectorP3.0在植物病原菌效应子预测中的高效应用

本文详细介绍了EffectorP 3.0在植物病原菌效应子预测中的高效应用。通过机器学习模型,该工具能快速从海量分泌蛋白中批量预测胞质与质外体效应子,显著提升研究效率。文章提供了从环境配置、RNA-seq数据处理到批量预测的完整实战流程,并分享了结果解读与避坑指南,是生信研究人员进行病原物效应子鉴定的必备利器。

weixin_29195615的博客 302

元素的子元素_LeetCode 例题精讲 | 16 最大子数组和:子数组类问题的动态规划技巧...

来源:面向大象编程本期例题:LeetCode 53. Maximum Subarray Sum 最大子数组和(Easy)LeetCode 718. Maximum Length of Repeated Subarray 最长公共子数组(Medium)在前面的文章中,我们分别讲解了一维和二维动态规划问题的解题步骤与基本思路。不过,仅仅掌握基本步骤是不够的。要想熟练做出动态规划题目,还要掌握...

weixin_32184991的博客 144

surfer10与Arcgis10.3 完美生成等值线和等值面方法

surfer10 与Arcgis10.3生成等值线和等值面的步骤如下: 1.准备surfer用的Book1.dat文件,格式如下: "x" "y" "z" 116.471992 40.809975 232 116.335556 40.700542 1235 116.541626 40.713333 214 116.73349 40.798607 566 2.在surfer中,选择Gr

哈里·雪利博客 1万+

Tesseract-Ocr踩坑下载以及进行检验

1 OCR,即Optical Character Recognition:光学字符识别,是指通过扫描字符,然后通过其形状将其翻译成电子文本的过程。下载完成后进行解压找到这两个,然后复制到之前下载Tesseract-Ocr的tessdata文件下。可能会出现报错说chi下载的问题咱们不用管继续ok让它先下载完毕,后续会有下载chi的步骤。四、检验tesseract使用(将以下图片进行下载我这里将以下图片命名为3.png)我这里选的是2024年的版本如果下载不了;4 在安装的时候(找到下载的exe文件,双击)

m0_51428325的博客 1916

共享内存中动态数组_LeetCode 例题精讲 | 16 最大子数组和:子数组类问题的动态规划技巧...

来源:面向大象编程本期例题:LeetCode 53. Maximum Subarray Sum 最大子数组和(Easy)LeetCode 718. Maximum Length of Repeated Subarray 最长公共子数组(Medium)在前面的文章中,我们分别讲解了一维和二维动态规划问题的解题步骤与基本思路。不过,仅仅掌握基本步骤是不够的。要想熟练做出动态规划题目,还要掌握...

weixin_39615257的博客 110

深入解析GEM5 McPAT NoC功耗模型:从arbiter电容计算到微架构关联

本文深入解析GEM5 McPAT NoC功耗模型,重点探讨arbiter电容计算与微架构的关联。通过拆解动态功耗公式和四类电容的物理本质,揭示能量与功率的转换关系,并提供参数调试和模型精度优化的实战技巧,帮助开发者更准确地预测和优化NoC功耗。

weixin_29305313的博客 201

LeetCode 例题精讲 | 17 动态规划如何拆分子问题,简化思路

点击上方“五分钟学算法”,选择“星标”公众号重磅干货,第一时间送达转自面向大象编程在上一篇文章中,我们讲解了「子数组」类动态规划题目的常见技巧。这篇文章继续讲解动态规划问题中的小技巧。今...

程序员吴师兄的博客 667

大模型系统构建的“三驾马车”——Prompt、Context与Harness工程

在LLM应用开发的早期阶段,我们热衷于比拼“提示词”的精妙程度。那时,一个精心构造的思维链CoT就能让模型的表现提升10%以上。随着大模型从“玩具”走向“生产系统”,我们逐渐意识到:单一的 Prompt 只是起点,而非终点。 真正的工业级应用,需要构建一套三层递进的工程体系——Prompt Engineering、Context Engineering 和 Harness Engineering。

西木风落 604

USRP B210 运行gnuradio时 FPGA固件版本冲突错误解决方法

事情起源于昨天拿到USRP后开始调试,结果整了一个下午都没解决这个问题。 昨天晚上自己思考了一下,觉得还是版本问题。 首先,uhd_find_devices和uhd_usrp_probe均可以正常运行,没有报错,固件正常加载,序列号也可看到。 teray@teray-X550JK:~$ uhd_find_devices [INFO] [UHD] linux; GNU C++ version 7.5...

斯哈研究所 4243

【LeetCode】力扣刷题热题100道(16-20题)附源码 容器 子数组 数组 连续序列 三数之和(C++)

【LeetCode】力扣刷题热题100道(16-20题)附源码(C++)

你要拥有一个你喜欢并且热爱的人生 7491

新代永久解锁西门子smart S7-200PLC 上传程序步骤

西门子 SMART S7-200 PLC 程序上传步骤 打开编程软件 或左下角依次打开 程序 所有程序 Siemens Automation Simatic STEP 7-MicroWIN SMART V2.4 STEP 7-MicroWIN SMART 技术交流 plcjiesuo1 2. 确认plc电源端以及输入电压; 3.用网线连接plc与电脑技术交流 plcjiesuo1 4.plc通电; 5.打开编程软件,双击通讯 6.按照...

m0_60773720的博客 2万+

LeetCode 2272.最大波动的子字符串:转为多次的最大子数组和 - 一步步思考推导

LeetCode 2272.最大波动的子字符串:转为多次的最大子数组和 - 一步步思考推导 字符串的 波动 定义为子字符串中出现次数 最多 的字符次数与出现次数 最少 的字符次数之差。 给你一个字符串 s ,它只包含小写英文字母。请你返回 s 里所有 子字符串的 最大波动 值。 子字符串 是一个字符串的一段连续字符序列。

<font color="Pink">Tisfy的博客</font> 974

AUTOSAR从入门到精通-汽车电子电气架构(EEA)

EEA(Electrical/Electronic Architecture),电子电气架构,是首先由德尔福公司提出的,集合汽车的电子电气系统原理设计、中央电器盒的设计、连接器的设计、电子电气分配系统等设计为一体的整车电子电气解决方案的概念。通过EEA的设计,可将动力总成、驱动信息、娱乐信息等车身信息转化为实际的电源分配的物理布局、信号网络、数据网络、诊断、容错、能量管理等的电子电气解决方案。

getusushu的博客 859

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

❤️思维导图整理大厂面试高频数组16: 最长湍流子数组的动态规划构建 和 滑动窗口解法, 力扣978❤️

此专栏文章是对力扣上算法题目各种方法的总结和归纳, 整理出最重要的思路和知识重点并以思维导图形式呈现, 当然也会加上我对导图的详解. 目的是为了更方便快捷的记忆和回忆算法重点(不用每次都重复看题解), 毕竟算法不是做了一遍就能完全记住的. 所以本文适合已经知道解题思路和方法, 想进一步加强理解和记忆的朋友, 并不适合第一次接触此题的朋友(可以根据题号先去力扣看看官方题解, 然后再看本文内容). 关于本专栏所有题目的目录链接, 刷算法题目的顺序/注意点/技巧, 以及思维导图源文件问题请点击此链接. 想进大厂.

孤柒的博客 1万+

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

leetcode:689. 三个无重叠子数组的最大和

题目解析 1

OceanStar的博客 449

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

LeetCode第152题_乘积最大子数组

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

qq_40263592的博客 1333

【动态规划】力扣918. 环形子数组的最大和

他的思想是所有数组元素之和减去中间的一段最小子段和,剩下的部分就是最大的经过头尾的子段和,再与正常子段和比较即可。针对特殊的跨越尾部的情况,使用leftMax来记录第 i 个元素之前的最大子段和,然后从右到左遍历数组,最后比较两种情况的最大值即可。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n]。给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和。

sjsjs11的博客 771

LeetCode 1746. 经过一次操作后的最大子数组和

LeetCode 1746. 经过一次操作后的最大子数组和 文章目录LeetCode 1746. 经过一次操作后的最大子数组和题目描述一、解题关键词二、解题报告1.思路分析2.时间复杂度3.代码示例2.知识点总结相同题目 题目描述 你有一个整数数组 nums。你只能将一个元素 nums[i] 替换为 nums[i] * nums[i]。 返回替换后的最大子数组和。 示例 1: 输入:nums = [2,-1,-4,-3] 输出:17 解释:你可以把-4替换为16(-4*(-4)),使nums = [2,-1

Datao_csdn的博客 1402

3.16【leetcode练习】最大子数组和

最大子数组和 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组 是数组中的一个连续部分。 示例 1: 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6。 示例 2: 输入:nums = [1] 输出:1 示例 3: 输入:nums = [5,4,-1,7,8] 输出:23 提示: 1 <= nums.length <= 10510^

Dream_Poem的博客 432

力扣日记3.16-【贪心算法篇】53. 最大子数组和

啊啊啊啊贪心算法好难啊,做一道不会一道 T^T,到底怎么想到局部最优啊……关键:不能让“连续和”为负数的时候加上下一个元素,而不是 不让“连续和”加上一个负数。所以要在当前“连续和”为负数的时候,调整最大子序列区间的起始位置为下一个元素(而不是在加上了一个负数的时候,因为只要当前连续和为正数,就对于增大总和是有利的)。思路来源于代码随想录TODO:动态规划。

RobinChan730的博客 765
上一篇: 经典动态规划:编辑距离
下一篇: LeetCode 例题精讲 | 17 动态规划如何拆分子问题,简化思路
nettee
博客等级 码龄14年 15粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值