第五章 - 高频考题(中等)
1906. 查询差绝对值的最小值
1186. 删除一次得到子数组最大和

题目地址(1186. 删除一次得到子数组最大和)

题目描述

1
给你一个整数数组,返回它的某个 非空 子数组(连续元素)在执行一次可选的删除操作后,所能得到的最大元素总和。
2
3
换句话说,你可以从原数组中选出一个子数组,并可以决定要不要从中删除一个元素(只能删一次哦),(删除后)子数组中至少应当有一个元素,然后该子数组(剩下)的元素总和是所有子数组之中最大的。
4
5
注意,删除一个元素后,子数组 不能为空。
6
7
请看示例:
8
9
示例 1:
10
11
输入:arr = [1,-2,0,3]
12
输出:4
13
解释:我们可以选出 [1, -2, 0, 3],然后删掉 -2,这样得到 [1, 0, 3],和最大。
14
示例 2:
15
16
输入:arr = [1,-2,-2,3]
17
输出:3
18
解释:我们直接选出 [3],这就是最大和。
19
示例 3:
20
21
输入:arr = [-1,-1,-1,-1]
22
输出:-1
23
解释:最后得到的子数组不能为空,所以我们不能选择 [-1] 并从中删去 -1 来得到 0。
24
我们应该直接选择 [-1],或者选择 [-1, -1] 再从中删去一个 -1。
25
26
27
提示:
28
29
1 <= arr.length <= 10^5
30
-10^4 <= arr[i] <= 10^4
Copied!

前置知识

  • 数组
  • 动态规划

公司

  • 字节

思路

暴力法

符合知觉的做法是求出所有的情况,然后取出最大的。 我们只需要两层循环接口,外循环用于确定我们丢弃的元素,内循环用于计算 subArraySum。
1
class Solution:
2
def maximumSum(self, arr: List[int]) -> int:
3
res = arr[0]
4
def maxSubSum(arr, skip):
5
res = maxSub = float("-inf")
6
7
for i in range(len(arr)):
8
if i == skip:
9
continue
10
maxSub = max(arr[i], maxSub + arr[i])
11
res = max(res, maxSub)
12
return res
13
# 这里循环到了len(arr)项,表示的是一个都不删除的情况
14
for i in range(len(arr) + 1):
15
res = max(res, maxSubSum(arr, i))
16
return res
Copied!

空间换时间

上面的做法在 LC 上会 TLE, 因此我们需要换一种思路,既然超时了,我们是否可以从空间换时间的角度思考呢?我们可以分别从头尾遍历,建立两个 subArraySub 的数组 l 和 r。 其实这个不难想到,很多题目都用到了这个技巧。
具体做法:
  • 一层遍历, 建立 l 数组,l[i]表示从左边开始的以 arr[i]结尾的 subArraySum 的最大值
这里以 xxx 结尾有两种情况,要么自身就是一个数,要么就是和前面结合构成一个子序列。只要取这两种情况的最大值即可(因为我们的目标是求最大值)
  • 一层遍历, 建立 r 数组,r[i]表示从右边开始的以 arr[i]结尾的 subArraySum 的最大值
  • 一层遍历, 计算 l[i - 1] + r[i + 1] 的最大值
l[i - 1] + r[i + 1]的含义就是删除 arr[i]的子数组最大值
  • 上面的这个步骤得到了删除一个的子数组最大值。题目要求的是最多删除一个,因此还有一种不删除的情况。不删除的只需要在上面循环顺便计算一下即可。
1
class Solution:
2
def maximumSum(self, arr: List[int]) -> int:
3
n = len(arr)
4
l = [arr[0]] * n
5
r = [arr[n - 1]] * n
6
if n == 1:
7
return arr[0]
8
res = arr[0]
9
for i in range(1, n):
10
l[i] = max(l[i - 1] + arr[i], arr[i])
11
res = max(res, l[i])
12
for i in range(n - 2, -1, -1):
13
r[i] = max(r[i + 1] + arr[i], arr[i])
14
res = max(res, r[i])
15
for i in range(1, n - 1):
16
res = max(res, l[i - 1] + r[i + 1])
17
18
return res
Copied!

动态规划

上面的算法虽然时间上有所改善,但是正如标题所说,空间复杂度是 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]), 一个都不删除
1
#
2
# @lc app=leetcode.cn id=1186 lang=python3
3
#
4
# [1186] 删除一次得到子数组最大和
5
#
6
7
# @lc code=start
8
9
10
class Solution:
11
def maximumSum(self, arr: List[int]) -> int:
12
# DP
13
max0 = arr[0]
14
max1 = arr[0]
15
res = arr[0]
16
n = len(arr)
17
if n == 1:
18
return max0
19
20
for i in range(1, n):
21
# 先更新max1,再更新max0,因为max1用到了上一个max0
22
max1 = max(max1 + arr[i], max0)
23
max0 = max(max0 + arr[i], arr[i])
24
res = max(res, max0, max1)
25
return res
Copied!
复杂度分析
  • 时间复杂度:$O(N)$
  • 空间复杂度:$O(1)$

关键点解析

  • 空间换时间
  • 头尾双数组
  • 动态规划

相关题目

大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。 大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。