Last updated
Last updated
首先我們要確立目標,目標是給定一個陣列能不能分成兩個子集合,讓這兩個子集合的總和相等。
第一個要去想的地方比較簡單,既然兩個子集合要相等,那一個子集合一定是原陣列總和的一半,我們的目標就是要找到一個一個子集合,其總和是原陣列總和的一半。
另外,有可能原陣列的元素總和是奇數:例如:[1, 2, 1, 1]
這樣的陣列總和是 5 ,一定不會有辦法分成兩個集合,所以可以先用這樣的方式去剪枝(把所有總和為奇數的直接排除掉)。
接著是要怎麼選擇,每一個元素我們都可以有「選」或是「不選」這兩種方式,如果選的話,我們的目標就會減少當下的數值,如果不選的話,目標並不會改變,一直到如果我們選了最後一個數字,如果最後目標剛好等於 0 ,那就是我們透過選擇,找到了目標。
這樣的關係可以用一個遞迴的方式來呈現。
既然遞迴的方式已經寫好了,上面這個函式很明顯的存在著重疊的子問題,例如最後一個位置的「選」或「不選」,在前面幾個元素在做選擇時,會一直重複的計算,因此可以用記憶法,來記憶著已經做過的選擇。而每個位置的選擇或不選擇,可以透過當前位置與目標來決定。
一直以來我都覺得自底向上比較難想到,這題也是不太好想。
原陣列的每個位置都會有選擇或是不選擇兩種,自底向上的想法是,如果我們知道第 i
的位置在選擇的時候,是不是可以達到某個比我們的目標還要小的數值。
文字敘述很抽象,換成一個數學的例子就是,現在最後的目標是 5
,前一個位置如果已經可以達到目標 5 了,那我這個位置就不用選擇,如果我的當前值是 2
,那在前一個位置如果可以達到 5 - 2 = 3
,那我就要選擇當前位置的元素。這一串選擇就是 L20 在做的事情。
一開始的 base case 則是如果說一開始的目標就是 0 ,那不管有沒有選都是符合條件了。