模板
回溯算法可以用来解决子集、排列、组合问题,主要形式是从一堆选项中挑选出符合要求的组合,基础模板如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |  def dining(nums):   desk = []      def backtrack(dish, menu):     if (满足结束条件):              desk.append(dish[:])     for val in menu:              dish.append(val)              backtrack(dish, 新菜单)              dish.pop()   backtrack([], nums)   return desk
 
  | 
 
解空间结构:常用的有两种,子集树(零一背包问题)和排列树(货郎问题)
- 子集树:从n个元素的集合S中找出满足某种性质的子集时,复杂度 O(2^n)
 
- 排列树:从n个元素的集合S中找出满足某种性质的排列时,复杂度 O(n!)
 
应用
子集
LeetCode 78 题
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | class Solution:     def subsets(self, nums: List[int]) -> List[List[int]]:         desk = []         def trackBack(dish, menu):             if (dish not in desk):                 desk.append(dish[:])
              for idx, val in enumerate(menu):                 dish.append(val)                 trackBack(dish, menu[(idx+1):])                 dish.pop()
          trackBack([], nums)         return desk
   | 
 
组合
LeetCode 77 题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | class Solution:     def combine(self, n: int, k: int) -> List[List[int]]:         desk = []         def backtrack(dish, menu):             if (len(dish) == k):                 desk.append(dish[:])                 return
              for val in menu:                 dish.append(val)                 trackBack(dish, menu[(idx+1):])                 dish.pop()
          backtrack([], range(1, n + 1))         return desk
   | 
 
全排列
LeetCode 46 题
1 2 3 4 5 6 7 8 9 10 11 12 13
   | class Solution:     def permute(self, nums: List[int]) -> List[List[int]]:         desk = []         long = len(nums)         def backtrack(dish, menu):             if dish not in desk and len(dish) == long:                 desk.append(dish[:])             for idx, val in enumerate(menu):                 dish.append(val)                 backtrack(dish, menu[:idx] + menu[idx + 1:])                 dish.pop()         backtrack([], nums)         return desk
   | 
 
变形
根据不同的题型,回溯算法中变化的部分是结束条件和新菜单。
新菜单指的是,每次可选项的集合,根据不同的题目,新菜单是不一样的。
不包含重复元素的情况
如果解集忽略子项的排序差异,则新菜单为:
1
   | newmenu = menu[(idx+1):]
   | 
 
如果解集不忽略子项的排序差异 ,则新菜单为:
1
   | menu[:idx] + menu[idx + 1:]
   | 
 
优化
回溯算法是一种穷举的算法,会遍历所有的情况,我们可以通过剪枝来跳过一些不合符要求的遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | def dining(nums):   desk = []   def backtrack(dish, menu):     if (满足结束条件):       desk.append(dish[:])     for val in menu:              if (满足剪枝条件):         continue       dish.append(val)       backtrack(dish, 新菜单)       dish.pop()   backtrack([], nums)   return desk
   | 
 
练习题
编号为 Leecode 题号
- 全排列(中等)
 
- 全排列 II(中等
 
- 组合总和(中等)
 
- 组合总和 II(中等)
 
- 组合(中等)
 
- 子集(中等)
 
- 子集 II(中等):剪枝技巧同 47 题、39 题、40 题;
 
- 第 k 个排列(中等)
 
- 复原 IP 地址(中等)