这是我参与11月更文挑战的21天,活动详情查看:2021最后一次更文挑战。
前言
一直都计划学习数据结构与基本算法,但是平时都看一阵停一阵。现在决心坚持下去,我准备从LeetCode的HOT100开始,每天完成1~2道习题,希望通过这种方式养成持续学习的习惯。因为我是做iOS开发的,主要是用Objective-C语言,最近也在学习Swift,所以本系列的题解都将使用swift语言完成,本文更新的是LeetCode中HOT100的第21题046 全排列。
题目
给定一个不含重复数字的数组 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 中的所有整数 互不相同
分析
本题的全排列问题显然是需要用到回溯法进行解决的,回溯算法在我们前面的习题中曾多次提到,所以其思路很简单。本题中我们以数组 [1, 2, 3] 的全排列为例。
- 先写以 1 开头的全排列,它们是:[1, 2, 3], [1, 3, 2],即 1 + [2, 3] 的全排列(注意:递归结构体现在这里);
- 再写以 2 开头的全排列,它们是:[2, 1, 3], [2, 3, 1],即 2 + [1, 3] 的全排列;
- 最后写以 3 开头的全排列,它们是:[3, 1, 2], [3, 2, 1],即 3 + [1, 2] 的全排列。 总结搜索的方法:按顺序枚举每一位可能出现的情况,已经选择的数字在 当前 要选择的数字中不能出现。按照这种策略搜索就能够做到 不重不漏。这样的思路,可以用一个树形结构表示。
看到这里的朋友,建议先尝试自己画出「全排列」问题的树形结构。
回溯算法的基本思路以及代码公式如下:
//backtrack的公式:
result = []
func backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
题解
class KLLC046 {
func permute(_ nums: [Int]) -> [[Int]] {
var retArr = [[Int]]() //保存结果
var retSubArr = [Int]() //已经排放好的数组
var remainArr = nums //保存剩余待排放数据
//开始回溯
backTrace(&retArr, &retSubArr, &remainArr)
//返回结果集
return retArr
}
//回溯算法
func backTrace(_ retArr:inout [[Int]], _ retSubArr:inout [Int], _ remainArr:inout [Int]) {
//终结条件:剩余待排放数组为空时,表明所有数据均以排列好
if remainArr.count == 0 {
//将当前排列添加到结果集中
let ans = retSubArr
retArr.append(ans)
return
}
//回溯精髓:从剩余待排放数组中依次取出元素放入排列数组的当前位置
for (index, val) in remainArr.enumerated() {
//选择 val,排序列表 添加val,待排序列表 删除val
retSubArr.append(val)
remainArr.remove(at: index)
//进行回溯
backTrace(&retArr, &retSubArr, &remainArr)
//撤销选择,排序列表 删除val,待排序列表 重新添加val
retSubArr.removeLast()
remainArr.insert(val, at: index)
}
}
}