This is a Facebook interview question I came across at an online portal.
Given a set S, find all the maximal subsets whose sum <= k. For example, if S = {1, 2, 3, 4,
I know it's late to answer, but I think I've found a simple solution for this problem. We enumerate subsets of S
in lexicographical order using backtracking and check the sum
of subset generated so far.
When the sum
exceeds k
, the interesting part comes: we need to check if the generated subset
is a proper subset of previously reported items.
One solution is to keep all the reported subsets and check for inclusion, but it's wasteful.
Instead, we calculate the difference between the k
and the sum
. If there is an element e
in S
such that e not in subset
and e <= (k - sum)
, then the set we generated is a proper subset of a previously reported subset, and we can safely skip it.
Here is the complete working program in plain old C++, demonstrating the idea:
#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
typedef std::set<int> Set;
typedef std::vector<int> SubSet;
bool seen_before(const Set &universe, const SubSet &subset, int diff) {
Set::const_iterator i = std::mismatch(universe.begin(), universe.end(),
subset.begin()).first;
return i != universe.end() && *i <= diff;
}
void process(const SubSet &subset) {
if (subset.empty()) {
std::cout << "{}\n";
return;
}
std::cout << "{" << subset.front();
for (SubSet::const_iterator i = subset.begin() + 1, e = subset.end();
i != e; ++i) {
std::cout << ", " << *i;
}
std::cout << "}\n";
}
void generate_max_subsets_rec(const Set &universe, SubSet &subset,
long sum, long k) {
Set::const_iterator i = subset.empty()
? universe.begin()
: universe.upper_bound(subset.back()),
e = universe.end();
if (i == e) {
if (!seen_before(universe, subset, k - sum))
process(subset);
return;
}
for (; i != e; ++i) {
long new_sum = sum + *i;
if (new_sum > k) {
if (!seen_before(universe, subset, int(k - sum)))
process(subset);
return;
} else {
subset.push_back(*i);
if (new_sum == k)
process(subset);
else
generate_max_subsets_rec(universe, subset, new_sum, k);
subset.pop_back();
}
}
}
void generate_max_subsets(const Set &universe, long k) {
SubSet subset;
subset.reserve(universe.size());
generate_max_subsets_rec(universe, subset, 0, k);
}
int main() {
int items[] = {1, 2, 3, 4, 5};
Set u(items, items + (sizeof items / sizeof items[0]));
generate_max_subsets(u, 7);
return 0;
}
The output is all maximum subsets in lexicographical order, one per line:
{1, 2, 3}
{1, 2, 4}
{1, 5}
{2, 5}
{3, 4}
This is a powerset problem. Recently I found this website about algorithms and it's been painting my imagination: hence the powerset/combinations solution following. You can simply copy, paste, and run the program.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public static void maximalSubset
(int sum, int[] set, int choose,List<Integer[]> exclusion) {
if(1>choose) return;
int combinationSize = combinationSize(set.length,choose);
int index[]=new int[choose];
Integer subSet[] = new Integer[choose];
for(int i=0; i<choose;i++)
index[i]=i;
for(int i=0; i<combinationSize; i++) {
if(i!=0)
nextCombination(index,set.length);
for(int x=0; x<choose; x++)
subSet[x]=set[index[x]];
if(summation(sum,subSet) && !excluded(subSet,exclusion)) {
System.out.println(Arrays.toString(subSet));
exclusion.add(Arrays.copyOf(subSet,subSet.length));
}
}
maximalSubset(sum,set,choose-1,exclusion);
}//
private static int combinationSize(int n, int r) {
int den,limit;
if(r>n-r) {
den=n-r;
limit=r;
}else {
den=r;
limit=n-r;
}
long result=1;
for(int i=n; i>limit;i--)
result*=i;
for(int i=2; i<=den;i++)
result/=i;
return (int)result;
}//
private static void nextCombination(int[] A, int n) {
int c=A.length;
int i=c-1;
while(n-c+i==A[i])
i--;
A[i]++;
for(int j=i; j<c; j++)
A[j]=A[i]+j-i;
}//
private static boolean summation(int sum, Integer[] S) {
for(int i:S)
sum-=i;
return sum>=0;
}//
private static boolean excluded(Integer[] needle,List<Integer[]> haystack) {
for(Integer[] H: haystack) {
int count=0;
for(int h: H)
for(int n:needle)
if(h==n) {
count++;
break;//it's a set
}
if(count==needle.length)
return true;
}
return false;
}//
public static void main(String[] args) {
int[] S = {1, 2, 3, 4, 5};
int k = 7;
List<Integer[]> exclusion = new ArrayList<Integer[]>();
maximalSubset(k,S,S.length,exclusion);
}
}
I am sorry for chipping in so late. But how about doing this?
1) Build a MIN-HEAP structure from the given array/set
2) traverse the structure from the root and keep subtracting the value at the node that you visit. Once you exceed the required sum (curr_sum > k), output this path, backtrack to the parent and take another path (this can be done recursively).
3) If backtracking takes you back to the original node that you started from, implement the entire algorithm recursively from root->left node.
4) Do the same two steps (2) and (3) above but with a MAX-HEAP now.
I am new to algorithms and data structures, and have only started reading Intro to Algos-Cormen. This might be a faulty solution, but I would be more than happy if anyone points out the fault to me :)
An old question but still an interesting one.
Here's a recursive Java 8 solution, with a "permutational" approach.
Optimized for cleaner and shorter code rather than performance -- for example the sorting and pruning would only need to take place once.
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import java.util.*;
import java.util.stream.Collectors;
public class SubsetFinder {
public List<List<Integer>> findSubsets(List<Integer> input, int k) {
List<List<Integer>> listOfLists = new ArrayList<>();
List<Integer> copy = Ordering.natural().sortedCopy(input);
while (!copy.isEmpty()) {
int v = copy.remove(copy.size() - 1);
if (v == k || (copy.isEmpty() && v <= k)) {
// No need to look for subsets if the element itself == k, or
// if it's the last remaining element and <= k.
listOfLists.add(new ArrayList<>(Arrays.asList(v)));
} else if (v < k) {
findSubsets(copy, k - v).forEach(subList -> {
subList.add(v);
listOfLists.add(subList);
});
}
}
// Prune sets which are duplicates or subsets of other sets
return listOfLists.stream().filter(
candidate -> listOfLists.stream().noneMatch(
lol -> candidate != lol && lol.containsAll(candidate)
)
).collect(Collectors.toList());
}
}
To test it:
public static void main(String[] args) {
new SubsetFinder()
.findSubsets(ImmutableList.of(1, 2, 3, 4, 5), 7)
.forEach(System.out::println);
}
Algorithm is the following:
Working solution on Java is below:
public class Test {
/**
* Assuming alphabet[] is already sorted in increasing order
*/
public static void printMaximalSubSetsToSum(int[] alphabet, int sum) {
if (alphabet == null || alphabet.length == 0) {
return;
}
if (alphabet[0] > sum) {
// no sense to search, since smallest element in array already bigger than sum
return;
} else if (alphabet[0] == sum) {
Set<Integer> subSet = new HashSet<>();
subSet.add(alphabet[0]);
printSubset(subSet);
}
Set<Integer> subSet = new HashSet<>();
processMaximalSubSetToSum(alphabet, sum, 0, 0, subSet);
}
private static void processMaximalSubSetToSum(int[] alphabet, int sum, int sumSoFar, int startFrom, Set<Integer> subSet) {
if (startFrom >= alphabet.length) {
if (isMaximalSubSet(alphabet, subSet, sum - sumSoFar)) {
printSubset(subSet);
}
return;
}
for (int i = startFrom; i < alphabet.length; i++) {
int newSum = sumSoFar + alphabet[i];
if (newSum > sum) {
if (isMaximalSubSet(alphabet, subSet, sum - sumSoFar)) {
printSubset(subSet);
}
return;
} else {
subSet.add(alphabet[i]);
if (newSum == sum) {
printSubset(subSet);
} else {
processMaximalSubSetToSum(alphabet, sum, newSum, i + 1, subSet);
}
subSet.remove(alphabet[i]);
}
}
}
private static boolean isMaximalSubSet(int[] alphabet, Set<Integer> subSet, int diff) {
// search first mismatch element between alphabet and current SubSet
Iterator<Integer> it = subSet.iterator();
int i = 0;
while (it.hasNext()) {
if (it.next() != alphabet[i]) {
break;
}
i++;
}
return i >= alphabet.length || alphabet[i] > diff;
}
private static void printSubset(Set<Integer> subset) {
System.out.println(subset);
}
public static void main(String[] args) throws java.lang.Exception {
//printMaximalSubSetsToSum(new int[]{1, 2, 3, 4, 5}, 7);
// Correct output is: {1, 2, 3}; {1, 2, 4}; {1, 5}; {2, 5}; {3, 4}
}
}
I have some idea - you need a tree.
If you have given input of {1, 2, 3, 4, 5}
, and you're searching for maximal subsets - you should build a tree starting from the biggest numbers, and allways expand while sum <= k
(so don't stop on 4-2, but go down to 1 to get 4-2-1).
So, nodes starting from 5 would be: 5-1 / 5-2 - only those 2 have sum <= 7
starting from 4: 4-3 / 4-2-1 / 4-1 (subset of previous)
starting from 3: 3-2-1 / 3-1 (subset of previous)
starting from 2: 2-1 (subset of 3-2-1)
starting from 1: 1 (subset of 2-1)
Then you can sort valid outputs and get {1, 2, 3} {1, 2, 4} {1, 5} {2, 5} {3, 4}