回溯法解决的问题
回溯法,一般可以解决如下几种问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
相信大家看着这些之后会发现,每个问题,都不简单!
另外,会有一些同学可能分不清什么是组合,什么是排列?
组合是不强调元素顺序的,排列是强调元素顺序。
例如:{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。
记住组合无序,排列有序,就可以了。
如何理解回溯法
回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。
回溯搜索的遍历过程
在上面我们提到了,回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。
如图:
![回溯算法理论基础](https://i-blog.csdnimg.cn/blog_migrate/34c777365367c6aece29e987f1fa3083.png)
回溯算法模板框架如下:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
1. 组合
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
res = []
path = []
def backtracking(n, k, startIndex):
if len(path)==k:
res.append(path[:])
return
for i in range(startIndex, n + 1):
path.append(i)
backtracking(n, k, i + 1)
path.pop()
backtracking(n, k, 1)
return res
剪枝优化
如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了
![77.组合4](https://i-blog.csdnimg.cn/blog_migrate/0c7c6f13a19223d2a7ec6c5821a750b4.png)
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
res = [] # 储存结果
path = [] # 收集路径上的元素
def backtracking(n, k, startindex):
if len(path) == k: # 终止条件
res.append(path[:])
return
for i in range(startindex, (n-(k-len(path))+2):# 剪枝优化
path.append(i)
backtracking(n, k, i + 1) # 递归,开始位置加1,深度搜索
path.pop() # 回溯,弹出path末尾,加入新元素(不好理解)
backtracking(n, k, 1)
return res
2.组合总和III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
- 所有数字都是正整数。
- 解集不能包含重复的组合。
示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]
回溯三部曲:
- 确定递归函数参数
- 确定终止条件
- 单层搜索过程
不剪枝
class Solution:
def __init__(self):
self.s = 0
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
res = [] # 存放结果
path = [] # 路径元素
def backtracking(n, k, startindex):
if len(path) == k:
if self.s == n:
res.append(path[:])
return
for i in range(startindex, 10):
self.s += i
path.append(i)
backtracking(n, k, i + 1)
path.pop()# 回溯1
self.s -= i# 回溯2
backtracking(n, k, 1)
return res
减枝:
class Solution:
def __init__(self):
self.res = []
self.sum_now = 0
self.path = []
def combinationSum3(self, k: int, n: int) -> [[int]]:
self.backtracking(k, n, 1)
return self.res
def backtracking(self, k: int, n: int, start_num: int):
if self.sum_now > n: # 剪枝1
return
if len(self.path) == k: # len(path)==k时不管sum是否等于n都会返回
if self.sum_now == n:
self.res.append(self.path[:])
return
for i in range(start_num, 10 - (k - len(self.path)) + 1): # 剪枝2
self.path.append(i)
self.sum_now += i
self.backtracking(k, n, i + 1)
self.path.pop()
self.sum_now -= i
3.电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
解决三个问题
- 数字和字母如何映射
- 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
- 输入1 * #按键等等异常情况
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if len(digits) == 0:
return []
dic = {2:'abc',3:'def',4: 'ghi',5: 'jkl',6: 'mno',7: 'pqrs',8: 'tuv',9:'wxyz'}
res = []
path = ''
def backtracking(path, digits, index): # 1.确定传入参数和返回值
if len(digits) == index: # 2.确定终止条件:数字遍历结束
res.append(path)
return
for i in dic[int(digits[index])]: # 3.确定单层递归
path += i
backtracking(path, digits, index + 1)
path = path[:-1] # 回溯
backtracking(path, digits, 0)
return res
方法2:队列 -copy
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits: return []
phone = ['abc','def','ghi','jkl','mno','pqrs','tuv','wxyz']
queue = [''] # 初始化队列
for digit in digits:
for _ in range(len(queue)):
tmp = queue.pop(0)
for letter in phone[ord(digit)-50]:# 这里我们不使用 int() 转换字符串,使用ASCII码
queue.append(tmp + letter)
return queue
4. 组合总和
给定一个无重复元素的数组 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: List[int], target: int) -> List[List[int]]:
res = []
path = []
def backtracking(index, s, candidates, target):
if s > target:
return
if s == target:
res.append(path[:])
return
for i in range(index, len(candidates)):
s += candidates[i]
path.append(candidates[i])
backtracking(i, s, candidates, target) # 从当前位置作为index,递归
s -= candidates[i]
path.pop()
backtracking(0, 0, candidates, target)
return res
减枝
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
# 剪枝优化
res = []
path = []
candidates.sort() # 为了剪枝,先排序
def backtracking(index, s, candidates, target):
if s > target:
return
if s == target:
res.append(path[:])
return
for i in range(index, len(candidates)):
s += candidates[i]
if s > target: # 提前结束遍历
return
path.append(candidates[i])
backtracking(i, s, candidates, target) # 从当前位置作为index,递归
s -= candidates[i]
path.pop()
backtracking(0, 0, candidates, target)
return res
5. 组合总和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]
]
加入uesd数组,确保同一层元素不能相同
树层去重的话,需要对数组排序!
如果candidates[i] == candidates[i - 1]
并且 used[i - 1] == false
,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。
used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的
![img](https://i-blog.csdnimg.cn/blog_migrate/78843a57aeee6ed03bec75f4975ffb1b.png)
class Solution:
def __init__ (self):
self.res = []
self.path = []
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
self.used = [0] * len(candidates)
candidates.sort()
self.backtracking(candidates, target, 0, 0)
return self.res
def backtracking(self,candidates, target, startindex, s):
if s == target:
self.res.append(self.path[:])
return
for i in range(startindex, len(candidates)):
if s + candidates[i] > target:
return
if i > 0 and candidates[i] == candidates[i - 1] and self.used[i - 1] == 0:
continue
s+= candidates[i]
self.used[i] = 1
self.path.append(candidates[i])
self.backtracking(candidates, target, i + 1, s)
self.used[i] = 0
self.path.pop()
s -= candidates[i]
不使用used - copy
class Solution:
def __init__(self):
self.paths = []
self.path = []
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
'''
类似于求三数之和,求四数之和,为了避免重复组合,需要提前进行数组排序
'''
self.paths.clear()
self.path.clear()
# 必须提前进行数组排序,避免重复
candidates.sort()
self.backtracking(candidates, target, 0, 0)
return self.paths
def backtracking(self, candidates: List[int], target: int, sum_: int, start_index: int) -> None:
# Base Case
if sum_ == target:
self.paths.append(self.path[:])
return
# 单层递归逻辑
for i in range(start_index, len(candidates)):
# 剪枝,同39.组合总和
if sum_ + candidates[i] > target:
return
# 跳过同一树层使用过的元素
if i > start_index and candidates[i] == candidates[i-1]:
continue
sum_ += candidates[i]
self.path.append(candidates[i])
self.backtracking(candidates, target, sum_, i+1)
self.path.pop() # 回溯,为了下一轮for loop
sum_ -= candidates[i] # 回溯,为了下一轮for loop
6.分割回文串
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例: 输入: “aab” 输出: [ [“aa”,“b”], [“a”,“a”,“b”] ]
- 切割问题可以抽象为组合问题
- 如何模拟那些切割线
- 切割问题中递归如何终止
- 在递归循环中如何截取子串
- 如何判断回文
class Solution:
def partition(self, s: str) -> List[List[str]]:
res = []
path = []
def backtracking(s, startindex):
if startindex == len(s): # 注意终止条件
res.append(path[:])
return
for i in range(startindex, len(s)):
tmp = s[startindex : i + 1]
if tmp == tmp[::-1]: # 判断子串是不是回文
path.append(tmp)
backtracking(s, i + 1)
path.pop()
# else:
# continue
backtracking(s, 0)
return res
class Solution:
# 定义函数判断回文
def partition(self, s: str) -> List[List[str]]:
res = []
path = []
def backtracking(s, startindex):
if startindex == len(s): # 注意终止条件
res.append(path[:])
return
for i in range(startindex, len(s)):
tmp = s[startindex : i + 1]
if self.is_palindrome(tmp): # 判断子串是不是回文
path.append(tmp)
backtracking(s, i + 1)
path.pop()
backtracking(s, 0)
return res
def is_palindrome(self, s):
i = 0
j = len(s) - 1
while i < j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
7.复原IP地址
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效的 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效的 IP 地址。
示例 1:
- 输入:s = “25525511135”
- 输出:[“255.255.11.135”,“255.255.111.35”]
示例 2:
- 输入:s = “0000”
- 输出:[“0.0.0.0”]
示例 3:
- 输入:s = “1111”
- 输出:[“1.1.1.1”]
示例 4:
- 输入:s = “010010”
- 输出:[“0.10.0.10”,“0.100.1.0”]
示例 5:
- 输入:s = “101023”
- 输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]
提示:
- 0 <= s.length <= 3000
- s 仅由数字组成
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
path = []
res = []
def backtracking(s, startindex):
if startindex == len(s) and len(path) == 4:
res.append('.'.join(path))
return
for i in range(startindex, len(s)):
if len(path)>3:
break
tmp = s[startindex: i + 1]
if (0 <= int(tmp) <= 255 and tmp[0] != '0') or tmp == '0':
path.append(tmp)
print(path)
backtracking(s, i + 1)
path.pop()
backtracking(s, 0)
return res
8.子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
path = []
def backtracking(startindex, nums):
res.append(path[:]) # 收集每一个节点
# if startindex == len(nums): # 可以不写终止条件
# return
for i in range(startindex, len(nums)):
path.append(nums[i])
backtracking(i + 1, nums)
path.pop()
backtracking(0, nums)
return res
9.子集II
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
- 输入: [1,2,2]
- 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]
使用used数组
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
res = []
path = []
nums.sort()
used = [0] * len(nums)
def backtracking(startindex, nums):
res.append(path[:])
# if startindex == len(nums):
# return
for i in range(startindex, len(nums)):
if i > 0 and nums[i] == nums[i - 1] and used[i - 1] == 0:
continue
used[i] = 1
path.append(nums[i])
backtracking(i + 1, nums)
used[i] = 0
path.pop()
backtracking(0, nums)
return res
不使用used数组
本题也可以不使用used数组来去重,因为递归的时候下一个startIndex是i+1而不是0
如果要是全排列的话,每次要从0开始遍历,为了跳过已入栈的元素,需要使用used。
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
res = []
path = []
nums.sort() # 去重需要先对数组进行排序
def backtracking(nums, startIndex):
# 终止条件
res.append(path[:])
if startIndex == len(nums):
return
# for循环
for i in range(startIndex, len(nums)):
# 数层去重
if i > startIndex and nums[i] == nums[i-1]: # 去重
continue
path.append(nums[i])
backtracking(nums, i+1)
path.pop()
backtracking(nums, 0)
return res
10.递增子序列
给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
示例:
- 输入: [4, 6, 7, 7]
- 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
说明:
-
给定数组的长度不会超过15。
-
数组中的整数范围是 [-100,100]。
-
给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况
本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。所以不能使用之前的去重逻辑
同一父节点下的同层上使用过的元素就不能再使用了,每层都重新定义一次uset数组
1 使用set
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
path = []
res = []
def backtracking(startindex, nums):
if len(path) > 1:
res.append(path[:])
uset = set()
for i in range(startindex, len(nums)):
if (path and nums[i] < path[-1]) or nums[i] in uset: # 注意去重判别
continue
uset.add(nums[i])
path.append(nums[i])
backtracking(i + 1, nums)
path.pop()
backtracking(0, nums)
return res
2 使用数组
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
path = []
res = []
def backtracking(startindex, nums):
if len(path) > 1:
res.append(path[:])
uset = [0] * 201 # 使用数组节约空间
for i in range(startindex, len(nums)):
if (path and nums[i] < path[-1]) or uset[nums[i] + 100] == 1:
continue
uset[nums[i] + 100] = 1
path.append(nums[i])
backtracking(i + 1, nums)
path.pop()
backtracking(0, nums)
return res
11.全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
- 输入: [1,2,3]
- 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
排列问题
- 每层都是从0开始搜索而不是startIndex
- 需要used数组记录path里都放了哪些元素了
同一树枝上不能重复使用
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
path = []
res = []
used = [0] * len(nums)
def backtracking(nums):
if len(path) == len(nums):
res.append(path[:])
return
for i in range(0, len(nums)):
if used[i] == 1:
continue
used[i] = 1
path.append(nums[i])
backtracking(nums)
path.pop()
used[i] = 0
backtracking(nums)
return res
12.全排列 II
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
- 输入:nums = [1,1,2]
- 输出: [[1,1,2], [1,2,1], [2,1,1]]
示例 2:
- 输入:nums = [1,2,3]
- 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示:
- 1 <= nums.length <= 8
- -10 <= nums[i] <= 10
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
path = []
res = []
nums.sort()
used = [0] * len(nums)
def backtracking(nums):
if len(path) == len(nums):
res.append(path[:])
return
for i in range(len(nums)):
if used[i] == 0: # 树枝去重
if i > 0 and nums[i] == nums[i - 1] and used[i - 1] == 0: # 树层去重
continue
used[i] = 1
path.append(nums[i])
backtracking(nums)
path.pop()
used[i] = 0
backtracking(nums)
return res
回溯算法去重问题的另一种写法
13.重新安排行程
给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。
提示:
- 如果存在多种有效的行程,请你按字符自然排序返回最小的行程组合。例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前
- 所有的机场都用三个大写字母表示(机场代码)。
- 假定所有机票至少存在一种合理的行程。
- 所有的机票必须都用一次 且 只能用一次。
示例 1:
- 输入:[[“MUC”, “LHR”], [“JFK”, “MUC”], [“SFO”, “SJC”], [“LHR”, “SFO”]]
- 输出:[“JFK”, “MUC”, “LHR”, “SFO”, “SJC”]
示例 2:
- 输入:[[“JFK”,“SFO”],[“JFK”,“ATL”],[“SFO”,“ATL”],[“ATL”,“JFK”],[“ATL”,“SFO”]]
- 输出:[“JFK”,“ATL”,“JFK”,“SFO”,“ATL”,“SFO”]
- 解释:另一种有效的行程是 [“JFK”,“SFO”,“ATL”,“JFK”,“ATL”,“SFO”]。但是它自然排序更大更靠后。
这道题目有几个难点:
- 一个行程中,如果航班处理不好容易变成一个圈,成为死循环
- 有多种解法,字母序靠前排在前面,让很多同学望而退步,如何记录映射关系呢 ?
- 使用回溯法(也可以说深搜) 的话,那么终止条件是什么呢?
- 搜索的过程中,如何遍历一个机场所对应的所有机场
![332.重新安排行程1](https://i-blog.csdnimg.cn/blog_migrate/e767c3e95983f98584a798c9a9d35885.png)
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
# defaultdic(list) 是为了方便直接append
tickets_dict = defaultdict(list) # tickets_dict = {key1:[], key2:[]}
for item in tickets:
tickets_dict[item[0]].append(item[1])
# 给每一个机场的到达机场排序,小的在前面,在回溯里首先被pop(0)出去
# 这样最先找的的path就是排序最小的答案,直接返回
for airport in tickets_dict: tickets_dict[airport].sort()
path = ["JFK"] # path 就是res
def backtracking(start_point):
if len(path) == len(tickets) + 1:
return True
for i in tickets_dict[start_point]:
end_point = tickets_dict[start_point].pop(0) # 删除已经遍历过的机票,避免死循环
path.append(end_point)
if backtracking(end_point):
return True
path.pop()
tickets_dict[start_point].append(end_point)
backtracking("JFK")
return path
14.N皇后
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例 1:
- 输入:n = 4
- 输出:[[“.Q…”,“…Q”,“Q…”,“…Q.”],[“…Q.”,“Q…”,“…Q”,“.Q…”]]
- 解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
- 输入:n = 1
- 输出:[[“Q”]]
了棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
if not n: return []
board = [['.'] * n for _ in range(n)] # 定义棋盘
res = []
def isVaild(board, col, row):
for i in range(row): # 判断列
if board[i][col] == 'Q':return False
i, j = row - 1, col - 1 # 判断左上角
while i >= 0 and j >= 0:
if board[i][j] == 'Q': return False
i, j = i - 1, j - 1 # 判断右上角
i, j = row - 1, col + 1
while i >= 0 and j < n:
if board[i][j] == 'Q': return False
i, j = i - 1, j + 1
return True
def backtracking(n, board, row):
if row == n: # 终止条件:找到了最后一行
tmp_res = [] # 转化数据结构,添加到结果
for tmp in board:
tmp_str = ''.join(tmp)
tmp_res.append(tmp_str)
res.append(tmp_res)
# return
for col in range(n):
if isVaild(board, col, row):
board[row][col] = 'Q'
backtracking(n, board, row + 1)
board[row][col] = '.'
backtracking(n, board, 0)
return res
15.解数独
编写一个程序,通过填充空格来解决数独问题。
提示:
- 给定的数独序列只包含数字 1-9 和字符 ‘.’ 。
- 你可以假设给定的数独只有唯一解。
- 给定数独永远是 9x9 形式的。
二维递归问题
本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深
递归函数的返回值需要是bool类型,为什么呢?
因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
def isVaild(board, row, col, val):
for i in range(9): # 检查行列
if board[row][i] == str(val):
return False
if board[i][col] == str(val):
return False
startrow, startcol = (row // 3) * 3 , (col // 3) * 3
for i in range(startrow, startrow + 3):
for j in range(startcol, startcol + 3):
if board[i][j] == str(val):
return False
return True
def backtracking(board): # 二维递归
for row in range(9):
for col in range(9):
if board[row][col] != '.': continue # 如果空格有数字则跳过
for val in range(1,10):
if not isVaild(board, row, col, val):
continue
board[row][col] = str(val)
if backtracking(board): return True
board[row][col] = '.'
return False # 遍历完九个数字都找不到
return True
backtracking(board)
return False
if board[i][col] == str(val):
return False
startrow, startcol = (row // 3) * 3 , (col // 3) * 3
for i in range(startrow, startrow + 3):
for j in range(startcol, startcol + 3):
if board[i][j] == str(val):
return False
return True
def backtracking(board): # 二维递归
for row in range(9):
for col in range(9):
if board[row][col] != ‘.’: continue # 如果空格有数字则跳过
for val in range(1,10):
if not isVaild(board, row, col, val):
continue
board[row][col] = str(val)
if backtracking(board): return True
board[row][col] = ‘.’
return False # 遍历完九个数字都找不到
return True
backtracking(board)