Kadane’s algorithm for maximum sum subarray does not directly work for products, because multiplying by a negative number flips the sign – the smallest (most negative) product ending at i-1 can become the largest product ending at i if nums[i] is negative. So track two running values at each position: maxDP[i], the largest product of a subarray ending exactly at i, and minDP[i], the smallest (most negative). Both are needed because either one might flip into the other’s role on the next step.
Brute Force
Time O(n²)Space O(1)Try every subarray directly: for each starting index, extend the ending index one step at a time, keeping a running product, and track the best seen.
class Solution: def maxProduct(self, nums: list[int]) -> int: n = len(nums) best = nums[0] for i in range(n): prod = 1 for j in range(i, n): prod *= nums[j] best = max(best, prod) return bestO(n²) subarrays, each extended in O(1) amortized work by reusing the running product → O(n²) time, O(1) space.
Bottom-Up DP (Track Max and Min)
OptimalTime O(n)Space O(n)At each index, the best subarray ending there either starts fresh at nums[i], or extends the previous best-max subarray, or extends the previous best-min subarray (which flips sign into a new max if nums[i] is negative). Take the max and min of all three candidates.
class Solution: def maxProduct(self, nums: list[int]) -> int: maxDP = [0] * len(nums) minDP = [0] * len(nums) maxDP[0] = minDP[0] = nums[0] ans = nums[0] for i in range(1, len(nums)): candidates = (nums[i], maxDP[i - 1] * nums[i], minDP[i - 1] * nums[i]) maxDP[i] = max(candidates) minDP[i] = min(candidates) ans = max(ans, maxDP[i]) return ansTrace for nums = [2, 3, -2, 4] (the maxDP array is shown; minDP and the running answer are tracked as variables):
maxDP[0] = minDP[0] = nums[0] = 2. Only one element so far.
One pass, constant work per index → O(n) time. The full maxDP/minDP arrays cost O(n) space here, though only the previous entry of each is ever needed, so this can be reduced to O(1) space with two rolling variables.