題目網址:https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/
題意: 給一整數 array nums
和一正整數 k
, 返回是否能把 nums
拆分成 k
的非空 subset, 且每個 subset 元素總和皆相等。
注意 :nums.length ≤ 16
Solution 1:
想法:總共有 n
顆球、k
個桶子, 以「球」為視角, 每顆球都有 k
個桶子可以選擇, 每個桶子的容量為 target = sum / k
。
e.g. nums = [1, 2, 2, 4, 3, 3], k = 3
class Solution {public : bool canPartitionKSubsets (vector<int >& nums, int k) { const int sum = accumulate (nums.begin (), nums.end (), 0 ); if (sum % k != 0 ) { return false ; } const int target = sum / k; vector<int > buckets (k, target) ; sort (nums.begin (), nums.end (), greater ()); return dfs (nums, 0 , nums.size (), buckets, k); } private : bool dfs (vector<int >& nums, int i, int n, vector<int >& buckets, int & k) { if (i == n) { return true ; } for (int j = 0 ; j < k; ++j) { if (nums[i] > buckets[j]) { continue ; } if (j > 0 && buckets[j - 1 ] == buckets[j]) { continue ; } buckets[j] -= nums[i]; if (dfs (nums, i + 1 , n, buckets, k)) { return true ; } buckets[j] += nums[i]; } return false ; } };
time: $O(k^n)$ ➔ 每個顆都有 k
個桶子可以選擇桶子, 所以組合出的結果為 $k^n$ 種
space: $O(n)$ ➔ 取決於遞迴深度, stack 中每一層代表第 i
顆球選擇哪個桶子, 總共 n
層
Solution 2: (TLE 無法通過)
想法: 總共有 n
顆球、k
個桶子, 以「桶子」為視角(每個桶子都會遍歷所有球), 每顆球有 2
種選擇(選 or 不選), 每個桶子都有 $O(2^n)$ 種選擇。
e.g. nums = [1, 2, 2, 4, 3, 3], k = 3
class Solution {public : bool canPartitionKSubsets (vector<int >& nums, int k) { const int sum = accumulate (nums.begin (), nums.end (), 0 ); if (sum % k != 0 ) { return false ; } const int target = sum / k; vector<int > buckets (k, target) ; vector<bool > used (nums.size(), false ) ; return dfs (nums, used, 0 , buckets, k - 1 ); } private : bool dfs (vector<int >& nums, vector<bool >& used, int i, vector<int >& buckets, int k) { if (k == 0 ) return { true ; } if (buckets[k] == 0 ) { return dfs (nums, used, 0 , buckets, k - 1 ); } for (int j = i; j < nums.size (); ++j) { if (used[j] || nums[j] > buckets[k]) { continue ; } buckets[k] -= nums[j]; used[j] = true ; if (dfs (nums, used, j + 1 , buckets, k)) { return true ; } buckets[k] += nums[j]; used[j] = false ; } return false ; } };
time: $O(k \cdot 2^n)$
每個桶要遍歷 n
顆球, 每顆球都有 2 種選擇(選 or 不選), 所以組合的結果有 $2^n$ 種
總共有 k
個桶, 故為 $O(k \cdot 2^n)$
space: $O(k \cdot n)$ ➔ 取決於遞迴深度, 每個桶子最多有 n
次選擇(n
層), 總共 k
個桶, 故為 $O(k \cdot n)$
Solution 3:
想法:改進 Solution 2, 之所以會 TLE 是因為會重複計算, 必須儲存已使用球的狀態來進行剪枝
剪枝
used array
可以認為一 backtracking 過程中的狀態。可以用一個 memo
來記錄每種情況的 used
返回 true
或 false
。如果當前的 used
狀態是曾經出現過的, 那就不用再繼續窮舉了
根據題目給的 nums.length ≤ 16
, 所以我們可以用「狀態壓縮 」的技巧, 用 int
類型的 used 來替代 used array , 其中 used
中最右邊的編號為 0
(e.g. used = 3 = 00...0011
, 代表 nums[0]
和 nums[1]
已被使用)
為了方便查找, 我們利用 hash map 來記錄每一種 used
狀態返回的是 true
還是 false
class Solution {public : bool canPartitionKSubsets (vector<int >& nums, int k) { const int sum = accumulate (nums.begin (), nums.end (), 0 ); if (sum % k != 0 ) { return false ; } const int target = sum / k; vector<int > buckets (k, target) ; int used = 0 ; return dfs (nums, used, 0 , buckets, k - 1 ); } private : unordered_map<int , bool > memo; bool dfs (vector<int >& nums, int & used, int i, vector<int >& buckets, int k) { if (k == 0 ) { return true ; } if (buckets[k] == 0 ) { const bool res = dfs (nums, used, 0 , buckets, k - 1 ); memo[used] = res; return res; } if (memo.find (used) != memo.end ()) { return memo[used]; } for (int j = i; j < nums.size (); ++j) { if (((used >> j) & 1 ) == 1 ) { continue ; } if (nums[j] > buckets[k]) { continue ; } buckets[k] -= nums[j]; used |= (1 << j); if (dfs (nums, used, j + 1 , buckets, k)) { return true ; } buckets[k] += nums[j]; used ^= (1 << j); } return false ; } };
time: $O(k \cdot 2^n)$
每個桶要遍歷 n
顆球, 每顆球都有 2 種選擇(選 or 不選), 所以組合的結果有 $2^n$ 種
總共有 k
個桶, 故為 $O(k \cdot 2^n)$
space: $O(2^n)$ ➔ memo
最大長度為 $2^n$, 因為最多 $2^n$ 種狀態