经过一次操作后的最大子数组和 图解题解
这道题到底在问什么
- 输入
- nums=[2,-1,-4,-3]
- 输出
- 17 (把 -4 平方成 16,子数组 [2,-1,16] 和为 17)
最优解:为什么这么做
一句话答案:LeetCode 1746 经过一次操作后的最大子数组和:必须把一个元素平方一次,求最大子数组和。在 Kadane 上加一维状态,no 记未用平方、yes 记已用平方的最大段和,答案只看 yes,时间 O(n)、空间 O(1)。
必须挑一个元素平方,最大子数组和会怎么变
给一个整数数组 nums,必须挑恰好一个元素平方一次,再在改后数组里找一段非空连续子数组,让和最大。题面 nums=[2,-1,-4,-3],把 -4 平方成 16、取 [2,-1,16],和是 17。哪怕平方不划算,『必须』也得用掉这次操作。
枚举『平方哪个元素』,为什么要跑 n 遍扫描
『平方哪个元素』有 n 种选法:固定平方第 k 个,跑一遍普通最大子数组和,取最好。每种都要重扫数组,一趟 O(n)(大 O 记号,记操作数随数据量怎么涨),合计 O(n²)。这 n 遍只差平方位置,大量子段和被反复重算,该一遍扫描把两种情形一起管住。
两个状态 no 和 yes,各自记的是什么
关键是给『平方用没用掉』单独记账。开两个状态(DP,即动态规划,把『以第 i 位结尾、平方用/没用过的最大和』记下来后面直接接着用):no 记以当前元素结尾、还没用平方的最大子段和,yes 同样以它结尾、但已用掉平方。拆开是因为『用没用过』决定还有没有资格再平方。题目必须平方,答案只从 yes 挑。
yes 为什么要在三条来路里挑最大
no 好办:以当前元素 x 结尾、不用平方,接前一段(no+x)或从 x 另起取大,这就是 Kadane(最大子数组和的经典扫法,每步只决定接前面还是另起)。yes 得盯『平方花在哪』,三条来路:平方早用在更前面,是 yes+x;平方正用在 x、前面接一段没平方的干净段,是 old_no + x²;平方用在 x 且前面不接,是 x²。取最大即新 yes。
第二、三条的 old_no 是命门:必须取『更新 no 之前』的旧值,每轮先存旧值再更新 no。
拿 [2,-1,-4,-3] 把两条账逐个元素填出来
起手 no=0(no 起手 0 是因为它允许空前缀、且答案不看 no),yes 和 ans 设成极小(还没有合法已平方段)。读 2:old_no=0,no=max(0+2, 2)=2,yes=max(极小+2, 0+4, 4)=4,ans=4。读 -1:old_no=2,no=max(2-1, -1)=1,yes=max(4-1, 2+1, 1)=3,ans 仍 4。读 -4:old_no=1,no=max(1-4, -4)=-3,yes=max(3-4, 1+16, 16)=17(平方花在 -4),ans 刷成 17。读 -3:old_no=-3,no=max(-3-3, -3)=-3,yes=max(17-3, -3+9, 9)=14,没超过 ans。扫完 ans=17,对上题面。
更新 yes 时用了刚改过的 no,这个数为什么被算了两次
病根在时序:old_no + x² 要的是『x 还没进账』的 no;先更新再配,x 被平方项和普通项各吃一次,yes 凭空变大。
复杂度:一趟循环,每元素几次比较加法,时间 O(n);只留 no、yes、ans、old_no 几个变量轮换(只留最近用得上的值、覆盖旧的,叫滚动),空间 O(1)。答案只能取 yes 的全程最大——顺手返回 no 等于允许不平方,[2,-1,-4,-3] 会答成 2。单元素同样逃不掉:只有一个 -3 时答案是 9、不是 -3。
▶ 动画逐步走查(共 22 步)——想跟着动画一帧帧对照就展开
- 3记住「no 是普通最大子段和,yes 是已用平方的最大子段和,yes 取三种来法的最大」,下面每一帧都在套它。
- 4读第 0 个 2。先更新 no(没用平方的最大子段和):从 2 自己重开更划算,no=2。
- 5更新 yes:把 2 平方成 4 最划算,yes=4(红色标出被平方的元素)。刷新 ans=4。
- 6读第 1 个 -1。先更新 no(没用平方的最大子段和):接在前面后面更划算,no=2+-1=1。
- 7更新 yes:平方留在更早的位置、当前元素正常加进来更划算,yes=3。没超过 ans=4。
- 8读第 2 个 -4。先更新 no(没用平方的最大子段和):接在前面后面更划算,no=1+-4=-3。
- 9更新 yes:把 -4 平方成 16 最划算,yes=17(红色标出被平方的元素)。刷新 ans=17。
- 10读第 3 个 -3。先更新 no(没用平方的最大子段和):从 -3 自己重开更划算,no=-3。
- 11更新 yes:平方留在更早的位置、当前元素正常加进来更划算,yes=14。没超过 ans=17。
- 12读第 4 个 1。先更新 no(没用平方的最大子段和):从 1 自己重开更划算,no=1。
- 13更新 yes:平方留在更早的位置、当前元素正常加进来更划算,yes=15。没超过 ans=17。
- 14读第 5 个 -5。先更新 no(没用平方的最大子段和):接在前面后面更划算,no=1+-5=-4。
- 15更新 yes:把 -5 平方成 25 最划算,yes=26(红色标出被平方的元素)。刷新 ans=26。
- 16读第 6 个 2。先更新 no(没用平方的最大子段和):从 2 自己重开更划算,no=2。
- 17更新 yes:平方留在更早的位置、当前元素正常加进来更划算,yes=28。刷新 ans=28。
- 18读第 7 个 3。先更新 no(没用平方的最大子段和):接在前面后面更划算,no=2+3=5。
- 19更新 yes:平方留在更早的位置、当前元素正常加进来更划算,yes=31。刷新 ans=31。
- 20读第 8 个 -2。先更新 no(没用平方的最大子段和):接在前面后面更划算,no=5+-2=3。
- 21更新 yes:平方留在更早的位置、当前元素正常加进来更划算,yes=29。没超过 ans=31。
- 22读第 9 个 4。先更新 no(没用平方的最大子段和):接在前面后面更划算,no=3+4=7。
- 23更新 yes:平方留在更早的位置、当前元素正常加进来更划算,yes=33。刷新 ans=33。
- 24扫完全程,最优是绿色这段 [下标 4..9],其中把红色的 nums[5]=-5 平方成 25,整段和 = 33。一遍扫描、两个状态就锁定了答案。
⚠️ 容易写错的地方
✗ 错:以为可以不平方
✓ 对:题目要求必须平方一次,答案只能从 yes 取
ans 取 yes 而非 no,否则会少算那次必须的操作
✗ 错:yes 只考虑 yes+x 一种来法
✓ 对:还要比 旧no+x² 和 x²
漏掉“在当前元素用平方”会错过把负数翻正的最优解
✗ 错:更新 yes 时用了已更新的 no
✓ 对:必须用本轮更新前的旧 no
旧no 才表示“当前元素之前都没用平方”
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def maxSumAfterOperation(self, nums: List[int]) -> int:
no = 0
yes = -10**30
ans = -10**30
for x in nums:
old_no = no
no = max(no + x, x)
yes = max(yes + x, old_no + x * x, x * x)
ans = max(ans, yes)
return ansC++
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
class Solution {
public:
int maxSumAfterOperation(vector<int>& nums) {
int no = 0, yes = INT_MIN / 4, ans = INT_MIN / 4;
for (int x : nums) {
int oldNo = no;
no = max(no + x, x);
yes = max({yes + x, oldNo + x * x, x * x});
ans = max(ans, yes);
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int maxSumAfterOperation(int[] nums) {
int no = 0, yes = Integer.MIN_VALUE / 4, ans = Integer.MIN_VALUE / 4;
for (int x : nums) {
int oldNo = no;
no = Math.max(no + x, x);
yes = Math.max(Math.max(yes + x, oldNo + x * x), x * x);
ans = Math.max(ans, yes);
}
return ans;
}
}复杂度
时间
O(n)
扫一遍数组
空间
O(1)
只用 no、yes、ans 三个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 经过一次操作后的最大子数组和 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
答案只看 yes,为什么还得费劲维护 no?+
因为 yes 的第二、三条来路要用 old_no —— yes 里『平方用在当前元素』的分支,前面接的是一段没用过平方的干净子段,那段的最大和正是 no 提供的。没有 no 一路记着『到目前为止、不用平方能凑多大』,yes 就没法在『平方花在当前 x、前面白捡一段』时接上正确的值。所以 no 不是答案,是给 yes 当垫脚的中间账,一步都不能省。
这题在 LeetCode 53 上加了什么?+
53 只求普通最大子数组和,一个 Kadane 扫一遍就是答案。本题在它上面加一维『有没有用掉那次平方』的状态:no 那条就是原封不动的 53,yes 那条是『已平方』的平行世界,靠 old_no + x² 把平方接到某个元素上。从 53 过来,本题多出来的只有 yes 这条递推,答案改从 yes 取最大。
『必须平方』这个约束在代码里体现在哪?+
就体现在答案只取 yes 的全程最大、完全不看 no。no 记的是『一次平方都没用』的普通子段和,yes 记的是『已经用掉一次平方』的子段和;题目强制必须平方一次,合法答案只能落在 yes 里。要是图直觉返回 no 的最大值,就等于允许『不平方』,[2,-1,-4,-3] 会给出 2 而不是 17。而 yes 里那三条来路,正好覆盖了平方落在当前元素、落在更早元素两种位置,保证『恰好平方一次』不重不漏。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 经过一次操作后的最大子数组和 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。