题目难度: 中等
原题链接
今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复
剑指offer2
就能看到该系列当前连载的所有文章了, 记得关注哦~
题目描述
给定一个不含重复数字的整数数组 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
思路
- 由于给定数组的各个数字都不相同, 所以我们可以每次从中挑选一个还未用过的数字, 将其加入当前排列中, 这样由于每次挑选的顺序不同, 最终形成的排列也各不相同
- 这就是典型递归的思想, 具体做法如下:
- 维护两个数组, 分别代表当前剩余未使用的数字, 以及当前的排列
- 针对当前未使用的数字, 逐一遍历它们, 并将其追加到当前排列中, 这样就保证了每个排列数字的顺序各不相同
- 然后继续传入新的未使用数字和排列, 继续递归调用
- 最后当未使用数字为空时结束递归, 此时就形成了一个有效的排列
- 下面的代码中有详细的注释, 方便大家理解
复杂度
- 时间复杂度
O(N!)
: N 个数字各不相同, 排列就有N!
种 - 空间复杂度
O(N)
: 递归栈, 剩余数字以及当前排列的空间占用最多都是 N
代码
Python 3
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
# 方法1: 简单递归
res = []
def dfs(remain, perm):
# remain是剩余数字对应的数组
# perm是当前排列
if not remain:
# 没有剩余数字了, 将当前排列加入最终结果中
# 因为nums没有重复数字, 所以保证了最终每个排列都各不相同
res.append(perm)
return
for i in range(len(remain)):
# 选择remain中的各个数字追加到当前排列中, 然后继续递归
dfs(remain[:i] + remain[i + 1 :], perm + [remain[i]])
# 初始时剩余数字数组为整个nums, 当前排列为空
dfs(nums, [])
return res
方案 2
思路
- 接下来我们尝试用迭代的思路来解决
- 我们可以定义一个方法, 来求按顺序的下一个排列, 例如
[1,2,3]->[1,3,2]->[2,1,3]->[2,3,1]->[3,1,2]->[3,2,1]
, 这样就能保证得到每个不重复的排列 - 先将给定数组按升序排序, 然后当遍历到的排列变成降序排序的时候, 就说明所有排列都被找到了
- 所以算法的核心就是如何通过一个排列找它按顺序的下一个排列
- 下一个排列一定是所有排列中大于当前排列且最接近它的, 所以我们可以利用贪心算法, 具体步骤如下:
- 从后向前找第一个小于后一个数字的下标 i (因为如果大于等于后一个数字的话, 就没法与后面的数字交换来使得整体排列更大了)
- 找刚才遍历的部分的大于且最接近下标 i 对应数字的下标 j
- 将它们两个互换
- 然后 i 往后的部分都按升序排列, 也即将这部分逆序 (因为现在这部分是降序排序的)
- 这样就保证了新的排列一定是大于当前排列且最接近它的, 不可能有更小的了
- 下面的代码中有详细的注释, 方便大家理解
复杂度
- 时间复杂度
O(N*N!)
: N 个数字各不相同, 排列就有N!
种, 从当前排列转入下一个排列需要O(N)
时间 - 空间复杂度
O(1)
: 只使用了几个常数空间的变量 (不考虑输出结果集)
代码
Python 3
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
# 方法2: 迭代getNext
res = []
n = len(nums)
def getNext(nums):
for i in range(n - 1)[::-1]:
# 从后向前查找
if nums[i] < nums[i + 1]:
# 找到目标数字了, 接下来找后面部分中大于nums[i]且最接近它的数字
j = i + 1
while j < n and nums[j] > nums[i]:
j += 1
# 此时nums[j]<=nums[i], 将它减1后的nums[j]就是后面部分中大于nums[i]且最接近它的数字, 它需要和nums[i]互换
j -= 1
# 单独拿出新的右边部分(已经将下标i和j互换了), 肯定严格按照降序排列
right = nums[i + 1 : j] + [nums[i]] + nums[j + 1 :]
# 将三部分拼接起来, 注意右边部分要逆序, 这样就变成升序排列
return nums[:i] + [nums[j]] + right[::-1]
# 没找到下一个排列, 说明当前排列就是顺序最大的了, 直接返回None
return None
# 先拿到顺序最小的排列
nums.sort()
while nums:
res.append(nums)
nums = getNext(nums)
return res
大家可以在下面这些地方找到我~😊
我的 GitHub
我的 Leetcode
我的 CSDN
我的知乎专栏
我的头条号
我的牛客网博客
我的公众号: 算法精选, 欢迎大家扫码关注~😊