Given a set S, find all the maximal subsets whose

2020-06-12 06:01发布

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, 5} and k = 7 Output is: {1, 2, 3} {1, 2, 4} {1, 5} {2, 5} {3, 4}

Hints:

  • Output doesn't contain any set which is a subset of other.
  • If X = {1, 2, 3} is one of the solution then all the subsets of X {1} {2} {3} {1, 2} {1, 3} {2, 3} are omitted.
  • Lexicographic ordering may be used to solve it.

Any ideas how could this be solved?

6条回答
【Aperson】
2楼-- · 2020-06-12 06:36

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}
查看更多
聊天终结者
3楼-- · 2020-06-12 06:41

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 :)

查看更多
对你真心纯属浪费
4楼-- · 2020-06-12 06:45

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);
}
}
查看更多
爷的心禁止访问
5楼-- · 2020-06-12 06:55

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);
}
查看更多
够拽才男人
6楼-- · 2020-06-12 06:57

Algorithm is the following:

  • Starting from empty subSet.
  • Cycle through original array from the beginning (assuming array is already sorted in ascending order) until currentSum is less or equal target sum.
  • If current element added to currentSum is less than target sum, adding to current subSet current element and running recursion starting from the next element.
  • Breaking current cycle if current sum exceed targetSum.
  • If we can't add more elements into current subSet, we checking if it is maximal and print it in this case.
  • To determine maximal subSets we can compare original array and current subSet element by element, searching for the first mismatch. If element at first mismatch index is greater than difference between currentSum and targetSum, subSet is maximal and should be printed.

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}
    }

}
查看更多
够拽才男人
7楼-- · 2020-06-12 06:59

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}

查看更多
登录 后发表回答