【问题标题】:Find all subsets of length k in an array在数组中查找长度为 k 的所有子集
【发布时间】:2022-04-20 07:11:10
【问题描述】:

给定一个包含 n 个元素的集合 {1,2,3,4,5...n},我们需要找到长度为 k 的所有子集。

例如,如果 n = 4 且 k = 2,则 output 将是 {1, 2}, {1, 3}, {1, 4}, {2, 3}, {2, 4}, {3, 4}

我什至不知道如何开始。我们不必使用 next_permutation 等内置库函数。

需要 C/C++ 或 Java 中的算法和实现。

【问题讨论】:

标签: arrays algorithm subset powerset


【解决方案1】:

递归是这个任务的好朋友。

对于每个元素-“猜测”是否在当前子集中,并使用猜测和可以从中选择的较小超集递归调用。对“是”和“否”的猜测都这样做 - 将导致所有可能的子集。
在停止子句中可以轻松地将自己限制在一定的长度内。

Java 代码:

private static void getSubsets(List<Integer> superSet, int k, int idx, Set<Integer> current,List<Set<Integer>> solution) {
    //successful stop clause
    if (current.size() == k) {
        solution.add(new HashSet<>(current));
        return;
    }
    //unseccessful stop clause
    if (idx == superSet.size()) return;
    Integer x = superSet.get(idx);
    current.add(x);
    //"guess" x is in the subset
    getSubsets(superSet, k, idx+1, current, solution);
    current.remove(x);
    //"guess" x is not in the subset
    getSubsets(superSet, k, idx+1, current, solution);
}

public static List<Set<Integer>> getSubsets(List<Integer> superSet, int k) {
    List<Set<Integer>> res = new ArrayList<>();
    getSubsets(superSet, k, 0, new HashSet<Integer>(), res);
    return res;
}

调用:

List<Integer> superSet = new ArrayList<>();
superSet.add(1);
superSet.add(2);
superSet.add(3);
superSet.add(4);
System.out.println(getSubsets(superSet,2));

将产生:

[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]

【讨论】:

  • 谢谢,就这样。我也有这个想法。但我一直在寻找有效的东西。
  • @sTEAK.:子集的数量是指数级的,所以恐怕效率不是一个真正的选择。祝你好运!
  • @AdarHefer 不,它是 k 的指数,它是输入的 - 不是恒定的。所以 n^k 绝对不是多项式。
  • @Nimrod 此答案针对“集合”(无序)。基本上,您将需要跟踪您使用过的元素(不允许多次使用) - 在“猜测”步骤中,您有多个“使用”和“不使用”选项,您需要迭代所有未使用的值。
  • @Nimrod 虽然我不记得 4 年前的想法,但我今天可能也会使用 set,因为它更具可读性。尽管该算法注意不重复,但使用 Set 类型使其更具可读性和更易于理解。
【解决方案2】:

使用集合的位向量表示,并使用类似于 std::next_permutation 对 0000.1111 所做的算法(n-k 个零,k 个一)。每个排列对应一个大小为 k 的子集。

【讨论】:

  • 这是一个非常没有帮助的答案。它没有足够的信息让某人实际实现您所绘制的算法。
  • 他的回答非常好,但你是对的@NickBailey - 不够详细。我在另一个线程中实现了这个(但直到现在我才看到这个线程)stackoverflow.com/questions/127704/…
  • 我相信这是迄今为止更好的答案。诠释 n = 3; std::string s = "ABCDEFGH"; std::vector 掩码 = {0,0,0,0,0,1,1,1}; std::set<:string> 集;做 { sets.insert(calculate_set(mask, s)); } while(std::next_permutation(mask.begin(),mask.end()));其中 calculate_set 的行为如下: ABCDEFGH 01010010 -> return BDG
【解决方案3】:

这是蟒蛇。对不起西班牙语;)

from pprint import pprint
conjunto = [1,2,3,4, 5,6,7,8,9,10]
k = 3
lista = []
iteraciones = [0]
def subconjuntos(l, k):
    if k == len(l):
        if not l in lista:
            lista.append(l)
        return
    for i in l:
        aux = l[:]
        aux.remove(i)
        result = subconjuntos(aux, k)
        iteraciones[0] += 1
        if not result in lista and result:
            lista.append( result)

subconjuntos(conjunto, k)
print (lista)
print ('cant iteraciones: ' + str(iteraciones[0]))

【讨论】:

    【解决方案4】:

    看看我的解决方案

    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.Set;
    
    
     public class Subset_K {
    public static void main(String[]args)
    {
        Set<String> x;
        int n=4;
        int k=2;
        int arr[]={1,2,3,4};
        StringBuilder sb=new StringBuilder();
        for(int i=1;i<=(n-k);i++)
            sb.append("0");
        for(int i=1;i<=k;i++)
            sb.append("1");
        String bin=sb.toString();
        x=generatePerm(bin);
        Set<ArrayList <Integer>> outer=new HashSet<ArrayList <Integer>>();
        for(String s:x){
            int dec=Integer.parseInt(s,2);
            ArrayList<Integer> inner=new ArrayList<Integer>();
            for(int j=0;j<n;j++){
                if((dec&(1<<j))>0)
                    inner.add(arr[j]);
            }
            outer.add(inner);
        }
        for(ArrayList<?> z:outer){
            System.out.println(z);
        }
    }
    
        public static Set<String> generatePerm(String input)
    {
        Set<String> set = new HashSet<String>();
        if (input == "")
            return set;
    
        Character a = input.charAt(0);
    
        if (input.length() > 1)
        {
            input = input.substring(1);
    
            Set<String> permSet = generatePerm(input);
    
            for (String x : permSet)
            {
                for (int i = 0; i <= x.length(); i++)
                {
                    set.add(x.substring(0, i) + a + x.substring(i));
                }
            }
        }
        else
        {
            set.add(a + "");
        }
        return set;
    }
    }
    

    我正在研究一个用于测试目的的 4 元素集,并使用 k=2。我尝试做的是最初生成一个二进制字符串,其中设置了 k 位并且未设置 n-k 位。现在使用这个字符串,我找到了这个字符串的所有可能排列。然后使用这些排列,我输出集合中的相应元素。如果有人能告诉我这个问题的复杂性,那就太好了。

    【讨论】:

      【解决方案5】:
         #include<iostream>
         #include<cstdio>
         #include<vector>
         using namespace std;
         vector<int> v;
         vector<vector<int> > result;
      
        void subset(int arr[],int k,int n,int idx){
        if(idx==n)
       return;
      
      if(k==1){
          for(int i=idx;i<n;i++)
           {
              v.push_back(arr[i]);
              result.push_back(v);
              v.pop_back();
           }
      }
      
       for(int j=idx;j<n;j++) {
        v.push_back(arr[j]);
        subset(arr,k-1,n,j+1);
        v.pop_back();
        }
       }
      
      int main(){
      int arr[] = {1,2,3,4,5,6,7};
      int k = 4;
      int n =sizeof(arr)/sizeof(arr[0]);
      subset(arr,k,n,0);
      
      for(int i = 0;i<result.size();i++)
       { 
        for(int j = 0;j<result[i].size();j++)
         {
           cout << result[i][j] << " ";
         }
         cout << endl;
       }
      }
      

      【讨论】:

        【解决方案6】:

        另一个有趣的解决方案。

        #include<bits/stdc++.h>
        using namespace std;
        long factorial(int n) { return (n==1|| n==0|| n < 0) ? 1 : n *factorial(n-1) ;}
        void printS(int set[],int n,int k) 
        { 
        
           long noofsubset =  factorial(n) / (factorial(n-k)*factorial(k));
           bitset<32> z ((1 << (k)) - 1);
           string s = z.to_string();
            int i = 0;
                while(i<noofsubset)
                { 
                          for (int j = 0; j  < n;j++)
                          {
                              if(s[(32-n)+j] == '1')
                                cout << set[j]<<" ";
                          }
                            cout << endl;
                        next_permutation(s.begin(),s.end());
                        i++;
                } 
        }
        
        void printSubsetsOfArray(int input[], int size) {
            int k  = 3;
          printS(input,size,k)  ;
        }
        

        【讨论】:

          【解决方案7】:

          @amit top 投票答案略有改进:

          即使组合没有任何机会达到所需长度,他的代码也会不断检查组合。我们可以更早地停止创建组合:

          例如对于 [1,2,3,4,5,6,7,8,9,10] ,长度 = 8 ,代码仍然会尝试长度为 7,6,5,4,3,2,1 的所有组合它们显然会被丢弃,只有在 idx 到达列表末尾时才会停止。

          当我们已经知道我们构建的集合 + 可选的剩余数字仍然太短时,我们可以通过提前停止来改善运行时间。

          改变:

          //unsuccessful stop clause
          if (idx == superSet.size()) return;
          
          

          进入:

          // unsuccessful stop clause
          Integer maxFutureElements = superSet.size() - idx;
          if (current.size() + maxFutureElements < length) return;
          

          【讨论】:

            【解决方案8】:

            这是一个简短的 Python 算法。我没有使用任何预定义的函数,所以我相信它可以很容易地翻译成 Java/C

            def subs(l,n):
            
            if(len(l)<k):
            
            return []
            
            elif(k==0):
            
            return [[]]
            
            else:
            
            lis=[[l[0]]+b for b in (subs(l[1:],k-1))]
            
            return (lis+subs(l[1:],k))
            

            这里 l 是列表 [1,2,...,m]

            【讨论】:

              【解决方案9】:

              请检查我的解决方案:-

              private static void printPermutations(List<Integer> list, int subSetSize) {
                  List<Integer> prefixList = new ArrayList<Integer>();
                  printPermutations(prefixList, list, subSetSize);
              }
              
              private static void printPermutations(List<Integer> prefixList, List<Integer> list, int subSetSize) {
                  if (prefixList.size() == subSetSize) {
                      System.out.println(prefixList);
                  } else {
                      for (int i = 0; i < list.size(); i++) {
                          Integer removed = list.remove(i);
                          prefixList.add(removed);
                          printPermutations(prefixList, list, subSetSize);
                          prefixList.remove(removed);
                          list.add(i, removed);
                      }
                  }
              }
              

              这类似于字符串排列:-

              private static void printPermutations(String str) {
                  printAllPermutations("", str);
              }
              
              private static void printAllPermutations(String prefix, String restOfTheString) {
                  int len = restOfTheString.length();
                  System.out.println(prefix);
                  for (int i = 0; i < len; i++) {
                      printAllPermutations(prefix + restOfTheString.charAt(i), restOfTheString.substring(0, i) + restOfTheString.substring(i + 1, len));
                  }
              }
              

              【讨论】:

              • 如果你能添加一些关于你做了什么以及为什么做的解释会很好
              • @UriAgassi 你现在可以关注解决方案了吗?
              • 口头解释比更多代码要好得多...您花时间回答了一个老问题 - 让我们了解您的答案比其他答案更好。
              【解决方案10】:

              这是 F# 中的一个实现:

              // allSubsets: int -> int -> Set<Set<int>>
              let rec allSubsets n k =
                  match n, k with
                  | _, 0 -> Set.empty.Add(Set.empty)
                  | 0, _ -> Set.empty
                  | n, k -> Set.union (Set.map (fun s -> Set.add n s) (allSubsets (n-1) (k-1)))
                                      (allSubsets (n-1) k)
              

              你可以在 F# REPL 中试试:

              > allSubsets 3 2;;
              
              val it : Set<Set<int>> = set [set [1; 2]; set [1; 3]; set [2; 3]]
              
              > allSubsets 4 2;;
              
              val it : Set<Set<int>> = set [set [1; 2]; set [1; 3]; set [1; 4]; set [2; 3]; set [2; 4]; set [3; 4]]
              

              这个 Java 类实现了相同的算法:

              import java.util.HashSet;
              import java.util.Set;
              
              public class AllSubsets {
              
                  public static Set<Set<Integer>> allSubsets(int setSize, int subsetSize) {
                      if (subsetSize == 0) {
                          HashSet<Set<Integer>> result = new HashSet<>();
                          result.add(new HashSet<>());
                          return result;
                      }
                      if (setSize == 0) {
                          return new HashSet<>();
                      }
                      Set<Set<Integer>> sets1 = allSubsets((setSize - 1), (subsetSize - 1));
                      for (Set<Integer> set : sets1) {
                          set.add(setSize);
                      }
                      Set<Set<Integer>> sets2 = allSubsets((setSize - 1), subsetSize);
                      sets1.addAll(sets2);
                      return sets1;
                  }
              }
              

              如果您不喜欢 F# 或 Java,请访问此网站。它列出了各种编程语言中针对您的特定问题的解决方案:

              http://rosettacode.org/wiki/Combinations

              【讨论】:

                【解决方案11】:

                JavaScript 实现:

                var subsetArray = (function() {
                  return {
                    getResult: getResult
                  }
                
                  function getResult(array, n) {
                
                    function isBigEnough(value) {
                      return value.length === n;
                    }
                
                    var ps = [
                      []
                    ];
                    for (var i = 0; i < array.length; i++) {
                      for (var j = 0, len = ps.length; j < len; j++) {
                        ps.push(ps[j].concat(array[i]));
                      }
                    }
                    return ps.filter(isBigEnough);
                  }
                })();
                
                
                
                 var arr = [1, 2, 3, 4,5,6,7,8,9];
                 console.log(subsetArray.getResult(arr,2));
                

                【讨论】:

                  【解决方案12】:

                  这是python中的迭代版本。它的本质是 increment_counters() 函数,它返回所有可能的组合。我们知道它需要被调用 C(n,r) 次。

                  def nchooser(n,r):
                      """Calculate the n choose r manual way"""
                      import math
                      f = math.factorial
                      return f(n) / f(n-r) / f(r)
                  
                  def increment_counters(rc,r,n):
                      """This is the essense of the algorithm. It generates all possible indexes.
                      Ex: for n = 4, r = 2, rc will have values (0,1),(0,2),(0,3),(1,2),(1,3),(2,3).
                      You may have better understanding if you print all possible 35 values for
                      n = 7, r = 3."""
                  
                      rc[r-1] += 1     # first increment the least significant counter
                      if rc[r-1] < n:  # if it does not overflow, return
                          return
                  
                      # overflow at the last counter may cause some of previous counters to overflow
                      # find where it stops (ex: in n=7,r=3 case, 1,2,3 will follow 0,5,6)
                      for i in range(r-2,-1,-1): # from r-2 to 0 inclusive
                          if rc[i] < i+n-r:
                              break
                      # we found that rc[i] will not overflow. So, increment it and reset the
                      # counters right to it. 
                      rc[i] += 1
                      for j in range(i+1,r):
                          rc[j] = rc[j-1] + 1
                  
                  def combinations(lst, r):
                      """Return all different sub-lists of size r"""
                      n = len(lst)
                      rc = [ i for i in range(r) ]  # initialize counters
                      res = []
                      for i in range(nchooser(n,r)): # increment the counters max possible times 
                          res.append(tuple(map(lambda k: lst[k],rc)))
                          increment_counters(rc,r,n)
                  
                      return res
                  

                  【讨论】:

                    【解决方案13】:

                    这是我认为 Simple 所说的 Java 版本,它使用幂集中所有集合的二进制表示。这与 Abhiroop Sarkar 的做法类似,但我认为当您仅表示二进制值时,布尔数组比字符串更有意义。

                    private ArrayList<ArrayList<Object>> getSubsets(int m, Object[] objects){
                        // m = size of subset, objects = superset of objects
                        ArrayList<ArrayList<Object>> subsets = new ArrayList<>();
                        ArrayList<Integer> pot = new ArrayList<>();
                        int n = objects.length;
                        int p = 1;
                        if(m==0)
                            return subsets;
                        for(int i=0; i<=n; i++){
                            pot.add(p);
                            p*=2;
                        }
                        for(int i=1; i<p; i++){
                            boolean[] binArray = new boolean[n];
                            Arrays.fill(binArray, false);
                            int y = i;
                            int sum = 0;
                            for(int j = n-1; j>=0; j--){
                                int currentPot = pot.get(j);
                                if(y >= currentPot){
                                    binArray[j] = true;
                                    y -= currentPot;
                                    sum++;
                                }
                                if(y<=0)
                                    break;
                            }
                            if(sum==m){
                                ArrayList<Object> subsubset = new ArrayList<>();
                                for(int j=0; j < n; j++){
                                    if(binArray[j]){
                                        subsubset.add(objects[j]);
                                    }
                                }
                                subsets.add(subsubset);
                            }
                        }
                    
                        return subsets;
                    }
                    

                    【讨论】:

                    • 我尝试使用它,但是它返回错误的结果并且对于较大的数组变得非常慢。我在这里有一个正确、快速的替代解决方案,它提供了一个迭代器而不是一个列表(节省内存):code.botcompany.de/1027914(点击“纯 Java 版本”)
                    【解决方案14】:

                    如果您正在寻找迭代器模式的答案,那么您就去吧。

                    public static <T> Iterable<List<T>> getList(final Iterable<? extends T> list) {
                    
                        List<List<T>> listOfList = new ArrayList<>();
                    
                        for (T t: list)
                            listOfList.add(Collections.singletonList(t));
                    
                        return listOfList;
                    }
                    public static <T> Iterable<List<T>> getIterable(final Iterable<? extends T> list, final int size) {
                    
                        final List<T> vals = new ArrayList<>();
                        int numElements = 0;
                        for (T t : list) {
                            vals.add(t);
                            numElements++;
                        }
                    
                        if (size == 1) {
                            return getList(vals);
                        }
                        if (size == numElements) {
                            return Collections.singletonList(vals);
                        }
                    
                        return new Iterable<List<T>>() {
                    
                            @Override
                            public Iterator<List<T>> iterator() {
                                return new Iterator<List<T>>() {
                    
                                    int currPos = 0;                    
                                    Iterator<List<T>> nextIterator = getIterable(
                                        vals.subList(this.currPos + 1, vals.size()), size - 1).iterator();
                    
                                    @Override
                                    public boolean hasNext() {
                                        if ((this.currPos < vals.size()-2) && (this.currPos+size < vals.size()))
                                            return true;
                                        return false;
                                    }
                    
                                    @Override
                                    public List<T> next() {
                                        if (!nextIterator.hasNext()) {
                                            this.currPos++;
                                            nextIterator = getIterable(vals.subList(this.currPos+1, vals.size()), size-1).iterator();
                                        }
                                        final List<T> ret = new ArrayList<>(nextIterator.next());
                                        ret.add(0, vals.get(this.currPos));
                                        return ret;
                                    }
                                };
                            }
                        };
                    }
                    

                    【讨论】:

                      【解决方案15】:

                      这是一个简单的算法,可以按字典顺序枚举 [n]={0,...,n-1} 的所有 k 子集。也就是说,这些子集的第一个是 S0=(0,1,2...,k-1),最后一个是 Slast=(n-k, n-k+1,...,n-1)。对于任何 k-子集 S 和任何 0

                      例如,如果 n=10 且 k=4,则 S0=(0,1,2,3) 且 Slast=(6,7,8,9)。请注意,例如,没有任何组合可以有 S[1]>7(在这种情况下,我们将有 S[j]>n+j-k),因为这样就没有足够的值来填充剩余的位置 j= 2..3.

                      算法的思想是从第一个组合S0开始,然后反复调用next(),每次生成下一个k-subset。函数 next() 向后遍历当前 k-子集,从最后一个位置 j=k-1 开始向下到 0,直到找到尚未达到其最大允许值 n+j-k 的条目 S[j] 并且可以从而增加。然后它将这个位置加一并用来自 S[j]+1 的连续值填充剩余的位置 j+1..k-1。一旦没有位置可以进一步增加,算法就会停止。

                      例如,假设我们有 S=(3,7,8,9)。从 j=3 开始,我们看到 S[3],S[2],S[1] 已经达到了它们的最大值。因此,仍然可以增加的最右边的位置是 S[0]。这个值更新为S[0]+1=4,后面的位置更新为5,6,7。因此下一个 k 子集将是 S=(4,5,6,7)。

                      #include <stdlib.h>
                      #include <stdbool.h>
                      #include <stdio.h>
                      
                      
                      bool next(int *S, int k, int n) {
                          int j = k-1;
                          while (j >= 0 && S[j] == n + j - k) 
                              j--;
                          if (j < 0) return false;
                          S[j] += 1;
                          for (int i = j+1; i < k ; i++) 
                              S[i] = S[i-1] + 1;
                          return true;
                      }
                      
                      int main(int argc, char *argv[])
                      {
                          int n = 10;
                          int k = 4;
                          int *S = (int *)calloc(k, sizeof(int));
                          for (int j = 0; j < k; S[++j] = j); //first k-subset
                          int no = 0;
                          do {
                              printf("subset #%d: ",no++);
                              for (int j=0; j < k; j++) {
                                  printf("%d ", S[j]);
                              }
                              printf("\n");
                          } while(next(S, k, n));
                          return 0;
                      }
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2021-10-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2021-02-17
                        • 1970-01-01
                        • 1970-01-01
                        • 2022-01-05
                        相关资源
                        最近更新 更多