Algorithm -- Permutation Combination Subset

Given a collection of numbers, return all possible Permutations, K-Combinations, or all Subsets are the most fundamental questions in algorithm.

They can be impelmented by simple recursion, iteration, bit-operation, and some other approaches. I mostly use Java to code in this post.

Subsets draft

Permutation

Given a collection of numbers, return all possible permutations.
For example,
[1,2,3] have the following permutations:
[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], and [3,2,1].

also see: CrackingCoding: C9Q5, LeetCode: Permutations

Time Complexity: \(O(n!)\)

Recursion – DFS

The idea of iteration to solve this problem is dervied from Depth First Search (DFS).

structure of DFS tree

Along with the increasing of recursing depth, the amount number of subnodes of each node is decreasing by one. This is why the time complexity is \(O(n!)\).

public List<String> perm(int[] nums) {
  List<int[]> result = new ArrayList();
  if (nums = null) return null;
  perm(nums, 0, result);
  return result;
}

public void perm(int[] nums, int start, List<int[]> result) {
  if (start == nums.length) {
    result.add((int[]) nums.clone());
    return;
  }

  for (int i = start; i < nums.length; i++) {
    swap(nums, start, i);
    perm(nums, start + 1, result);
    swap(nums, start, i);
  }
}

public void swap(int[] nums, int start, int end) {
  int tmp = nums[start];
  nums[start] = nums[end];
  nums[end] = tmp;
}

Recursion – DFS, Permutation II

What if there are some duplicated characters in the given set? We can modify the previous algorithm to achieve the new solution.

Use a HashSet<Character> to remember whether a Char has been swap or not.

Set<Character> has = new HashSet();
for (int i = st; i < set.length; i++) {
   if (has.add(set[i])) {
       swap(set, st, i);
       permutations(set, st + 1, res);
       swap(set, st, i);
   }
}

Where has.add(set[i]) will return FALSE is set[i] is already in the has.

Recursion – Generated by Permutation(n-1)

The same solution as that of CrackingCoding. Insert the current number at every possible position into each of the last permutations.

public List<List<Integer>> permute(int[] num) {
    if (num == null || num.length == 0) {
        return new ArrayList<List<Integer>>();
    }
    return permute(num, num.length - 1);
}

public List<List<Integer>> permute(int[] num, int ed) {
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    if (ed == 0) {
        List<Integer> one = new ArrayList<Integer>();
        one.add(num[ed]);
        res.add(one);
        return res;
    }

    List<List<Integer>> lastRes = permute(num, ed - 1);
    for (List<Integer> lastOne : lastRes) {
        for (int i = 0; i < lastOne.size(); i++) {
            lastOne.add(i, num[ed]);
            List<Integer> one = new ArrayList<Integer>(lastOne);
            lastOne.remove(i);
            res.add(one);
        }
        lastOne.add(num[ed]);
        res.add(lastOne);
    }
    return res;
}

Iteration – Next Permutation

The iteration idea is derived from a solution for Next Permutation. It could also be used to solve Unique Permutation, while there are duplicated characters existed in the given array.

For example,
[1,1,2] have the following unique permutations:
[1,1,2], [1,2,1], and [2,1,1].

The function of nextPermutation(int[] num) is used to generate the smallest permutation among the possible permutations which are greater than the given int[] num in numeric concept.

Here are some examples of `nextPermutation`
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
public List<List<Integer>> permuteUnique(int[] num) {
    Arrays.sort(num);
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    do {
        List<Integer> l = new ArrayList<Integer>();
        for (int n : num) {
            l.add(n);
        }
        ans.add(l);
    } while (nextPermutation(num));
    return ans;
}

private boolean nextPermutation(int[] num) {
    if (num.length <= 1) {
        return false;
    }
    int i = num.length - 1;
    while (true) {
        i--;
        if (num[i] < num[i + 1]) {
            int j = num.length;

            while (num[i] >= num[--j]) {}
// !!! This non-content loop is useful to set the position of j, 
// UNTIL `num[i] < num[j]`

            swap(num, i, j);
            reverse(num, i + 1, num.length);
            return true;
        }
        if (i == 0) {
            reverse(num, 0, num.length); //!!! back to the first smallest array
            return false;
        }
    }
}

private void swap(int[] num, int i, int j) {
    int temp = num[i];
    num[i] = num[j];
    num[j] = temp;
}

private void reverse(int[] num, int begin, int end) {
    end--;
    while (begin < end) {
        swap(num, begin++, end--);
    }
}

Combination

Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
For example,
If n = 4 and k = 2, a solution is:
[ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4] ]

also see: LeetCode: Combinations

Time Complexity: \(O(2^n)\) without triming branches, \(O(2^k)\) with triming.

Recursion – DFS

structure of DFS tree

public List<List<Integer>> combine(int n, int k) {
  List<List<Integer>> result = new ArrayList<List<Integer>>();
  combine(0, n, k, new ArrayList<Integer>(), result);
  return result;
}

public void combine(int depth, int n, int k,
    List<Integer> r, List<List<Integer>> result)
{
  if(r.size()==k)
  {
    result.add(new ArrayList<Integer>(r));
    return;
  }
  if(depth == n) return;

  r.add(depth + 1);
  combine(depth + 1, n, k, r, result);
  r.remove(r.size() - 1);
  combine(depth + 1, n, k, r, result);
}

Recursion – Generated by Last Combination

e.g. combine(4,2):
depth == 0: [ ]
depth == 1: [1], [2], [3], [4]
depth == 2: [1,2], [1,3], [1,4], [2,3], [2,4], [3,4]

/* n is total numbers length: 1..n
   depth is current recursion depth
   k is final, used to trim branches */
public List<List<Integer>> combine(int n, int depth, final int k) {
  List<List<Integer>> result = new ArrayList<List<Integer>>();

  if (depth == 0) {
    List<Integer> empty = new ArrayList<Integer>();
    result.add(empty);
    return result;
  }

  List<List<Integer>> preRes = combine(n, depth - 1, k);
  for (List<Integer> one : preRes) {
    int last = one.isEmpty() ? 0 : one.get(depth - 2);
    for (int i = last + 1; i <= n; i++) {
      if (n - i + depth < k) { break; } // trim branches
      List<Integer> newOne = new ArrayList<Integer>(one);
      newOne.add(i);
      result.add(newOne);
    }
  }
  return result;
}

Subset

Given a set of distinct integers, S, return all possible subsets.

also see: CrackingCoding: C9Q4, LeetCode: Subsets

Time Complexity: \( O(2^n) \)

Recursion – DFS

DFS of Subset is similar to that of Combination. Actually, Subset problem is to get all Combination from [n,0] to [n,n].

That is, NO triming branches during recursion. Retrieving all the results when recurion depth == S.length.

Subset tree structure I

public List<List<Integer>> subsets(int[] S) {
    int len = S.length;
    List<List<Integer>> result = new ArrayList<List<Integer>>();
    subsets(S, 0, new ArrayList<Integer>(), result);
    return result;
}

private void subsets(int[] S, int depth, List<Integer> r, List<List<Integer>> result) {
    if (S.length == depth) {
        result.add(new ArrayList<Integer>(r));
        return;
    }

    r.add(S[depth]);
    subsets(S, depth + 1, r, result);
    r.remove(r.size() - 1);             //!!! MUST have; otherwise
    subsets(S, depth + 1, r, result);
}

Or, there is another recursion approach of recursion with inner loop:

Subset tree structure I

private void subSets(int[] set, int depth, Deque<Integer> curr, List<List<Integer>> result) {
    result.add(new ArrayList(curr));
    for (int i = depth; i < set.length; i++) {
        curr.offerLast(set[depth]);
        subSets(set, depth + 1, curr, result);
        curr.pollLast();
    }
}
  • MUST have: becuase once [] hit the return and the recursion back to add level 2 (which adding 3 into []), the 3 will be never removed from [] object.

Recursion – Generated by Subsets(n -1)

Generating Subsets(n): compute Subsets(n-1), clone the results, and then add \( a_n \) to each of these cloned sets.

e.g. Subset(3)
Base case n = 0: []
Case n = 1: [], [a1]
Case n = 2: [], [a1], [a2], [a1,a2]
Case n = 3: [], [a1], [a2], [a1,a2], [a3], [a1,a3], [a2,a3], [a1,a2,a3]

public List<List<Integer>> getSubsets(List<Integer> set, int index) {
  List<List<Integer>> allsubsets;
  if (set.size() == index) { // Base case - add empty set
    allsubsets = new ArrayList<List<Integer>>();
    allsubsets.add(new ArrayList<Integer>()); 
  } else {
    allsubsets = getSubsets(set, index + 1);
    int item = set.get(index);
    List<List<Integer>> moresubsets = new ArrayList<List<Integer>>();
    for (List<Integer> subset : allsubsets) {
      List<Integer> newsubset = new ArrayList<Integer>();
      newsubset.addAll(subset); //
      newsubset.add(item);
      moresubsets.add(newsubset);
    }
    allsubsets.addAll(moresubsets);
  }
  return allsubsets;
}

And there is another approach:

public List<String> subSets(String set) {
    List<String> res =  new ArrayList<>();
    if (set == null) return res;
    subSets(set, 0, new StringBuilder(), res);
    return res;
}

private void subSets(String set, int st, StringBuilder sb, List<String> res) {
    if (st == set.length()) {
      res.add(sb.toString());
      return;
    }
    subSets(set,st + 1, sb, res);
    sb.append(set.charAt(st));
    subSets(set, st + 1, sb, res);
    sb.deleteCharAt(sb.length() - 1);
}

Iteration – Similar Logic as Previous, more Space Efficient

public List<List<Integer>> subsets(int[] num) {
  Arrays.sort(num);
  List<List<Integer>> subsets = new ArrayList<List<Integer>>();
  subsets.add(new ArrayList<Integer>());
  for (int i = 0; i < num.length; i++) {
    int prevLast = subsets.size(); //!!!
    for (int j = 0; j < prevLast; j++) {
      List<Integer> subset = new ArrayList<Integer>(subsets.get(j));
      subset.add(num[i]);
      subsets.add(subset);
    }
  }
  return subsets;
}

Binary Operation

All subsets problem could be described as a unique problem: generating each one set from a number among 0 to \( 2^n \), where n is the number of given set. Each set and number are one to one mapping.

explain: in order to get subsets from {1,2,3}, we need to do following choices when generating each one set:
pick {1} or not pick {1}
pick {2} or not pick {2}
pick {3} or not pick {3}
So, there are \( 2^3 \) possibilities altogether, exactly, the amount of subsets.

Each of those choices could be considered as a binary operation choice: pick is 1, not pick is 0.
Then, {} could be represented as \(000_2 == 0_{10}\), {1} as \(100_2 = 4_{10}\), {1,3} as \(101_2 == 5_{10}\), {1,2,3} as \(111_2 == 7_{10}\).

public List<List<Integer>> subsets(int[] num) {
  List<List<Integer>> set = new ArrayList<List<Integer>>();
  for (int i = 0; i < 1 << num.length; i++) {
    List<Integer> one = generateOne(i, num);
    set.add(one);
  }
  return set;
}

public List<Integer> generateOne(int k, int[] num) {
  List<Integer> set = new ArrayList<Integer>();
  for (int i = 0; i < num.length; i++) {
    if ((k & (1 << i)) != 0) {
      set.add(num[i]);
    }
  }
  return set;
}

Follow up

Subsets in Range [a,b]

Actually, this problem could also be described as retrieving Combinations (n,a), (n,a+1) … (n,b).

We can generate those Combinations one by one, using same apporaches in Combination; or here is another choise: binary operation.

Remember in the last approach of Subset, we generate all the subsets using numbers from 0 ~ \(2^n\). During these numbers, should we have a function to judge how many 1's is in each number, we could generating Subsets in ranger [a,b] by checking number of 1's is in ranger [a,b].

public List<List<Integer>> subsetsInRange(int[] num, int a, int b) {
  List<List<Integer>> set = new ArrayList<List<Integer>>();
  for (int i = 0; i < 1 << num.length; i++) {
    int numberOf1bit = numberOf1(i);
    if (numberOf1bit >= a && numberOf1bit <=b) {
      List<Integer> one = generateOne(i, num);
      set.add(one);
    }
  }
  return set;
}

/*ref: http://codercareer.blogspot.com/2011/11/no-20-number-of-1-in-binary.html */
public int numberOf1(int n)
{
  int count = 0;
  while (n != 0)
  {
    ++ count;
    n = (n - 1) & n;
  }
  return count;
}

With Duplicated Characters

Subsets with Duplicated Characters

also see: LeetCode: Subsets II

According to Subsets recursion tree I:

There are more than one options to generate the unique subsets.

Here is one of them:

We keep left children (which means append the current level element); and discard those right children (not append) on condition that the current level element is same as the last element in the parent recursion result.

  • According to Subset recursion tree I

Subset tree structure I

private void subSets(char[] set, int st, Deque<Character> curr, List<String> res) {
    if (st == set.length) {
        res.add(currToString(curr));
        return;
    }
    curr.offerLast(set[st]);
    subSets(set, st + 1, curr, res);
    curr.pollLast();
    if (curr.isEmpty() || set[st] != curr.peekLast()) {
        subSets(set, st + 1, curr, res);
    }
}

Or,

Subset tree structure I

private void subSets(char[] set, int st, Deque<Character> curr, List<String> res) {
    if (st == set.length) {
        res.add(currToString(curr));
        return;
    }
    curr.offerLast(set[st]);
    subSets(set, st + 1, curr, res);
    curr.pollLast();
    // Skips all the rest of duplicated letters (e.g. a1, a2, ... in this example)
    while (st < set.length - 1 && set[st + 1] == set[st]) {
        st++;
    }
    subSets(set, st + 1, curr, res);
}
  • According to Subsets recursion tree II:

There are two options to generate the unqiue subsute:

  1. Use a Set to avoid adding same element in each loop;

  2. Judge if the current element is as same as the previous one inside each loop.

Subset tree structure I

//OR, Set<Character> mark = new HashSet();
for (int i = st; i < set.length; i++) {
    if (i > st && set[i] == set[i-1]) { //OR, mark.contains(set[i])
      continue;
    }
    curr.offerLast(set[i]);
    subSets(set, i + 1, curr, res);
    //OR, mark.add(set[i]);
    curr.pollLast();
}

Combination Sum

Print All Combinations of a Number as a Sum of Candidate Numbers

alse see: LeetCode: Combination Sum Combination Sum II

References

Permutations and Subsets of a collection -- mostly using DFS, a Chinese post from CSDN, 集合元素的排列与子集
这篇博客的优点是画出了树形式的解空间,帮助理解递归调用。
点击查看评论