一套模板搞定全排列,组合,子集问题(递归嵌套for循环)
组合问题:leetcode77、leetcode39、leetcode40、leetcode216
全排列问题:leetcode46、leetcode47
子集问题:leetcode78、leetcode90
基础模板
def __init__(self):
#收集所有符合条件的集合
self.paths = []
#临时结果存放并需要回溯
self.path = []
def backtracking(参数值):
if 递归终止条件:
return
for 循环:
self.path.append(元素)
backtracking(参数值)
self.path.pop()
1.组合问题
leetcode77:
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
解题关键:递归函数中的start_index参数用于防止元素选取重复
class Solution:
def __init__(self):
self.paths = []
self.path = []
def combine(self, n: int, k: int) -> List[List[int]]:
def backtracking(n, k, start_index):
#递归终止条件
if len(self.path) == k:
#将符合条件的临时集合加入到最终集合中
self.paths.append(self.path[:])
return
#start_index用于进行控制深度递归时,元素不会进行重复选取,
#而是从本轮递归选取的元素的下一个元素开始。n-(k-len(self.path))+2
#为剪枝操作,当元素从数组中最后几位开始选取时,后面的长度不足为k,故不需要再操作
for i in range(start_index, n-(k-len(self.path))+2):
#加入元素
self.path.append(i)
#递归
backtracking(n, k, i+1)
#元素回溯,会弹出一个元素,进行下一个元素的选取
self.path.pop()
backtracking(n, k, 1)
return self.paths
leetcode39:
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
解题关键:start_index在递归函数的变化,每一个元素可被重复选取,因此递归函数中start_index
仍然为i。
class Solution:
def __init__(self):
self.paths = []
self.path = []
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
def backtracking(candidates, target, start_index):
if target == 0:
self.paths.append(self.path[:])
return
for i in range(start_index, len(candidates)):
#剪枝,大于target的集合不需要再往后选取
if target < 0:
return
#对每个元素做减法,与递归结束条件target==0呼应
target -= candidates[i]
self.path.append(candidates[i])
#题目条件每个元素可以重复被选取,所以这里start_index仍然为i而不是i+1
backtracking(candidates, target, i)
self.path.pop()
#对每个元素做加法,与递归结束条件target==0呼应
target += candidates[i]
backtracking(candidates, target, 0)
return self.paths
leetcode40
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
解题关键:数组中含有重复元素,每个元素不能重复使用,需要在选取过程中去重
class Solution:
def __init__(self):
self.path = []
self.paths = []
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
def backtracking(candidates, target, start_index):
if target == 0:
self.paths.append(self.path[:])
return
for i in range(start_index, len(candidates)):
#剪枝
if target < 0:
return
#存在重复元素,去重,这个前提条件是数组为有序
if i > start_index and candidates[i] == candidates[i-1]:
continue
target -= candidates[i]
self.path.append(candidates[i])
#元素不可重复使用,start_index需为i+1,即从下一个元素开始选取
backtracking(candidates, target, i+1)
self.path.pop()
target += candidates[i]
#这里sorted()将数组元素排序,是去重操作的前提条件
backtracking(sorted(candidates), target, 0)
return self.paths
leetcode216
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
只使用数字1到9
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
class Solution:
def __init__(self):
self.path = []
self.paths = []
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
def backtracking(k, n, start_index):
if len(self.path) == k:
#无论有没有符合条件的集合,都要结束递归,后面的递归超出条件k个,无意义
if n == 0:
self.paths.append(self.path[:])
return
for i in range(start_index, 9 - (k-len(self.path))+2):
if n < 0:
return
n -= i
self.path.append(i)
backtracking(k, n, i+1)
self.path.pop()
n += i
backtracking(k, n, 1)
return self.paths
2.全排列问题
leetcode46
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
class Solution:
def __init__(self):
self.paths = []
self.path = []
def permute(self, nums: List[int]) -> List[List[int]]:
def backtracking(nums):
if len(self.path) == len(nums):
self.paths.append(self.path[:])
return
for i in range(0, len(nums)):
#去掉含有重复元素的组合
if nums[i] in self.path:
continue
self.path.append(nums[i])
#全排列有顺序的讲究,故每一次递归的遍历都可以从数组的第0个元素开始
backtracking(nums)
self.path.pop()
backtracking(nums)
return self.paths
leetcode47
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
class Solution:
def __init__(self):
self.path = []
self.paths = []
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
used = [0] * len(nums)
def backtracking(nums, used):
if len(self.path) == len(nums):
self.paths.append(self.path[:])
return
for i in range(0, len(nums)):
#保证每个元素只可用一次
if not used[i]:
#去掉数组中因存在重复元素而形成的重复组合
if i > 0 and nums[i]==nums[i-1] and not used[i-1]:
continue
#含有重复元素,需要记录每个元素的使用情况,并且作为递归函数的参数
used[i] = 1
self.path.append(nums[i])
backtracking(nums, used)
self.path.pop()
used[i] = 0
backtracking(sorted(nums), used)
return self.paths
3.子集问题
leetcode78
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
class Solution:
def __init__(self):
self.path = []
self.paths = []
def subsets(self, nums: List[int]) -> List[List[int]]:
def backtracking(nums, start_index):
#每加进来一个元素就做一次结果收集
self.paths.append(self.path[:])
if len(self.path) == len(nums):
return
for i in range(start_index, len(nums)):
self.path.append(nums[i])
#保证元素无重复选取,start_index为i+1
backtracking(nums, i+1)
self.path.pop()
backtracking(nums, 0)
return self.paths
leetcode90
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
解题关键:数组中有重复元素,需要去除因存在重复元素而形成的重复集合
class Solution:
def __init__(self):
self.paths = []
self.path = []
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
def backtracking(nums, start_index):
self.paths.append(self.path[:])
if len(self.path) == len(nums):
return
for i in range(start_index, len(nums)):
#数组中有重复元素,需要去除因存在重复元素而形成的重复集合
if i > start_index and nums[i] == nums[i-1]:
continue
self.path.append(nums[i])
backtracking(nums, i+1)
self.path.pop()
backtracking(sorted(nums), 0)
return self.paths
参考文献:代码随想录