回溯法概况 回溯法,一般可以解决如下几种问题:
组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等
1 2 3 4 5 6 7 8 9 10 11 12 void backtracking(参数) { if (终止条件) { 存放结果; return; } for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) { 处理节点; backtracking(路径,选择列表); // 递归 回溯,撤销处理结果 } }
startIndex
在回溯中的作用与用法在 回溯(Backtracking) 相关的算法(如 子集、组合、排列、分割问题 )中,startIndex
的作用非常重要,它决定了递归搜索的范围,影响 是否允许重复元素 以及 是否需要剪枝 。
1. startIndex
作用 (1) 控制搜索范围,避免重复
startIndex
确保搜索从当前索引或后面的元素开始,防止回溯时重复选择之前已经选择过的元素 。
适用于子集、组合问题 ,但 不适用于排列问题 (排列问题允许元素交换顺序)。
(2) 递归层次推进
递归进入下一层时,startIndex
增加,确保搜索不回头,保证组合的唯一性 。
startIndex
的值通常是 i + 1
,表示当前元素 nums[i]
选中后,下次从 i+1
位置开始搜索。
2. startIndex
在不同回溯问题中的使用 (1) 组合问题(startIndex
需要) 问题:从 [1,2,3]
中选出长度为 2 的组合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution : def combine (self, nums, k ): result = [] self .backtracking(nums, k, 0 , [], result) return result def backtracking (self, nums, k, startIndex, path, result ): if len (path) == k: result.append(path[:]) return for i in range (startIndex, len (nums)): path.append(nums[i]) self .backtracking(nums, k, i + 1 , path, result) path.pop() sol = Solution() print (sol.combine([1 , 2 , 3 ], 2 ))
📌 startIndex
的作用
避免重复选择 :
startIndex
让搜索从 i+1
位置开始,保证组合中的元素不重复使用。
输出结果
**不会出现 [2,1]
或 [3,1]
**,因为 startIndex
控制了搜索方向,确保元素不会向前选择。
(2) 排列问题(startIndex
不需要) 问题:从 [1,2,3]
中选出所有排列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution : def permute (self, nums ): result = [] self .backtracking(nums, [], result) return result def backtracking (self, nums, path, result ): if len (path) == len (nums): result.append(path[:]) return for i in range (len (nums)): if nums[i] in path: continue path.append(nums[i]) self .backtracking(nums, path, result) path.pop() sol = Solution() print (sol.permute([1 , 2 , 3 ]))
📌 为什么不用 startIndex
(3) 子集问题(startIndex
控制唯一性) 问题:求 [1,2,3]
的所有子集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution : def subsets (self, nums ): result = [] self .backtracking(nums, 0 , [], result) return result def backtracking (self, nums, startIndex, path, result ): result.append(path[:]) for i in range (startIndex, len (nums)): path.append(nums[i]) self .backtracking(nums, i + 1 , path, result) path.pop() sol = Solution() print (sol.subsets([1 , 2 , 3 ]))
📌 startIndex
作用
保证子集中的元素不重复
避免 [2,1]
这样的情况出现
输出结果 1 [[], [1 ], [1 ,2 ], [1 ,2 ,3 ], [1 ,3 ], [2 ], [2 ,3 ], [3 ]]
3. startIndex
总结
问题
startIndex
是否需要
作用
组合(Combination)
✅ 需要
避免重复选择,控制搜索范围
排列(Permutation)
❌ 不需要
排列允许交换顺序,不限制搜索方向
子集(Subsets)
✅ 需要
避免重复选择,控制唯一性
回文分割(Palindrome Partitioning)
✅ 需要
控制切割点,避免重复分割
🚀 记住这条规则
如果问题允许元素交换顺序(如排列) → startIndex
不需要 。
如果问题需要保证元素顺序(如组合、子集) → startIndex
需要 。
如果是分割问题(如回文分割) → startIndex
也需要,控制切割点。
4. startIndex
与 used
数组的区别 有时候,在排列问题中,我们会用 used
数组代替 startIndex
:
1 used = [False ] * len (nums)
startIndex
作用 → 限制搜索范围,保证递增、不重复选择 (用于组合、子集)。
used
作用 → 记录某个元素是否被用过,避免同层重复选择 (用于排列、含重复元素的组合)。
例子:含重复元素的排列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution : def permuteUnique (self, nums ): result = [] used = [False ] * len (nums) nums.sort() self .backtracking(nums, [], used, result) return result def backtracking (self, nums, path, used, result ): if len (path) == len (nums): result.append(path[:]) return for i in range (len (nums)): if used[i]: continue if i > 0 and nums[i] == nums[i - 1 ] and not used[i - 1 ]: continue used[i] = True path.append(nums[i]) self .backtracking(nums, path, used, result) path.pop() used[i] = False
77. 组合 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。
直接想到的是for循环嵌套,但是如果k很大的话就会需要很多个for循环进行嵌套,这显然是不可能的,所以需要用到回溯算法. 递归来做层叠嵌套(可以理解是开k层for循环),每一次的递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了。 但是回溯过程可以进行剪枝优化,如下图,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。 优化过程如下: 已经选择的元素个数:path.size(); 还需要的元素个数为: k - path.size(); 在集合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]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { List<List<Integer>> result = new ArrayList <>(); LinkedList<Integer> path = new LinkedList <>(); public List<List<Integer>> combine (int n, int k) { backtracking(n, k, 1 ); return result; } public void backtracking (int n, int k, int startIndex) { if (path.size() == k){ result.add(new ArrayList <>(path)); return ; } for (int i = startIndex; i <= n - (k - path.size()) + 1 ; i++){ path.add(i); backtracking(n, k, i+1 ); path.removeLast(); } } }
216. 组合总和 III 找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
只使用数字1到9 每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { List<List<Integer>> result = new ArrayList <>(); LinkedList<Integer> path = new LinkedList <>(); public List<List<Integer>> combinationSum3 (int k, int n) { backTracking(n, k, 1 , 0 ); return result; } private void backTracking (int targetSum, int k, int startIndex, int sum) { if (sum > targetSum){ return ; } if (path.size() == k){ if (sum == targetSum) result.add(new ArrayList <>(path)); return ; } for (int i = startIndex; i <= 9 - (k - path.size())+1 ; i++){ path.add(i); sum += i; backTracking(targetSum, k, i+1 , sum); path.removeLast(); sum -= i; } } }
17.电话号码的字母组合 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { List<String> list = new ArrayList <>(); public List<String> letterCombinations (String digits) { if (digits == null || digits.length() == 0 ){ return list; } String[] numString = {"" , "" , "abc" ,"def" , "ghi" , "jkl" , "mno" , "pqrs" , "tuv" , "wxyz" }; backTracking(digits, numString, 0 ); return list; } StringBuilder temp = new StringBuilder (); public void backTracking (String digits, String[] numString, int num) { if (num == digits.length()){ list.add(temp.toString()); return ; } String str = numString[digits.charAt(num) - '0' ]; for (int i = 0 ; i < str.length(); i++){ temp.append(str.charAt(i)); backTracking(digits, numString, num+1 ); temp.deleteCharAt(temp.length() - 1 ); } } }
39. 组合总和 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 对于给定的输入,保证和为 target 的不同组合数少于 150 个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public List<List<Integer>> combinationSum (int [] candidates, int target) { List<List<Integer>> res = new ArrayList <>(); Arrays.sort(candidates); backtracking(res, new ArrayList <>(), candidates, target, 0 , 0 ); return res; } public void backtracking (List<List<Integer>> res, List<Integer> path, int [] candidates, int target, int sum, int idx) { if (sum == target){ res.add(new ArrayList <>(path)); return ; } for (int i=idx; i < candidates.length; i++){ if (sum + candidates[i] > target) break ; path.add(candidates[i]); backtracking(res, path, candidates, target, sum+candidates[i], i); path.remove(path.size() - 1 ); } } }
40.组合总和II 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明: 所有数字(包括目标数)都是正整数。解集不能包含重复的组合。
示例 1: 输入: candidates = [10,1,2,7,6,1,5]
, target = 8, 所求解集为:
1 2 3 4 5 6 [ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ]
这道题目和39.组合总和 (opens new window)如下区别: 本题candidates 中的每个数字在每个组合中只能使用一次。 本题数组candidates的元素是有重复的,而39.组合总和 (opens new window)是无重复元素的数组candidates
我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。 为了理解去重我们来举一个例子,candidates = [1, 1, 2], target = 3,(方便起见candidates已经排序了) 强调一下,树层去重的话,需要对数组排序!
如何判断同一树层上元素(相同的元素)是否使用过了呢。 如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { LinkedList<Integer> path = new LinkedList <>(); List<List<Integer>> ans = new ArrayList <>(); boolean [] used; int sum = 0 ; public List<List<Integer>> combinationSum2 (int [] candidates, int target) { used = new boolean [candidates.length]; Arrays.fill(used, false ); Arrays.sort(candidates); backtracking(candidates, target, 0 ); return ans; } private void backtracking (int [] candidates, int target, int startIndex) { if (sum == target){ ans.add(new ArrayList (path)); } for (int i = startIndex; i < candidates.length; i++){ if (sum + candidates[i] > target){ break ; } if (i > 0 && candidates[i] == candidates[i - 1 ] && !used[i-1 ]){ continue ; } used[i] = true ; sum += candidates[i]; path.add(candidates[i]); backtracking(candidates, target, i+1 ); used[i] = false ; sum -= candidates[i]; path.removeLast(); } } }
下面这个是不使用used数组的版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { LinkedList<Integer> path = new LinkedList <>(); List<List<Integer>> res = new ArrayList <>(); int sum = 0 ; public List<List<Integer>> combinationSum2 (int [] candidates, int target) { Arrays.sort(candidates); backtracking(candidates, target, 0 ); return res; } private void backtracking (int [] candidates, int target, int startIndex) { if (sum == target){ res.add(new ArrayList <>(path)); } for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++){ if (i > startIndex && candidates[i] == candidates[i-1 ]){ continue ; } sum += candidates[i]; path.add(candidates[i]); backtracking(candidates, target, i+1 ); int temp = path.getLast(); sum -= temp; path.removeLast(); } } }
131. 分割回文串 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
示例 1: 输入:s = “aab” 输出:[[“a”,”a”,”b”],[“aa”,”b”]]
示例 2: 输入:s = “a” 输出:[[“a”]]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution : def partition (self, s: str ) -> List [List [str ]]: result = [] self .backtracking(s, 0 , [], result); return result def backtracking (self, s, start_index, path, result ): if start_index == len (s): result.append(path[:]) return for i in range (start_index, len (s)): if self .is_palindrome(s, start_index, i): path.append(s[start_index:i+1 ]) self .backtracking(s, i+1 , path, result) path.pop() def is_palindrome (self, s:str , start:int , end:int ) -> bool : i:int = start j:int = end while i<j: if s[i] != s[j]: return False i += 1 j -= 1 return True
上面的代码还存在一定的优化空间, 在于如何更高效的计算一个子字符串是否是回文字串。上述代码isPalindrome函数运用双指针的方法来判定对于一个字符串s, 给定起始下标和终止下标, 截取出的子字符串是否是回文字串。但是其中有一定的重复计算存在: 例如给定字符串”abcde”, 在已知”bcd”不是回文字串时, 不再需要去双指针操作”abcde”而可以直接判定它一定不是回文字串。 具体来说, 给定一个字符串s, 长度为n, 它成为回文字串的充分必要条件是s[0] == s[n-1]且s[1:n-1]是回文字串。 大家如果熟悉动态规划这种算法的话, 我们可以高效地事先一次性计算出, 针对一个字符串s, 它的任何子串是否是回文字串, 然后在我们的回溯函数中直接查询即可, 省去了双指针移动判定这一步骤.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution : def partition (self, s: str ) -> List [List [str ]]: result = [] isPalindrome = [[False ] * len (s) for _ in range (len (s))] self .computePalindrome(s, isPalindrome) self .backtracking(s, 0 , [], result, isPalindrome) return result def backtracking (self, s, startIndex, path, result, isPalindrome ): if startIndex >= len (s): result.append(path[:]) return for i in range (startIndex, len (s)): if isPalindrome[startIndex][i]: substring = s[startIndex:i+1 ] path.append(substring) self .backtracking(s, i+1 , path, result, isPalindrome) path.pop() def computePalindrome (self, s, isPalindrome ): for i in range (len (s) - 1 , -1 , -1 ): for j in range (i, len (s)): if j == i: isPalindrome[i][j] = True elif j - i == 1 : isPalindrome[i][j] = (s[i] == s[j]) else : isPalindrome[i][j] = (s[i] == s[j] and isPalindrome[i+1 ][j-1 ])
93. 复原 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 地址。 给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 ‘.’ 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution : def restoreIpAddresses (self, s: str ) -> List [str ]: result = [] self .backtracking(s, 0 , 0 , "" , result) return result def backtracking (self, s, start_index, point_num, current, result ): if point_num == 3 : if self .is_valid(s, start_index, len (s) - 1 ): current += s[start_index:] result.append(current) return for i in range (start_index, len (s)): if self .is_valid(s, start_index, i): sub = s[start_index:i+1 ] self .backtracking(s, i+1 , point_num+1 , current+sub+'.' , result) else : break def is_valid (self, s, start, end ): if start > end: return False if s[start] == '0' and start != end: return False num = 0 for i in range (start, end+1 ): if not s[i].isdigit(): return False num = num * 10 + int (s[i]) if num > 255 : return False return True
78. 子集 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
1 2 3 4 5 6 7 示例 1: 输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] 示例 2: 输入:nums = [0] 输出:[[],[0]]
这道题主要就是要遍历所有的路径 而不需要减枝
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution : def subsets (self, nums: List [int ] ) -> List [List [int ]]: result = [] path = [] self .backtracking(nums, 0 , path, result) return result def backtracking (self, nums, startIndex, path, result ): result.append(path[:]) for i in range (startIndex, len (nums)): path.append(nums[i]) self .backtracking(nums, i+1 , path, result) path.pop()
90.子集II 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 说明:解集不能包含重复的子集。
示例:
1 2 输入: [1,2,2] 输出: [ [2], [1], [1,2,2], [2,2], [1,2], []]
这道题目和78.子集 (opens new window)区别就是集合里有重复元素了,而且求取的子集要去重。
491. 非递减子序列 给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
1 2 3 4 5 6 7 示例 1: 输入:nums = [4,6,7,7] 输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]] 示例 2: 输入:nums = [4,4,3,2,1] 输出:[[4,4]]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution : def findSubsequences (self, nums: List [int ] ) -> List [List [int ]]: result = [] path = [] self .backtracking(nums, 0 , path, result) return result def backtracking (self, nums, startIndex, path, result ): if len (path) > 1 : result.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]) self .backtracking(nums, i + 1 , path, result) path.pop()
也可以用哈希表 也就是数组去重 只要标记每个位置有没有用过就好了
332. 重新安排行程 给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。 例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前。 假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。 输入:tickets = [[“MUC”,”LHR”],[“JFK”,”MUC”],[“SFO”,”SJC”],[“LHR”,”SFO”]] 输出:[“JFK”,”MUC”,”LHR”,”SFO”,”SJC”]
主要是构建邻接表,然后进行深度优先搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution : def findItinerary (self, tickets: List [List [str ]] ) -> List [str ]: self .adj = {} tickets.sort(key=lambda x:x[1 ]) for u, v in tickets: if u in self .adj: self .adj[u].append(v) else : self .adj[u] = [v] self .result = [] self .dfs("JFK" ) return self .result[::-1 ] def dfs (self, s ): while s in self .adj and len (self .adj[s]) > 0 : v = self .adj[s][0 ] self .adj[s].pop(0 ) self .dfs(v) self .result.append(s)
N皇后 按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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 ): if row == n: board = generateBoard() solutions.append(board) else : for i in range (n): 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
37. 解数独 编写一个程序,通过填充空格来解决数独问题。 数独的解法需 遵循如下规则: 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图) 数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
棋盘搜索问题可以使用回溯法暴力搜索,只不过这次我们要做的是二维递归。N皇后问题 (opens new window)是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。 本题就不一样了,本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution : def solveSudoku (self, board: List [List [str ]] ) -> None : """ Do not return anything, modify board in-place instead. """ row_used = [set () for _ in range (9 )] col_used = [set () for _ in range (9 )] box_used = [set () for _ in range (9 )] for row in range (9 ): for col in range (9 ): num = board[row][col] if num == "." : continue row_used[row].add(num) col_used[col].add(num) box_used[(row // 3 )*3 +col//3 ].add(num) self .backtracking(0 , 0 , board, row_used, col_used, box_used) def backtracking (self, row, col, board, row_used, col_used, box_used ): if row == 9 : return True next_row, next_col = (row, col+1 ) if col < 8 else (row + 1 , 0 ) if board[row][col] != "." : return self .backtracking(next_row, next_col, board, row_used, col_used, box_used) for num in map (str , range (1 ,10 )): if (num not in row_used[row] and num not in col_used[col] and num not in box_used[(row//3 )*3 +col//3 ]): board[row][col] = num row_used[row].add(num) col_used[col].add(num) box_used[(row//3 )*3 +col//3 ].add(num) if self .backtracking(next_row, next_col, board, row_used, col_used, box_used): return True board[row][col] = "." row_used[row].remove(num) col_used[col].remove(num) box_used[(row//3 )*3 +col//3 ].remove(num) return False