代码随想录系列文章目录

回溯篇 - 棋盘问题 图的dfs


文章目录

  • 代码随想录系列文章目录
  • 51.N皇后
  • 37.解数独
  • 332.重新安排行程
  • 回溯篇小结

51.N皇后

题目链接
这道题的思路是什么样的,也就是递归树是怎么画的

其实是,棋盘的宽度,就是每层递归的遍历宽度,我们在每层递归逻辑中,就是一列列去试着放,如果不合法(就是同行同列对角线),我们就continue, 如果合法了,和之前几行放的都不冲突,我们递归下一行

递归的深度其实就是我们棋盘的高度,我们是一行一行的从上往下去递归,所以递归的出口也就是 我们的行数参数 row == n了。 为什么是等于n,不是n-1呢,因为我们需要把n-1行的操作完成了,我们再把所有的棋盘内容塞进res

这道题我再把递归三部曲写一下吧:

1.递归函数参数,我们需要 chessboard, row, n
因为我们需要把棋盘传进来,也需要row控制深度也就是出口,n来给每层遍历的宽度

2.出口
出口我上面分析了,row == n,表示0 - (n-1)行都合法并递归填Q完成了,我们把这种方案给加到res里

3.每层递归的逻辑
其实就是遍历每一列,一列一列的去试,如果说,合法了,好我们就把Q填到这一列,然后递归下一行;
isvalid函数的构造需要chessbord, row, col 就是此时的键盘矩阵 和 此时的坐标

代码写在下面了

class Solution:def solveNQueens(self, n: int) -> List[List[str]]:chessboard = [['.'] * n for _ in range(n)]res = []def isvalid(chessboard, row, col):# 1.皇后不能在同一行 (不用特判,因为我们一行就填一个Q就递归进入下一行了)# 2.皇后不能在同一列, 由于我们的判别在填Q之前,所以我们遍历每行的这一列,看有无Qfor i in range(n):if chessboard[i][col] == 'Q':return False#3.特判45°角,也就是左上方是否有Q, 因为左上角递归填过了i, j = row - 1, col - 1while i >= 0 and j >= 0:if chessboard[i][j] == 'Q':return Falsei -= 1j -= 1#4.特判135°角,右上角是否有Q,因为右上角递归填过i, j = row - 1, col + 1while i >= 0 and j < n:if chessboard[i][j] == 'Q':return Falsei -= 1j += 1return Truedef rec(chessboard, row, n):# 写出口,看要求,力扣要求是要把chessboard转成每一行字符串,如果不要求就不用if row == n:    #出口temp_res = []for i in chessboard:       temp_str = ''.join(i)temp_res.append(temp_str)res.append(temp_res)for col in range(n):  if not isvalid(chessboard,row,col): continuechessboard[row][col] = 'Q'rec(chessboard,row+1,n)chessboard[row][col] = '.'rec(chessboard,0,n)return res

37.解数独

题目链接
N皇后问题,是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来来遍历列,然后一行一列确定皇后的唯一位置。

解数独就不一样了,本题中棋盘的每一个位置都要放一个数字,并检查数字是否合法,解数独的树形结构要比N皇后更宽更深。


本题是一种二维的递归,所以和之前的操作有些不一样,特别是递归函数的返回值等这些细节需要好好琢磨一下,因此我把递归三部曲写一下:
1.递归函数以及参数
递归函数的返回值应该是bool类型的
因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值,
2. 递归出口
本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。

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

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

那么有没有永远填不满的情况呢?

这个问题我在递归单层搜索逻辑里在来讲!
3.单层递归的搜索逻辑

在树形图中可以看出我们需要的是一个二维的递归(也就是两个for循环嵌套着递归)

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

每一层的遍历就是在一个空格位置上去试1-9 九个数,如果可以就填上,然后反正下一层递归会直接找到下一个空格再1-9去试

def rec(board):for i in range(9):for j in range(9):if board[i][j] != '.':continuefor k in range(1,10):  #对于每一个空格 用1-9去填if isValid(board,i,j,k):board[i][j] = str(k)if rec(board): return True #解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回                                                      #节点到叶子节点一条唯一路径,所以需要使用bool返回值board[i][j] = '.'return False          #如果1-9填都不行,返回Falsereturn True

注意这里return false的地方,这里放return false 是有讲究的。

因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!

那么会直接返回, 这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去。

所以说,递归函数是bool返回值,以及没有出口会不会一直无限递归在这里填上了

判断棋盘是否合法:
同行 同列 九宫格是否重复

 def isValid(board,row,col,k):#每一行不能重复for i in range(9):if board[row][i] == str(k):return False#每一列不能重复for i in range(9):if board[i][col] == str(k):return False#每一个九宫格不能重复start_i = (row//3) * 3start_j = (col//3) * 3for i in range(start_i,start_i+3):for j in range(start_j,start_j+3):  #遍历某个空格所在的九宫格if board[i][j] == str(k):return Falsereturn True

整体代码如下:

class Solution:def solveSudoku(self, board: List[List[str]]) -> None:"""Do not return anything, modify board in-place instead."""def isValid(board, row, col, k):#每一行不能重复for j in range(9):if board[row][j] == str(k):return False#每一列不能重复for i in range(9):if board[i][col] == str(k):return False#每一小块九宫格里的数字不能重复start_i, start_j = (row // 3)*3, (col // 3)*3  #satrt_i, start_j是把普通(row,col)坐标转换到它所在的小九宫格的[0][0]位置for i in range(start_i, start_i+3):for j in range(start_j, start_j+3):if board[i][j] == str(k):return Falsereturn Truedef rec(board):#不用写出口,返回值是bool值#直接写每层递归搜索逻辑# 每层都从第一个数开始,遍历行列,找到第一个空格的位置,然后1-9一个个去试for i in range(9):for j in range(9):if board[i][j] != '.':continuefor k in range(1, 10):if isValid(board, i, j, k):board[i][j] = str(k)if rec(board): return True  #也许这就是出口的一种吧,没有遍历完棋盘没有空了,都填满了,说明找到合适一组立刻返回board[i][j] = '.'return False #某个位置1-9试了个遍,说明这个无解,返回Falsereturn Truerec(board)

332.重新安排行程

题目链接
defaultdict(list)的用法
所有的机票 必须都用一次 且 只能用一次,因此不能重复飞

递归三部曲:
1.递归函数以及参数
函数的返回值应该是bool, 只需要找到一个行程,就是在树形结构中唯一的一条通向叶子节点的路线,如图, 找到了这个叶子节点了直接返回, 参数 我就有一个start,表示每层递归开始的地方,一开始从’JFT’,每层都更新

2.递归的出口
什么时候找到一条路呢?就是三张票,我肯定有4个机场吧,如果我们的path里收集的机场数是ticket+1了我们就return True

3.单层递归的逻辑
还是根据图示的递归树去实现,这一层的开始结点,是上一层的fly to 的结点(字典序第一个)

dict[start].sort()  #字典序
for i in dict[start]:end = dict[start].pop(0) #递归的下一个开始点path.append(end)if rec(end):# 只要找到一个就可以返回了return Truepath.pop()dict[start].append(end)#递归的下一个开始点

整体代码:

class Solution:def findItinerary(self, tickets: List[List[str]]) -> List[str]:dict = defaultdict(list)for item in tickets:dict[item[0]].append(item[1])path = ['JFK']def rec(start):if len(path) == len(tickets)+1: #出口return Truedict[start].sort()  #将start结点能飞到的机场做字典序排序for i in dict[start]:end = dict[start].pop(0) #递归的下一个开始点path.append(end)if rec(end):# 只要找到一个就可以返回了return Truepath.pop()dict[start].append(end)#递归的下一个开始点rec('JFK')return path

回溯篇小结

在代码随想录里学习了回溯算法能解决的这几种问题:

  1. 组合 切割 组合问题 切割问题
  2. 子集 子集问题
  3. 全排列 全排列
  4. 棋盘问题 本篇

组合问题和切割问题收集的是叶子结点, 子集问题收集的是所有结点,这两种的每层递归逻辑里都需要一个start, 下层从上层start的下一个开始

全排列问题不需要start, 它是下一层也从头开始遍历。所以涉及了很多去重问题

如果说nums本身有很多重复,那么我们需要在每一层的遍历时去重, 可以排序之后去重也可以用一个set去重

如果说像子集问题需要在每个枝条上去重的话,有简单的if nums[i] in path,但是如果每层去重和枝条上去重混合的时候就不能用这个了,因为可能每一层就相同的数字,必须用used数组去回溯,标记每一位上一层是否用过,上一层用过的位置标1,下一层不能继续用

以上是我刷完回溯篇的简单体会

代码随想录回溯篇所有题的递归树分析就在这一篇里14道这里的总结比我更细致

代码随想录算法训练营第30天 | 51. N皇后 37.解数独 332.重新安排行程 回溯篇小结相关推荐

  1. 代码随想录算法训练营第30天| 332.重新安排行程 、51. N皇后 、 37. 解数独

    代码随想录算法训练营第30天| 332.重新安排行程 .51. N皇后 . 37. 解数独 332.重新安排行程 开始想的是将行程进行全排列之后,然后选出一个字典排序最小的.就也是使用的回溯的思路. ...

  2. 代码随想录刷题|LeetCode 332.重新安排行程 51. N皇后 37. 解数独

    目录 332.重新安排行程 思路 重新安排行程 51. N皇后 思路 N皇后 37. 解数独 思路 解数独         这三道题目都是困难题目,都是根据代码随想录的思路总结书写,慢慢理解,慢慢熟练 ...

  3. 代码随想录算法训练营第⑦天 | 454.四数相加II ,383. 赎金信,15. 三数之和,18. 四数之和 9.30

    代码随想录算法训练营第⑦天 | 454.四数相加II ,383. 赎金信,15. 三数之和,18. 四数之和 9.30 454.四数相加II 可以先用2次for循环遍历前两个数组a,b 并存储到map ...

  4. 代码随想录算法训练营day42 | 01背包问题,你该了解这些!,01背包问题,你该了解这些! 滚动数组 , 416. 分割等和子集

    代码随想录算法训练营day42 | 背包理论基础,背包理论基础(滚动数组), 416. 分割等和子集 1.01背包理论基础 背包问题概述 01背包 二维dp数组01背包案例 2.01背包理论基础(滚动 ...

  5. 代码随想录算法训练营day1

    代码随想录算法训练营第一天| 704. 二分查找.27. 移除元素. 704.二分查找 题目链接:leetcode704 Binary search 暴力解法: class Solution {pub ...

  6. 代码随想录算法训练营第七天| 哈希表理论基础 ,454.四数相加II, 383. 赎金信, 15. 三数之和, 18. 四数之和

    代码随想录算法训练营第七天| 哈希表理论基础 ,454.四数相加II, 383. 赎金信, 15. 三数之和, 18. 四数之和 454.四数相加II 建议:本题是 使用map 巧妙解决的问题,好好体 ...

  7. 代码随想录算法训练营第二天| 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II。

    代码随想录算法训练营第二天| 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II. 977.有序数组的平方 209. 长度最小的子数组 59. 螺旋矩阵 II 977.有序数组的 ...

  8. 代码随想录算法训练营第二天 | 力扣977.有序数组的平方,209.长度最小的子数组,59.螺旋矩阵II

    代码随想录算法训练营第二天 | 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II 977.有序数组的平方 题目链接:有序数组的平方 题目描述: 给你一个按 非递减顺序 排序的整 ...

  9. 代码随想录算法训练营第二天 | LeetCode977.有序数组的平方 ,209.长度最小的子数组,59.螺旋矩阵II

    代码随想录算法训练营第二天 | LeetCode977.有序数组的平方 ,209.长度最小的子数组,59.螺旋矩阵II 一. LeetCode977.有序数组的平方 1. 题目链接[LeetCode9 ...

最新文章

  1. android模拟器越狱,关于iOS Simulator(模拟器)是否可以越狱。
  2. Java之定时任务详解
  3. 软件工程概论 课堂练习 第2次作业1【思考:POS系统的对象关联】
  4. 44 | 套路篇:网络性能优化的几个思路(下)
  5. 【进阶技术】一篇文章搞掂:Spring高级编程
  6. SkyWalking加入Apache孵化器
  7. Halcon教程十一:小球识别,初识腐蚀与膨胀,开运算和闭运算
  8. 深度学习Tir-Hi3559A使用unbuntu系统的烧写步骤
  9. 用Mysql得到Webshell(MySql Backup WebShell)
  10. ppt加载html5,当PPT遇见H5,这才是真爱!
  11. “酷我音乐”借“大数据”名义 恐已窥探并收集用户隐私长达数年
  12. NCA:九岁的已经发起了 DDoS 攻击
  13. 如何将开发好的安卓应用程序发布到安卓市场或商店
  14. 股市中的马太效应带给我们什么股票道理?
  15. 【ABAQUS2022】ABAQUS2022安装+汉化+帮助文档下载教程
  16. 菜鸟写jquery入门教程(for web前端开发群4)(03)
  17. Pytorch快速搭建Alexnet实现手写英文字母识别+PyQt实现鼠标绘图
  18. [vim与gvim技巧]vimgvim技巧大全(1)
  19. 敏捷开发 SOLID 原则
  20. 华中师范大学计算机入学考试题目及分值,2018秋华师计算机的作业满分.docx

热门文章

  1. 手机右上角出现“HD”标识,好多人不知道它是什么,是否要关闭
  2. java compareTo 整数_Java中的compareTo()函数是怎么用的?
  3. FastBoot BootLoader Recovery 模式简介
  4. 如果让我再读一次研究生
  5. 掌财社:HTML版权符号写法及美化
  6. 002945华林证券75天亏86%中签的人却亏了近200%
  7. 苹果6怎么截屏_怎样使苹果无线键盘在Windows10 PC工作
  8. 四角号码查询器 第3版 发布
  9. Spark系列四:Spark的经典入门案列之ip地址归属地查询
  10. COLORREF、COLOR、RGB转化总结分析及在VC++中的使用