โจทย์: Partition to K Equal Sum Subsets
ให้อาร์เรย์จำนวนเต็ม nums และจำนวนเต็มบวก k ตรวจสอบว่าสามารถแบ่งอาร์เรย์ออกเป็น k ส่วนย่อย (subsets) ที่ไม่ว่างเปล่า ได้หรือไม่ โดยที่ ผลรวมของแต่ละส่วนเท่ากันทุกส่วน
ตัวอย่าง: nums = [4,3,2,3,5,2,1], k = 4
สามารถแบ่งได้: [5], [1,4], [2,3], [2,3] ผลรวมแต่ละชุด = 5 → True
อัลกอริทึม: DP with Bitmask (Dynamic Programming on Subsets)
เนื่องจาก n ≤ 16 (ในปัญหา LeetCode ทั่วไป) สามารถใช้ bitmask แทน subset ได้ (2^n สถานะ)
- คำนวณ
total_sum = sum(nums)หากtotal_sum % k != 0→false target = total_sum / k(ผลรวมเป้าหมายของแต่ละ subset)- หากมีองค์ประกอบใด > target →
false - เรียง
numsจากมากไปน้อย (pruning เร็วขึ้น) - DP state:
dp[mask]= true หาก subset ที่แทนโดย mask สามารถแบ่งเป็นกลุ่มที่สมบูรณ์ได้ (บางกลุ่มอาจยังไม่เต็ม) total[mask]= ผลรวมของ subset ปัจจุบันที่ยังไม่เต็ม (mod target)- เริ่ม
dp[0] = true, total[0] = 0 - วนทุก mask จาก 0 ถึง 2^n - 1:
- ถ้า
dp[mask]เป็น true:- ลองเพิ่มแต่ละองค์ประกอบ
jที่ยังไม่อยู่ใน mask new_mask = mask | (1 << j)- ถ้า
nums[j] <= target - total[mask](ใส่ได้ใน subset ปัจจุบัน):dp[new_mask] = truetotal[new_mask] = total[mask] + nums[j]
- มิฉะนั้น break (เพราะ nums เรียงจากมากไปน้อย ตัวหลังจะใหญ่กว่า ใส่ไม่ได้แน่)
- ลองเพิ่มแต่ละองค์ประกอบ
- ถ้า
- ส่งคืน
dp[(1<<n)-1]
ตัวอย่างโค้ด C++
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
int n = nums.size();
int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum % k != 0) return false;
int target = sum / k;
sort(nums.begin(), nums.end(), greater<int>()); // เรียงมากไปน้อย
if (nums[0] > target) return false;
vector<bool> dp(1 << n, false);
vector<int> total(1 << n, 0);
dp[0] = true;
for (int mask = 0; mask < (1 << n); mask++) {
if (!dp[mask]) continue;
for (int j = 0; j < n; j++) {
if (mask & (1 << j)) continue; // ใช้แล้ว
int new_mask = mask | (1 << j);
if (new_mask == mask) continue;
int curr_sum = total[mask];
if (nums[j] <= target - curr_sum) {
dp[new_mask] = true;
total[new_mask] = curr_sum + nums[j];
} else {
break; // nums เรียงมากไปน้อย ตัวต่อไปจะใหญ่กว่า ใส่ไม่ได้แน่
}
}
}
return dp[(1 << n) - 1];
}
};
int main() {
Solution ob;
vector<int> v = {4, 3, 2, 3, 5, 2, 1};
cout << (ob.canPartitionKSubsets(v, 4) ? "true" : "false") << endl;
return 0;
}
Input
[4,3,2,3,5,2,1] 4
Output
true
ความซับซ้อน (Complexity)
- เวลา: O(n × 2^n) - n ≤ 16 ทำได้ในเวลาอันเหมาะสม
- พื้นที่: O(2^n)
วิธีแก้อื่น: Backtracking/DFS กับ pruning (เรียงมากไปน้อย, skip duplicates) ซึ่งในทางปฏิบัติมักเร็วกว่า DP bitmask สำหรับ n ใหญ่