【leetcode总结】解析回溯法系列:)

回溯法抽象为树形结构后,其遍历过程就是:for循环横向遍历,递归纵向遍历,回溯不断调整结果集

@ 代码随想录

目录

一、排列问题

46. 全排列(middle)

47. 全排列 II(middle)

剑指 Offer 38. 字符串的排列(middle)

784. 字母大小写全排列(middle)

二、组合问题

17. 电话号码的字母组合(middle)

39. 组合总和 (middle)(candidates 中的数字可以无限制重复被选取)

40. 组合总和 II (middle)(candidates 中的每个数字在每个组合中只能使用一次)

77.组合(middle)

216. 组合总和 III (middle)(与上面的题目区别是:给了res的长度限制,所以这个数字不行,就要腾出位置来给其他的元素) 

三、子集问题

78.子集(middle)

90.子集2(middle)

四、分割问题

93.复原IP地址(middle)

131.分割回文串(middle)

五、棋盘问题

37.解数独(hard)

51.N皇后(hard)

六、其他

491.递增子序列(middle)

332.重新安排行程(middle)


常规的回溯思想,就是普通的一个 for 循环,尝试插入 1, 2 ... n,然后进入递归,在原来的基础上继续尝试插入 1, 2... n。直到树包含了所有的数字。就是差不多下边这样的框架。

递归{
    递归出口;
    for(int i = 1;i<=n;i++){
    	add(i);
    	进入递归;
   		remove(i);
	}
}

一、排列问题

46. 全排列(middle)

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

力扣

这里和77.组合问题、131.切割问题和78.子集问题最大的不同就是for循环里不用startIndex了。

因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。

而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次。

"""
middle
dfs 回溯法
"""
class Solution(object):
    def __init__(self):
        self.res = []
        self.path = []

    def permute(self, nums):
        self.dfs(nums)
        return self.res

    def dfs(self, nums):
        if len(self.path) == len(nums):
            self.res.append(self.path[:])  # 此时说明找到了一组
        for i in range(len(nums)):
            if nums[i] in self.path:  # path里已经收录的元素,直接跳过
                continue
            self.path.append(nums[i])
            self.dfs(nums)  # 递归
            self.path.pop()  # 回溯

总结

  • 每层都是从0开始搜索而不是startIndex
  • 需要used数组记录path里都放了哪些元素了

47. 全排列 II(middle)

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

输入:nums = [1,1,2]
输出:[[1,1,2], [1,2,1], [2,1,1]]
class Solution:
    def permuteUnique(self, nums):
        result = []
        self.dfs(nums,0,[],result)
        print(list(result))

    def dfs(self,nums,start,path,result):
        if len(path)==len(nums):
            if path not in result:
                result.append(path[:])
            return
        for i in range(start,len(nums)):
            path.append(nums[i])
            self.dfs(nums,i+1,path,result)
            # path.pop()

剑指 Offer 38. 字符串的排列(middle)

输入一个字符串,打印出该字符串中字符的所有排列。里面不能有重复元素。

输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
class Solution:
    def permutation(self, s: str) -> List[str]:
        res = []
        if not s:return res
        self.dfs("", res, s)
        return list(set(res))

    def dfs(self, path, res, s):
        # 定义dfs出口
        if s=='':res.append(path)
        # 轮流当首字母
        for i in range(len(s)):
            self.dfs(path+s[i], res, s[:i]+s[i+1:])
        return res

784. 字母大小写全排列(middle)

给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。

示例:
输入:S = "a1b2"
输出:["a1b2", "a1B2", "A1b2", "A1B2"]

二、组合问题

17. 电话号码的字母组合(middle)

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
输入:digits = "2"
输出:["a","b","c"]
# 求abc和def的全排列
# 全排列就是回溯, 求最短路径也是DFS
# DFS
class Solution(object):
    def letterCombinations(self, digits):
        # 存储结果的数组
        res = []
        inputstr = []
        if len(digits) == 0:
            return res
        hash_map = {0:"",1:"",2:"abc",3:"def",4:"ghi",5:"jkl",6:"mno",7:"pqrs",8:"tuv",9:"wxyz"}
        for i in digits:
            inputstr.append(hash_map[int(i)])
        # print(inputstr) # ['abc', 'def']
        # 闭包
        def dfs(cur_str,i,res):
            if len(cur_str)==len(inputstr): # abc def ghi
                res.append(cur_str)
                return
            for count in range(len(inputstr[i])): # abc
                dfs(cur_str+inputstr[i][count],i+1,res)
 
        dfs("",0,res)
        return res

39. 组合总和 (middle)(candidates 中的数字可以无限制重复被选取)

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。 

输入: candidates = [2,3,6,7], 
target = 7
输出: [[7],[2,2,3]]

https://github.com/youngyangyang04/leetcode-master/blob/master/problems/0039.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.md

# 给定一个无重复元素的数组 candidates 和一个目标数 target ,
# 找出 candidates 中所有可以使数字和为 target 的组合。
# candidates 中的数字可以无限制重复被选取。
class Solution:
    def __init__(self):
        self.res = []
        self.path = []

    def combinationSum(self, candidates, target):
        candidates.sort() # 首先要排序,必不可少,记得归纳总结这里,什么时候要排序??
        self.dfs(candidates,target,0, 0)
        return self.res

    def dfs(self,candidates, target, sums, start_index):
        if sums==target:
            self.res.append(self.path[:])
            return

        for i in range(start_index,len(candidates)):
            # 剪枝
            if sums+candidates[i]>target:
                return

            self.path.append(candidates[i])
            sums+=candidates[i]
            self.dfs(candidates,target,sums,i) # 不用i+1了,表示可以重复读取当前的数
            self.path.pop()
            sums-=candidates[i]

40. 组合总和 II (middle)(candidates 中的每个数字在每个组合中只能使用一次)

# 与上一道题唯一的区别就在于下面这个递归我们加了1,这样下次递归的循环就从i+1开始了。

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
# 给定一个数组 candidates 和一个目标数 target ,
# 找出 candidates 中所有可以使数字和为 target 的组合。
# candidates 中的每个数字在每个组合中只能使用一次。
# -----------------------------------------------
# 递归的时候将 idx 加 1(需判断是否超出candidates的范围),另外由于题目输入的candidates可能包含相同的元素,
# 所以我们需要对得到的答案进行去重处理。
class Solution:
    def combinationSum2(self, candidates, target):
        path=[]
        result=[]
        candidates.sort()
        self.dfs(candidates,target,0,path,result)
        return result
 
    # 我的想法是每次找过的元素都标记一下,然后下次找的时候就不可以选这个了
    # i+1表明一个数字只能使用一次
    def dfs(self,candidates,target,start,path,result):
        # 判断出口
        if target == 0 and path not in result:
            result.append(path[:])
            return
        # 开始循环遍历
        for i in range(start,len(candidates)):
            if candidates[i]>target:
                return
            # i+1表明一个数字只能使用一次,这样下次递归的循环就从i+1开始了
            self.dfs(candidates,target-candidates[i],i+1,path+[candidates[i]],result)

77.组合(middle)

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

「本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?」

我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:回溯算法:求组合问题!回溯算法:求组合总和!

如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:回溯算法:电话号码的字母组合

"""
回溯法——组合问题(middle)
"""
class Solution:
    def __init__(self):
        self.path = []
        self.res = []
    def combine(self, n, k):
        if not k:return self.res
        self.dfs(n,k,1)
        return self.res

    def dfs(self, n, k, start_index):
        # dfs出口
        if len(self.path)==k:
            self.res.append(self.path[:])
            # return # return加不加都不影响结果,所以作用是什么?
        # 横向遍历
        for i in range(start_index, n+1):
            self.path.append(i)
            self.dfs(n,k,i+1) # 深度遍历
            self.path.pop() # 回溯,上一级

77.组合(剪剪枝)link

  1. 已经选择的元素个数:path.size();
  2. 还需要的元素个数为: k - path.size();
  3. 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历

为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。

举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。

从2开始搜索都是合理的,可以是组合[2, 3, 4]。

 这里还不明白。

216. 组合总和 III (middle)(与上面的题目区别是:给了res的长度限制,所以这个数字不行,就要腾出位置来给其他的元素) 

216:[[1, 2, 6], [1, 3, 5], [2, 3, 4]];40:[[1, 2, 2], [5]] (个人理解,求指点啊~~)

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:1、所有数字都是正整数。2、解集不能包含重复的组合。 

输入: k = 3, n = 7
输出: [[1,2,4]]
# 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
# 说明:
# 所有数字都是正整数。
# 解集不能包含重复的组合。
class Solution:
    def combinationSum3(self, k, n):
        path=[]
        result=[]
        nums = [i for i in range(1,10)]
        self.dfs(nums,k,n,0,path,result)
        return result
 
    def dfs(self,nums,k,n,start,path,result):
        if k==0 and n==0 and path not in result:
            result.append(path[:])
            return
        for i in range(start,len(nums)):
            if nums[i]>n or k<0:
                return
            k-=1
            self.dfs(nums,k,n-nums[i],i+1,path+[nums[i]],result)
            k+=1
# 控制path
class Solution:
    def combinationSum3(self, k, n):
        path=[]
        result=[]
        nums = [i for i in range(1,10)]
        self.dfs(nums,k,n,0,path,result)
        return result
 
    def dfs(self,nums,k,n,start,path,result):
        if len(path)==k and n==0 and path not in result:
            result.append(path[:])
            return
        for i in range(start,len(nums)):
            if nums[i]>n or k<0:
                return
            # k-=1
            path.append(nums[i])
            self.dfs(nums,k,n-nums[i],i+1,path,result)
            # k+=1
            path.pop()

三、子集问题

78.子集(middle)

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

90.子集2(middle)

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
# 输入: [1,2,2] 输出:[[], [1], [1, 2], [1, 2, 2], [2], [2, 2]]
class Solution(object):
    def subsetsWithDup(self, nums):
        path = []
        result = []
        nums.sort()
        self.dfs(nums,0,path,result)
        return result
 
    def dfs(self,nums,start,path,result):
        if path not in result:
            result.append(path[:])
        if start == len(nums): # 之前报错,改了这里
            return
        for i in range(start,len(nums)):
            path.append(nums[i])
            self.dfs(nums,i+1,path,result)
            path.pop()

四、分割问题

93.复原IP地址(middle)

131.分割回文串(middle)

五、棋盘问题

37.解数独(hard)

51.N皇后(hard)

六、其他

491.递增子序列(middle)

332.重新安排行程(middle)

回溯算法总结篇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值