回溯算法集合(全排列,组合,子集)

一套模板搞定全排列,组合,子集问题(递归嵌套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

参考文献:代码随想录

你可能感兴趣的:(python,回溯算法,算法,leetcode,组合/全排列/子集)