代码随想录算法训练营第三十二天| 51. N皇后、37. 解数独

51. N皇后

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

这道题目的难点:之前我们用回溯解决多了组合、切割、子集、排列问题,这些问题给的都是一个一维的集合,但是该题我们要在二维矩阵上进行搜索。

首先来看一下皇后们的约束条件:

  1. 不能同行
  2. 不能同列
  3. 不能同斜线

这也就告诉我们:每一行肯定会有一个皇后,每一列肯定也会有一个皇后

一个暴利的想法就是嵌套n个for循环,遍历每一行的每一个位置,但是随着n的增多,我们就写不出来了,所以我们就要回溯,这样可以用递归的方式来帮助我们控制for循环的层数,每次递归都是在遍历新的一行

确定完约束条件,来看看究竟要怎么去搜索皇后们的位置,其实搜索皇后的位置,可以抽象为一棵树。(我们做过的所有题都是抽象为了一棵树,可见先抽象为一棵树是我们解题的关键)

下面用一个 3 * 3 的棋盘,将搜索过程抽象为一棵树,如图:

难点:① 如何遍历放置皇后的位置 ②判断在某个位置放置皇后是否有效 

回溯三部曲 

  • 递归函数参数

依然是定义全局变量二维数组result来记录最终结果。

参数n是棋盘的大小,然后用row来记录当前遍历到棋盘的第几层了。

  • 递归终止条件

当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了。

  • 单层搜索的逻辑

递归深度就是row控制棋盘的行,每一层里for循环的col控制棋盘的列,一行一列,确定了放置皇后的位置。

每次都是要从新的一行的起始位置开始搜,所以都是从0开始。

  • 验证棋盘是否合法

按照如下标准去重:

  1. 不能同行
  2. 不能同列
  3. 不能同斜线 (45度和135度角)
class Solution:
    def __init__(self):
        
        self.result = []
        

    def solveNQueens(self, n: int) -> List[List[str]]:
        def backtracking(n,row,chessboard):
            if row == n:
                self.result.append(chessboard[:])
            for i in range(0,n):
                
                if self.isVaild(row,i,chessboard):
                    chessboard[row] = chessboard[row][:i] + 'Q' + chessboard[row][i+1:]  # 放置皇后
                    backtracking(n,row+1,chessboard)
                    #回溯
                    chessboard[row] = chessboard[row][:i] + '.' + chessboard[row][i+1:]
        
        chessboard = ['.' * n for _ in range(n)]  # 初始化棋盘
        print(chessboard)
        backtracking(n,0,chessboard)
        return [[''.join(row) for row in solution] for solution in self.result]
    
    def isVaild(self,row,col,chessboard):
   
        #检验列
        for _ in range(row):
            if chessboard[_][col] == 'Q':
                return False  # 当前列已经存在皇后,不合法
        
        # 检查 45 度角是否有皇后
        i, j = row - 1,col - 1  #i和形参有重复,下面改变导致出现混乱
        while i >= 0 and j >= 0:
            if chessboard[i][j] == 'Q':
                return False  # 左上方向已经存在皇后,不合法
            i -= 1
            j -= 1

        # 检查 135 度角是否有皇后
        i, j = row - 1, col + 1
        while i >= 0 and j < len(chessboard):
            if chessboard[i][j] == 'Q':
                return False  # 右上方向已经存在皇后,不合法
            i -= 1
            j += 1
        return True

 

37. 解数独

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

难点: 本题与上一题的区别是,上一题只要选中一个格子就是一种情况,但是本题选中一个格子之后还要判断在格子中放哪个数字(0-9),比上一题多一个维度;并且只要找到符合条件的就可以返回,所以回溯函数有bool返回值

回溯三部曲

  • 递归函数以及参数

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

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

  • 递归终止条件

本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。

不用终止条件会不会死循环?

递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止(填满当然好了,说明找到结果了),所以不需要终止条件!

  • 递归单层搜索逻辑

在树形图中可以看出我们需要的是一个二维的递归 (一行一列)

一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!

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

    def backtracking(self, board: List[List[str]]) -> bool:
        # 若有解,返回True;若无解,返回False
        for i in range(len(board)): # 遍历行
            for j in range(len(board[0])):  # 遍历列
                # 若空格内已有数字,跳过
                if board[i][j] != '.': continue
                for k in range(1, 10):
                    if self.is_valid(i, j, k, board):
                        board[i][j] = str(k)
                        if self.backtracking(board): return True
                        board[i][j] = '.'
                # 若数字1-9都不能成功填入空格,返回False无解
                return False
        return True # 有解

    def is_valid(self, row: int, col: int, val: int, board: List[List[str]]) -> bool:
        # 判断同一行是否冲突
        for i in range(9):
            if board[row][i] == str(val):
                return False
        # 判断同一列是否冲突
        for j in range(9):
            if board[j][col] == str(val):
                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(val):
                    return False
        return True

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二天的算法训练主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天的算法训练的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值