LeetCode 1567. 乘积为正数的最长子数组长度
题目描述


题意分析
求乘积为正数的最长连续子数组长度。数值本身并不影响目标,只需要知道元素的正负与是否为零;直接计算乘积反而可能溢出。
可以从左到右维护以当前位置结尾的最长正乘积、负乘积片段。负片段虽然暂时不能作为答案,遇到下一个负数后却能变成正片段,因此也必须保留。
解法:动态规划状态转移
核心思路
[!blue]
pos表示以当前处理位置结尾、乘积为正的最长长度,neg表示同样结尾、乘积为负的最长长度。长度为0表示不存在这种片段。每轮读取新元素x时,旧状态对应前一个位置。若
x > 0,乘积符号不变。旧正片段可以延长,pos = oldPos+1;即使旧正片段不存在,当前正数单独就能形成长度1。负片段只能从已有负片段延长,因此旧neg > 0时才加一,否则仍为0。若
x < 0,乘积符号交换。新的正片段必须由旧负片段接上当前负数得到,所以旧neg > 0时新正长度是oldNeg+1,否则不存在。新的负片段可以由旧正片段延长,长度为oldPos+1;旧正片段不存在时,这个式子得到1,表示当前负数单独成段。两个新值都必须读取旧状态,再一起赋值。若
x == 0,任何跨过它的片段乘积都为零,不属于正或负状态,因此将pos、neg一起清空,之后从新位置重新开始。这些转移覆盖了单个元素成段和延长前一位置片段的所有可能。同一符号只保留最长长度就足够,因为接上同一个新元素时,它们的符号变化相同,长度都只增加一,较短片段不会反而更优。每轮用
pos更新全局最大值best,最终就覆盖所有可能的结束位置。
解题步骤
- 初始化
pos = 0、neg = 0、best = 0。- 当前元素为正数时,正长度加一;只有负长度已经存在时,才将负长度加一。
- 当前元素为负数时,先由两个旧状态计算新正、新负长度,再同时替换。
- 当前元素为零时清空两种结尾状态,但不清空全局答案。
- 每轮更新
best = max(best, pos),遍历结束后返回best。若始终没有正乘积片段,结果自然为0。
代码实现
class Solution {
public int getMaxLen(int[] nums) {
int pos = 0;
int neg = 0;
int best = 0;
for (int x : nums) {
if (x > 0) {
pos = pos + 1;
if (neg > 0) {
neg = neg + 1;
}
} else if (x < 0) {
int newPos = 0;
if (neg > 0) {
newPos = neg + 1;
}
// 负数把旧正片段转负;先保存旧状态再一起替换。
int newNeg = pos + 1;
pos = newPos;
neg = newNeg;
} else {
// 零切断连续乘积,两种结尾状态都清空。
pos = 0;
neg = 0;
}
best = Math.max(best, pos);
}
return best;
}
}
func getMaxLen(nums []int) int {
pos, neg := 0, 0
best := 0
for _, x := range nums {
if x > 0 {
pos = pos + 1
if neg > 0 {
neg = neg + 1
}
} else if x < 0 {
newPos := 0
if neg != 0 {
newPos = neg + 1
}
// 负数把旧正片段转负;先保存旧状态再一起替换。
newNeg := pos + 1
pos, neg = newPos, newNeg
} else {
// 零切断连续乘积,两种结尾状态都清空。
pos, neg = 0, 0
}
if pos > best {
best = pos
}
}
return best
}
复杂度分析
- 时间复杂度:$O(n)$。每个元素只进行一次符号分类和常数次状态更新。
- 空间复杂度:$O(1)$。只保存正负长度、临时新状态和全局最大长度。
关键点总结
[!green]
- 状态要求片段恰好在当前位置结束,全局答案再比较所有结束位置。
- 符号相同的候选只保留最长长度,之后的统一延长不会改变长短关系。
- 正数保持符号,负数交换符号,零切断所有跨越它的片段。
0表示状态不存在,不能让不存在的负片段直接加一变成正片段。
易错点总结
[!yellow]
- 只保留正片段,会漏掉旧负片段乘上负数后形成的更长答案。
- 负数分支先覆盖
pos再计算neg,会把新旧状态混用。- 旧负长度为
0时仍计算newPos = neg+1,会把当前单个负数误当作正乘积。- 遇到零不重置状态,会错误连接零两侧的子数组;同时把
best清零,又会丢掉之前找到的答案。- 直接连乘数值没有必要,长子数组的乘积很容易溢出,符号和长度已经足够。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 152. 乘积最大子数组 | 中等 | 原题最大化乘积数值,本题只关心乘积符号,可维护正负两种最长长度而不实际相乘。 |
| 525. 连续数组 | 中等 | 同样可用前缀状态寻找最长匹配区间,本题状态是负数个数奇偶,并在0处切断。 |
| 53. 最大子数组和 | 中等 | 维护以当前位置结尾的最优连续区间;本题只维护正负乘积对应的最长长度,该题记录最大连续和。 |
| 918. 环形子数组的最大和 | 中等 | 维护以当前位置结尾的最优连续区间;本题只维护正负乘积对应的最长长度,该题同时计算最小区间和处理首尾相接。 |
| 1186. 删除一次得到子数组最大和 | 中等 | 维护以当前位置结尾的最优连续区间;本题只维护正负乘积对应的最长长度,该题增加已经删除一次的状态。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!