字符的排列组合问题,使用递归+回溯方法。对于有重复元素或者需要组合的元素具有一定顺序,需要先进行排序。
排列问题因为对所有元素进行排列,判断是否为结果的条件是list的大小和数组的长度相同,否则,依次将没有排列的元素添加到list中,结束一次排列后需要回溯;对于数组元素唯一,只需要在循环中判断list中是否包含该元素,不包含,进行添加,否则,跳过。对于数组元素不唯一,设置Boolean数组来标记是否访问过,并对重复出现的组合去重。
组合问题相当于对N个元素,挑选M个元素进行全排列,需要将M作为参数进行传递 ,并设置当m==0时结束一次组合,m<0时返回。对于非递减组合,需要事先对数组进行排序,如果结果需要按照size的大小进行排序输出,可以在排列时通过for循环传入m的值,对于数组元素不唯一,可在将元素添加到list中时进行判断,由于已经进行排序,所以,只需要判定当前值与前一个元素不同即可,保证同一层搜索时只选一次一个相同的数。
常见题解答(均已AC):
1.数组元素唯一的全排列:(LeetCode :permutations)
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].
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
public ArrayList<ArrayList<Integer>> permute(int[] num) {
ArrayList<Integer> list = new ArrayList<Integer>();
dfs(num,list);
return result;
}
public void dfs(int[] num ,ArrayList<Integer> list) {
if(list.size() == num.length){
result.add(new ArrayList<Integer>(list));
}else{
for(int i = 0 ;i<num.length;i++){
if(list.contains(num[i]))
continue;
list.add(num[i]);
dfs(num,list);
list.remove(list.size()-1);
}
}
}
}
2.数组元素不唯一的全排列:(LeetCode:permutations-ii)
Given a collection of numbers that might contain duplicates, return all possible unique permutations.For example,[1,1,2]have the following unique permutations:[1,1,2],[1,2,1], and[2,1,1].
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
ArrayList<Integer> list = new ArrayList<Integer>();
boolean visited[] = new boolean[num.length];
Arrays.sort(num);
dfs(num,list,visited);
return result;
}
public void dfs(int[] num,ArrayList<Integer> list ,boolean visited[] ){
if(list.size()==num.length){
result.add(new ArrayList<Integer>(list));
return;
}else{
for(int i =0;i<num.length;i++){
if(visited[i])
continue;
if(i>0 && num[i]==num[i-1] && !visited[i-1])
continue;//112,在对第二个1进行排列时
list.add(num[i]);
visited[i]= true;
dfs(num,list,visited);
visited[i]= false;
list.remove(list.size()-1);
}
}
}
}
3.数组元素唯一的组合:(LeetCode:subsets)
Given a set of distinct integers, S, return all possible subsets.
Note:
- Elements in a subset must be in non-descending order.
- The solution set must not contain duplicate subsets.
For example,
If S =[1,2,3], a solution is:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
题意:数组元素唯一的数组,求其不递减子数组集合
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> list = new ArrayList<Integer>();
public ArrayList<ArrayList<Integer>> subsets(int[] S) {
Arrays.sort(S);
int len = S.length;
for(int i = 0 ;i<=len;i++){
find(S,0,i);
}
return result;
}
public void find(int[] S,int start,int m) {
if(m<0){
return;
}else if(m==0){
result.add(new ArrayList<Integer>(list));
}else{
for(int i = start;i<S.length;i++){
list.add(S[i]);
find(S,i+1,m-1);
list.remove(list.size()-1);
}
}
}
}
4.数组元素不唯一的组合:(LeetCode:subsets-ii)
Given a collection of integers that might contain duplicates, S, return all possible subsets.
Note:
- Elements in a subset must be in non-descending order.
- The solution set must not contain duplicate subsets.
For example,If S =[1,2,2], a solution is:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
题意:数组元素不唯一的数组,求其不递减子数组集合
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> list = new ArrayList<Integer>();
public ArrayList<ArrayList<Integer>> subsetsWithDup(int[] num) {
Arrays.sort(num);
int len = num.length;
dfs(num,0);
return result;
}
public void dfs(int[] num,int start){
result.add(new ArrayList<Integer>(list));
for(int i = start;i<num.length;i++){
if(i>start && num[i]==num[i-1])
continue;
list.add(num[i]);
dfs(num,i+1);
list.remove(list.size()-1);
}
}
}
5.下一个排列:(LeetCode:next-permutation)
Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).The replacement must be in-place, do not allocate extra memory.Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.
1,2,3→1,3,2
3,2,1→1,2,3
1,1,5→1,5,1
题意:下一个排列是指按词典序的下一个排列。降序的排列已经是按词典序的最大的排列了,所以它的下一个就按升序排列。
解题:找到第一个逆序的地方,如果找不到,重新对num进行排序,如果可以找到,保存具体位置pos,并从num的尾部开始找到第一个大于pos位置的数字,进行交换,并将pos以后的数字进行排序。
import java.util.*;
public class Solution {
public void nextPermutation(int[] num) {
int len = num.length;
int j = len-2;
boolean flag = false;
for(int k = len-1;k>=0;k--){
while(j>=0){
if(num[len-1]>num[j]){
int t = num[len-1];
num[len-1] = num[j];
num[j]= t;
flag = true;
if(len-j>2)
Arrays.sort(num,j+1,len);
break;
}
j--;
}
if(!flag){
Arrays.sort(num);
break;
}
}
}
}
6.第K个排列:(LeetCode:permutation-sequence)
The set[1,2,3,…,n]contains a total of n! unique permutations.By listing and labeling all of the permutations in order,
We get the following sequence (ie, for n = 3):
- "123"
- "132"
- "213"
- "231"
- "312"
- "321"
Given n and k, return the k th permutation sequence.
Note: Given n will be between 1 and 9 inclusive.
题意:返回已知数组的全排列中的第K个排列。
利用康托公式,康托展开的实质是计算当前排列在所有由小到大全排列中的顺序。X=a[n]*(n-1)!+a[n-1]*(n-2)!+…+a[i]*(i-1)!+…+a[1]*0! ,a[m]代表比在第m位的数字小并且没有在第m位之前出现过的数字的个数(以个位数为第1位),x代表比这个数小的数的个数,所以这个数的顺序就是x+1
https://blog.youkuaiyun.com/scarecrow398966925/article/details/25543587
对于解码,需要先将k-1,然后依次除以阶乘获得商和余数,商表示有多少个比该位小的数字,从而确定该位的数字,然后利用余数除以下一个阶乘,获得下一位的数字,使用list来保存剩余的可选数字,每次确定一位数字后添加到结果中,并将list中的数字删除。
import java.util.*;
public class Solution {
public String getPermutation(int n, int k) {
StringBuffer sb = new StringBuffer();
ArrayList<Integer> list = new ArrayList<Integer>();
int [] mul = new int[n+1];
mul[0] = 1;
for(int i =1;i<=n;i++){
mul[i]= mul[i-1]*i;
list.add(i);
}
k--;
boolean [] flag = new boolean[n+1];
for(int i = n-1;i>=0;i--){
int s = k/mul[i];
int y = k%mul[i];
sb.append(list.get(s));
list.remove(list.get(s));
k = y;
}
return sb.toString();
}
}
另:
对于寻找第K个的问题:
1. 寻找无序数组中的第k小的数字(快排) ---剑指offer 40
import java.util.*;
public class Solution {
public int Partition(int [] input, int start, int end) {
int flag = input[start];
int first = start;
start++;
while(start<end){
while(input[end]>flag && start<=end)
end--;
while(input[start]<flag && start<=end)
start++;
if(start<end){
int temp = input[end];
input[end] = input[start];
input[start] = temp;
}
}
input[first] = input[end];
input[end] =flag;
return end;
}
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> result = new ArrayList<Integer>();
if(input.length<k || input.length==0 || k==0)
return result;
int start = 0;
int end = input.length-1;
int index = Partition(input,start,end);
while(index!=k-1){
if(index<k-1){
start = index+1;
index = Partition(input,start,end);
}
if(index>k-1){
end= index-1;
index = Partition(input,start,end);
}
}
for(int i =0;i<k;i++)
result.add(input[i]);
return result;
}
}
2. 寻找链表中的倒数第K个结点(快慢指针)---剑指offer 22
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if(head==null || k==0)
return null;
ListNode listNode = head;
for(int i = 1 ;i<k;i++){
if(head.next!=null){
head = head.next;
}else
return null;
}
while(head.next!=null){
head = head.next;
listNode = listNode.next;
}
return listNode;
}
}
3. 寻找二叉搜索树的第K大结点 (中序遍历) -- 剑指offer 54
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
//中序遍历
int index = 0;
TreeNode KthNode(TreeNode pRoot, int k){
TreeNode target = null;
if(pRoot!=null){
if(pRoot.left!=null)
target = KthNode(pRoot.left,k);
if(target!=null)
return target;
//左子树中没有
index++;
if(index==k)
return pRoot;
target = KthNode(pRoot.right,k);
if(target!=null)
return target;
}
return null;
}
}