【问题标题】:Given a set S, find all the maximal subsets whose sum <= k给定一个集合 S,找出总和 <= k 的所有最大子集
【发布时间】:2012-03-27 18:22:59
【问题描述】:

这是我在一个在线门户网站上遇到的一个 Facebook 面试问题。

给定一个集合 S,找出总和

提示:

  • 输出不包含任何其他集合的子集。
  • 如果 X = {1, 2, 3} 是解之一,则 X {1} {2} {3} {1, 2} {1, 3} {2, 3} 的所有子集都被忽略.
  • 可以使用字典顺序来解决它。

有什么办法可以解决这个问题吗?

【问题讨论】:

  • S 可以包含重复值吗?比如S = {1, 2, 3, 3, 4, 5}?
  • @Seph,不,是set = 没有重复

标签: arrays algorithm set computer-science


【解决方案1】:

我有个主意 - 你需要一棵树。

如果您输入了{1, 2, 3, 4, 5},并且您正在搜索最大子集 - 您应该从最大的数字开始构建一棵树,并始终展开while sum &lt;= k(所以不要停在4-2,但下降到 1 得到 4-2-1)。

因此,从 5 开始的节点将是:5-1 / 5-2 - 只有那些 2 的总和

从 4 开始:4-3 / 4-2-1 / 4-1(上一个的子集)

从 3 开始:3-2-1 / 3-1(上一个的子集)

从 2 开始:2-1(3-2-1 的子集)

从 1:1(2-1 的子集)开始

然后可以对有效输出进行排序,得到 {1, 2, 3} {1, 2, 4} {1, 5} {2, 5} {3, 4}

【讨论】:

  • 你能想出一些算法吗??
  • 我们将包含子集的节点(满足
【解决方案2】:

我知道现在回答已经晚了,但我想我已经找到了解决这个问题的简单方法。我们使用回溯按字典顺序枚举S 的子集,并检查到目前为止生成的子集sum

sum 超过k 时,有趣的部分来了:我们需要检查生成的subset 是否是先前报告的项目的适当子集。

一种解决方案是保留所有报告的子集并检查是否包含,但这很浪费。

相反,我们计算ksum 之间的差异。如果S 中有一个元素e 使得e not in subsete &lt;= (k - sum),那么我们生成的集合是先前报告的子集的适当子集,我们可以安全地跳过它。

下面是用普通的旧 C++ 编写的完整工作程序,展示了这个想法:

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

输出是按字典顺序排列的所有最大子集,每行一个:

{1, 2, 3}
{1, 2, 4}
{1, 5}
{2, 5}
{3, 4}

【讨论】:

    【解决方案3】:

    这是一个powerset问题。最近我发现this website 关于算法,它一直在描绘我的想象力:因此下面是 powerset/combinations 解决方案。您可以简单地复制、粘贴和运行程序。

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

    【讨论】:

    • @Raman Bhatia 你有没有接受这个答案的原因?很乐意提供帮助。
    • 这个解决方案似乎非常复杂,如果没有额外的 cmets/解释,很难理解
    【解决方案4】:

    一个老问题,但仍然很有趣。

    这是一个递归 Java 8 解决方案,采用“置换”方法。

    针对更简洁和更短的代码而非性能进行了优化——例如,排序和修剪只需要进行一次。

    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());
        }
    }
    

    测试它:

    public static void main(String[] args) {
        new SubsetFinder()
            .findSubsets(ImmutableList.of(1, 2, 3, 4, 5), 7)
            .forEach(System.out::println);
    }
    

    【讨论】:

      【解决方案5】:

      算法如下:

      • 从空子集开始。
      • 从头开始循环遍历原始数组(假设数组已经按升序排序),直到 currentSum 小于或等于目标总和。
      • 如果添加到 currentSum 的当前元素小于目标总和,则将当前元素添加到当前子集并从下一个元素开始运行递归。
      • 如果当前总和超过 targetSum,则中断当前周期。
      • 如果我们不能将更多元素添加到当前子集中,我们会检查它是否最大并在这种情况下打印它。
      • 要确定最大子集,我们可以逐个元素地比较原始数组和当前子集,查找第一个不匹配项。如果第一个不匹配索引处的元素大于 currentSum 和 targetSum 之间的差值,则 subSet 是最大的并且应该被打印。

      Java 上的工作解决方案如下:

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

      【讨论】:

        【解决方案6】:

        我很抱歉这么晚才加入。但是这样做呢?

        1) 从给定的数组/集合构建一个 MIN-HEAP 结构

        2) 从根开始遍历结构,不断减去你访问的节点的值。一旦超过所需的总和(curr_sum > k),输出这条路径,回溯到父路径并走另一条路径(这可以递归完成)。

        3)如果回溯将您带回您开始的原始节点,则从根节点->左节点递归实现整个算法。

        4) 执行上面相同的两个步骤 (2) 和 (3),但现在使用 MAX-HEAP。

        我是算法和数据结构的新手,刚开始阅读 Algos-Cormen 简介。这可能是一个错误的解决方案,但如果有人向我指出错误,我会非常高兴:)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-07-12
          • 1970-01-01
          • 2021-05-23
          相关资源
          最近更新 更多