Given an integer array nums
, find the contiguous subarray within an array (containing at least one number) which has the largest product.html
Example 1:java
Input: [2,3,-2,4] Output: Explanation: [2,3] has the largest product 6. 6
Example 2:git
Input: [-2,0,-1] Output: 0 Explanation: The result cannot be 2, because [-2,-1] is not a subarray.
這個求最大子數組乘積問題是由最大子數組之和 Maximum Subarray 演變而來,可是卻比求最大子數組之和要複雜,由於在求和的時候,遇到0,不會改變最大值,遇到負數,也只是會減少最大值而已。而在求最大子數組乘積的問題中,遇到0會使整個乘積爲0,而遇到負數,則會使最大乘積變成最小乘積,正由於有負數和0的存在,使問題變得複雜了很多。好比,如今有一個數組 [2, 3, -2, 4],能夠很容易的找出全部的連續子數組,[2],[3],[-2],[4],[2, 3],[3, -2],[-2, 4],[2, 3, -2],[3, -2, 4],[2, 3, -2, 4],而後能夠很輕鬆的算出最大的子數組乘積爲6,來自子數組 [2, 3]。但如何寫代碼來實現自動找出最大子數組乘積呢,博主最早想到的方比較簡單粗暴,就是找出全部的子數組,而後算出每個子數組的乘積,而後比較找出最大的一個,須要兩個 for 循環,第一個 for 遍歷整個數組,第二個 for 遍歷含有當前數字的子數組,就是按如下順序找出子數組: [2],[2, 3],[2, 3, -2],[2, 3, -2, 4],[3],[3, -2],[3, -2, 4],[-2],[-2, 4],[4],在本地測試的一些數組所有經過,因而興高采烈的拿到 OJ 上測試,結果喪心病狂的 OJ 用一個有 15000 個數字的數組來測試,而後說程序的運行時間超過了要求值,一看代碼,果真如此,時間複雜度 O(n2), 得想辦法只用一次循環搞定。想來想去想不出好方法,因而到網上搜各位大神的解決方法。其實這道題最直接的方法就是用 DP 來作,並且要用兩個 dp 數組,其中 f[i] 表示子數組 [0, i] 範圍內而且必定包含 nums[i] 數字的最大子數組乘積,g[i] 表示子數組 [0, i] 範圍內而且必定包含 nums[i] 數字的最小子數組乘積,初始化時 f[0] 和 g[0] 都初始化爲 nums[0],其他都初始化爲0。那麼從數組的第二個數字開始遍歷,那麼此時的最大值和最小值只會在這三個數字之間產生,即 f[i-1]*nums[i],g[i-1]*nums[i],和 nums[i]。因此用三者中的最大值來更新 f[i],用最小值來更新 g[i],而後用 f[i] 來更新結果 res 便可,因爲最終的結果不必定會包括 nums[n-1] 這個數字,因此 f[n-1] 不必定是最終解,不斷更新的結果 res 纔是,參見代碼以下:github
解法一:數組
class Solution { public: int maxProduct(vector<int>& nums) { int res = nums[0], n = nums.size(); vector<int> f(n, 0), g(n, 0); f[0] = nums[0]; g[0] = nums[0]; for (int i = 1; i < n; ++i) { f[i] = max(max(f[i - 1] * nums[i], g[i - 1] * nums[i]), nums[i]); g[i] = min(min(f[i - 1] * nums[i], g[i - 1] * nums[i]), nums[i]); res = max(res, f[i]); } return res; } };
咱們能夠對上面的解法進行空間上的優化,如下摘自 OJ 官方解答,大致思路相同,寫法更加簡潔:ide
Besides keeping track of the largest product, we also need to keep track of the smallest product. Why? The smallest product, which is the largest in the negative sense could become the maximum when being multiplied by a negative number.post
Let us denote that:測試
f(k) = Largest product subarray, from index 0 up to k.
Similarly,優化
g(k) = Smallest product subarray, from index 0 up to k.
Then,url
f(k) = max( f(k-1) * A[k], A[k], g(k-1) * A[k] ) g(k) = min( g(k-1) * A[k], A[k], f(k-1) * A[k] )
There we have a dynamic programming formula. Using two arrays of size n, we could deduce the final answer as f(n-1). Since we only need to access its previous elements at each step, two variables are sufficient.
public int maxProduct(int[] A) { assert A.length > 0; int max = A[0], min = A[0], maxAns = A[0]; for (int i = 1; i < A.length; i++) { int mx = max, mn = min; max = Math.max(Math.max(A[i], mx * A[i]), mn * A[i]); min = Math.min(Math.min(A[i], mx * A[i]), mn * A[i]); maxAns = Math.max(max, maxAns); } return maxAns; }
根據上述描述能夠寫出代碼以下:
解法二:
class Solution { public: int maxProduct(vector<int>& nums) { if (nums.empty()) return 0; int res = nums[0], mn = nums[0], mx = nums[0]; for (int i = 1; i < nums.size(); ++i) { int tmax = mx, tmin = mn; mx = max(max(nums[i], tmax * nums[i]), tmin * nums[i]); mn = min(min(nums[i], tmax * nums[i]), tmin * nums[i]); res = max(res, mx); } return res; } };
下面這種方法也是用兩個變量來表示當前最大值和最小值的,可是沒有無腦比較三個數,而是對於當前的 nums[i] 值進行了正負狀況的討論:
2. 當遍歷到一個負數時,先用一個變量t保存以前的最大值 mx,而後此時的最大值等於以前最小值乘以這個負數和當前負數中的較大值,此時的最小值等於以前保存的最大值t乘以這個負數和當前負數中的較小值。
3. 在每遍歷完一個數時,都要更新最終的最大值。
解法三:
class Solution { public: int maxProduct(vector<int>& nums) { int res = nums[0], mx = res, mn = res; for (int i = 1; i < nums.size(); ++i) { if (nums[i] > 0) { mx = max(mx * nums[i], nums[i]); mn = min(mn * nums[i], nums[i]); } else { int t = mx; mx = max(mn * nums[i], nums[i]); mn = min(t * nums[i], nums[i]); } res = max(res, mx); } return res; } };
下面這道題使用了一個 trick 來將上面解法的分狀況討論合成了一種,在上面的解法中分析了當 nums[i] 爲正數時,最大值和最小值的更新狀況,爲負數時,稍有不一樣的就是最小值更新時要用到以前的最大值,而不是更新後的最大值,因此纔要用變量t來保存以前的結果。而下面這種方法的巧妙處在於先判斷一個當前數字是不是負數,是的話就交換最大值和最小值。那麼此時的 mx 就是以前的 mn,因此 mx 的更新仍是跟上面的方法是統一的,而在在更新 mn 的時候,以前的 mx 已經保存到 mn 中了,並且並無改變,因此能夠直接拿來用,不得不說,確實叼啊,參見代碼以下:
解法四:
class Solution { public: int maxProduct(vector<int>& nums) { int res = nums[0], mx = res, mn = res; for (int i = 1; i < nums.size(); ++i) { if (nums[i] < 0) swap(mx, mn); mx = max(nums[i], mx * nums[i]); mn = min(nums[i], mn * nums[i]); res = max(res, mx); } return res; } };
再來看一種畫風不太同樣的解法,這種解法遍歷了兩次,一次是正向遍歷,一次是反向遍歷,至關於正向創建一個累加積數組,每次用出現的最大值更新結果 res,而後再反響創建一個累加積數組,再用出現的最大值更新結果 res,注意當遇到0的時候,prod 要重置爲1。至於爲啥正反兩次遍歷就能夠獲得正確的結果了呢?主要仍是因爲負數個數的關係,由於負數可能會把最大值和最小值翻轉,那麼當有奇數個負數時,若是隻是正向遍歷的話,可能會出錯,好比 [-1, -2, -3],累加積會獲得 -1,2,-6,看起來最大值只能爲2,其實不對,而若是咱們再反向來一遍,累加積爲 -3,6,-6,就能夠獲得6了。因此當負數個數爲奇數時,首次出現和末尾出現的負數就很重要,有可能會是最大積的組成數字,因此遍歷兩次就不會漏掉組成最大值的機會,參見代碼以下:
解法五:
class Solution { public: int maxProduct(vector<int>& nums) { int res = nums[0], prod = 1, n = nums.size(); for (int i = 0; i < n; ++i) { res = max(res, prod *= nums[i]); if (nums[i] == 0) prod = 1; } prod = 1; for (int i = n - 1; i >= 0; --i) { res = max(res, prod *= nums[i]); if (nums[i] == 0) prod = 1; } return res; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/152
相似題目:
Maximum Product of Three Numbers
參考資料:
https://leetcode.com/problems/maximum-product-subarray/
https://leetcode.com/problems/maximum-product-subarray/discuss/48302/2-passes-scan-beats-99
https://leetcode.com/problems/maximum-product-subarray/discuss/48261/share-my-dp-code-that-got-ac