代码随想录python笔记7 回溯

回溯法解决的问题

回溯法,一般可以解决如下几种问题:

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

相信大家看着这些之后会发现,每个问题,都不简单!

另外,会有一些同学可能分不清什么是组合,什么是排列?

组合是不强调元素顺序的,排列是强调元素顺序

例如:{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。

记住组合无序,排列有序,就可以了。

如何理解回溯法

回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!

因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度

回溯搜索的遍历过程

在上面我们提到了,回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。

如图:

回溯算法理论基础

回溯算法模板框架如下:

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

1. 组合

力扣题目链接

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

示例:
输入: n = 4, k = 2
输出:
[ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        res = []
        path = []
        def backtracking(n, k, startIndex):
            if len(path)==k:
                res.append(path[:])
                return
            for i in range(startIndex, n + 1):
                path.append(i)
                backtracking(n, k, i + 1)
                path.pop()
        backtracking(n, k, 1)
        return res

剪枝优化

如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了

77.组合4
class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        res = []  # 储存结果
        path = [] # 收集路径上的元素
        def backtracking(n, k, startindex):
            if len(path) == k: # 终止条件
                res.append(path[:])
                return 
            for i in range(startindex, (n-(k-len(path))+2):#  剪枝优化
                path.append(i)
                backtracking(n, k, i + 1) # 递归,开始位置加1,深度搜索
                path.pop()  # 回溯,弹出path末尾,加入新元素(不好理解)
        backtracking(n, k, 1)
        return res

2.组合总和III

力扣题目链接(opens new window)

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

  • 所有数字都是正整数。
  • 解集不能包含重复的组合。

示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]

回溯三部曲:

  1. 确定递归函数参数
  2. 确定终止条件
  3. 单层搜索过程

不剪枝

class Solution:
    def __init__(self):
        self.s = 0
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        res = []  # 存放结果
        path = []  # 路径元素
        def backtracking(n, k, startindex):
            if len(path) == k:
                if self.s == n:
                    res.append(path[:])
                return
            for i in range(startindex, 10):
                self.s += i 
                path.append(i)
                backtracking(n, k, i + 1)
                path.pop()# 回溯1
                self.s -= i# 回溯2
        backtracking(n, k, 1)
        return res

减枝:

class Solution:
    def __init__(self):
        self.res = []
        self.sum_now = 0
        self.path = []
    def combinationSum3(self, k: int, n: int) -> [[int]]:
        self.backtracking(k, n, 1)
        return self.res

    def backtracking(self, k: int, n: int, start_num: int):
        if self.sum_now > n:  # 剪枝1
            return
        if len(self.path) == k:  # len(path)==k时不管sum是否等于n都会返回
            if self.sum_now == n:
                self.res.append(self.path[:])
            return
        for i in range(start_num, 10 - (k - len(self.path)) + 1): # 剪枝2
            self.path.append(i)
            self.sum_now += i
            self.backtracking(k, n, i + 1)
            self.path.pop()
            self.sum_now -= i

3.电话号码的字母组合

力扣题目链接(opens new window)

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

17.电话号码的字母组合

解决三个问题

  1. 数字和字母如何映射
  2. 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
  3. 输入1 * #按键等等异常情况
class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if len(digits) == 0:
            return []
        dic = {2:'abc',3:'def',4: 'ghi',5: 'jkl',6: 'mno',7: 'pqrs',8: 'tuv',9:'wxyz'}
        res = []
        path = ''
        def backtracking(path, digits, index): # 1.确定传入参数和返回值
            if len(digits) == index: # 2.确定终止条件:数字遍历结束
                res.append(path)
                return
            for i in dic[int(digits[index])]:  # 3.确定单层递归
                path += i
                backtracking(path, digits, index + 1)
                path = path[:-1] # 回溯
        backtracking(path, digits, 0)
        return res

方法2:队列 -copy

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits: return []
        phone = ['abc','def','ghi','jkl','mno','pqrs','tuv','wxyz']
        queue = ['']  # 初始化队列
        for digit in digits:
            for _ in range(len(queue)):
                tmp = queue.pop(0)
                for letter in phone[ord(digit)-50]:# 这里我们不使用 int() 转换字符串,使用ASCII码
                    queue.append(tmp + letter)
        return queue

4. 组合总和

力扣题目链接(opens new window)

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

  • 输入:candidates = [2,3,6,7], target = 7,
  • 所求解集为: [ [7], [2,2,3] ]

示例 2:

  • 输入:candidates = [2,3,5], target = 8,
  • 所求解集为: [   [2,2,2,2],   [2,3,3],   [3,5] ]
class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        res = []
        path = []
        def backtracking(index, s, candidates, target):
            if s > target:
                return
            if s == target:
                res.append(path[:])
                return
            for i in range(index, len(candidates)):
                s += candidates[i]
                path.append(candidates[i])
                backtracking(i, s, candidates, target) # 从当前位置作为index,递归
                s -= candidates[i]
                path.pop()
        backtracking(0, 0, candidates, target)
        return res

减枝

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        # 剪枝优化
        res = []
        path = []
        candidates.sort()  # 为了剪枝,先排序
        def backtracking(index, s, candidates, target):
            if s > target:
                return
            if s == target:
                res.append(path[:])
                return
            for i in range(index, len(candidates)):
                s += candidates[i]
                if s > target: # 提前结束遍历
                    return
                path.append(candidates[i])
                backtracking(i, s, candidates, target) # 从当前位置作为index,递归
                s -= candidates[i]
                path.pop()
        backtracking(0, 0, candidates, target)
        return res

5. 组合总和II

力扣题目链接(opens new window)

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

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

说明: 所有数字(包括目标数)都是正整数。解集不能包含重复的组合。

  • 示例 1:
  • 输入: candidates = [10,1,2,7,6,1,5], target = 8,
  • 所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
] 
  • 示例 2:
  • 输入: candidates = [2,5,2,1,2], target = 5,
  • 所求解集为:
[
  [1,2,2],
  [5]
]

加入uesd数组,确保同一层元素不能相同

树层去重的话,需要对数组排序!

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]

used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的

img
class Solution:
    def __init__ (self):
        self.res = []
        self.path = []
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        self.used = [0] * len(candidates)
        candidates.sort()
        self.backtracking(candidates, target, 0, 0)
        return self.res
    def backtracking(self,candidates, target, startindex, s):
        if s == target:
            self.res.append(self.path[:]) 
            return
        for i in range(startindex, len(candidates)):
            if s + candidates[i] > target:
                return
            if i > 0 and candidates[i] == candidates[i - 1] and self.used[i - 1] == 0:
                continue
            s+= candidates[i]
            self.used[i] = 1
            self.path.append(candidates[i])
            self.backtracking(candidates, target, i + 1, s)
            self.used[i] = 0
            self.path.pop()
            s -= candidates[i]

不使用used - copy

class Solution:
    def __init__(self):
        self.paths = []
        self.path = []

    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        '''
        类似于求三数之和,求四数之和,为了避免重复组合,需要提前进行数组排序
        '''
        self.paths.clear()
        self.path.clear()
        # 必须提前进行数组排序,避免重复
        candidates.sort()
        self.backtracking(candidates, target, 0, 0)
        return self.paths

    def backtracking(self, candidates: List[int], target: int, sum_: int, start_index: int) -> None:
        # Base Case
        if sum_ == target:
            self.paths.append(self.path[:])
            return
        
        # 单层递归逻辑
        for i in range(start_index, len(candidates)):
            # 剪枝,同39.组合总和
            if sum_ + candidates[i] > target:
                return
            
            # 跳过同一树层使用过的元素
            if i > start_index and candidates[i] == candidates[i-1]:
                continue
            
            sum_ += candidates[i]
            self.path.append(candidates[i])
            self.backtracking(candidates, target, sum_, i+1)
            self.path.pop()             # 回溯,为了下一轮for loop
            sum_ -= candidates[i]       # 回溯,为了下一轮for loop

6.分割回文串

力扣题目链接(opens new window)

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

示例: 输入: “aab” 输出: [ [“aa”,“b”], [“a”,“a”,“b”] ]

  • 切割问题可以抽象为组合问题
  • 如何模拟那些切割线
  • 切割问题中递归如何终止
  • 在递归循环中如何截取子串
  • 如何判断回文
class Solution:
    def partition(self, s: str) -> List[List[str]]:
        res = []
        path =  []
        def backtracking(s, startindex):
            if startindex == len(s): # 注意终止条件
                res.append(path[:])
                return
            for i in range(startindex, len(s)):
                tmp = s[startindex : i + 1]
                if tmp == tmp[::-1]: # 判断子串是不是回文
                    path.append(tmp)
                    backtracking(s, i + 1)
                    path.pop()
                # else:
                #     continue
        backtracking(s, 0)
        return res
class Solution:
    # 定义函数判断回文
    def partition(self, s: str) -> List[List[str]]:
        res = []
        path =  []
        def backtracking(s, startindex):
            if startindex == len(s): # 注意终止条件
                res.append(path[:])
                return
            for i in range(startindex, len(s)):
                tmp = s[startindex : i + 1]
                if self.is_palindrome(tmp): # 判断子串是不是回文
                    path.append(tmp)
                    backtracking(s, i + 1)
                    path.pop()
        backtracking(s, 0)
        return res
    def is_palindrome(self, s):
        i = 0
        j = len(s) - 1
        while i < j:
            if s[i] != s[j]:
                return False
            i += 1
            j -= 1
        return True

7.复原IP地址

力扣题目链接(opens new window)

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。

例如:“0.1.2.201” 和 “192.168.1.1” 是 有效的 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效的 IP 地址。

示例 1:

  • 输入:s = “25525511135”
  • 输出:[“255.255.11.135”,“255.255.111.35”]

示例 2:

  • 输入:s = “0000”
  • 输出:[“0.0.0.0”]

示例 3:

  • 输入:s = “1111”
  • 输出:[“1.1.1.1”]

示例 4:

  • 输入:s = “010010”
  • 输出:[“0.10.0.10”,“0.100.1.0”]

示例 5:

  • 输入:s = “101023”
  • 输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]

提示:

  • 0 <= s.length <= 3000
  • s 仅由数字组成
class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        path = []
        res = []
        def backtracking(s, startindex):
            if startindex == len(s) and len(path) == 4:
                res.append('.'.join(path))
                return
            for i in range(startindex, len(s)):
                if len(path)>3:
                    break
                tmp = s[startindex: i + 1]
                if (0 <= int(tmp) <= 255 and tmp[0] != '0') or tmp == '0':
                    path.append(tmp)
                    print(path)
                    backtracking(s, i + 1)
                    path.pop()
        backtracking(s, 0)
        return res

8.子集

力扣题目链接(opens new window)

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例: 输入: nums = [1,2,3] 输出: [ [3],   [1],   [2],   [1,2,3],   [1,3],   [2,3],   [1,2],   [] ]

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        path = []
        def backtracking(startindex, nums):
            res.append(path[:])  # 收集每一个节点
            # if startindex == len(nums):  # 可以不写终止条件
            #     return
            for i in range(startindex, len(nums)):
                path.append(nums[i])
                backtracking(i + 1, nums)
                path.pop()
        backtracking(0, nums)
        return res

9.子集II

力扣题目链接(opens new window)

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

  • 输入: [1,2,2]
  • 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]

使用used数组

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        res = []
        path = []
        nums.sort()
        used = [0] * len(nums)
        def backtracking(startindex, nums):
            res.append(path[:])
            # if startindex == len(nums):
            #     return
            for i in range(startindex, len(nums)):
                if i > 0 and nums[i] == nums[i - 1] and used[i - 1] == 0:
                    continue
                used[i] = 1
                path.append(nums[i])
                backtracking(i + 1, nums)
                used[i] = 0
                path.pop()
        backtracking(0, nums)
        return res

不使用used数组

本题也可以不使用used数组来去重,因为递归的时候下一个startIndex是i+1而不是0

如果要是全排列的话,每次要从0开始遍历,为了跳过已入栈的元素,需要使用used。

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        res = []
        path = []
        nums.sort() # 去重需要先对数组进行排序

        def backtracking(nums, startIndex):
            # 终止条件
            res.append(path[:])
            if startIndex == len(nums):
                return
            # for循环
            for i in range(startIndex, len(nums)):
                # 数层去重
                if i > startIndex and nums[i] == nums[i-1]: # 去重
                    continue
                path.append(nums[i])
                backtracking(nums, i+1)
                path.pop()
        backtracking(nums, 0)
        return res

10.递增子序列

力扣题目链接(opens new window)

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

说明:

  • 给定数组的长度不会超过15。

  • 数组中的整数范围是 [-100,100]。

  • 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况

本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。所以不能使用之前的去重逻辑

同一父节点下的同层上使用过的元素就不能再使用了,每层都重新定义一次uset数组

1 使用set

class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        path = []
        res = []
        def backtracking(startindex, nums):
            if len(path) > 1:
                res.append(path[:])
            uset = set()
            for i in range(startindex, len(nums)):
                if (path and nums[i] < path[-1]) or nums[i] in uset: # 注意去重判别
                    continue
                uset.add(nums[i])
                path.append(nums[i])
                backtracking(i + 1, nums)
                path.pop()
        backtracking(0, nums)
        return res

2 使用数组

class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        path = []
        res = []
        def backtracking(startindex, nums):
            if len(path) > 1:
                res.append(path[:])
            uset = [0] * 201 # 使用数组节约空间
            for i in range(startindex, len(nums)):
                if (path and nums[i] < path[-1]) or uset[nums[i] + 100] == 1:
                    continue
                uset[nums[i] + 100] = 1
                path.append(nums[i])
                backtracking(i + 1, nums)
                path.pop()
        backtracking(0, nums)
        return res

11.全排列

力扣题目链接(opens new window)

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

  • 输入: [1,2,3]
  • 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

排列问题

  • 每层都是从0开始搜索而不是startIndex
  • 需要used数组记录path里都放了哪些元素了

同一树枝上不能重复使用

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        path = []
        res = []
        used = [0] * len(nums)
        def backtracking(nums):
            if  len(path) == len(nums):
                res.append(path[:])
                return
            for i in range(0, len(nums)):
                if used[i] == 1:
                    continue
                used[i] = 1
                path.append(nums[i])
                backtracking(nums)
                path.pop()
                used[i] = 0
        backtracking(nums)
        return res

12.全排列 II

力扣题目链接(opens new window)

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

示例 1:

  • 输入:nums = [1,1,2]
  • 输出: [[1,1,2], [1,2,1], [2,1,1]]

示例 2:

  • 输入:nums = [1,2,3]
  • 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10
class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        path = []
        res = []
        nums.sort()
        used = [0] * len(nums)
        def backtracking(nums):
            if  len(path) == len(nums):
                res.append(path[:])
                return
            for i in range(len(nums)):
                if used[i] == 0:  # 树枝去重
                    if i > 0 and nums[i] == nums[i - 1] and used[i - 1] == 0: # 树层去重
                        continue
                    used[i] = 1
                    path.append(nums[i])
                    backtracking(nums)
                    path.pop()
                    used[i] = 0
        backtracking(nums)
        return res

回溯算法去重问题的另一种写法

13.重新安排行程

力扣题目链接(opens new window)

给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。

提示:

  • 如果存在多种有效的行程,请你按字符自然排序返回最小的行程组合。例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前
  • 所有的机场都用三个大写字母表示(机场代码)。
  • 假定所有机票至少存在一种合理的行程。
  • 所有的机票必须都用一次 且 只能用一次。

示例 1:

  • 输入:[[“MUC”, “LHR”], [“JFK”, “MUC”], [“SFO”, “SJC”], [“LHR”, “SFO”]]
  • 输出:[“JFK”, “MUC”, “LHR”, “SFO”, “SJC”]

示例 2:

  • 输入:[[“JFK”,“SFO”],[“JFK”,“ATL”],[“SFO”,“ATL”],[“ATL”,“JFK”],[“ATL”,“SFO”]]
  • 输出:[“JFK”,“ATL”,“JFK”,“SFO”,“ATL”,“SFO”]
  • 解释:另一种有效的行程是 [“JFK”,“SFO”,“ATL”,“JFK”,“ATL”,“SFO”]。但是它自然排序更大更靠后。

这道题目有几个难点:

  1. 一个行程中,如果航班处理不好容易变成一个圈,成为死循环
  2. 有多种解法,字母序靠前排在前面,让很多同学望而退步,如何记录映射关系呢 ?
  3. 使用回溯法(也可以说深搜) 的话,那么终止条件是什么呢?
  4. 搜索的过程中,如何遍历一个机场所对应的所有机场
332.重新安排行程1
class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        # defaultdic(list) 是为了方便直接append 
        tickets_dict = defaultdict(list)  # tickets_dict = {key1:[], key2:[]}
        for item in tickets:
            tickets_dict[item[0]].append(item[1])
        # 给每一个机场的到达机场排序,小的在前面,在回溯里首先被pop(0)出去
        # 这样最先找的的path就是排序最小的答案,直接返回
        for airport in tickets_dict: tickets_dict[airport].sort()
        path = ["JFK"] # path 就是res
        def backtracking(start_point):
            if len(path) == len(tickets) + 1:
                return True
            for i in tickets_dict[start_point]:
                end_point = tickets_dict[start_point].pop(0)  # 删除已经遍历过的机票,避免死循环
                path.append(end_point)
                if backtracking(end_point):
                    return True
                path.pop()
                tickets_dict[start_point].append(end_point)
        backtracking("JFK")
        return path

14.N皇后

力扣题目链接(opens new window)

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

示例 1:

img

  • 输入:n = 4
  • 输出:[[“.Q…”,“…Q”,“Q…”,“…Q.”],[“…Q.”,“Q…”,“…Q”,“.Q…”]]
  • 解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

  • 输入:n = 1
  • 输出:[[“Q”]]
  • 51.N皇后

了棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度

class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        if not n: return []
        board = [['.'] * n for _ in range(n)]  # 定义棋盘
        res = []
        def isVaild(board, col, row):
            for i in range(row):  # 判断列
                if board[i][col] == 'Q':return False
            i, j = row - 1, col - 1  # 判断左上角
            while i >= 0 and j >= 0:
                if board[i][j] == 'Q': return False
                i, j = i - 1, j - 1  # 判断右上角
            i, j = row - 1, col + 1
            while i >= 0 and j < n:
                if board[i][j] == 'Q': return False
                i, j = i - 1, j + 1
            return True
        def backtracking(n, board, row):
            if row == n: # 终止条件:找到了最后一行
                tmp_res = []  # 转化数据结构,添加到结果
                for tmp in board:
                    tmp_str = ''.join(tmp)
                    tmp_res.append(tmp_str)
                res.append(tmp_res)
                # return
            for col in range(n):
                if isVaild(board, col, row):
                    board[row][col] = 'Q'
                    backtracking(n, board, row + 1)
                    board[row][col] = '.'
        backtracking(n, board, 0)
        return res

15.解数独

力扣题目链接(opens new window)

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

提示:

  • 给定的数独序列只包含数字 1-9 和字符 ‘.’ 。
  • 你可以假设给定的数独只有唯一解。
  • 给定数独永远是 9x9 形式的。

二维递归问题

本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深

37.解数独

递归函数的返回值需要是bool类型,为什么呢?

因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值

class Solution:
    def solveSudoku(self, board: List[List[str]]) -> None:
        def isVaild(board, row, col, val):
            for i in range(9):  # 检查行列
                if board[row][i] == str(val):
                    return False
                if board[i][col] == str(val):
                    return False
            startrow, startcol = (row // 3) * 3 , (col // 3) * 3 
            for i in range(startrow, startrow + 3):
                for j in range(startcol, startcol + 3):
                    if board[i][j] == str(val):
                        return False
            return True
        def backtracking(board):  # 二维递归
            for row in range(9):
                for col in range(9):
                    if board[row][col] != '.': continue  # 如果空格有数字则跳过
                    for val in range(1,10):
                        if not isVaild(board, row, col, val):
                            continue
                        board[row][col] = str(val)
                        if backtracking(board): return True
                        board[row][col] = '.'
                    return False  # 遍历完九个数字都找不到
            return True
        backtracking(board)

return False
if board[i][col] == str(val):
return False
startrow, startcol = (row // 3) * 3 , (col // 3) * 3
for i in range(startrow, startrow + 3):
for j in range(startcol, startcol + 3):
if board[i][j] == str(val):
return False
return True
def backtracking(board): # 二维递归
for row in range(9):
for col in range(9):
if board[row][col] != ‘.’: continue # 如果空格有数字则跳过
for val in range(1,10):
if not isVaild(board, row, col, val):
continue
board[row][col] = str(val)
if backtracking(board): return True
board[row][col] = ‘.’
return False # 遍历完九个数字都找不到
return True
backtracking(board)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值