排列和组合 Permutation and Combination ---- Based on DFS and Backtracking
Basic Concepts
Permutation
- A permutation of n distinct objects is an arrangment ( or ordering) of all n objects.
- Denoted as P(n)
- P(n) = n !
Example 1:
Suppose 10 students are sitting for a class picture. How many ways are there to arrange 10 students.
Answer: 10 * 9 *… * 2 * 1 = 10!
- An r-permutation n distinct objects is an arrangment (or ording) of r of the n objects.
- Denoted as P(n,r)
- P(n, r) = n! / (n-r)!
Example 1:
Suppose 10 students are sitting for a class picture. How many ways are there to arrange the row of 3 students.
Answer: 10 * 9 * 8 = 720
Combination
- An r-combination of n distinct objects is a selection (order doesn’t matter) of r of n objects. (Alson known as Sets)
- Denoted as C(n, r)
- C(n, r) = {n !} / {(n-r) ! r!}
Example 1:
How many 5 letters subsets can be made from 26 English letter.
Answer C(26, 5)Example 2:
Calculate all subsets of number 12345
Answer = C(5,5) + C(5,4) + C(5,3) + C(5,2) + C(5,1) + C(5,0) = 25
Because for each digit, it has two options: choose or not choose
Therefore: 2 * 2 * 2 * 2 * 2
Permutation
Permutation — Leetcode 46
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> output = new ArrayList<List<Integer>>();
List<Integer> tempList = new ArrayList<Integer>();
return permutation(tempList, nums, output);
}
public List<List<Integer>> permutation(List<Integer> tempList, int [] nums, List<List<Integer>> output) {
//output长度和数组长度一样,代表本次排列完成,可以返回
if (tempList.size() == nums.length) {
output.add(new ArrayList(tempList));
//output.add(tempList); 就不可以, 因为加入的是tempList的地址,所以之前加入的tempList会因为后面对tempList的改变而改变
return output;
}
//对数组中每个元素进行遍历
for(int i = 0; i < nums.length; i++) {
if (!tempList.contains(nums[i])) { //如果目前记录的output里有num[i],则跳过此次循环(防止重复)
tempList.add(nums[i]); //把当前元素加入output
permutation(tempList, nums, output); //进行下一次循环, 完成dfs
tempList.remove(tempList.size()-1); //回到本层之后,删除之前num[i],完成回溯
}
}
return output;
}
}
String Permutation — Lintcode 10
public class Solution {
/**
* @param str: A string
* @return: all permutations
*/
public List<String> stringPermutation2(String str) {
str = sortString(str);
List<String> ans = new ArrayList<>();
StringBuilder temp = new StringBuilder();
boolean [] visited = new boolean[str.length()];
dfs(visited, str, temp, ans);
return ans;
}
public void dfs(boolean [] visited, String str, StringBuilder temp, List<String> ans) {
if (temp.length() == str.length()) {
//不能用注释掉的方式去重,会超时
/*
if (!ans.contains(temp.toString())) {
ans.add(temp.toString());
}
*/
ans.add(temp.toString());
return;
}
for(int i = 0; i < str.length(); i++) {
if (visited[i]) {
continue;
}
//这里if的作用是去重: 不同位置的同样的字符,必须按照顺序用
// a' a'' b
// a' a'' b ok
// a'' a' b not ok
if (i > 0 && str.charAt(i) == str.charAt(i-1) && !visited[i-1]) {
continue;
}
temp.append(str.charAt(i));
visited[i] = true;
dfs(visited, str, temp, ans);
visited[i] = false;
temp = temp.deleteCharAt(temp.length()-1);
}
}
public String sortString(String str) {
char [] charArray = str.toCharArray();
Arrays.sort(charArray);
str = new String(charArray);
return str;
}
}
Combination
Subsets (without duplicates) — Leetcode 78
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
if (nums.length == 0) {
ans.add(new ArrayList<Integer>(temp));
return ans;
}
dfs(ans, temp, nums, 0);
return ans;
}
public void dfs(List<List<Integer>> ans, List<Integer> temp, int [] nums, int startIndex) {
//将集合加入ans
//用startIndex防止重复
//take N to copy to ans list
ans.add(new ArrayList<Integer>(temp));
for (int i = startIndex; i < nums.length; i++) {
temp.add(nums[i]);
dfs(ans, temp, nums, i + 1);
//backtracking
temp.remove(temp.size()-1);
}
}
}
Subsets II (with duplicates) — Leetcode 90
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums); //must sort in order to skip all the duplicate subsets
List<List<Integer>> ans = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
dfs(nums, ans, temp, 0);
return ans;
}
public void dfs(int [] nums, List<List<Integer>> ans, List<Integer> temp, int startIndex) {
ans.add(new ArrayList<Integer>(temp));
for(int i = startIndex; i < nums.length; i++) {
// skip all the duplicate
//if the current number is same as previous one, then skip
//Exception: if the previous number is the startIndex ( i > startIndex )
//Example: a b' b'' b''' d
//startIndex: 1
//a b' b'' d ok
//a b' b''' d not ok
if ( i > startIndex && nums[i-1] == nums[i]) {
continue;
}
temp.add(nums[i]);
dfs(nums, ans, temp, i + 1);
temp.remove(temp.size() - 1); //backtracking
}
}
}
Summary
- Examples shown above are template for solving all combination and permutation problems.
- Combination needs startIndex, Permutations doesn’t need startIndex
- For handling duplicated elements in input:
- Sort Input
- Add following code in for loop
Combination:
if ( i > startIndex && nums[i-1] == nums[i]) { continue; }
Permutation:
if (i > 0 && str.charAt(i) == str.charAt(i-1) && !visited[i-1]) { continue; }
Time Complexity of Combination and Permutation
- Combination — Subsets problem (Leetcode 78)
2n * n : A total of 2n possibilities and each possibility takes N to copy to the ans list
Why 2n: for example, for string 12345, each number has an option of choose or not choose, therefore 2n- Permutation — Permutation problem (Leetcode 46)
n! * n: A total of n! possibilities and each possibility takes N to copy to the ans list
General formula for DFS:
Number of possibilities * Time to create each possibility