# 1186. 删除一次得到子数组最大和 ## 题目地址(1186. 删除一次得到子数组最大和) <https://leetcode-cn.com/problems/maximum-subarray-sum-with-one-deletion/> ## 题目描述 ``` <pre class="calibre18">``` 给你一个整数数组,返回它的某个 非空 子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。 换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),(删除后)子数组中至少应当有一个元素,然后该子数组(剩下)的元素总和是所有子数组之中最大的。 注意,删除一个元素后,子数组 不能为空。 请看示例: 示例 1: 输入:arr = [1,-2,0,3] 输出:4 解释:我们可以选出 [1, -2, 0, 3],然后删掉 -2,这样得到 [1, 0, 3],和最大。 示例 2: 输入:arr = [1,-2,-2,3] 输出:3 解释:我们直接选出 [3],这就是最大和。 示例 3: 输入:arr = [-1,-1,-1,-1] 输出:-1 解释:最后得到的子数组不能为空,所以我们不能选择 [-1] 并从中删去 -1 来得到 0。 我们应该直接选择 [-1],或者选择 [-1, -1] 再从中删去一个 -1。 提示: 1 <= arr.length <= 10^5 -10^4 <= arr[i] <= 10^4 ``` ``` ## 前置知识 - 数组 - 动态规划 ## 公司 - 字节 ## 思路 ### 暴力法 符合知觉的做法是求出所有的情况,然后取出最大的。 我们只需要两层循环接口,外循环用于确定我们丢弃的元素,内循环用于计算subArraySum。 ``` <pre class="calibre18">``` <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span>:</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">maximumSum</span><span class="hljs-params">(self, arr: List[int])</span> -> int:</span> res = arr[<span class="hljs-params">0</span>] <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">maxSubSum</span><span class="hljs-params">(arr, skip)</span>:</span> res = maxSub = float(<span class="hljs-string">"-inf"</span>) <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(arr)): <span class="hljs-keyword">if</span> i == skip: <span class="hljs-keyword">continue</span> maxSub = max(arr[i], maxSub + arr[i]) res = max(res, maxSub) <span class="hljs-keyword">return</span> res <span class="hljs-title"># 这里循环到了len(arr)项,表示的是一个都不删除的情况</span> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(arr) + <span class="hljs-params">1</span>): res = max(res, maxSubSum(arr, i)) <span class="hljs-keyword">return</span> res ``` ``` ### 空间换时间 上面的做法在LC上会TLE, 因此我们需要换一种思路,既然超时了,我们是否可以从空间换时间的角度思考呢?我们可以分别从头尾遍历,建立两个subArraySub的数组l和r。 其实这个不难想到,很多题目都用到了这个技巧。 具体做法: - 一层遍历, 建立l数组,l\[i\]表示从左边开始的以arr\[i\]结尾的subArraySum的最大值 - 一层遍历, 建立r数组,r\[i\]表示从右边开始的以arr\[i\]结尾的subArraySum的最大值 - 一层遍历, 计算 l\[i - 1\] + r\[i + 1\] 的最大值 > l\[i - 1\] + r\[i + 1\]的含义就是删除arr\[i\]的子数组最大值 - 上面的这个步骤得到了删除一个的子数组最大值, 不删除的只需要在上面循环顺便计算一下即可 ``` <pre class="calibre18">``` <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span>:</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">maximumSum</span><span class="hljs-params">(self, arr: List[int])</span> -> int:</span> n = len(arr) l = [arr[<span class="hljs-params">0</span>]] * n r = [arr[n - <span class="hljs-params">1</span>]] * n <span class="hljs-keyword">if</span> n == <span class="hljs-params">1</span>: <span class="hljs-keyword">return</span> arr[<span class="hljs-params">0</span>] res = arr[<span class="hljs-params">0</span>] <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-params">1</span>, n): l[i] = max(l[i - <span class="hljs-params">1</span>] + arr[i], arr[i]) res = max(res, l[i]) <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(n - <span class="hljs-params">2</span>, <span class="hljs-params">-1</span>, <span class="hljs-params">-1</span>): r[i] = max(r[i + <span class="hljs-params">1</span>] + arr[i], arr[i]) res = max(res, r[i]) <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-params">1</span>, n - <span class="hljs-params">1</span>): res = max(res, l[i - <span class="hljs-params">1</span>] + r[i + <span class="hljs-params">1</span>]) <span class="hljs-keyword">return</span> res ``` ``` ### 动态规划 上面的算法虽然时间上有所改善,但是正如标题所说,空间复杂度是O(n),有没有办法改进呢?答案是使用动态规划。 具体过程: - 定义max0,表示以arr\[i\]结尾且一个都不漏的最大子数组和 - 定义max1,表示以arr\[i\]或者arr\[i - 1\]结尾,可以漏一个的最大子数组和 - 遍历数组,更新max1和max0(注意先更新max1,因为max1用到了上一个max0) - 其中`max1 = max(max1 + arr[i], max0)`, 即删除arr\[i - 1\]或者删除arr\[i\] - 其中`max0 = max(max0 + arr[i], arr[i])`, 一个都不删除 ``` <pre class="calibre18">``` <span class="hljs-title">#</span> <span class="hljs-title"># @lc app=leetcode.cn id=1186 lang=python3</span> <span class="hljs-title">#</span> <span class="hljs-title"># [1186] 删除一次得到子数组最大和</span> <span class="hljs-title">#</span> <span class="hljs-title"># @lc code=start</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span>:</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">maximumSum</span><span class="hljs-params">(self, arr: List[int])</span> -> int:</span> <span class="hljs-title"># DP</span> max0 = arr[<span class="hljs-params">0</span>] max1 = arr[<span class="hljs-params">0</span>] res = arr[<span class="hljs-params">0</span>] n = len(arr) <span class="hljs-keyword">if</span> n == <span class="hljs-params">1</span>: <span class="hljs-keyword">return</span> max0 <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-params">1</span>, n): <span class="hljs-title"># 先更新max1,再更新max0,因为max1用到了上一个max0</span> max1 = max(max1 + arr[i], max0) max0 = max(max0 + arr[i], arr[i]) res = max(res, max0, max1) <span class="hljs-keyword">return</span> res ``` ``` **复杂度分析** - 时间复杂度:O(N)O(N)O(N) - 空间复杂度:O(1)O(1)O(1) ## 关键点解析 - 空间换时间 - 头尾双数组 - 动态规划 ## 相关题目 - [42.trapping-rain-water](42.trapping-rain-water.html) 大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:<https://github.com/azl397985856/leetcode> 。 目前已经 37K star 啦。 大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。 ![](https://img.kancloud.cn/cf/0f/cf0fc0dd21e94b443dd8bca6cc15b34b_900x500.jpg)