可以划分为两个总和相等的非空集的子集计数
给定一个大小为N的数组Arr[] ,任务是找到Arr[]的子集的计数,这些子集可以划分为两个总和相等的非空组。
例子:
Input: Arr[] = {2, 3, 4, 5}
Output: 2
Explanation: The subsets are:
{2, 3, 5} which can be split into {2, 3} and {5}
{2, 3, 4, 5} which can be split into {2, 5} and {3, 4}
Input: Arr[] = {1, 2, 3, 4, 5}
Output: 7
朴素方法:最简单的方法是生成所有子集,并且对于每个子集S ,如果它的总和为A ,则找到S的所有子集并检查其总和是否为A/2。
时间复杂度: O(4 N )
辅助空间: O(1)
有效方法:有效解决问题的想法是使用 中间的相遇 技术。
- Split the array equally into two halves and for each half compute all the possible subsets.
- For elements included in a subset in either half, assign it to either first group or second group by adding to or subtracting from the variable ‘sum‘ ( sum is initialized to 0).
- For the first half of the array, if an element is assigned to the first group simply add its value to sum, else subtract it. For the second half of the array, if an element is assigned to the first group subtract it from sum, else add it.
请按照以下步骤解决问题:
- 声明一个大小为(1 << N)的全局布尔数组 ' canSplit ' 以存储每个子集的结果,如果它可以分成两个总和相等的组。
- 声明一个全局Map以将子集的总和分配给一个 ID,并查找是否已经存在与当前子集具有相同总和的子集。
- 声明 Vector 的全局Vector以将具有相同总和的所有位掩码存储在一起。
- 定义一个递归函数,比如makeSubsets1(i, sum, mask)从数组的前半部分构造所有子集。
- 基本情况,如果i == N/2 ,则数组的前半部分已被完全遍历。
- 如果位掩码表示的当前子集的总和与已经形成的子集不同,则将变量ID增加1并将sum 的值分配给该ID 。
- 检索当前总和的 ID 号。
- 将位掩码表示的子集添加到检索到的 ID 的向量中。
- 当前索引处的数字可以是:
- 从当前子集中排除。
- 包含到子集的第一组。在这种情况下,我们将数字添加到“ sum ”。
- 包含到子集的第二组。在这种情况下,我们从“ sum ”中减去该数字。
- 基本情况,如果i == N/2 ,则数组的前半部分已被完全遍历。
- 定义一个递归函数,比如makeSubsets2(i, sum, mask)从数组的后半部分构造所有子集。
- 基本情况,如果i == N ,则整个数组已从中间开始完全遍历。
- 如果数组前半部分中的任何子集与当前子集的和相同,则两者的按位或形成有效子集,因为它们具有相同的不平衡值。换句话说,如果:
- ∑(First group) 1 – ∑(Second group) 1 = ∑(Second group) 2 – ∑(First group) 2 ,通过重新排列术语,
- ∑(第一组) 1 + ∑(第一组) 2 = ∑(第二组) 1 + ∑(第二组) 2
- 因此,两个子集的按位或可以分为两个总和相等的组。遍历具有相同总和的前半部分的所有子集,并将其与后半部分的当前子集的按位或标记为“真”,表示有效子集。
- 如果数组前半部分中的任何子集与当前子集的和相同,则两者的按位或形成有效子集,因为它们具有相同的不平衡值。换句话说,如果:
- 当前索引处的数字可以是:
- 从当前子集中排除。
- 包含到子集的第二组。在这种情况下,我们将数字添加到“ sum ”。
- 包含到子集的第一组。在这种情况下,我们从“ sum ”中减去该数字。
- 基本情况,如果i == N ,则整个数组已从中间开始完全遍历。
- 如果canSplit[mask] = true遍历所有子集并将答案增加1 。
- 打印答案。
以下是上述方法的代码:
C++14
// C++ program for the above approach.
#include
using namespace std;
// Boolean array to check if elements
// represented by a bitmask(subset)
// can be split into two non-empty groups
// having equal sum.
bool canSplit[1 << 20];
// Map to store sum of subsets
// and find if there is a subset
// with the current sum.
map mp;
// Vector of vector to store
// all the bitmasks having the
// same sum together.
vector > subsets(1 << 20);
// Variable to count subsets
// having unique sums.
int ID;
// Function to generate all possible
// subsets from first half
// of the array.
void makeSubsets1(int i, int N, int sum,
int mask, int Arr[])
{
// If first half of the array
// is traversed.
if (i == N) {
// If none of the previously formed
// subsets have the same sum
// as the current subset.
if (mp.find(sum) == mp.end()) {
// Increase ID by 1 as the
// subsets having a unique
// sum value has increased by 1.
++ID;
// Assign the value of sum to ID.
mp[sum] = ID;
}
// Retrieve the subset number
// having this sum.
int id = mp[sum];
// Add the bitmask to the vector
// of this particular subset id.
subsets[id].push_back(mask);
return;
}
// Exclude the current element
// from the subset
makeSubsets1(i + 1, N, sum,
mask, Arr);
// Include the current element
// to the first group of the subset.
makeSubsets1(i + 1, N, sum + Arr[i],
mask | (1 << i), Arr);
// Include the current element
// to the second group of the subset.
makeSubsets1(i + 1, N, sum - Arr[i], mask | (1 << i),
Arr);
}
// Function to generate all possible
// subsets from second half of array.
void makeSubsets2(int i, int N, int sum,
int mask, int Arr[])
{
// If the second half
// of the array is traversed.
if (i == N) {
// If the current subset sum has
// occurred before in the
// first part of the array, then the
// combined subset from both halves
// of the array forms a valid subset
if (mp.find(sum) != mp.end()) {
// Iterate through all the bitmasks
// from the first part of the array
// having the same current sum.
for (auto num : subsets[mp[sum]]) {
// Mark the bitwise OR
// of both subsets as TRUE.
canSplit[num | mask] = 1;
}
}
return;
}
// Exclude the current element
// from the subset.
makeSubsets2(i + 1, N, sum, mask, Arr);
// Include the current element
// to the second group of the subset.
makeSubsets2(i + 1, N, sum + Arr[i], mask | (1 << i),
Arr);
// Include the current element
// to the first group of the subset.
makeSubsets2(i + 1, N, sum - Arr[i], mask | (1 << i),
Arr);
}
// Utility function to find all subsets from both halves of
// the array.
int UtilCountOfSubsets(int N, int Arr[])
{
// Split the array into two parts.
int mid = N / 2;
// Function calls
makeSubsets1(0, mid, 0, 0, Arr);
makeSubsets2(mid, N, 0, 0, Arr);
int ans = 0;
// Iterate through all bitmasks
// from 1 to 2^N - 1.
for (int i = 1; i < (1 << N); ++i) {
// If canSplit[i] is true,
// increase the answer by 1.
ans += canSplit[i];
}
// Return the answer.
return ans;
}
// Driver code
int main()
{
// Input Array
int Arr[] = { 2, 3, 4, 5 };
// Size of array
int N = sizeof(Arr) / sizeof(Arr[0]);
cout << UtilCountOfSubsets(N, Arr) << endl;
}
Python3
# python3 program for the above approach.
# Boolean array to check if elements
# represented by a bitmask(subset)
# can be split into two non-empty groups
# having equal sum.
canSplit = [0 for _ in range(1 << 20)]
# Map to store sum of subsets
# and find if there is a subset
# with the current sum.
mp = {}
# Vector of vector to store
# all the bitmasks having the
# same sum together.
subsets = [[] for _ in range(1 << 20)]
# Variable to count subsets
# having unique sums.
ID = 0
# Function to generate all possible
# subsets from first half
# of the array.
def makeSubsets1(i, N, sum, mask, Arr):
global canSplit, mp, subsets, ID
# If first half of the array
# is traversed.
if (i == N):
# If none of the previously formed
# subsets have the same sum
# as the current subset.
if (not sum in mp):
# Increase ID by 1 as the
# subsets having a unique
# sum value has increased by 1.
ID += 1
# Assign the value of sum to ID.
mp[sum] = ID
# Retrieve the subset number
# having this sum.
id = mp[sum]
# Add the bitmask to the vector
# of this particular subset id.
subsets[id].append(mask)
return
# Exclude the current element
# from the subset
makeSubsets1(i + 1, N, sum, mask, Arr)
# Include the current element
# to the first group of the subset.
makeSubsets1(i + 1, N, sum + Arr[i], mask | (1 << i), Arr)
# Include the current element
# to the second group of the subset.
makeSubsets1(i + 1, N, sum - Arr[i], mask | (1 << i), Arr)
# Function to generate all possible
# subsets from second half of array.
def makeSubsets2(i, N, sum, mask, Arr):
global canSplit, mp, subsets, ID
# If the second half
# of the array is traversed.
if (i == N):
# If the current subset sum has
# occurred before in the
# first part of the array, then the
# combined subset from both halves
# of the array forms a valid subset
if (sum in mp):
# Iterate through all the bitmasks
# from the first part of the array
# having the same current sum.
for num in subsets[mp[sum]]:
# Mark the bitwise OR
# of both subsets as TRUE.
canSplit[num | mask] = 1
return
# Exclude the current element
# from the subset.
makeSubsets2(i + 1, N, sum, mask, Arr)
# Include the current element
# to the second group of the subset.
makeSubsets2(i + 1, N, sum + Arr[i], mask | (1 << i),
Arr)
# Include the current element
# to the first group of the subset.
makeSubsets2(i + 1, N, sum - Arr[i], mask | (1 << i),
Arr)
# Utility function to find all subsets from both halves of
# the array.
def UtilCountOfSubsets(N, Arr):
global canSplit, mp, subsets, ID
# Split the array into two parts.
mid = N // 2
# Function calls
makeSubsets1(0, mid, 0, 0, Arr)
makeSubsets2(mid, N, 0, 0, Arr)
ans = 0
# Iterate through all bitmasks
# from 1 to 2^N - 1.
for i in range(1, 1 << N):
# If canSplit[i] is true,
# increase the answer by 1.
ans += canSplit[i]
# Return the answer.
return ans
# Driver code
if __name__ == "__main__":
# Input Array
Arr = [2, 3, 4, 5]
# Size of array
N = len(Arr)
print(UtilCountOfSubsets(N, Arr))
# This code is contributed by rakeshsahni
输出
2
时间复杂度: O(6 N/2 ) 。
- 从数组的前半部分和后半部分生成所有子集需要 O(3 N/2 )。
- 在最坏的情况下,前半部分的所有子集都可能与后半部分的当前子集匹配,取 O(2 N/2 )。
- 因此总体时间复杂度为 O(3 N/2 * 2 N/2 ) = O(6 N/2 )。
辅助空间: O(2 N )