当前位置: 首页 > news >正文

leetcode hot100 中等难度 day05-刷题

day05 leetcode hot100题单

1. 前言

对应资料

​ leetcode网站:https://leetcode.cn/

​ hot100题单:https://leetcode.cn/problem-list/2cktkvj/

在这里插入图片描述

说明

​ 本人没有系统的刷过题目,临时抱佛脚,先从hot100开始刷,这是第一次刷。写博客,是强迫自己刷题。当前是第二天刷题,与前一次刷题隔了n天(国庆节放飞自我了)

  • 从简单难度开始往后刷。
  • 这是第一遍刷题,主要目的是通过,而不是追求效率。
  • 语言采用的python

目录

文章目录

    • day05 leetcode hot100题单
      • 1. 前言
      • 2. 中等 - 不同路径
      • 3. 中等 - 最小路径和
      • 4. 中等 - 编辑距离
      • 5. 中等 - 颜色分类
      • 6. 中等 - 子集
      • 7. 中等 - 单词搜索
      • 8. 中等 - 不同的二叉搜索树
      • 9. 中等 - 验证二叉搜索树
      • 10. 中等 - 二叉树的层序遍历
      • 11. 总结

2. 中等 - 不同路径

题目

​ 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

​ 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

​ 问总共有多少条不同的路径?

思考

​ 这非常明显是一个动态规划问题。

​ 思考:无论如何,想要走到右下角,必须向右走n-1步,向下走m-1步,无非就是什么时候向右、向下造成了不同的解法。

​ 因此,可以这么解决:

  • 如果选择向右走1步,它还可以选择的就是向右n-2步,向下m-1步
  • 如果选择向下走1步,…

​ 这个问题的边界条件是:当走完的时候,维护全局总路径数目+1

实现

  1. 照着上面这个思路,可以轻松地写下下面的代码,但是运行到后面,超出时间限制了
class Solution:def uniquePaths(self, m: int, n: int) -> int:# 全局变量ans = 0# 定义递归函数def dfs(row,column):# 边界条件if row == 0 and column == 0:nonlocal ansans += 1return None# 选或者不选if row >= 1: # 表示可以选择向下走dfs(row-1,column)if column >= 1:dfs(row,column-1)dfs(m-1,n-1)return ans
  1. 对上面的代码进行优化,可以不用全局变量来更新,直接用递归去体现,然后用@cache装饰器,可以避免重复计算已经计算出的dfs值,如下:
class Solution:def uniquePaths(self, m: int, n: int) -> int:# 定义递归函数@ cachedef dfs(row,column):# 边界条件if row < 0 or column < 0:return 0if row == 0 and column == 0:return 1return dfs(row-1,column) + dfs(row,column-1)return dfs(m-1,n-1)

3. 中等 - 最小路径和

题目

​ 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

​ **说明:**每次只能向下或者向右移动一步。

思考

​ 这道题和上面很类似,我们尝试用上一道题的思路去解决这个问题。

​ 首先出发点还是在左上角,对于向右走还是向下走,不能单单看右边和下边的哪个值大,还应该判断后续的和哪个大,比如下面:

在这里插入图片描述

​ 不能因为1比3小,就选择走1。

​ 应该这么想,如果我走3,后续最小的值是多少,如果我走1,后续最小值是多少。

​ 那么,假设为m*n,dfs(m,n)

  • 如果向右走1步,此时路径和temp,递归dfs(m,n-1)
  • 如果向下走1步,此时路径和temp,递归dfs(m-1,n)
  • 判断两者的大小,取其中小的那个作为return的值

​ 边界条件:如果m=n=0,说明正常结束,返回即可,或者m/n中有小于0的,说明错误路径

实现

class Solution:def minPathSum(self, grid: List[List[int]]) -> int:m = len(grid)n = len(grid[0])@ cachedef dfs(row,column):# 边界条件if row < 0 or column < 0:return infif row == 0 and column == 0:return grid[row][column]# 判断大小return min(dfs(row,column-1),dfs(row-1,column)) + grid[row][column]return  dfs(m-1,n-1)

4. 中等 - 编辑距离

题目

​ 给你两个单词 word1word2请返回将 word1 转换成 word2 所使用的最少操作数

​ 你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse ('h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

思考

​ 假设两者长度为m、n,我们从末尾开始判断:

  • 如果word[m] = word[n],那么,这表示不操作,直接进行下一步dfs(m-1,n-1)
  • 如果word[m] != word[n],那么有三种操作方式,插入、删除、替换,但是三者的代价是不同的,比如万一前面有相同的字符呢?因此,我们需要返回三者中代价最小的那个即可。即,min( dfs(m-1,n), dfs(m,n-1) , dfs(m-1,n-1) ) ,这三个分别对应:删除 - 需要判断新的索引是否相等、插入 - word2变化,word1索引不变、 替换 - 两者匹配了,都需要变化

​ 然后,边界条件:

  • 如果m<0 或者 n < 0:返回另外一个字符串剩下的+1,比如上面的字符串,匹配结束后,还剩下h,这个无论删除也好,还是替换,都是需要处理它
  • 如果两个元素相同,也直接下一步即可,因为不会占次数

实现

class Solution:def minDistance(self, word1: str, word2: str) -> int:# 初始化m,n = len(word1),len(word2)# 递归@cachedef dfs(i,j):# 边界条件if i < 0:return j + 1if j < 0:return i + 1if word1[i] == word2[j]:return dfs(i-1,j-1)# 正常递归,只是每次操作,需要+1return min(dfs(i-1,j),dfs(i,j-1),dfs(i-1,j-1)) + 1return dfs(m-1,n-1)

5. 中等 - 颜色分类

题目

​ 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

​ 我们使用整数 012 分别表示红色、白色和蓝色。

​ 必须在不使用库内置的 sort 函数的情况下解决这个问题。

思考

​ 我看了下提示,数组的最大长度不超过300,然后要求不创建新的数组返回,直接对原数组进行修改。

​ 我现在的想法是,迭代数组,创建一个哈希表,统计0、1、2的个数,然后直接取修改数组,尝试一下。

实现

  1. 可以正常通过
class Solution:def sortColors(self, nums: List[int]) -> None:"""Do not return anything, modify nums in-place instead."""hs = defaultdict(int)# 统计个数for v in nums:if v in hs:hs[v] += 1else:hs[v] = 1len_0 = hs[0]len_1 = hs[1]+hs[0]len_2 = hs[2]+hs[1]+hs[0]# 直接修改数组for i,v in enumerate(nums):if i < len_0:nums[i] = 0elif i < len_1:nums[i] = 1else:nums[i] = 2

6. 中等 - 子集

题目

​ 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

​ 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

思考

​ 这道题和上面的路径问题有点类似,都是选不选的问题,对于这种无法直接找出结果的问题,多半涉及递归。

​ 这么去想:

  • 如果选择i作为子集一元,路径更新,就是递归后续的i+1元素是否选择,记得维护现场
  • 如果不选择i作为一元,直接递归后续的I+1

​ 边界条件:如果递归到n了,说明路径已经凑齐了,此时全局变量添加此时的路径即可

实现

class Solution:def subsets(self, nums: List[int]) -> List[List[int]]:n = len(nums)ans = []path = []# 递归def dfs(i):# 边界条件if i == n:ans.append(path.copy())return None# 选择或者不选dfs(i+1)  # 不选# 选择,记得更新路径和维护现场path.append(nums[i])dfs(i+1)path.pop()dfs(0)return ans

7. 中等 - 单词搜索

题目

​ 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

​ 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

思考

​ 这道题和上面的路径基本一模一样,只是最终返回的结果是能不能找到满足条件的路径。

​ 但是,这道题的特殊点,在于起点不是左上角,终点也不是右下角这样限制条件,而是开放条件。因此,首先关注起点是啥(函数的输入是啥),终点是啥(函数的边界条件是啥)。

​ 起点:每一个坐标(i,j) + 当前判断到word中的哪个位置了,定为k。(参考灵神题解

​ 那么算法应该是这样的:

def dfs(i,j,k):# 边界条件1. 如果 board[i][j] != word[k],说明当前这个起点并不是合适的起点,返回False2. 如果k = len(word),说明已经正常走完了程序,找到了路径,返回True# 递归1. 为了防止重复访问某个元素,我们需要标明这个元素以及访问过了board[i][j] = ''2. 子问题:去枚举访问相邻位置3. 如果这个位置合法 and dfs(x,x,k+1),则返回True1. 维护现场board[i][j] = word[k]# 4. 如果上面都执行完了,说明没有找到return False

实现

  1. 如果能够理清上面的算法,并且想到如何维护现场,写出代码并不难
class Solution:def exist(self, board: List[List[str]], word: str) -> bool:m,n = len(board),len(board[0])# 递归def dfs(i,j,k):# 边界条件if board[i][j] != word[k]:return Falseif k == len(word) - 1:return True# 递归board[i][j] = ''for x,y in [(i,j-1),(i,j+1),(i-1,j),(i+1,j)]:if 0 <= x < m and 0 <= y < n and dfs(x,y,k+1):return Trueboard[i][j] = word[k]return False# 执行函数ans = []for i in range(m):for j in range(n):ans.append(dfs(i,j,0))return any(ans) 

8. 中等 - 不同的二叉搜索树

题目

​ 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

​ 二叉搜索树:

  1. 若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值。

  2. 若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值。

思考

​ 这明显是一个动态规划的问题。按照套路来思考一下。注意:二叉搜索树,左节点小于根节点,右节点大于根节点。

​ 先把它当作一个回溯问题,那么标准流程:

  1. **初始化:**这里由于只需要考虑数量,因此不需要path变量存储组合形式,因此我们可以通过参数节点个数来表示递归对象,这样节点数的减少,表明当前递归的进行深度,当节点数只有1个的时候说明该结束运行了
  2. **当前操作是什么?**我觉得是,枚举每一个节点作为根节点
  3. **子问题是啥?**然后递归去求解左子树的二叉搜索树、递归去求解右子树的二叉搜索树,两者相乘的结果就是总的可能数目
  4. **边界条件是啥?**如果节点数目小于等于1,返回1.

实现

  1. 先根据回溯去实现这个问题:可以正常解决,如果想要提高效率,需要改为递推的方式来解决,这里暂时不改,后期再次实现的时候来思考。
class Solution:def numTrees(self, n: int) -> int:# 回溯@cachedef dfs(n):# 边界条件if n <= 1:return 1count = 0# 当前操作for i in range(1,n+1):# 左子树left = dfs(i-1)# 右子树right = dfs(n-i)# 结果count += left*rightreturn countreturn dfs(n)

9. 中等 - 验证二叉搜索树

题目

​ 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 严格小于 当前节点的数。
  • 节点的右子树只包含 严格大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

思考

​ 这就是一个普通递归问题。我们以遍历这个二叉树的方式去思考如何解决它:

  • 子问题是啥:左子树是否为有效二叉搜索树,右子树是否为有效二叉搜索树
  • 判断操作是啥:当前节点记为node,如果node.left < node < node.right,就正常递归下去即可,否则返回False
  • 边界条件是啥:如果遇到叶子节点,说明正常递归完,返回True即可

​ ok,再考虑一下递归的模板,开始实现。

实现过程中,发现一个问题:上面的判断没用考虑到当前树允许的最大值或者最小值是多少,比如我根节点通过node.left < node < node.right,但是其左子树的左右子树都必须小于根节点的值吗,这一点我没有考虑到。因此上面的思路需要维护一个变量来维护。

实现

  1. 我最初版本的代码。忽略了最值的维护:
class Solution:def isValidBST(self, root: Optional[TreeNode]) -> bool:# 边界条件if root.left is None and root.right is None:return True# 处理一下只有一个叶子节点情况if root.left is None:return root.right.val > root.valif root.right is None:return root.left.val < root.val# 子问题与判断if root.left.val < root.val and root.right.val > root.val:return  self.isValidBST(root.left) and self.isValidBST(root.right)else:return False
  1. 更新后
class Solution:def isValidBST(self, root: Optional[TreeNode],left=-inf,right=inf) -> bool:# 边界条件if root is None:return True# 子问题与判断# 1. 必须满足 left < node < right,其中left为左边的最大值,right为右边的最小值# 2. 左子树满足条件 + 右子树满足条件(记得维护最值)return (left < root.val < right) and self.isValidBST(root.left,left,root.val) and self.isValidBST(root.right,root.val,right)

10. 中等 - 二叉树的层序遍历

题目

​ 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

思考

​ 二叉树的四种遍历方式,基本是必背的题目。

​ 而层序遍历又是最为特殊的,它需要一个对列 + while循环来实现。记住这个代码就行,并且理解它。

​ 这里我分析一下如何实现:

  1. 初始化:我们需要一个结果列表ans、一个当前层的节点列表cur
  2. 第一重迭代,只要当前层不为空,我们就需要继续处理,用while cur实现
  3. 然后,我们需要一个列表存储下一个层,并且在最后赋值给cur,还需要一个列表记录当前层的值
  4. 接着,二重迭代,去访问cur里面的每个值,然后按照左右子树的先后顺序添加到下一个层中即可

实现

class Solution:def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:# 特殊情况if root is None:return []# 初始化cur = [root]  # 第一层只有rootans = []# 第一重while cur:# 初始化nxt = []val = []# 第二重for node in cur:val.append(node.val)# 更新层if node.left: nxt.append(node.left)if node.right: nxt.append(node.right)# 赋值cur = nxtans.append(val)return ans

11. 总结

​ 今日收获:

  • 二叉树层序遍历思考路径:
  1. 初始化:我们需要一个结果列表ans、一个当前层的节点列表cur
  2. 第一重迭代,只要当前层不为空,我们就需要继续处理,用while cur实现
  3. 然后,我们需要一个列表存储下一个层,并且在最后赋值给cur,还需要一个列表记录当前层的值
  4. 接着,二重迭代,去访问cur里面的每个值,然后按照左右子树的先后顺序添加到下一个层中即可
  • 动态规划的思考路径:先当作回溯问题,再记忆优化(python直接加个装饰器@cache),最后再变为递推(需要找到公式)
http://www.dtcms.com/a/457876.html

相关文章:

  • 企业网站每年的费用wordpress目录魔板
  • 做一个小公司网站多少钱网站备案归属地
  • Access调用Azure翻译:轻松实现系统多语言切换
  • R语言从入门到精通Day5之【数据输入】
  • 网站开发挣不挣钱南通网站建设知识
  • 仿手机底部导航栏制作
  • 二维码生成的技术原理与全场景实践
  • 做网站 嵌入支付wordpress优化攻略
  • Chromium Embedded Framework (CEF)的构建及运行
  • 批量替换yaml文件url字段
  • “软件维护” 分 4 类?用 “奶茶店售后” 讲透更正 / 适应性 / 完善性维护
  • 恋爱ppt模板免费下载网站网站建设项目风险管理的主要内容
  • 网站主机选择98建筑人才网
  • Windows中在QTCreator中调试,提示缺少debug information files问题的解决
  • 做宠物店网站的素材seo一级域名和二级域名
  • 施工工地云监管平台,工程建设现场管理,智慧工地云平台源码,以AI、物联网、BIM技术为手段,对施工现场进行立体化、全方位、全时段管理
  • 用单调栈高效解决 “首尾均为最大值” 的子数组计数问题(Leetcode 3113)
  • 企业网站自己可以做吗wordpress 登陆 插件
  • 初学c#-c#和.NET Framework - onecopper
  • 大沥南庄网站建设网站开发建设流程
  • nvMolKit:一套基于GPU加速的RDKit核心函数集
  • LOBE-GS:分块致密化效率提升
  • 福州建设招聘信息网站pt网站怎么下载与做
  • dede免费手机网站模板象山seo的优化
  • 央视支持新消费模式:积分助力商家锁客,复购率翻倍
  • 专业政务软件开发北京移动端网站优化
  • 怎样提高网站访问速度一起做网站欧洲站
  • 公司网站建设费属于宣传费吗重庆专业的网站建设公司
  • 广西网站建设哪里好wordpress消息通知
  • 海南网站建设网站开发网站建设的流程步骤