leetcode回溯法

目录

  1. 组合总和
  2. 组合总和 II
  3. 全排列
  4. 全排列 II
  5. 括号生成
  6. 分割回文串
  7. N 皇后

回溯法:深度优先搜索
回溯法思路的简单描述:把问题的解空间转化成了图或树结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
基本思想类似于:
1.图的深度优先搜搜
2.二叉树的后序遍历
回溯法非常适合由多个步骤组成的问题,并且每个步骤都有多个选项,当我们在每一步选择了其中一个选项时,就进入下一步,然后又面临新的选项。就这样重复的选择着,直到达到最后的状态
1.路径:也就是已经做出的选择。
2.选择的列表:也就是你当前可以做的选择。
3.结束条件:也就是达到决策树底层,无法再做选择的条件

回溯法框架:

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

核心思想:就是for 循环里面的递归,在递归调用之前,做选择,在递归调用之后,撤回选择。

  1. 组合总和

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

candidates 中的数字可以无限制重复被选取。

解题思路:
题目分析:本题采用递归较容易解答。首先,将数组内的元素由小到大排序,然后不断地进行试探,直到和为target。若当前和小于target,则继续往里面加元素;若当前和等于target,就可以将结果保留下来;若当前和已经大于target,那就可以排除掉这种情况,回溯到上一层,寻找其他可能的组合。本质上我觉得可以归纳为深度搜索。

代码如下:

class Solution:
    def combinationSum(self, candidates, target):
        candidates.sort()
        n = len(candidates)
        res = []
        def backtrack(i, tmp_sum, tmp):
            if tmp_sum == target:
                res.append(tmp)
                return   # 满足条件,返回上一级,继续深度遍历寻找可能解
            for j in range(i, n):
                if tmp_sum + candidates[j] > target:  # 在当前位置上,停止往下深度遍历搜索
                    break
                backtrack(j,tmp_sum + candidates[j],tmp+[candidates[j]])
        backtrack(0, 0, [])
        return res
  1. 组合总和 II

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

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

注意:解集不能包含重复的组合。

class Solution(object):
    def combinationSum2(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        if not candidates:
            return []
        candidates.sort()
        n = len(candidates)
        res = []

        def backtrack(i, tmp_sum, tmp_list):
            if tmp_sum == target:
                res.append(tmp_list)
                return 
            for j in range(i, n):
                if tmp_sum + candidates[j]  > target :
                	 break
                
                if j > i and candidates[j] == candidates[j-1]: # 解集不能包含重复的组合。
                	continue
                # candidates 中的每个数字在每个组合中只能使用 一次 
                backtrack(j + 1, tmp_sum + candidates[j], tmp_list + [candidates[j]])
        backtrack(0, 0, [])    
        return res
  1. 全排列

给定一个没有重复数字的序列,返回其所有可能的全排列。

解题思路: 使用一个和nums长度一样的数列,记录是否使用到这个元素,使用到以后把这个数列对应的位置改为True

解题代码:

回溯思想:

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
            mark = [False] * len(nums)
            outcome =[]
            curr = []
            def dfs():
                if sum(mark) ==len(nums):
                    outcome.append(curr[:]) #变量curr所指向的列表 在深度优先遍历的过程中只有一份 ,深度优先遍历完成以后,回到了根结点,成为空列表。
                for i  in range(len(nums)):
                    if mark[i] :
                        continue
                    curr.append(nums[i])
                    mark[i] = True
                    dfs()
                    mark[i] = False
                    curr.pop()
            dfs()

            return outcome

递归思想:

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res = []
        def backtrack(nums, tmp):
            if not nums:
                res.append(tmp)
                return 
            for i in range(len(nums)):
                backtrack(nums[:i] + nums[i+1:], tmp + [nums[i]])
        backtrack(nums, [])
        return res
  1. 全排列 II

给定一个可包含重复数字的序列,返回所有不重复的全排列。
剪枝条件

  1. 用过的元素不能再使用之外,又添加了一个新的剪枝条件,也就是我们考虑重复部分思考的结果,于是 剪枝条件
  2. 当当前元素和前一个元素值相同(此处隐含这个元素的 index>0 ),并且前一个元素还没有被使用过的时候,我们要剪枝
class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        self.res = []
        check = [0 for i in range(len(nums))]
        
        self.backtrack([], nums, check)
        return self.res
        
    def backtrack(self, sol, nums, check):
        if len(sol) == len(nums):
            self.res.append(sol)
            return
        
        for i in range(len(nums)):
            if check[i] == 1:
                continue
            if i > 0 and nums[i] == nums[i-1] and check[i-1] == 0:
                continue
            check[i] = 1
            self.backtrack(sol+[nums[i]], nums, check)
            check[i] = 0

递归:对比46题,只需添加判断前后是否相等,若nums[i] == nums[i - 1],则此情况前面已经讨论过,因此continue就好;需要事先对nums进行排序!

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        if not nums: return []
        nums.sort()
        res = []
        
        def backtrack(nums, tmp):
            if not nums:
                res.append(tmp)
                return
            for i in range(len(nums)):
                if i > 0 and nums[i] == nums[i - 1]:
                    continue
                backtrack(nums[:i] + nums[i + 1:], tmp + [nums[i]])
        backtrack(nums, [])  
        return res

131. 分割回文串

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

递归方法:

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        res = []
        
        def helper(s, tmp):
            if not s:
                res.append(tmp)
            for i in range(1, len(s) + 1):
                if s[:i] == s[:i][::-1]:
                    helper(s[i:], tmp + [s[:i]])
        helper(s, [])
        return res

动态规划方法, 我们用 dp[j][i] 字符串从位置 j 到位置 i(闭区间)是否为回文子串,再用 DFS 把所有可能找到!

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        n = len(s)
        f = [[True] * n for _ in range(n)]

        for i in range(n - 1, -1, -1):
            for j in range(i + 1, n):
                f[i][j] = (s[i] == s[j]) and f[i + 1][j - 1]

        ret = list()
        ans = list()

        def dfs(i: int):
            if i == n:
                ret.append(ans[:])
                return
            
            for j in range(i, n):
                if f[i][j]:
                    ans.append(s[i:j+1])
                    dfs(j + 1)
                    ans.pop()

        dfs(0)
        return ret

51. N 皇后

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        if n<1:
            return 0

        record = [0]*n       # rocord[i] 表示第i行的皇后放在了第几列
        # res =[]
        # cnt= self.process(0,record,n,res)
        # print(cnt)
        # return res
        return self.process(0,record,n)

    def process(self,i,record,n):
        if i ==n:
            return 1
        cnt =0

        for j in range(n):
            # 当第i行的皇后放在第j列是,会不会和之前(0~ i-1)的皇后共行、共列或者共斜线
            if self.isValid(record,i,j):
                record[i] =j
                cnt+= self.process(i+1,record,n)

            # tmp = []
            # for i in record:
            #     tmp_str ='.'*n
            #     tmp_str = tmp_str[:i] +'Q' + tmp_str[i:i+1]
            #     tmp.append(tmp_str)
            # res.append(tmp)
        return cnt


    def isValid(self,record,i,j):
        for k in range(i):
        	# 判断前i-1个皇后是否放在第j列, 以及是否产生共斜线位置:横坐标的差值等于纵坐标的差值,为共斜线
            if j==record[k] or abs(record[k]-j) ==abs(i-k):
                return False
        return True

优化时间复杂度

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        def generateBoard():
            board = list()
            for i in range(n):
                row[queens[i]] = "Q"  #对应位置放上皇后
                board.append("".join(row))
                row[queens[i]] = "."   # 还原
            return board

        def backtrack(row: int):  # row 表示行号
            if row == n:  # 表示n个皇后的位置已经安排完成
                board = generateBoard()
                solutions.append(board)
            else:
                for i in range(n):   
                # columns 表示前0~row的皇后存放列下表的集合
                # row - i,表示皇后所在位置的行列差值, 相同的行列差值表示共左上对角线.
                # 相同的行列和表示共右上对角线
                    if i in columns or row - i in diagonal1 or row + i in diagonal2:
                        continue
                    queens[row] = i 

                    columns.add(i)
                    diagonal1.add(row - i)
                    diagonal2.add(row + i)

                    backtrack(row + 1)
                    
                    columns.remove(i)
                    diagonal1.remove(row - i)
                    diagonal2.remove(row + i)
                    
        solutions = list()
        queens = [-1] * n  # 使用一个数组记录每行放置的皇后的列下标
        columns = set()     # 已经放好的皇后的列下表集合
        diagonal1 = set()   # 左斜线对应的皇后的横纵坐标的差值
        diagonal2 = set()   # 右斜线对应皇后的横纵坐标的和
        row = ["."] * n
        backtrack(0)
        return solutions

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值