Reputation: 21395
I have an array of numbers say [1,2,3,1,1000] , now I want to get all possible combinations of this array and calculate its sum. Combinations are valid such that two combinations have different subset of elements. Then order all the sum values in descending order and get the top k elements.
Example:
[1,2,3,1,1000]
Combinations:
Duplicates of earlier ones are striked out, for example (3,1) matches the earlier (1,3).
(), (1), (2), (3), (1), (1000), (1,2), (1,3), (1,1), (1,1000), (2,3), (2,1), (2,1000), (3,1), (3,1000), (1,1000), (1,2,3), (1,2,1), (1,2,1000), (1,3,1), (1,3,1000), (1,1,1000), (2,3,1), (2,3,1000), (2,1,1000), (3,1,1000), (1,2,3,1), (1,2,3,1000), (1,2,1,1000), (1,3,1,1000), (2,3,1,1000), (1,2,3,1,1000)
And the corresponding sums:
0, 1, 2, 3, 1, 1000, 3, 4, 2, 1001, 5, 3, 1002, 4, 1003, 1001, 6, 4, 1003, 5, 1004, 1002, 6, 1005, 1003, 1004, 7, 1006, 1004, 1005, 1006, 1007
Getting top k=3, sums = 1007, 1006, 1005
So output is [1007, 1006, 1005].
Constraints:
This is my code, reference taken from here:
static List<Long> printDistSum(int arr[]) {
List<Long> list = new ArrayList<>();
int n = arr.length;
// There are totoal 2^n subsets
long total = (long) Math.pow(2, n);
// Consider all numbers from 0 to 2^n - 1
for (int i = 0; i < total; i++) {
long sum = 0;
// Consider binary representation of
// current i to decide which elements
// to pick.
for (int j = 0; j < n; j++)
if ((i & (1 << j)) != 0)
sum += arr[j];
// Print sum of picked elements.
list.add(sum);
}
return list;
}
This code works for small range of inputs but times out for large range of inputs. How to solve this program.
Upvotes: 2
Views: 6407
Reputation: 1
I was also asked the same question yesterday but sadly I was not able to solve it yesterday. I have tried solving it today and think I have the answer today.
First of all I don't think that different subsets mean different costs in a set i.e in array of [1,2,3,1] both subsets are valid => [1,2,3] and [2,3,1] as they both use different 1's. Now here is my solution keeping this in mind. But if you really want to keep distinct elements in set then you can simply remove the multiple elements and do partial_sort then.
Logic
The proposed solution has time complexity of O(n*log(k) + k^2).
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int ll;
template <class T>
void print(vector<T> topSumm)
{
for (ll itr : topSumm)
cout << itr << '\t';
cout << '\n';
}
vector<ll> mergeSortedArrays(vector<ll> &minns, vector<ll> &temp)
{
vector<ll> ans(minns.size() + temp.size());
int i{0}, j{0}, k{0};
while (i < minns.size() && j < temp.size())
{
if (temp[j] < minns[i])
ans[k++] = temp[j++];
else
ans[k++] = minns[i++];
}
while (i < minns.size())
ans[k++] = minns[i++];
while (j < temp.size())
ans[k++] = temp[j++];
return ans;
}
vector<ll> topKSum(vector<int> &arr, int k)
{
int n{(int)arr.size()};
ll maxSumm{0};
for (int i{0}; i < n; ++i)
{
if (arr[i] > 0)
maxSumm += arr[i];
else
arr[i] = -arr[i];
}
int nk{min(k - 1, n)};
partial_sort(arr.begin(), arr.begin() + nk, arr.end());
vector<ll> minns{0, maxSumm};
ll summ{};
bool breakOuter{false};
for (int i{0}; i < nk; ++i)
{
vector<ll> temp;
for (ll nums : minns)
{
summ = nums + arr[i];
if (minns.size() + temp.size() < k)
temp.push_back(summ);
else
{
if (minns.back() > summ)
{
minns.pop_back();
temp.push_back(summ);
}
else
{
if (nums == 0)
breakOuter = true;
break;
}
}
}
if (breakOuter)
break;
minns = mergeSortedArrays(minns, temp);
}
vector<ll> ans(k);
int i{0};
for (ll nums : minns)
ans[i++] = maxSumm - nums;
return ans;
}
int main()
{
int t;
cin >> t;
while (t--)
{
int n, k;
cin >> n >> k;
vector<int> arr(n);
ll maxSumm{0};
for (int i{0}; i < n; ++i)
cin >> arr[i];
vector<ll> topSums = topKSum(arr, k);
print<ll>(topSums);
}
return 0;
}
Upvotes: 0
Reputation: 1
Pls ignore all previous posts cuz they are all wrong. Intuitively, we gotta use backtrack to find all desired combos, but it's impossible to backtrack on 10^5 elements.
Constraint 1 <= n <= 10^5 alludes that our algorithm bottlenecked by O(nlogn) sorting
Constraint 1 <= k <= min(2000,2^n) alludes that we can backtrack on k elements since k is less than 11. 2^11=2024/log(2000)=11 -- actually this "2^n" gives away solution :)
My algorithm (nlog(n) + 2^k)
Upvotes: 0
Reputation: 333
I probably have solution that should be good enough. It has time complexity O(n * k * log(k)).
First we need to calculate max sum - sum of all positive values.
Next we need to iterate over positive values, from smallest to largest. For each of these values we calculate sums of new combinations (at the start we have one combination with max sum). New combinations will not contains given value so we need to substract it from sum.
At the end we need to iterate over negative values. These values are not belongs to combinations from previous step so we need to add these values to sums.
In every iteration are needed only k maximum sums. I used the PriorityQueue to store these sums. That class use heap data structure so adding/removing values has logarithmic time.
Code:
private static long[] findSums(int[] array, int k) {
long maxSum = Arrays.stream(array).filter(it -> it >= 0).sum();
int[] positives = Arrays.stream(array).filter(it -> it >= 0).sorted().toArray();
int[] negatives = Arrays.stream(array).filter(it -> it < 0).sorted().toArray();
// sort time complexity is O(n*log(n))
PriorityQueue<Long> sums = new PriorityQueue<>(k); // priority queue is implemented using heap so adding element has time complexity O(log(n))
sums.add(maxSum); // we start with max sum - combination of all positive elements
int previous = Integer.MIN_VALUE;
Long[] previousAddedSums = {};
Long[] sumsToIterate;
// iterate over positive values
for (int i = 0; i < positives.length; i++) {
if (positives[i] == previous) {
sumsToIterate = previousAddedSums;
} else {
sumsToIterate = sums.toArray(new Long[sums.size()]);
}
previousAddedSums = new Long[sumsToIterate.length];
for (int j = 0; j < sumsToIterate.length; j++) {
long newSum = sumsToIterate[j] - positives[i];
// new sum is calculated - value positives[i] is removed from combination (subtracted from sum of that combination)
sums.add(newSum);
previousAddedSums[j] = newSum;
if (sums.size() > k) {
sums.poll(); // only first k maximum sums are needed at the moment
}
}
previous = positives[i];
}
previous = Integer.MAX_VALUE;
// iterate over negative values in reverse order
for (int i = negatives.length - 1; i >= 0; i--) {
if (negatives[i] == previous) {
sumsToIterate = previousAddedSums;
} else {
sumsToIterate = sums.toArray(new Long[sums.size()]);
}
previousAddedSums = new Long[sumsToIterate.length];
for (int j = 0; j < sumsToIterate.length; j++) {
long newSum = sumsToIterate[j] + negatives[i]; // value negatives[i] is added to combination (added to sum of that combination)
sums.add(newSum);
previousAddedSums[j] = newSum;
if (sums.size() > k) {
sums.poll();
}
}
previous = negatives[i];
}
long[] result = new long[sums.size()];
for (int i = sums.size() - 1; i >=0 ; i--) {
result[i] = sums.poll();
}
// get sums from priority queue in proper order
return result;
// this whole method has time complexity O(n * k * log(k))
// k is less than or equal 2000 so it should be good enough ;)
}
Demo: https://ideone.com/yf6POI
Edit: I have fixed my solution. Instead of iterating over distinct values I check if current value is same like previous. In that case I use combinations (sums) created in previous step. This prevents from creating duplicates of combinations.
I'm sorry if I didn't explain this well enough. I don't have experience in describing algorithmic / mathematical things in english.
Upvotes: 0