這是一道水題,作為沒有貨的水貨樓主如是說。
題意:已知一個數組nums {a1,a2,a3,.....,an}(其中0<ai <=1000(1<=k<=n, n<=20))和一個數S
c1a1c2a2c3a3......cnan = S, 其中ci(1<=i<=n)可以在加號和減號之中任選。
求有多少種{c1,c2,c3,...,cn}的排列能使上述等式成立。
例如:
輸入:nums is [1, 1, 1, 1, 1], S is 3.
輸出 : 5
符合要求5種等式:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
但是看完題的你們說,樓主這個題目的難度是中等題呀!
好吧,樓主說錯了,它是中等題,但毋庸置疑這是一道動態規划的水題,是一個背包問題,不知道的背包問題的同學請百度下《背包九講》。
注意:看那個《背包九講》可能開始會感到生澀難懂,但是前方高能東西都是牛逼的東西。
回歸到本題中,乍一看它是一個搜索問題,即搜索出所有可能的解即可,因為n最多為20,搜索一把也能順利完成,解決也是比較容易的。
但是就沒有更好的方法了嗎?這時我們注意到0<ai <=1000這個條件,這么小的數值讓我們很快聯想到了動態規划。
沒錯,這是一個多階段的背包問題,其中的難點是負數這么表示。
我們可以將[-max,max]映射到[0,2*max]就解決問題了。
我們現在可以想出以下的狀態轉移方程:
dp[i][j] = dp[i-1][j-a[i]] + dp[i-1][j+a[i]](1 <= i <= n, 0< j < 2 * sum(a[i]) + 1)
即i代表有多少個數,j - sum(a[i])代表每一種算出來的答案,dp[i][j]代表在答案j - sum(a[i])的情況下的c1,c2,c3,...,ci}的排列牌數。很明顯當前的狀態dp[i][j]是從上一次(i-1)的數加上當前的 a[i]得到的。
這樣我們只要開出一個n * (2 * sum(a[i]) + 1)的數組,在O(n * (2 * sum(a[i]) + 1))的時間復雜度下解決這個問題。
那么還可以優化嗎?答案是肯定的。
我們從狀態轉移方程中不難看出,在每一次轉移的時候都只用了i-1次的答案和i次的結果,為此我們可以使用滾動數據對它進行優化。
只要我們開出2 * (2 * sum(a[i]) + 1)的數據,這樣我們又再次優化了內存。
即我們可以在時間復雜度為O(n * (2 * sum(a[i]) + 1)) 和空間復雜度(2 * (2 * sum(a[i]) + 1)) 的情況下解決該問題。
下面上golang的代碼(居然沒有golang的語言編輯器,求增加)
1 func findTargetSumWays(nums []int, S int) int { 2 mid := 0 3 for _,v := range nums{ 4 mid += v 5 } 6 dp := make([][]int, 2) 7 for i,_:=range dp{ 8 dp[i] = make([]int, mid + mid + 1) 9 } 10 dp[0][mid] = 1 11 for i,v := range nums{ 12 for j,_:= range dp[(i + 1)%2]{ 13 dp[(i + 1)%2][j] = 0 14 } 15 for j:=0; j <= mid + mid; j++{ 16 if j >= v { 17 dp[(i+1)%2][j-v] += dp[i%2][j] 18 } 19 if j + v <= mid + mid { 20 dp[(i+1)%2][j+v] += dp[i%2][j] 21 } 22 } 23 } 24 if S > mid || S < -mid{ 25 return 0 26 } 27 28 return dp[len(nums)%2][S+mid] 29 }