给定一个 没有重复 数字的序列,返回其所有可能的全排列,如图:
首先看到这道题,简单的暴力算法很难得出,因为数字的个数未知,一味地将数字不断的打乱重写,有两个问题,第一是很难做到将每种可能都列举出来,另一个是,时间复杂度过高,为阶层式的叠加,一旦给的数字过多,可能无法被计算得出。
看到这道题,按照我们正常的思路,应该会采取定一动一的方法来排列,这种思维方法和回溯算法的想法是一样的,比如我们先定好首个字母,以 [1,2,3] 为例,我们第一次先定好首个数字 1 ,然后求 [2,3] 的全排列,对于 [2,3] 的全排列,又可以采取定好首个字母来确定,以此类推,将首数字为 1 的情况列举完以后,在查看其他的情况。
我们可以用这道题,详细分析一下回溯算法,回溯算法可以有如下模板,时间复杂度为O(n2):
result = [] func backtrack(路径,选择列表) { if 满足结束条件 { result.add(路径) } return for 选择 in 选择列表 { 做选择 backtrack(路径,选择列表) 撤销选择 } }我们用模板,则有以下算法可以:
var result [][]int func permute(nums []int) [][]int { //先设定最后结果,路径和被使用过的数字 result = [][]int{} pathNums := []int{} used := make([]bool, len(nums)) backtrack(nums, pathNums, used) return result } func backtrack(nums, pathNums []int, used []bool) { //设定结束条件,当走完这个数列是,回溯结束 if len(nums) == len(pathNums) { tmp := make([]int, len(nums)) //只使用公用数据 copy(tmp, pathNums) result = append(result, tmp) return } for i:=0; i<len(nums); i++ { //设定判断,若未使用,进入内部并标记此数字被使用 if !used[i] { used[i] = true //将数字放入尾部, pathNums = append(pathNums, nums[i]) //继续搜索 backtrack(nums, pathNums, used) //撤销一步 pathNums = pathNums[:len(pathNums)-1] //标记为未使用 used[i] = false } } }