数组和最多为 N 的子集和问题
给定一个大小为N的数组arr[] ,使得所有数组元素的总和不超过 N,并且数组queries[]包含Q个查询。对于每个查询,任务是查找是否存在数组的子集,其总和与query[i]相同。
例子:
Input: arr[] = {1, 0, 0, 0, 0, 2, 3}, queries[] = {3, 7, 6}
Output:
Possible
Not Possible
Possible
Explanation: 3 is spossible. 6 can be obtained by the subset {1, 2, 3}
7 is greater than thhe sum o fall array elements.
Input: arr[] = {0, 1, 2}, queries[] = {1, 2, 3, 0}
Output:
Possible
Possible
Possible
Possible
Explanation: All the sums can be obtained by using the elements.
方法:可以使用子集和问题中的方法来解决问题。
但是,可以使用总和最多为N的事实来降低时间复杂度。由于总和最多为 N,因此可以证明最多有√2N 个唯一的正元素,其中所有的频率都为 1。
Say there are √2N unique positive elements starting from 1 to √2N.
Therefore the sum of those numbers is N + √(N/2).
This sum is more than N itself. So there can be at most √2N unique elements.
上述事实可以在动态规划中使用和实现。使用坐标压缩,所有这些独特的元素都可以存储在最小的空间中。
For each element check what is the minimum contribution of that element to achieve a sum j (j in the range [0, N]) or if it is not possible to achieve the sum j. The contribution of each item (say i) depends on the contribution of the other smaller items till the sum (j – i).
按照下图可以更好地理解正常子集的未使用状态以及总和为 N 时的最大差异:
比较:
Say the arr[] = {1, 2, 2, 2, 3, 3}. (Here sum is greater, so does not follow the condition of sum at most N. But here unique elements maintain the threshold. That’s why it is used here just for understanding purpose)
Red cells signify the useless states, these are much more in traditional algorithm than optimized one.
按照下面提到的步骤实施该方法;
- 对所有独特元素使用坐标压缩。
- 构建一个二维 dp[][] 数组,其中dp[i][j]存储第 i项的贡献以获得总和j 。 (如果不可能,则存储 - 1 ,如果不需要第 i 个项目,则将0存储在dp[i][j]中)。
- 从i = 0 迭代到最大元素:
- 迭代j = 0 到 N :
- 如果 dp[i][j-arr[i]] + 1 < dp[i][j] 的值,则更新它。
- 否则,保持原样。
- 迭代j = 0 到 N :
- 然后从 i = 0 迭代到 Q:
- 检查该总和 (query[i]) 是否可能。
- 如果它超过数组总和或所有元素一起无法获得某个总和,即dp[len][query[i]] = -1 ,则这是不可能的。 (len 是唯一元素元素的总数)
下面是上述方法的实现。
C++
// C++ code to implement the approach
#include
using namespace std;
// Function to find if the queries
// are possible or not
void findSol(vector& arr,
vector& queries)
{
int s = 0;
// Calculating sum of array
for (auto& item : arr) {
s += item;
}
// Coordinate compression,
// make frequency-value pairs
map mp;
for (auto& item : arr) {
mp[item]++;
}
vector val, freq;
// Frequency mapping
for (auto& x : mp) {
val.push_back(x.first);
freq.push_back(x.second);
}
int len = val.size();
vector > dp(len + 1,
vector(
s + 1, 0));
for (int j = 1; j <= s; ++j) {
dp[0][j] = -1;
}
// Loop to build the dp[][]
for (int i = 1; i <= len; ++i) {
for (int j = 1; j <= s; ++j) {
int v = val[i - 1];
int f = freq[i - 1];
if (dp[i - 1][j] != -1) {
dp[i][j] = 0;
}
else if (j >= v
&& dp[i][j - v] != -1
&& dp[i][j - v] + 1 <= f) {
dp[i][j] = dp[i][j - v] + 1;
}
else {
dp[i][j] = -1;
}
}
}
// Answer queries
for (auto& q : queries) {
if (q > s || dp[len][q] == -1) {
cout << "Not Possible" << endl;
}
else {
cout << "Possible" << endl;
}
}
}
// Driver Code
int main()
{
vector arr = { 1, 0, 0, 0, 0, 2, 3 };
vector queries = { 3, 7, 6 };
// Function call
findSol(arr, queries);
return 0;
}
Possible
Not Possible
Possible
时间复杂度:O(N * √N)
辅助空间: O(N * √N)