目录
0 文章介绍
本文使用python语言,以leetcode里的题目来介绍算法中使用深度优先搜索的排列组合问题。
我把排列和组合分别分成3种情况
- 无重复数,不重复用
- 无重复数,可重复用
- 有重复数,不重复用
根据排列组合,应该是4种啊,还有有重复数,可重复用啊,这种情况,先把重复数去重,就转换回第3种情况了
1 dfs套路
对于每一种情况x:
dfs(x)
def dfs(x):
if 情况x不满足当前条件:
return
if x是最后一个元素了:
搜到了,做操作
return
标记x情况搜过了
for y in 由x衍生出的其他条件:
dfs(y)
标记x情况没搜过
2 排列
2.1 给定数组,无重复数,不重复用的排列
可以把数组调换顺序
题意:给定没有重复数字的数组,返回所有排列的情况
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def dfs(i):
if i==n: ans.append(nums.copy())
for x in range(i,n):
nums[i],nums[x]=nums[x],nums[i]
dfs(i+1)
nums[i],nums[x]=nums[x],nums[i]
n=len(nums)
ans=[]
dfs(0)
return ans
2.2 给定数组,有重复数,不重复用的排列
47. 全排列 II - 力扣(LeetCode) (leetcode-cn.com)
题意:给定有重复数字的数组,返回所有的排列情况
只要确定当前位置这个值没有出现过就行。把元素放进dict,key为元素,value为出现的次数,每次遍历dict就行。
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def dfs(i):
if i==n: ans.append(nums.copy())
for x in range(i,n):
nums[i],nums[x]=nums[x],nums[i]
dfs(i+1)
nums[i],nums[x]=nums[x],nums[i]
n=len(nums)
ans=[]
dfs(0)
return ans
2.3 给定数组,无重复数,可重复用的排列
这个是完全背包问题,偏题了
题意:给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
dp=[0]*(target+1)
dp[0]=1
for i in range(target+1):
for num in nums:
if i>=num:
dp[i]+=dp[i-num]
return dp[target]
3 组合
组合相对排列就简单多了,不需要考虑先后顺序,只考虑有没有这个元素。
3.1 给定数组,无重复数,不重复用的组合
题意:找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:所有数字都是正整数。解集不能包含重复的组合。
解答:正常深度优先搜索就行
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
def dfs(num,tmpsum):
if len(tmp)==k:
if tmpsum==n:ans.append(tmp.copy())
return
elif tmpsum>n:return
for i in range(num,10):
tmp.append(i)
dfs(i+1,tmpsum+i)
tmp.pop()
ans,tmp=[],[]
dfs(1,0)
return ans
3.2 给定数组,无重复数,可重复用的组合
题意:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
解答:因为可重复选取元素,稍微改造以下深度优先搜索方式即可
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
def dfs(idx,total):
if total==target:ans.append(tmp.copy())
elif total>target:return
for i in range(idx,n):
tmp.append(candidates[i])
dfs(i,total+candidates[i])
tmp.pop()
candidates.sort()
n=len(candidates)
ans,tmp=[],[]
dfs(0,0)
return ans
3.3 给定数组,有重复数,不重复用的组合(难)
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
解析:本题难点在于如果有1,1,1,3,target=5。组成5的两个1有3种组合方式,但是是一样的,需合并。本题骚操作是,重复的元素只允许第一遍时扫过去。也就是说,组成的元素里有1,且这个1来自第一个1的位置,正常向后搜索。组成的元素里有1,且这个1不是来自第一个1的位置,那就放弃搜索,找到第一个不是1的数字的位置搜索就行。
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
def dfs(idx,total):
if total>target:return
elif total==target:
ans.append(tmp.copy())
return
for i in range(idx,n):#重复的只允许第一遍扫过去
if i>idx and candidates[i]==candidates[i-1]:continue
tmp.append(candidates[i])
dfs(i+1,total+candidates[i])
tmp.pop()
candidates.sort()
n=len(candidates)
tmp,ans=[],[]
dfs(0,0)
return ans