全排列
- https://leetcode.cn/problems/permutations/
描述
- 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3
输入:nums = [1]
输出:[[1]]
提示
- 1 <= nums.length <= 6
- -10 <= nums[i] <= 10
- nums 中的所有整数 互不相同
Typescript 版算法实现
1 )回溯1: 基于数组
function permute(nums: number[]): number[][] {
const res: number[][] = [];
// 回溯函数
const backtrack = (path: number[]) => {
// 满足当前条件
if(path.length === nums.length) {
res.push(path);
return;
}
// 遍历
for(let i = 0; i < nums.length; i++) {
if(path.includes(nums[i])) continue;
backtrack(path.concat(nums[i]));
}
}
backtrack([]);
return res;
}
- 时间复杂度:O(n*n!)
- 假设输入数组的长度为 n
- 遍历每个元素的时间复杂度为 O(n)
- 对于每个元素,都会进行递归调,假设数组中有 n 个不同的元素
- 那么对于每个元素,都会有 n-1 个可能的选择,然后对于每个选择,又会有 n-2 个可能的选择,以此类推
- 因此,递归的时间复杂度可以表示为 O(n!)。
- 在每一层递归中,都会进行一次包含操作(includes),其时间复杂度为 O(n)。
- 综合考虑,这段代码的时间复杂度为 O(n * n!),其中 n 表示输入数组的长度。
- 需要注意的是,这里的时间复杂度分析基于平均情况。
- 在最坏情况下,全排列的数量是 n!,因此在最坏情况下,时间复杂度为 O(n * n!)
- 注意: n的阶乘公式:
n! = 1*2*3*...*(n-1)*n
- 空间复杂度:O(n)
- 递归,堆栈,不是常量,是线性增长的
- 是递归的层数
2 )回溯2: 基于交换
function permute(nums: number[]): number[][] {
const res: number[][] = [];
const backtrack = function(start: number) {
if (start === nums.length - 1) {
res.push([...nums]);
return;
}
for (let i: number = start; i < nums.length; i++) {
[nums[i], nums[start]] = [nums[start], nums[i]]; // 交换
backtrack(start + 1); // 下一个数
[nums[i], nums[start]] = [nums[start], nums[i]]; // 交换撤销
}
}
backtrack(0); // 从 0 开始
return res;
};
- 这个问题可以看作有 n 个排列成一行的空格,我们需要从左往右依此填入题目给定的 n 个数,每个数只能使用一次
- 那么很直接的可以想到一种穷举的算法,即从左往右每一个位置都依此尝试填入一个数
- 看能不能填完这 n 个空格,在程序中我们可以用「回溯法」来模拟这个过程
- 时间复杂度:O(n*n!),其中 n 为序列的长度
- 空间复杂度:O(n)
回溯算法、递归和深度优先遍历之间的关系
-
它们通常在算法中一起使用,因为回溯算法通常使用递归来实现,并且常常涉及到深度优先遍历的思想。
-
回溯算法与递归
- 回溯算法是一种渐进式寻找并构建问题解决方案的策略。
- 在回溯算法中,通常通过递归的方式来尝试所有可能的情况。
- 当找到一个可能的解决方案时,它会继续探索下去,如果发现不符合条件,就会回溯到上一个状态,尝试其他可能的情况。
- 因此,回溯算法通常使用递归来实现,因为递归天然地适合于处理这种尝试所有可能情况的问题。
-
回溯算法与深度优先遍历(DFS)
- 回溯算法通常使用深度优先遍历的思想。
- 在回溯算法中,我们会尝试一条路走到底,直到无法再继续下去,然后回溯到上一个状态,尝试其他的可能性。
- 这与深度优先遍历的思想是一致的,深度优先遍历也是尽可能深地搜索树的分支,直到无法再继续为止
- 然后回溯到上一个节点,继续搜索其他分支
-
因此,回溯算法通常使用递归来实现,并且其思想与深度优先遍历密切相关。
-
在许多情况下,回溯算法可以被视为一种特殊的深度优先遍历。
Python3 版算法实现
1 ) 方案1
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(path: list[int]):
# 满足当前条件
if len(path) == len(nums):
res.append(path)
return
# 遍历
for i in range(len(nums)):
if nums[i] in path:
continue
backtrack(path + [nums[i]])
backtrack([])
return res
2 ) 方案2
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(start: int):
if start == len(nums) - 1:
res.append(nums[:])
return
for i in range(start, len(nums)):
# 交换
nums[i], nums[start] = nums[start], nums[i]
# 下一个数
backtrack(start + 1)
# 交换撤销
nums[i], nums[start] = nums[start], nums[i]
backtrack(0) # 从 0 开始
return res
Golang 版算法实现
1 ) 方案1
func permute(nums []int) [][]int {
var res [][]int
// 回溯函数
var backtrack func(path []int)
backtrack = func(path []int) {
// 满足当前条件
if len(path) == len(nums) {
// 创建一个新的切片,防止后续修改影响结果
newPath := make([]int, len(path))
copy(newPath, path)
res = append(res, newPath)
return
}
// 遍历
for i := 0; i < len(nums); i++ {
if contains(path, nums[i]) {
continue
}
backtrack(append(path, nums[i]))
}
}
backtrack([]int{})
return res
}
// contains 检查切片中是否包含某个元素
func contains(slice []int, val int) bool {
for _, v := range slice {
if v == val {
return true
}
}
return false
}
2 ) 方案2
// permute 生成所有排列
func permute(nums []int) [][]int {
var res [][]int
// 回溯函数
var backtrack func(start int)
backtrack = func(start int) {
if start == len(nums)-1 {
// 创建一个新的切片,防止后续修改影响结果
newNums := make([]int, len(nums))
copy(newNums, nums)
res = append(res, newNums)
return
}
for i := start; i < len(nums); i++ {
// 交换
nums[i], nums[start] = nums[start], nums[i]
// 下一个数
backtrack(start + 1)
// 交换撤销
nums[i], nums[start] = nums[start], nums[i]
}
}
backtrack(0) // 从 0 开始
return res
}
2542



被折叠的 条评论
为什么被折叠?



