排列&组合&子集
模板
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
排列问题,讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为不同列表时),需要记录哪些数字已经使用过,此时用 used 数组;
组合问题,不讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为相同列表时),需要按照某种顺序搜索,此时使用 begin 变量。
全排列
LeetCode入口👉👉👉No.46
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
思路
使用深度优先遍历
coding
python库简洁实现
#--python
from itertools import permutations
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
return list(permutations(nums))
深度优先遍历 回溯
#--python
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def dfs(nums,path,used):
if len(path) == size:
res.append(path[:])
return
for i in range(len(nums)):
if not used[i]:
used[i] = True
path.append(nums[i])
dfs(nums,path,used)
path.pop()
used[i] = False
size = len(nums)
res = []
used = [False for _ in range(len(nums))]
dfs(nums,[],used)
return res
全排列2
LeetCode入口👉👉👉No.47
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
思路
-
暴力法(后剪枝)
首先 生成全排列,然后对结果进行去重
-
在生成过程中去重(预剪枝)
什么情况下会出现重复?
当一个组合的起始数字和上一个数字相同,并且上一个数字没有在路径中出现
例如,红框内组合的起始数字
1
与之前组合的起始数字1
相同,并且在新组合中1
没有被加入路径即
nums[i] == nums[i - 1] && !used[i - 1]
coding
暴力法 后剪枝
#--python
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
def backtrack(nums,path,used):
if len(path)==size:
res.append(path[:])
return
for i in range(len(nums)):
if not used[i]:
used[i] = True
path.append(nums[i])
backtrack(nums,path,used)
path.pop()
used[i] = False
size = len(nums)
res = []
used = [False for _ in range(len(nums))]
backtrack(nums,[],used)
#去重
unique = []
for p in res:
if p not in unique:
unique.append(p)
return unique
在生成过程中去重 (需要先对数组排序)
#--python
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
def backtrack(nums,path,used):
if len(path)==size:
res.append(path[:])
return
for i in range(len(nums)):
if not used[i]:
#去重
if (i>0 and nums[i]==nums[i-1] and not used[i-1]):
continue
used[i] = True
path.append(nums[i])
backtrack(nums,path,used)
path.pop()
used[i] = False
size = len(nums)
res = []
used = [False for _ in range(len(nums))]
nums.sort()
backtrack(nums,[],used)
return res
组合
LeetCode入口👉👉👉No.77
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
思路
回溯
遍历从i
到n
,添加到路径;递归从i+1
开始添加
coding
python库简洁实现
#--python
from itertools import combinations
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
return list(combinations(list(range(1,n+1)), k))
回溯
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
def backtrack(start,path):
if len(path) == k:
res.append(path[:])
return
for i in range(start,len(nums)):
path.append(nums[i])
backtrack(i+1,path)
path.pop()
res = []
nums = list(range(1,n+1))
# used = [False]*len(nums)
backtrack(0,[])
return res
子集
LeetCode入口👉👉👉No.78
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
思路
回溯:生成所有给定长度子集
递归:每一次向已生成子集添加新元素
coding
递归
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = [[]]
for num in nums:
res+=[[num]+cur for cur in res]
return res
回溯
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
def backtrack(start,path):
if len(path) == k:
res.append(path[:])
for i in range(start,len(nums)):
path.append(nums[i])
backtrack(i+1,path)
path.pop()
res = []
path = []
for k in range(len(nums)+1):
backtrack(0,path)
return res