Permutations

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)

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值