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

news2024/12/24 20:13:25

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)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WanAndroid(鸿蒙版)开发的第三篇

前言 DevEco Studio版本&#xff1a;4.0.0.600 WanAndroid的API链接&#xff1a;玩Android 开放API-玩Android - wanandroid.com 其他篇文章参考&#xff1a; 1、WanAndroid(鸿蒙版)开发的第一篇 2、WanAndroid(鸿蒙版)开发的第二篇 3、WanAndroid(鸿蒙版)开发的第三篇 …

三级等保技术建议书

1信息系统详细设计方案 1.1安全建设需求分析 1.1.1网络结构安全 1.1.2边界安全风险与需求分析 1.1.3运维风险需求分析 1.1.4关键服务器管理风险分析 1.1.5关键服务器用户操作管理风险分析 1.1.6数据库敏感数据运维风险分析 1.1.7“人机”运维操作行为风险综合分析 1.2…

EPICS和Arduino Uno之间基于串行文本协议的控制开发

Arduino Uno的串口服务程序设置如文本的串口通信协议设计以及在Arduino上的应用-CSDN博客中所示。通过在串口上发送约定的文本协议&#xff0c;它实现的功能如下&#xff1a; 实现功能&#xff1a; 读取三路0.0V~5.0V模拟量输入&#xff0c;读取端口A0~A2设置三路0.0V~5.0V的模…

Github 2024-03-16 开源项目日报Top10

根据Github Trendings的统计,今日(2024-03-16统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目5非开发语言项目2TypeScript项目1C++项目1Lua项目1Swift项目1《Hello 算法》:动画图解、一键运行的数据结构与算法教程 创建周期:4…

spring boot3登录开发-微信小程序用户登录设计与实现

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途 目录 写在前面 登录流程 流程解析 具体实现 相关代码 说明 服务端 小程序端 写在最后 写在前面 本文介绍了springb…

BigDL-LLM 安装指南——在iGPU集成显卡下使用BigDL-LLM大模型库加速LLM

文章目录 iGPU是什么&#xff1f;一、环境准备1.1 Visual Studio 2022 Community 安装1.2 安装或更新最新版本的GPU驱动程序1.3 安装英特尔oneAPI工具包2024.0版本1.4 安装Anaconda 二、BigDL -LLM 安装2.1 创建虚拟环境2.2 激活虚拟环境2.3 安装bigdl-llm[xpu] 三、运行环境配…

Etcd 介绍与使用(入门篇)

etcd 介绍 etcd 简介 etc &#xff08;基于 Go 语言实现&#xff0c;&#xff09;在 Linux 系统中是配置文件目录名&#xff1b;etcd 就是配置服务&#xff1b; etcd 诞生于 CoreOS 公司&#xff0c;最初用于解决集群管理系统中 os 升级时的分布式并发控制、配置文件的存储与分…

哔哩哔哩后端Java一面

前言 作者&#xff1a;晓宜 个人简介&#xff1a;互联网大厂Java准入职&#xff0c;阿里云专家博主&#xff0c;csdn后端优质创作者&#xff0c;算法爱好者 最近各大公司的春招和实习招聘都开始了&#xff0c;这里分享下去年面试B站的的一些问题&#xff0c;希望对大家有所帮助…

PLC_博图系列☞基本指令“RESET_BF”复位位域

PLC_博图系列☞基本指令“RESET_BF”复位位域 文章目录 PLC_博图系列☞基本指令“RESET_BF”复位位域背景介绍RESET_BF&#xff1a;复位位域说明类型为 PLC 数据类型、STRUCT 或 ARRAY 的位域参数示例 关键字&#xff1a; PLC、 西门子、 博图、 Siemens 、 RESET_BF 背景…

java 开发工具

新建项目 打开idea选择 New Project 新建一个项目 左边选择 Java项目&#xff0c;右边选择Java版本 接着next 修改项目名称和保存路径&#xff0c;然后点击下面的 Finish 最终页面; 在 src 目录右键&#xff0c;新建一个包 在 src 目录右键&#xff0c;新建java 文件 有时候会需…

Git全套教程一套精通git.跟学黑马笔记

Git全套教程一套精通git.跟学黑马笔记 文章目录 Git全套教程一套精通git.跟学黑马笔记1.版本管理工具概念2. 版本管理工具介绍2.1版本管理发展简史(维基百科)2.1.1 SVN(SubVersion)2.1.2 Git 3. Git 发展简史4. Git 的安装4.1 git 的下载4.2 安装4.3 基本配置4.4 为常用指令配置…

智能工具柜-RFID智能工具柜管理系统

RFID工具柜管理系统是一种便捷化的工具管理系统&#xff0c;它采用RFID技术实现信息化&#xff0c;可以大大提高工具管理的效率和准确性。 日常的工具管理也确实存在一定的管理问题&#xff0c;如工具管理效率低、管理不准确等。因此&#xff0c;采用RFID技术实现信息化已经成…

【深度学习】深度估计,Depth Anything Unleashing the Power of Large-Scale Unlabeled Data

论文标题&#xff1a;Depth Anything Unleashing the Power of Large-Scale Unlabeled Data 论文地址&#xff1a;https://arxiv.org/pdf/2401.10891.pdf 项目主页&#xff1a;https://depth-anything.github.io/ 演示地址&#xff1a;https://huggingface.co/spaces/LiheYoung…

【Elasticsearch】windows安装elasticsearch教程及遇到的坑

一、安装参考 1、安装参考&#xff1a;ES的安装使用(windows版) elasticsearch的下载地址&#xff1a;https://www.elastic.co/cn/downloads/elasticsearch ik分词器的下载地址&#xff1a;https://github.com/medcl/elasticsearch-analysis-ik/releases kibana可视化工具下载…

火车订票管理系统|基于springboot框架+ Mysql+Java+B/S结构的火车订票管理系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 管理员功能登录前台功能效果图 用户功能模块 系统功能设计 数据库E-R图设计 lunwen…

【深度学习目标检测】二十三、基于深度学习的行人检测计数系统-含数据集、GUI和源码(python,yolov8)

行人检测计数系统是一种重要的智能交通监控系统&#xff0c;它能够通过图像处理技术对行人进行实时检测、跟踪和计数&#xff0c;为城市交通规划、人流控制和安全管理提供重要数据支持。本系统基于先进的YOLOv8目标检测算法和PyQt5图形界面框架开发&#xff0c;具有高效、准确、…

[WUSTCTF2020]颜值成绩查询 --不会编程的崽

这题也是一个很简单的盲注题目&#xff0c;这几天sql与模板注入做麻了&#xff0c;也是轻松拿捏。 它已经提示&#xff0c;enter number&#xff0c;所有猜测这里后台代码并没有使用 " 闭合。没有明显的waf提示&#xff0c; 但是or&#xff0c;and都没反应。再去fuzz一…

C++17之std::variant

1. std::variant操作 如下列出了为std:: variable <>提供的所有操作。

Spring Boot整合STOMP实现实时通信

目录 引言 代码实现 配置类WebSocketMessageBrokerConfig DTO 工具类 Controller common.html stomp-broadcast.html 运行效果 完整代码地址 引言 STOMP&#xff08;Simple Text Oriented Messaging Protocol&#xff09;作为一种简单文本导向的消息传递协议&#xf…

基础---nginx 启动不了,跟 Apache2 服务冲突

文章目录 查看 nginx 服务状态nginx 启动后 访问页面 127.0.0.1停止 nginx 服务&#xff0c;访问不了页面停止/启动 Apache2 服务&#xff0c;启动 Apache2 页面访问显示正确nginx 莫名启动不了卸载 Apache2 服务器 启动 nginx &#xff0c;但是总是不能实现反向代理&#xff0…