1. 程式人生 > >[LeetCode] Maximum Product Subarray的4種解法

[LeetCode] Maximum Product Subarray的4種解法

Find the contiguous subarray within an array (containing at least one number) which has the largest product.

For example, given the array [2,3,-2,4],
the contiguous subarray [2,3] has the largest product = 6.

這題和之前的一題Maximum Subarray非常類似,一個是求最大和,而這個是求最大乘積。可以用大致類似的思路解,但是求乘積比求和要複雜些,多了不少corner case。不過這題出得還算仁慈,因為這題其實有兩個地方簡化了:

1)注意這裡的陣列是整型的,如果含有浮點數,就有可能出現0-1之間類似0.25這樣的小數,所以即使是全都是正數,也可以越乘越小。如果數組裡的數字全為正數還好說,因為可以用求對數的方式把求乘積轉化為求和,從而轉換為之前的Maximum Subarray。但是因為這題有負數和0的存在,所以求對數的方法行不通。

2)這題的測試用例裡陣列元素的絕對值都非常的小,而實際中如果真的連乘起來,最後數值越界很容易發生的。如果考慮這一點,要麼估計得用類似Java裡BigInteger類這樣的東西去避免越界。

這題我一共嘗試了4種解法。

解法1——基於數學分析的解法

類似Maximum Subarray的解題思路,在遍歷過程中,不斷更新以當前元素為終點的subarray的乘積極大值(下面簡稱極大值)和最大值。本質上無非就是要做出一個二選一:要麼繼續把當前遍歷的元素算上,擴充套件當前的subarray,要麼就重新開始一個subarray。此外,如何更新最大值?由於有整數,負數和0的存在,需要分為三種情況,並且還需要維護一個極小值

。為了方便連乘操作,這裡規定維護的最大乘積必須大於等於1,即不能等於0。另外需要注意,在沒有遍歷到負數之前,極小值這裡其實和極大值是一樣大的(不考慮為0的情況),也可以是正數。

綜合考慮如下:

1)如果當前元素為正數,那麼極大值只可能擴大,所以應該繼續擴充套件當前subarray:

此種情況簡單,極大值應該更新為原極大值乘以當前元素,極小值更新為原極小值乘以當前元素。全域性最大值跟極大值比較。

2)如果當前元素為負數,那麼極大值可能會變小,所以不清楚應該繼續擴充套件當前subarray還是新起一個subarray:

對於極大值的更新:如果擴充套件當前subarray,極大值為原極小值乘以當前元素;如果另外新起一個subarray,由於當前元素為負數,所以直接捨棄,根據規定,設為初始值1。由於這裡原極小值不一定為負數,所以前者和後者之間比較沒有絕對的誰大。

對於極小值的更新:如果擴充套件當前subarray,極小值為原極大值乘以當前元素;如果另外新起一個subarray,極小值為當前元素。不過由於之前極大值肯定大於1,所以前者肯定比後者大,所以極小值更新為原極大值乘以當前元素。

最應該小心的地方是更新全域性最大值:這裡的全域性最大值不能和極大值比較,而應該和極小值乘以當前元素值比較,即擴充套件當前subarray的選擇比較。因為如果極大值此時為1,則並不是靠實實在在存在的,以當前元素結尾的subarray獲得,而是靠捨棄當前元素,寄希望於之後“可能”出現的新subarray。舉個例子,如果陣列為{-2},或者{-1, 0, -2},那麼無論如何是不會出現最大值為1這種情況的,因為負數的後面沒有出現過正數。

3)如果當前元素為0,那麼包括一個0會使得極大值成為0,而按照操作規定,這裡的極大值應該大於等於1,所以應該捨棄當前元素,新起一個subarray。

對於極大值和極小值,由於新起一個subarray,全部還原為1。

對於全域性最大值的更新,這裡和2)類似。由於極大值的獲取是寄希望於之後“可能”出現的新subarray,所以更新全域性最大值的時候不能和此時的極大值1進行比較,而應該和實實在在的0比較。

以下程式碼,loc_min和loc_max表示極小值和極大值,glb_max為所求。

	public int maxProduct(int[] A) {
	    int loc_min = 1, loc_max = 1;   // Make sure thoese local min/max values are greater than or equal to 1 all the time.
	    int glb_max = A[0];
        for (int i : A) {
            if (i > 0) {
                glb_max = Math.max(glb_max, loc_max * i);
                loc_max *= i;
                loc_min *= i;
            } else if (i < 0) {
                glb_max = Math.max(glb_max, loc_min * i);  
                int temp = loc_max;
                loc_max = Math.max(loc_min * i, 1);
                loc_min = temp * i;
            } else {    // i == 0.
                glb_max = Math.max(glb_max, 0);
                loc_max = 1;
                loc_min = 1;
            }
        }
        
        return glb_max;
	}

解法2——樸素的DP解法

其實以上的解法本質上是一種DP的解法,不過以上解法太過於注重細節上的分析,所以程式碼看起來比較繁瑣。由於負數的存在,需要同時儲存當前最大值和當前最小值,所以需要維護兩個DP表,可以分別表示為dp_min和dp_max。所以即為dp_max裡的最大值。

有時候做題目應該有一種大局觀,從細節中解脫出來。這點類似於做物理題時,不一定非要用動力學原理弄清整個過程和每個細節,有時候用簡單的從功和能的角度,只關注最終狀態也可以順利解題。這題裡需要維護的當前最大值和當前最小值,都是在dp_min[i-1] * A[i],dp_max[i] * A[i],和A[i]這三者裡面取一即可。有了這個只關乎最終狀態,不關乎過程細節的結論,解題過程可以大大簡化。

	public int maxProduct_naiveDP(int[] A) {
		int max = A[0];
		int length = A.length;
		int[] dp_min = new int[length];
		int[] dp_max = new int[length];
		dp_min[0] = dp_max[0] = A[0];
		for (int i = 1; i < length; ++i) {
			dp_min[i] = Math.min(
					Math.min(dp_max[i - 1] * A[i], dp_min[i - 1] * A[i]), A[i]);
			dp_max[i] = Math.max(
					Math.max(dp_max[i - 1] * A[i], dp_min[i - 1] * A[i]), A[i]);
			max = Math.max(dp_max[i], max);
		}
		return max;
	}

這裡用了O(N)空間,所以記憶體開銷比第一種解法大。

解法3——優化的DP解法

思考以上DP解法的空間開銷過大的原因,是因為儲存了整個DP表。其實整個過程中,獲得dp[i]的值只需要dp[i-1]的值,所以是不需要儲存整個DP表的。

這樣一來,DP可以用滾動陣列進行優化。簡單的寫法其實就是設一對prevMin/prevMax表示上一個值,以及還有一對curMin/curMax表示當前值。

	public int maxProduct(int[] A) {
		int max = A[0];
		int prevMin = A[0], prevMax = A[0];
		int curMin, curMax;
		for (int i = 1; i < A.length; ++i) {
			curMin = Math.min(Math.min(prevMax * A[i], prevMin * A[i]), A[i]);
			curMax = Math.max(Math.max(prevMax * A[i], prevMin * A[i]), A[i]);
			prevMin = curMin;
			prevMax = curMax;
			max = Math.max(curMax, max);
		}
		return max;
	}

解法4——分治法

如果數組裡面沒有0,這題可以得到大大簡化,所以我們可以先考慮如何處理一個不含0的陣列。如此一來,不管怎麼乘,絕對值都會增長,所以最重要的就是要保證最後的乘積為正數。因此,可以分為兩種情況討論:

1)如果陣列內負數的個數為偶數,直接包括整個陣列即可,最大乘積就是整個陣列元素的乘積。

2)如果陣列內負數的個數為奇數,則應該丟棄一個含有一個奇數的部分。為了使得所剩的陣列最大限度的連續,無非就是兩種情況:

2.1) 丟棄掉數組裡出現的第一個負數和在它之前的元素。例如{1, 2, -3, 4, -5, 6, 7},由於第一個負數為-3,丟棄最開始的1,2和-3。

2.2) 丟棄掉數組裡出現的最後一個負數和在它之後的元素。用上個例子,由於最後一個負數為-5,丟棄最後的-5,6和7。

現在有了對這個題目簡化版的解法,如果擴充套件到這題的解法呢?很簡單,首先掃一遍陣列,以0為邊界,將整個陣列分為若干個不含0的subarray,對於每個subarray逐一求解,並求得它們的最大值。另外,由於每個subarray的最大值可能都是負數,所以一旦有0出現,還應該和0比較。程式碼如下,start表示subarray的起始下標,為-1的時候表示正在尋找下一個subarray。

	public int maxProduct(int[] A) {
		int start = -1;
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < A.length; ++i) {
			if (start == -1) {
				if (A[i] != 0) {
					start = i;
				}

				if (i == A.length - 1) {
					max = Math.max(max, A[i]);
				}
			} else {
				if (A[i] == 0) {
					max = Math.max(max, maxProductNonZero(A, start, i - 1));
					max = Math.max(max, 0);
					start = -1;
				} else if (i == A.length - 1) {
					max = Math.max(max, maxProductNonZero(A, start, i));
					start = -1;
				}
			}
		}

		return max;
	}

	private int maxProductNonZero(int[] A, int start, int end) {
		if (start == end)
			return A[start];

		int count_negs = 0;
		int product = 1;
		for (int i = start; i <= end; ++i) {
			if (A[i] < 0) {
				count_negs++;
			}

			product *= A[i];
		}

		if (count_negs % 2 == 0) {
			return product;
		} else {
			int productBeforeFirstNeg = 1;
			for (int i = start; i <= end; ++i) {
				productBeforeFirstNeg *= A[i];
				if (A[i] < 0) {
					break;
				}
			}

			int productAfterLastNeg = 1;
			for (int i = end; i >= start; --i) {
				productAfterLastNeg *= A[i];
				if (A[i] < 0) {
					break;
				}
			}

			product = Math.max(product / productBeforeFirstNeg, product
					/ productAfterLastNeg);
		}

		return Math.max(product, 0);
	}

這種解法比較繁瑣,而且由於為了思路清晰,這裡我對陣列進行了多次掃描,其實可以將迴圈合併。不過無所謂了,O(n)的時間複雜度不會因為多掃了一兩遍而變化。

不過這種程式碼量大的解法不推薦。相比較之下,前幾種適用性更廣,對浮點數也同樣適用,而這種解法則不能允許有0-1之間的小數。

相關推薦

[LeetCode] Maximum Product Subarray的4解法

Find the contiguous subarray within an array (containing at least one number) which has the largest product. For example, given the arr

[LeetCode] Maximum Product of Three Numbers

clas style find leet 數組元素 cat 兩個 bit bsp Given an integer array, find three numbers whose product is maximum and output the maximum produ

[LeetCode] Maximum Product Subarray

題目 Given an integer array nums, find the contiguous subarray within an array (containing at least one number) which has the largest product.

[LeetCode] Maximum Product of Word Lengths 單詞長度的最大積

Given a string array words, find the maximum value of length(word[i]) * length(word[j]) where the two words do not share common letters. You may assume t

[LeetCode] Maximum Product Subarray 求最大子陣列乘積

Find the contiguous subarray within an array (containing at least one number) which has the largest product. For example, given the array [2,3,-2,4],the

[LeetCode] Maximum Product of Three Numbers 三個數字的最大乘積

Given an integer array, find three numbers whose product is maximum and output the maximum product. Example 1: Input: [1,2,3] Output: 6 Example 2

[LeetCode] Maximum Product Subarray 求連續子陣列的最大乘積

宣告:原題目轉載自LeetCode,解答部分為原創 Problem :     Find the contiguous subarray within an array (containing at least one number) which has the l

LeetCode演算法題-Third Maximum Number(Java實現-四解法

這是悅樂書的第222次更新,第235篇原創 01 看題和準備 今天介紹的是LeetCode演算法題中Easy級別的第89題(順位題號是414)。給定非空的整數陣列,返回此陣列中的第三個最大數字。如果不存在,則返回最大數量。時間複雜度必須在O(n)中。例如: 輸入:[3,2,1] 輸出:1 說明:第三個

leetcode 152. Maximum Product Subarray

style 最小數 int least 求最大子數組 ast fin urn bsp leetcode 152. Maximum Product Subarray Find the contiguous subarray within an array (contain

leetcode 152. Maximum Product Subarray 最大連乘子序列

maximum subarray leetcode cxf sub gin max pro rod 灼op胃o躥鐐儷8eukahttp://www.docin.com/app/user/userinfo?userid=178504825 賂u勤蠢40訝m摯4iyoehttp

[Leetcode]628. Maximum Product of Three Numbers

hose maximum cnblogs rip max etc 就是 find not Given an integer array, find three numbers whose product is maximum and output the maxim

leetcode-628-Maximum Product of Three Numbers

ssi arr spa tip iss beat nth col 就是 題目描述: Given an integer array, find three numbers whose product is maximum and output the maximum prod

(Java) LeetCode 152. Maximum Product Subarray —— 乘積最大子序列

ann solution least posit 當前 res 暴力 根據 with Given an integer array nums, find the contiguous subarray within an array (containing at least

[LeetCode] 152. Maximum Product Subarray 求最大子數組乘積

lan range ++i logs local i++ www. spl 題目 Given an integer array nums, find the contiguous subarray within an array (containing at least o

leetcode threeSumMulti 的三python解法(後兩特別好)

上週末參加了leetcode的考試, 做出兩道題。不得不接受自己的愚笨。 第三題在看了排名前兩名java的答案和另一個python做的答案之後,除了感嘆人家演算法的精妙,也只能暗下決心,要花更多的時間來刷題! https://leetcode.com/contest/weekly-conte

LeetCode 926. 將字串翻轉到單調遞增 遞迴實現動態規劃 兩解法

這個題做了一個多小時,考慮複雜了。 開始推動規沒有推出來,然後找到一個遞推關係:從左往右,如果是0,則不需要變動;如果是1,則有兩種選擇(1)將1變為0(2)將1後面的所有數字變為1,這兩種方法中的變動數字最小的方法就是最佳方法,然後依次遞推,很容易寫出遞迴程式。但是這裡面存

[LeetCode] 628. Maximum Product of Three Numbers 三個數字的最大乘積 [LeetCode] 152. Maximum Product Subarray 求最大子陣列乘積 All LeetCode Questions List 題目彙總

Given an integer array, find three numbers whose product is maximum and output the maximum product. Example 1: Input: [1,2,3] Output: 6  Example 2

LeetCode 647. Palindromic Substrings的三解法

轉載地址 https://www.cnblogs.com/AlvinZH/p/8527668.html#_label5 題目詳情 給定一個字串,你的任務是計算這個字串中有多少個迴文子串。 具有不同開始位置或結束位置的子串,即使是由相同的字元組成,也會被計為是不

LeetCode 923. 三數之和的多種可能(機智題 三解法

題意: 給定一個整數陣列 A,以及一個整數 target 作為目標值,返回滿足 i < j < k 且 A[i] + A[j] + A[k] == target 的元組 i, j, k 的數量。

leetcodeMaximum Product of Three Numbers)

Title:Maximum Product of Three Numbers    605 Difficulty:Easy 原題leetcode地址:https://leetcode.com/problems/can-place-flowers/