ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

回溯法详解

2022-06-27 15:32:22  阅读:220  来源: 互联网

标签:trace nums self back 详解 result 回溯 path


一、模板格式

  回溯法问题实际上是一个决策树的遍历过程。可以分为三个部分:

  1、路径:也就是已经做出的选择。

  2、选择列表:也就是当前可以做的选择。

  3、结束条件:也就是到达决策树底层,无法再做选择的条件。

  回溯法不好理解的地方应该在撤销选择这一步,回溯会沿着一条路径走到结束状态,到这一步之后,需要返回到上一状态,这时候就需要执行撤销操作。

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

二、示例讲解

1、全排列

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

示例:

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

"""


class Solution:
    def permute(self, nums):
        result = []
        path = []
        self.back_trace(nums, result, path)
        return result

    def back_trace(self, nums, result, path):
        """
        用递归来回溯
        :param nums:
        :param result:
        :param path:
        :return:
        """
        if len(path) == 3:  # 满足结束条件
            result.append(path[:])
            return
        for i in range(len(nums)):
            path.append(nums[i])  # 做选择
       # 全排序中可选择的列表是除了当前节点外的所有节点,所以是num[:i] + nums[i+1:] self.back_trace(nums[:i] + nums[i+1:], result, path) path.pop() # 撤销选择

2、全排列II

  在包含重复数字的情况下,返回不重复的全列表。核心在于如何去重,即遍历是做剪枝操作,减少复杂度。为确保重复的数字不会重复组合,则需要对每个重复的数字标记一个顺序,并保证他们的相对位置不变,举例对于3个1,可以标记为1a、1b、1c。如果执行时1a没有被选择的话1b是不可以被选择的。这样就能避免重复。所以第一步对数组排序,确定好重复的数字的顺序。"""给定一个可包含重复数字的序列,返回所有不重复的全排列示例:


输入: [1,1,2]
输出:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

"""


import copy


class Solution:
    def permuteUnique(self, nums):
        result = []
        path = []
        nums.sort()  # 确定好重复数字的顺序
        self.back_trace(nums, result, path, len(nums))
        return result

    def back_trace(self, nums, result, path, length):
        if len(path) == 3:
            result.append(path[:])
            return
        for i in range(len(nums)):
       # 这一步是关键,以[1, 1, 2]为例,第一步选择第一个 1 之后,因为i = 0不会触发continue,而剩余的[1,2]没有重复,所以会得到[1] + [1, 2] = [1, 1, 2]
       # 和[1] + [2, 1] = [1, 2, 1]。当遍历到第二个 1 时满足下面的条件会触发continue,所以会直接跳到 2,而剩余的[1, 1]有重复,和上面一样分析只会得到[1, 1],
       # 最终就是 [2] + [1, 1] = [2, 1, 1]
if i > 0 and nums[i] == nums[i-1]: continue path.append(nums[i]) self.back_trace(nums[:i] + nums[i+1:], result, path, length) path.pop()

3、子集

  子集和全排列有两个不同的地方,一是没有终止条件,即所有状态下的结果都要;二是有顺序的,不可扰乱。

"""
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

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

"""


class Solution:
    def subsets(self, nums):
        result = []
        path = []
        self.back_trace(nums, result, path)
        return result

    def back_trace(self, nums, result, path):
        result.append(path[:])  # 保存任意状态下的路径
        for i in range(len(nums)):
            path.append(nums[i])
       # num[i+1:] 表示不可扰乱数组的顺序,只能取当前状态后的元素 self.back_trace(nums[i+1:], result, path) path.pop()

4、子集II

  同全排列II,对重复的元素排序,保证它们之间的相对位置。

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

说明:解集不能包含重复的子集。

示例:

输入: [1,2,2]
输出:
[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]

"""


class Solution:
    def subsetsWithDup(self, nums):
        result = []
        path = []
        nums.sort()  # 一定要排序,因为是要按照后面是否和前面重复来筛选的,所以要将相同的元素排列在一起
        self.back_trace(nums, result, path)
        return result

    def back_trace(self, nums, result, path):
        result.append(path[:])
        for i in range(len(nums)):
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            path.append(nums[i])
            self.back_trace(nums[i + 1:], result, path)
            path.pop()

5、组合

  组合中要注意三个点:一是终止条件是选择的元素相加等于target;二是如果当前选择的元素和大于target,需要剪枝。三是每个元素可以重复使用。

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

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

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。 
示例 1:

输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]
示例 2:

输入: candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

"""


class Solution:
    def combinationSum(self, candidates, target: int):
        result = []
        path = []
        self.back_trace(candidates, result, path, target)
        return result

    def back_trace(self, nums, result, path, target):
     # 满足终止条件 if sum(path) == target: result.append(path[:]) return for i in range(len(nums)):
       # 满足剪枝的条件 if sum(path) + nums[i] > target: continue path.append(nums[i])
       # num[i:] 是确保元素可以重复使用,不同于子集中的num[i+1:] self.back_trace(nums[i:], result, path, target) path.pop()

6、组合总数II

  重复元素的问题同上全排列II和子集II。

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

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

说明:

所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。 
示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]
示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]

"""


class Solution:
    def combinationSum2(self, candidates, target: int):
        result = []
        path = []
        candidates.sort()  # 排序保证重复元素的相对位置
        self.back_trace(candidates, result, path, target)
        return result

    def back_trace(self, nums, result, path, target):
        if sum(path) == target:
            result.append(path[:])
            return
        for i in range(len(nums)):
# 多增加一个重复元素的剪枝条件 if sum(path) + nums[i] > target or (i > 0 and nums[i] == nums[i - 1]): continue path.append(nums[i]) self.back_trace(nums[i + 1:], result, path, target) path.pop()

 7、括号生成

  括号要有效的前提是右括号要在左括号的右边,所以可以先选择左括号,再选择右括号,并且保证已经选择的左括号的个数是要大于或等于右括号的。

"""
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

"""


class Solution:
    def generateParenthesis(self, n: int):
        result = []
        path = ""
    
     # left 和 right用来记录左括号和右括号的数量 self.back_trace(n, result, path, left=0, right=0) return result def back_trace(self, n, result, path, left, right):
# 终止条件 if len(path) == 2 * n: result.append(path)
     # 先选择左括号 if left < n: self.back_trace(n, result, path + "(", left + 1, right)
     # 选择右括号时保证右括号的数量小于左括号 if right < left: self.back_trace(n, result, path + ")", left, right + 1)

8、电话号码的字母组合

  本道题是一个二维的遍历,先遍历数字列表,再遍历每个数字对应的字母列表

"""
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。



示例:

输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

"""


class Solution:
d = {"2": "abc",
"3": "def",
"4": "ghi",
"5": "jkl",
"6": "mno",
"7": "pqrs",
"8": "tuv",
"9": "wxyz"}

def letterCombinations(self, digits: str):
result = []
path = []
self.back_trace(digits, result, path, len(digits))
return result

def back_trace(self, digits, result, path, length):
if len(path) == length:
result.append("".join(path))
return
for i in range(len(digits)):
for j in range(len(self.d[digits[i]])):
path.append(self.d[digits[i]][j])
self.back_trace(digits[i + 1:], result, path, length)
path.pop()

9、八皇后问题

  有一个 8x8 的棋盘,往里放 8 个棋子,每个棋子所在的行、列、对角线都不能有另一个棋子。找出有多少种排列?

  八皇后的核心问题是如何判断冲突的问题,在摆放棋子的时候是按行逐个排列的,所以行不会冲突,但是列、对角线(左上、右上两条对角线)会有可能冲突,所以需要把这三个已发生的状态存储下来,对新的皇后选择时要判断和已选的是否冲突,冲突则直接跳过。

class Solution:
    def __init__(self):
        self.result = 0
        self.columns = set()  # 存储已经选择过的列
        self.diag_left = set()  # 存储已经选择过的左上角的元素
        self.diag_right = set()  # 存储已经选择过的右上角的元素

    def is_conflict(self, row, col):
        if col in self.columns or row - col in self.diag_left or row + col in self.diag_right:
            return True
        return False

    def eightQueen(self, n):
        self.dfs(0, n)
        return self.result

    def dfs(self, row, n):
        # 终止条件
        if row == n:
            self.result += 1
            return

        for col in range(n):
            # 剪枝
            if self.is_conflict(row, col):
                continue

            # 执行选择,保存状态
            self.columns.add(col)
            self.diag_left.add(row - col)
            self.diag_right.add(row + col)
            # 回溯
            self.dfs(row + 1, n)
            # 撤销选择,删除状态
            self.columns.remove(col)
            self.diag_left.remove(row - col)
            self.diag_right.remove(row + col)

 

标签:trace,nums,self,back,详解,result,回溯,path
来源: https://www.cnblogs.com/jiangxinyang/p/16415540.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有