LeetCode刷题小记 八、【回溯算法】

1.回溯算法

写在前面

本系列笔记主要作为笔者刷题的题解,所用的语言为Python3,若于您有助,不胜荣幸。

1.1回溯算法基本知识

回溯算法也叫回溯搜索法,是一种搜索方法,回溯算法虽然难以理解,但是其并不高效,因为回溯的本质是穷举,穷举所有的可能,然后限制条件获得我们的想要的结果。想要回溯算法高效一点可以添加一些剪枝的操作,但是这并不能改变回溯算法的本质。

回溯算法一般用于解决以下的问题:

  • 组合问题:N个数里面按照一定规则找出k个数的集合
  • 切割问题:一个字符串按照一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少种符合条件的子集
  • 排列问题:N个数按照一定规则全排列,有多少种排列方式
  • 棋盘问题:N皇后,解数独等。

回溯算法一般具有以下的模板:

  • 回溯函数模板返回值及参数

回溯算法中函数的返回值一般为空None,回溯算法的参数不会像二叉树递归那样容易确定,一般是先写逻辑,然后需要什么参数的时候再进行修改。

回溯函数的函数定义一般如下:

def backtracking(参数) -> None:
  • 回溯函数的终止条件

回溯函数一定是树形结构的,所有一定有终止条件,满足一定的终止条件的时候,我们就应该进行返回了,一般是我们遇到叶子节点的时候,这时候就要收集结果了,所以回溯函数终止条件的伪代码如下:

if 终止条件:
    存放结果
    return
  • 回溯搜索的遍历过程

回溯法一般是再集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成了树的深度。

回溯函数遍历过程伪代码如下:

for 选择:本层集合中元素:
    处理节点
    backtracking(路径, 选择列表)
    回溯,撤销处理结果

所以回溯法的整体结构如下:

def backtracking(参数) -> None:
    if 终止条件:
        存放结果
        return
    for 选择:本层集合中元素(树中节点孩子的数量就是集合的大小):
        处理节点
        backtracking(路径, 选择列表) # 递归
        回溯,撤销处理结果

回溯算法相当于是递归算法里面还嵌套了for循环。

1.2组合问题

77. 组合

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

思路:中说到回溯法解决的问题都可以抽象为树形结构(N叉树),用树形结构来理解回溯就容易多了,我们可以将回溯算法抽象成下列的一个二叉树,当我们在叶子节点的时候,我们就可以收获结果了,就得到了所有的组合。

Image

如何确定终止条件呢?遇到叶子节点的时候就终止了,其实这个时候我们用于保存中间结果的path的长度为k,所以我们可以进行判断if len(path) == k:,如果满足条件则遇到叶子节点可以保存结果。

class Solution:
    def __init__(self):
        self.res: List[List[int]] = []
        self.path: List[int] = []
    
    def backtracking(self, n: int, k: int, startIndex: int) -> None:
        if len(self.path) == k:                 # 终止条件,收获结果
            self.res.append(self.path[:])       # 这里需要进行copy
            return
        for i in range(startIndex, n):          # 循环遍历
            self.path.append(i+1)
            self.backtracking(n, k, i+1)
            self.path.pop()

    def combine(self, n: int, k: int) -> List[List[int]]:
        self.backtracking(n, k, 0)
        return self.res

1.3组合问题的剪枝操作

77. 组合

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

思路:回溯算法的剪枝一般是在回溯搜索的过程中进行的,来举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。

Image

所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置

这个剪枝的条件可以则么看:

当前已经选择的元素个数为len(self.path),还需要选择的元素个数为k-len(self.path),列表中剩余元素的个数为(n-i),剩余元素的个数一定要大于等于所需要的元素的个数即(n-i) >= k - len(self.path),则有i <= n-(k-len(self.size)),这里由于range()函数是一个左闭右开的函数,所以我们这里的值应该设为range(startIndex, n-(k-len(self.path))+1)

class Solution:
    def __init__(self):
        self.path: List[int] = []
        self.res: List[List[int]] = []

    def backtracking(self, n: int, k: int, startIndex: int) -> None:
        if len(self.path) == k:
            self.res.append(self.path[:])
            return 
        for i in range(startIndex, n-(k-len(self.path))+1):     # 剪枝操作
            self.path.append(i+1)
            self.backtracking(n, k, i+1)
            self.path.pop()

    def combine(self, n: int, k: int) -> List[List[int]]:
        self.backtracking(n, k, 0)
        return self.res

1.4组合总和III

216. 组合总和 III

找出所有相加之和为 nk 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

思路k控制树的深度,而n控制树的宽度

Image
class Solution:
    def __init__(self):
        self.path: List[int] = []
        self.res: List[List[int]] = []
    
    def backtracking(self, targetSum: int, Sum: int, k: int, startIndex: int) -> None:
        if len(self.path) == k:                 # 终止条件
            if Sum == targetSum:                # 收获结果
                self.res.append(self.path[:])
            return
        for i in range(startIndex, 9-(k-len(self.path))+1):                      # 循环遍历,进行剪枝限制至少有k个元素
            self.path.append(i+1)
            self.backtracking(targetSum, Sum+i+1, k, i+1)   # 显式回溯
            self.path.pop()

    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        self.backtracking(n, 0, k, 0)
        return self.res

1.5电话号码的字母组合

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

1 1 1 2 a b c 2_{abc} 2abc 3 d e f 3_{def} 3def
4 g h i 4_{ghi} 4ghi 5 j k l 5_{jkl} 5jkl 6 m n o 6_{mno} 6mno
7 p q r s 7_{pqrs} 7pqrs 8 t u v 8_{tuv} 8tuv 9 w x y z 9_{wxyz} 9wxyz
∗ * 0 0 0 #

思路:我们这里遍历的是两个集合,所以不需要startIndex来去重,但是需要一个index来指向当前遍历的数字

解法一:使用list来保存结果

class Solution:
    def __init__(self):
        self.str_map: List[str] = [
                        "",         # 0
                        "",         # 1
                        "abc",      # 2
                        "def",      # 3
                        "ghi",      # 4
                        "jkl",      # 5
                        "mno",      # 6
                        "pqrs",     # 7
                        "tuv",      # 8
                        "wxyz"      # 9
                        ]
        self.res: List[str] = []
        self.path: List[str] = []
    
    def backtracking(self, digits: str, index: int) -> None:
        if index == len(digits):
            self.res.append(''.join(self.path))
            return
        
        letters: str = self.str_map[int(digits[index])]
        for char in letters:
            self.path.append(char)
            self.backtracking(digits, index+1)
            self.path.pop()
        

    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []
        self.backtracking(digits, 0)
        return self.res

解法二:精简回溯,将s作为参数

class Solution:
    def __init__(self):
        self.str_map: List[str] = [
                        "",         # 0
                        "",         # 1
                        "abc",      # 2
                        "def",      # 3
                        "ghi",      # 4
                        "jkl",      # 5
                        "mno",      # 6
                        "pqrs",     # 7
                        "tuv",      # 8
                        "wxyz"      # 9
                        ]
        self.res: List[str] = []

    
    def backtracking(self, digits: str, index: int, s: str) -> None:
        if index == len(digits):
            self.res.append(s)
            return
        
        letters: str = self.str_map[int(digits[index])]
        for char in letters:
            self.backtracking(digits, index+1, s+char)
        
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []
        self.backtracking(digits, 0, "")
        return self.res

1.6组合总和

39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

思路:对于前面进行选择的元素,我们可以重复地选取,对于后面选取的元素,我们不能进行重复选取,我们需要用一个index参数来控制每次选取的位置,这个参数和startIndex还不太一样,每次更新的时候,我们需要设置index=i

对于组合问题,什么时候需要starIndex来控制for循环的起始位置,什么时候不需要呢?举个例子,如果是一个集合来求组合的话,就需要startIndex,如果是多个集合来求组合的话,各个集合之间互不影响,这时就不需要用startIndex了。

class Solution:
    def __init__(self):
        self.path: List[int] = []
        self.res: List[List[int]] = []
    
    def backtracking(self, candidates: List[int], targetSum: int, Sum: int, index: int) -> None:
        if Sum > targetSum:         # 剪枝
            return 
        elif Sum == targetSum:      # 叶子节点并收获结果
            self.res.append(self.path[:])
            return 
        
        for i in range(index, len(candidates)):
            self.path.append(candidates[i])
            self.backtracking(candidates, targetSum, Sum+candidates[i], i)          # 注意这里index的修改为i
            self.path.pop()

    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        if not candidates:
            return []
        self.backtracking(candidates, target, 0, 0)
        return self.res

1.7组合总和II

40. 组合总和 II

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

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

思路:这道题已经明确表示了会出现重复的元素,所以我们需要进行去重操作,如何进行去重呢?我们先对整个candidates进行排序,如果后一个遍历的元素,等于前一个遍历的元素,那么则跳过当前遍历的过程,因为对前一个元素的处理过程相当于已经处理过当前的元素了。

class Solution:
    def __init__(self):
        self.res: List[List[int]] = []
        self.path: List[int] = []
    
    def backtracking(self, candidates: List[int], targetSum: int, Sum: int, startIndex: int) -> None:
        if Sum > targetSum:                                                 # 剪枝
            return 
        elif Sum == targetSum:
            self.res.append(self.path[:])
            return 

        for i in range(startIndex, len(candidates)):
            if i > startIndex and candidates[i] == candidates[i-1]:          # 要对同一树层使用过的元素进行跳过,进行树层去重,跳过当前处理的元素
                continue
            self.path.append(candidates[i])
            self.backtracking(candidates, targetSum, Sum+candidates[i], i+1)        
            self.path.pop()

    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        if not candidates:
            return []
        candidates.sort()                                           # 首先让当前元素有序
        self.backtracking(candidates, target, 0, 0)
        return self.res

1.8分割回文串

131. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串(回文串是向前和向后读都相同的字符串) 。返回 s 所有可能的分割方案。

class Solution:
    def __init__(self):
        self.path: List[str] = []
        self.res: List[List[str]] = []
    
    def isPalidrome(self, s: str, start: int, end: int) -> bool:
        """
        判断是否是回文数
        """
        if start > end:
            return False
        while start < end:
            if s[start] == s[end]:
                start += 1
                end -= 1
            else:
                return False
        return True
    
    def backtracking(self, s: str, startIndex: int) -> None:
        if startIndex == len(s):
            self.res.append(self.path[:])
        
        for i in range(startIndex, len(s)):
            if self.isPalidrome(s, startIndex, i):      # 如果是回文串则加入到中间结果中
                self.path.append(s[startIndex:i+1])     # 注意这里的区间是左闭右开
                self.backtracking(s, i+1)
                self.path.pop()
            else:
                continue

    def partition(self, s: str) -> List[List[str]]:
        if not s:
            return []
        self.backtracking(s, 0)
        return self.res

1.9复原IP地址

93. 复原 IP 地址

有效 IP 地址 正好由四个整数(每个整数位于 0255 之间组成,且不能含有前导 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 中的任何数字。你可以按 任何 顺序返回答案。

class Solution:
    def __init__(self):
        self.path: List[str] = []
        self.res: List[str] = []
    
    def isVaild(self, s: str, start: int, end: int) -> bool:
        """
        判断切割是否合法
        """
        if end - start >= 1 and s[start] == '0':
            return False
        return True if int(s[start:end+1]) <= 255 else False
    
    def backtracking(self, s: str, startIndex: int) -> None:
        if len(self.path) == 4 and startIndex == len(s):        # 终止条件
            self.res.append('.'.join(self.path))
            return
        
        for i in range(startIndex, len(s)):
            if self.isVaild(s, startIndex, i):
                self.path.append(s[startIndex:i+1])
                self.backtracking(s, i+1)
                self.path.pop()
            else:
                continue
            
    def restoreIpAddresses(self, s: str) -> List[str]:
        if not s:
            return []
        self.backtracking(s, 0)
        return self.res

1.10子集问题

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的

子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

思路:在解决子集问题的时候,我们需要时刻进行结果的收获,一个问题的就是,我们需要收获的结果不全是叶子节点,我们只要遇到结果就进行收获,所以收获节点会放在终止条件之前。

class Solution:
    def __init__(self):
        self.path: List[int] = []
        self.res: List[List[int]] = []
    
    def backtracking(self, nums: List[int], startIndex: int) -> None:
        self.res.append(self.path[:])           # 只要是节点就满足条件,收获结果在终止条件之前
        if startIndex == len(nums):
            return 
        for i in range(startIndex, len(nums)):
            self.path.append(nums[i])
            self.backtracking(nums, i+1)
            self.path.pop()

    def subsets(self, nums: List[int]) -> List[List[int]]:
        self.backtracking(nums, 0)
        return self.res

1.11子集II

90. 子集 II

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的

子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

思路:这道题涉及到去重,我们一般有两种去重的思路,第一种思路是直接对子集进行排序,判断后一个元素是否等于前一个元素,如果是则表明已经处理过相同的元素,则可以直接跳过;第二种思路是用一个set来完成去重,set可以用于树层的去重,在每一层我都建立一个set,如果在当前层中遍历的元素在这个set中,则表明已经使用过该元素,则跳过,反之则继续使用。

解法一:回溯+排序去重

class Solution:
    def __init__(self):
        self.path: List[int] = []
        self.res: List[List[int]] = []
    
    def backtracking(self, nums: List[int], startIndex: int) -> None:
        self.res.append(self.path[:])
        if startIndex == len(nums):
            return
        for i in range(startIndex, len(nums)):
            if i > startIndex and nums[i] == nums[i-1]:             # 进行排序去重
                continue
            else:
                self.path.append(nums[i])
                self.backtracking(nums, i+1)
                self.path.pop()

    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        self.backtracking(nums, 0)
        return self.res

解法二:回溯+set去重

class Solution:
    def __init__(self):
        self.path: List[int] = []
        self.res: List[List[int]] = []
    
    def backtracking(self, nums: List[int], startIndex: int) -> None:
        self.res.append(self.path[:])
        if startIndex == len(nums):
            return
        uset: set = set()
        for i in range(startIndex, len(nums)):
            if nums[i] in uset:             # 用set进行去重
                continue
            else:
                uset.add(nums[i])
                self.path.append(nums[i])
                self.backtracking(nums, i+1)
                self.path.pop()

    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        nums.sort()							# 用set去重也要排序
        self.backtracking(nums, 0)
        return self.res

1.12非递减子序列

491. 非递减子序列

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

思路:我们收集结果的时候,也是在终止条件之前,唯一的要求就是当前的至少有两个元素,并且我们还要对同一树层进行去重,这里不能使用排序比较大小去重,因为我们不能对这个序列进行排序,如果进行排序了那么整个结果的顺序就改变了,所以我们只能用set进行去重。

class Solution:
    def __init__(self):
        self.path: List[int] = []
        self.res: List[List[int]] = []

    def backtracking(self, nums: List[int], startIndex: int) -> None:
        if len(self.path) >= 2:
            self.res.append(self.path[:])
        if startIndex == len(nums):
            return 
        uset: set = set()
        for i in range(startIndex, len(nums)):
            if (self.path and nums[i] < self.path[-1]) or nums[i] in uset:      # 判断是否是非递减序列,或者在当前层中当前元素没有被使用
                continue
            else:
                uset.add(nums[i])
                self.path.append(nums[i])
                self.backtracking(nums, i+1)
                self.path.pop()

    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        self.backtracking(nums, 0)
        return self.res

1.13全排列

46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

思路:通常的思路是,对于每个树的分支,我们都维护一个used数组,如果其中的对应下表的元素被使用过,那么在当前树枝接下来的选取中,我们就跳过这个元素,并且我们将这个used数组沿着树的深度进行传播,同样需要进行回溯,为什么在排列问题中不使用startIndex呢?因为在组合问题中我们使用startIndex是为了避免后面的元素在同一树层中取之前取过的元素,用startIndex来进行限制,全排列问题,我们不需要进行这一限制,所以不需要startIndex总结一句话组合问题需要用startIndex,排列问题不需要startIndex

class Solution:
    def __init__(self):
        self.path: List[int] = []
        self.res: List[List[int]] = []
    
    def backtracking(self, nums: List[int], used: List[bool]) -> None:
        if len(self.path) == len(nums):
            self.res.append(self.path[:])
            return
        
        for i in range(len(nums)):                  # 排列问题,不需要使用startIndex来限制树层中取重复元素
            if used[i] == True:
                continue
            else:
                used[i] = True
                self.path.append(nums[i])
                self.backtracking(nums, used)
                self.path.pop()                     # 回溯
                used[i] = False                     # 回溯

    def permute(self, nums: List[int]) -> List[List[int]]:
        used: List[bool] = [False] * len(nums)
        self.backtracking(nums, used)
        return self.res

1.14全排列II

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

思路:利用set进行树层去重,利用used进行树枝去重

解法一:利用set进行树层去重,利用used进行树枝去重

class Solution:
    def __init__(self):
        self.path: List[int] = []
        self.res: List[List[int]] = []
    
    def backtracking(self, nums: List[int], used: List[bool]):
        if len(self.path) == len(nums):
            self.res.append(self.path[:])
            return 
        uset: set = set()                           # 利用set进行树层去重,利用used进行树枝去重
        for i in range(len(nums)):
            if nums[i] in uset or used[i] == True:
                continue
            else:
                uset.add(nums[i])                   
                used[i] = True
                self.path.append(nums[i])
                self.backtracking(nums, used)
                self.path.pop()                     # 回溯
                used[i] = False                     # 回溯

    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        used: List[bool] = [False] * len(nums)
        self.backtracking(nums, used)
        return self.res

解法二:利用排列进行树层去重,利用used进行树枝去重

class Solution:
    def __init__(self):
        self.path: List[int] = []
        self.res: List[List[int]] = []
    
    def backtracking(self, nums: List[int], used: List[bool]):
        if len(self.path) == len(nums):
            self.res.append(self.path[:])
            return                          
        for i in range(len(nums)):                  
            if (i > 0 and nums[i] == nums[i-1] and used[i-1] == False ) or used[i] == True:  # 利用排列进行树层去重,利用used进行树枝去重,这里一定要used[i-1]==False才能保证是在树层上进行去重,这种解法比较复杂,不如解法一
                continue
            else:                
                used[i] = True
                self.path.append(nums[i])
                self.backtracking(nums, used)
                self.path.pop()                     # 回溯
                used[i] = False                     # 回溯

    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        used: List[bool] = [False] * len(nums)
        nums.sort()
        self.backtracking(nums, used)
        return self.res

1.15N皇后

51. N 皇后

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

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。

思路:我们需要对棋盘进行二维的一个遍历,在回溯算法模板的for循环中,即回溯算法的树层中,我们来遍历这个棋盘的col,在回溯算法的深度中,即回溯算法的树深中,我们来遍历这个棋盘的row,然后通过一个isValid()函数来判断当前的位置是否合法,如果合法我们就放置皇后Q,如果非法我们就继续搜索,最后当我们遍历完所有的row之后,我们即可以保存搜索的结果了,然后再进行回溯,这样我们就能够搜索所有想要的结果。

class Solution:
    def __init__(self):
        self.res: List[List[str]] = []
    
    def isValid(self, chessboard: List[List[str]], row: int, col: int) -> bool:
        # 检查相同列
        for i in range(row):                # 相同col的前n row已经有Q,则非法
            if chessboard[i][col] == 'Q':
                return False
        # 检查相同行
        for j in range(col):
            if chessboard[row][j] == 'Q':
                return False
        # 检查45°方向
        i = row - 1
        j = col - 1
        while i >=0 and j >=0:
            if chessboard[i][j] == 'Q':
                return False
            i -= 1
            j -= 1
        # 检查135°方向
        i = row - 1
        j = col + 1
        while i >= 0 and j < len(chessboard):
            if chessboard[i][j] == 'Q':
                return False
            i -= 1
            j += 1
        return True

    def backtracking(self, chessboard: List[List[str]], n: int, row: int) -> None:
        if row == n:                # 遍历完所有行则收集结果
            self.res.append(chessboard[:])
            return 

        # 通过树层来遍历col
        for col in range(n):
            if self.isValid(chessboard, row, col):
                # print(chessboard)
                chessboard[row] = chessboard[row][:col] + 'Q' +chessboard[row][col+1:]
                self.backtracking(chessboard, n, row+1)                                     # 通过树深来遍历row
                chessboard[row] = chessboard[row][:col] + '.' +chessboard[row][col+1:]      # 回溯

    def solveNQueens(self, n: int) -> List[List[str]]:
        chessboard: List[List[str]] = ['.'*n]*n
        self.backtracking(chessboard, n, 0)
        return self.res

1.16解数独

37. 解数独

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 '.' 表示。

解法一:双重for循环

class Solution:
    def isValid(self, board: List[List[str]], num: int, row: int, col: int) -> bool:
        # 当前列是否有效
        for i in range(len(board)):
            if board[i][col] == str(num):
                return False
        # 当前行是否有效
        for j in range(len(board)):
            if board[row][j] == str(num):
                return False
        # 当前方格是否有效
        start_row = (row // 3) * 3
        start_col = (col // 3) * 3
        for i in range(start_row, start_row+3):
            for j in range(start_col, start_col+3):
                if board[i][j] == str(num):
                    return False
        return True

    def backtracking(self, board: List[List[str]]) -> bool:
        for i in range(len(board)):
            for j in range(len(board[0])):
                if board[i][j] != '.':
                    continue
                else:
                    for num in range(1,10):
                        if self.isValid(board, num, i, j):
                            board[i][j] = str(num)
                            if self.backtracking(board): 
                                return True
                            board[i][j] = '.'               # 回溯
                return False # 若数字1-9都不能成功填入空格,返回False无解
        return True # 有解

    def solveSudoku(self, board: List[List[str]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        self.backtracking(board)

相关推荐

  1. LeetCode小记 一、【数组】

    2024-03-17 18:32:04       29 阅读
  2. backtracking Leetcode 回溯算法

    2024-03-17 18:32:04       11 阅读
  3. leetcode算法

    2024-03-17 18:32:04       52 阅读
  4. 算法day24】回溯算法+简单剪枝

    2024-03-17 18:32:04       76 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-17 18:32:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-17 18:32:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-17 18:32:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-17 18:32:04       18 阅读

热门阅读

  1. 简单理解promise。。。

    2024-03-17 18:32:04       22 阅读
  2. python爬取B站CC字幕(隐藏式字幕)

    2024-03-17 18:32:04       18 阅读
  3. 微服务的无状态、版本控制向后兼容、流量整型

    2024-03-17 18:32:04       16 阅读
  4. IBatis与MyBatis区别

    2024-03-17 18:32:04       18 阅读
  5. MongoDB聚合运算符:$expMovingAvg

    2024-03-17 18:32:04       14 阅读
  6. 【每日前端面经】2024-03-14

    2024-03-17 18:32:04       18 阅读