Given a collection of distinct integers, return all possible permutations.
Example:
Input: [1,2,3]
Output: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
Golang slice
思路:递归拆分成子数组,当数组不能再拆分时(即数组长度length=1,也为递归结束的条件),在父数组除子数组以外的唯一数字末尾加上子数组中的数字,以此递归循环
1.以数组[1,2,3]为例,循环访问数组里面的元素1,2,3
2.当访问第某个 Ni 元素时,剩余其他元素形成子数组,又再次循环访问,重复步骤2,直到子数组不能再拆分
3.根据最后的子数组的结果,在结果末尾append上该子数组的上一级父数组的访问元素 Ni
4.递归的结束条件即子数组(或原数组)的长度为1
/*
author:monkey
time:2020-3-5
*/
func permute(nums []int) [][]int {
if len(nums) == 1{
return [][]int{nums}
}
var res [][]int
for i := 0; i < len(nums); i++{
ChildArray := make([]int,len(nums)-1)
copy(ChildArray,nums[:i])
copy(ChildArray[i:],nums[i+1:])
temp := permute(ChildArray)
for _,v := range temp{
res = append(res,append(v,nums[i]))
}
}
return res
}
func main() {
MyArray1 :=[]int{1,2,3}
fmt.Println(MyArray1)
MyArray2 := permute(MyArray1)
for _,v := range MyArray2{
fmt.Println(v)
}
}
Backtracking
回溯法的思路:把问题的解空间转化成图或树的结构表示,使用深度优先搜索策略进行遍历,遍历过程中记录和寻找所有可行解或者最优解。类似于图的深度优先搜索和二叉树前序遍历
解题思路:
以原数组[1,2,3]为例子,生成的排列组合输出可以是
[1,2,3]
[1,3,2]
[2,1,3]
[2,3,1]
[3,1,2]
[3,2,1]
可以归类,第一个索引位置上的固定数字分别是1,2,3,再分别将剩余的数字(未被固定位置的)进行位置选择固定。注意对已经使用过的元素值进行标记,以免后面放置元素时发生冲突。
代码思路:
1.题目要求返回存有全排列结果的集合(数组),而全排列结果是Integer集合(数组)类型,声明并创建一个空集合(数组),以便返回结果
2.在原函数中,增加判断条件,如果原数组是单个元素组成的,那么全排列返回其本身
3.声明调用函数,此函数作为回溯法递归求解问题的解。
其中参数为
res:List<List<Integer>> 类型,存放最终问题解的集合,存放的数据类型是List<Integer>
new ArrayList<>() :List<Integer>类型,存放最新的子数组
nums:原数组
new boolean[nums.length]: boolean类型的数组,标记存放已经被访问过元素的布尔值
4.固定索引位置上的值,对剩余的元素进行同样的操作递归,将元素存于子数组上,当最终满足条件时必然是子数组的长度等于原数组的长度,代表所有元素都已经有了自己的位置。满足全排的数组,就存放到结果集合res中。
5.循环从索引下标为0开始,依次访问下标为0,1,2…的元素。在每一次循环(大循环)访问一个元素,该元素就被固定于子数组的第一个位置,再分别对其进行递归,依次将其余元素放置于其他位置上。此时需要保证此次循环中出现过的元素不能再被进行使用,需要使用标记判断,即对其对应的下标索引值标记visited[i]=true,表示nums[i]这个值已经被访问过,在此次循环里不可再次使用该元素进行排列。
6.使用continue语句,continue的作用是跳过本次循环体中余下尚未执行的语句,立即进行下一次的循环条件判定,可以理解为仅结束本次循环,但continue并没有使整个循环终止。
7.每一次回溯完毕,都是子数组末尾数组交出位置使用权,同时把交出位置使用权的子数组末尾元素标记为未访问使用,回退到上一层循环,继续让下一个数字使用
/*
author:monkey
time:2020-3-5
*/
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if(nums.length < 1)
return res;
Backtracking(res,new ArrayList<>(),nums,new boolean[nums.length]);
return res;
}
/*
res: Save the result ChildArray
nums:Givened Array
cur: Current Array
visited: Judge whether the element has been visited
one by one
Q:How do we decide to or not to add cur to res list?
A:While(ChileArray.length == nums.length)
*/
private void Backtracking(List<List<Integer>> res,List<Integer> cur,int[] nums,boolean[] visited){
if(cur.size() == nums.length){
res.add(new ArrayList(cur));
return;
}
for(int i = 0;i < nums.length; i++){
if(visited[i]) continue;
cur.add(nums[i]);
visited[i] = true;
Backtracking(res,cur,nums,visited);
cur.remove(cur.size()-1);
visited[i] = false;
}
}
非递归
解题思路:循环处理每一个数,使其在之前的子数组基础上中插空补位
/*
author:monkey
time:2020-3-5
*/
public List<List<Integer>> permute(int[] nums) {
if(nums.length < 1){
return Collections.emptyList();
}
/*calculate the list capacity to save the room*/
int cnt = 1;
for(int i = nums.length; i > 1; i--){
cnt *= i;
}
List<List<Integer>> res = new ArrayList<>(cnt);
res.add(new ArrayList<>(nums.length));//增加一个子数组
res.get(0).add(nums[0]);
//子数组的第一位上放置元素组的第一位数,在此基础上扩张
//res = [[1,_,_,_]]
//从下标为1的数字开始循环补位
for(int depth = 1;depth < nums.length; depth++){
int size = res.size();//目前res里面存在的子数组数量
//循环res中的子数组,分别由目前子数组数量扩展
for(int i = 0; i < size ; i++){
//到下标为depth的数字时,其有补位depth+1个位置
for(int insertix = 0;insertix <= depth; insertix++){
List<Integer> temp = new ArrayList<>(nums.length);
//[1]
if(insertix < depth){
temp.addAll(res.get(i));//相当于将目前处理的子数组拷贝给temp
temp.add(insertix,nums[depth]);//给定索引补位数字
res.add(temp);
}else{
res.get(i).add(nums[depth]);
}
//[2,1] insert = 0,depth = 1
// [1]→size = 1→insert = 1,depth = 1→size = 2; [1,2]
}
}
}
return res;
}
关于List类中add和addAll方法的踩坑
boolean add(E e);
向列表末尾添加新元素
void add(int index,E elem);
列表的指定位置插入元素(可选),而当前处于该位置的元素(前提有元素)和所有的后续元素向右移动(即索引值+1)
boolean addAll(Collection<? extends E> c);
一次性向列表末尾插入多个元素(插入一个集合的值),可用来拷贝临时集合
boolean addAll(int index,Collection<? extends E> c);
指定索引位置开始一次性插入多个元素(插入一个集合的值)
特别注意:
add(index,elem)和addAll(index,elem)在操作时,在执行插入前会检查size的值,若指定的index值 > size的值,会抛出异常IndexOutOfBoundsException
例如
List<Integer> res1 = new ArrayList<>();
List<Integer> res2 = new ArrayList<>();
res1.add(2);
res1.add(3);
System.out.println(res1);//[2,3]
res1.add(3,5);
System.out.println(res1)