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

1.回溯算法

文章目录

    • 1.回溯算法
    • 写在前面
      • 1.1回溯算法基本知识
      • 1.2组合问题
      • 1.3组合问题的剪枝操作
      • 1.4组合总和III
      • 1.5电话号码的字母组合
      • 1.6组合总和
      • 1.7组合总和II
      • 1.8分割回文串
      • 1.9复原IP地址
      • 1.10子集问题
      • 1.11子集II
      • 1.12非递减子序列
      • 1.13全排列
      • 1.14全排列II
      • 1.15N皇后
      • 1.16解数独

写在前面

本系列笔记主要作为笔者刷题的题解,所用的语言为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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/461029.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

代码贴--动态顺序表--数据结构

本博客将记录操作系统中的动态顺序表的相关代码 头文件&#xff08;SeList.h&#xff09; #pragma once #include<stdio.h> #include<string.h> #include<stdlib.h> #include<assert.h> typedef int SQDataType; //动态顺序表typedef struct SeqList…

可视化展示与交互编辑:探索3D Web轻量化平台HOOPS WEB Platform在BIM中的新可能性

随着数字技术的飞速发展&#xff0c;建筑行业也在不断迈向数字化转型的道路。在这个过程中&#xff0c;BIM&#xff08;Building Information Modeling&#xff0c;建筑信息模型&#xff09;技术已经成为建筑设计、施工和管理领域中的一项重要工具。 而在BIM的应用中&#xff…

Javascript抓取京东、淘宝商品数据(商品采集商品详情图片抓取)

之前用的方法&#xff1a; let temp []var lists $(#J_goodsList li.gl-item)$.each(lists,function(idx,item){ temp.push({ id:$(item).data(sku), goods_img:$(item).find(img).attr(src), goods_name:$(item).find(.p-name em).text(), market_price:$(item).fi…

C++:函数传参到函数执行结束发生了什么

首先要明确两个概念 函数实参的入栈从右向左栈区从高地址向低地址偏移 接下来看下面一段代码 void fun(int a,int b,int c){std::cout<<&a<<" "<<&b<<" "<<&c<<std::endl; } int main(){fun(1,2,3); }…

中国首个基于区块链的分布式算力网络上线

随着美国人工智能公司OpenAI近期发布的Sora视频模型&#xff0c;全球对高性能算力的需求突破了历史新高。Sora的创新在于它能够以超长生成时间、多角度镜头捕捉&#xff0c;理解物理世界的能力&#xff0c;这不仅是技术的一大突破&#xff0c;更是对算力需求的一大挑战。在这样…

Qt教程 — 3.3 深入了解Qt 控件:Input Widgets部件(2)

目录 1 Input Widgets简介 2 如何使用Input Widgets部件 2.1 QSpinBox组件-窗口背景不透明调节器 2.2 DoubleSpinBox 组件-来调节程序窗口的整体大小 2.3 QTimeEdit、QDateEdit、QDateTimeEdit组件-编辑日期和时间的小部件 Input Widgets部件部件较多&#xff0c;将分为三…

基于Python django的人脸识别门禁系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、Python技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&…

报Invalid value type for attribute ‘factoryBeanObjectType‘: java.lang.String错误

在springboot中使用Mybatis出现Invalid value type for attribute factoryBeanObjectType: java.lang.String 1、没有使用mybatis 检查pom文件里面的mybatis 可能是缺少这个依赖&#xff0c;或者版本过低 重新导入依赖 <dependency><groupId>org.mybatis.spri…

ubuntu(20.04)-安装JAVA环境-IDEA

1.下载IDEA 2.解压文件 sudo tar -zxvf idealC-2022.2.3.tar.gz -C /opt 3.添加环境变量&#xff1a; .vim ~/.bashrc export IDEA_HOME/opt/ideaIC-2022.2.3/ export PATH${IDEA_HOME}/bin:$PATH source ~/.bashrc 4.启动&#xff1a; cd /opt/ideaIC-2…

数据结构 第3章:栈与队列

文章目录 1. 栈1.1 栈的基本概念1.2 栈的基本操作1.3 栈的顺序存储实现1.4 栈的链式存储实现 2. 队列2.1 队列的基本概念2.2 队列的基本操作2.3. 队列的顺序存储实现2.4 队列的链式存储实现2.5 双端队列 3. 栈与队列的应用3.1 栈在括号匹配中的应用3.2 栈在表达式求值中的应用3…

聚观早报 | 微博2023年Q4营收;宝马集团2023财年营收

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 3月16日消息 微博2023年Q4营收 宝马集团2023财年营收 海信发布中文大模型 岚图梦想家新增两款车型 vivo X Fold…

Transformer中注意力层和位置感知前馈层的分工与合作

1. 注意力层和位置感知前馈层的分工与合作 在Transformer模型中&#xff0c;注意力层&#xff08;自注意力机制&#xff09;和位置感知前馈层&#xff08;Position-wise Feed-Forward Networks, FFNs&#xff09;分别承担着不同的任务&#xff1a; 注意力层&#xff08;自注意…

【Godot4自学手册】第二十四节利用DirectionalLight2D节点实现夜幕降临

根据我们的游戏情节&#xff0c;今天我们将要实现夜晚的来临&#xff0c;主要是用DirectionalLight2D节点来实现&#xff0c;当与NPC对完话后&#xff0c;整改场景逐渐变得黑暗。 一、添加DirectionalLight2D节点 切换到主场景main&#xff0c;选择根目录&#xff0c;单击添加…

Linux下的多线程编程:原理、工具及应用(2)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;Flower of Life—陽花 0:34━━━━━━️&#x1f49f;──────── 4:46 &#x1f504; ◀️ ⏸ ▶️ ☰ …

Acwing.1262 鱼塘钓鱼(多路归并)

题目 有 N个鱼塘排成一排&#xff0c;每个鱼塘中有一定数量的鱼&#xff0c;例如&#xff1a;N5时&#xff0c;如下表&#xff1a; 即&#xff1a;在第 1个鱼塘中钓鱼第 1分钟内可钓到 10条鱼&#xff0c;第 2分钟内只能钓到 8条鱼&#xff0c;……&#xff0c;第 5分钟以后再…

Vite为什么比Webpack快

一、引言 主流的前端构建工具包括以下几种&#xff1a; Webpack&#xff1a;当下最热门的前端资源模块化管理和打包工具。它能够将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。同时&#xff0c;Webpack还支持代码分割&#xff0c;可以按需加载模块&#…

webshell隐藏哥斯拉流量修改sqlmap改ua

webshell隐藏 windows 1.隐藏shell attrib "文件名" s h attrib "文件名" -s -h 2.利用系统代号隐藏shell 创建文件夹名为Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}&#xff0c;此时文件夹将变成我的电脑&#xff0c;无法看到里面的东西&…

【快捷部署】002_Flink(1.17.2)

&#x1f4e3;【快捷部署系列】002期信息 编号选型版本操作系统部署形式部署模式002Flink1.17.2CentOS 7.Xtgz包单机 &#x1f449; 演示视频 Flink一键安装&#xff08;本地模式&#xff09; install-flink.sh 脚本内容 #!/bin/bash ####变量 ###执行脚本的当前目录 mydir$…

《Learning Hierarchical Modular Networks for Video Captioning》论文笔记

论文信息 原文链接&#xff1a; Learning Hierarchical Modular Networks for Video Captioning | IEEE Journals & Magazine | IEEE Xplore 原文代码 GitHub - MarcusNerva/HMN: [CVPR2022] Official code for Hierarchical Modular Network for Video Captioning. Ou…

HarmonyOS应用开发-Stage模型开发概述

基本概念 UI框架 HarmonyOS提供了一套UI开发框架&#xff0c;即方舟开发框架&#xff08;ArkUI框架&#xff09;。提供了应用UI开发所必需的能力&#xff1a;多种组件、布局计算、动画能力、UI交互、绘制。 方舟开发框架针对开发者提供了两种开发范式&#xff1a; 基于ArkTS…