一句话总结:
找乘积最大子数组就像炒股——既要抓住暴涨,也要警惕暴跌(负数可能负负得正),还要记得及时止损(遇到零重置)!
一、动态规划解法(最优解)
核心思路: 同时维护当前最大值和最小值(因为负数可能翻转局面)
Kotlin 代码:
fun maxProduct(nums: IntArray): Int {
if (nums.isEmpty()) return 0
var maxSoFar = nums[0] // 全局最大值
var currentMax = nums[0] // 当前最大值
var currentMin = nums[0] // 当前最小值(应对负数)
for (i in 1 until nums.size) {
val num = nums[i]
// 计算新的候选值(考虑三种情况:当前数、当前数×旧最大、当前数×旧最小)
val candidates = listOf(num, currentMax * num, currentMin * num)
currentMax = candidates.maxOrNull()!!
currentMin = candidates.minOrNull()!!
// 更新全局最大值
maxSoFar = maxOf(maxSoFar, currentMax)
}
return maxSoFar
}
时间复杂度: O(n) (只遍历一次数组)
空间复杂度: O(1) (仅用几个变量存储状态)
二、测试用例
fun main() {
println(maxProduct(intArrayOf(2, 3, -2, 4))) // 输出6(子数组[2,3])
println(maxProduct(intArrayOf(-2, 0, -1))) // 输出0(子数组[0])
println(maxProduct(intArrayOf(-3, -1, -2))) // 输出6(子数组[-3,-1,-2])
println(maxProduct(intArrayOf(0, 2))) // 输出2(子数组[2])
println(maxProduct(intArrayOf(-2, 3, -4))) // 输出24(子数组[-2,3,-4])
}
三、分步图解(以数组[-2,3,-4]为例)
初始状态:
currentMax = -2, currentMin = -2, maxSoFar = -2
第1步(num=3):
候选值:3, (-2)*3=-6, (-2)*3=-6 → currentMax=3, currentMin=-6
maxSoFar更新为3
第2步(num=-4):
候选值:-4, 3*(-4)=-12, (-6)*(-4)=24 → currentMax=24, currentMin=-12
maxSoFar更新为24
最终结果:24
四、关键点解析
- 负数反转:遇到负数时,之前的最大值可能变最小值,最小值可能变最大值
- 零的处理:遇到零时,当前最大值和最小值都重置为零
- 候选值比较:每次必须同时考虑三种可能性(当前数、当前数×旧最大、当前数×旧最小)
五、常见错误
-
忽略最小值:只维护最大值会导致负数反转情况丢失
// 错误示例(只跟踪最大值) fun wrongMaxProduct(nums: IntArray): Int { var maxSoFar = nums[0] var currentMax = nums[0] for (i in 1 until nums.size) { currentMax = maxOf(nums[i], currentMax * nums[i]) maxSoFar = maxOf(maxSoFar, currentMax) } return maxSoFar } // 测试用例 [-2,3,-4] 会错误返回3,正确应为24 -
未处理空数组:需检查输入是否为空
六、适用场景
- 股票最大收益计算(考虑价格波动)
- 信号处理中的最大能量区间
- 游戏中的连续增益效果计算
口诀:
最大乘积子数组,
动态规划双维护。
最大最小两手抓,
负数反转别马虎!