【算法题目】【Python】彻底刷遍DFS/BFS的算法题目

news2024/10/3 4:41:33

文章目录

  • 参考资料
  • 树的前序、中序、后序遍历
  • 树的层次遍历
  • 回溯与剪枝
    • 组合
    • 组合总和 III
    • 电话号码的字母组合
    • 组合总和
  • 组合总和 II

参考资料

参考这里面的一些讲解: https://github.com/youngyangyang04/leetcode-master。

树的前序、中序、后序遍历

看完 树的种类 之后,就需要熟悉好树的遍历。

树的前序遍历leetcode:

DFS递归写法很重要,需要重点理解掌握:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res=[]
        def dfs(root):
            if not root:
                return
            res.append(root.val)
            dfs(root.left)
            dfs(root.right)
        dfs(root)
        return res

在树结构中,DFS可以搞定的,使用栈构成迭代法也是可以搞定的:

# 首先将根节点入栈,然后每次从栈顶取出一个节点,并将其值加入到结果列表中;
# 接着按照右-左-根的顺序将其子节点入栈。
# 由于栈是先进后出的结构,所以弹出左子节点时会优先处理它的子树,从而实现了前序遍历的效果。
class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        stack = [root]
        while stack:
            node = stack.pop()
            if not node:
                continue
            res.append(node.val)
            stack.append(node.right)
            stack.append(node.left)
        return res

迭代法 中序遍历:

# 先将根节点及其所有左子节点入栈,然后每次从栈顶取出一个节点,并将其右子节点入栈,直到栈为空。由于出栈顺序为左-中-右,所以可得到中序遍历的结果
class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        stack = []
        node = root
        while node or stack:
            while node:
                stack.append(node)
                node = node.left
            node = stack.pop()
            res.append(node.val)
            node = node.right
        return res

迭代法后序遍历 和 迭代法前序遍历 写法类似:

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        stack = [root]
        while stack:
            node = stack.pop()
            if not node:
                continue
            res.append(node.val)
            stack.append(node.left)
            stack.append(node.right)
        return res[::-1]

树的层次遍历

可以使用递归法写出:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        res=[]
        
        def dfs(root,depth):
            if not root:
                return []
            if len(res)==depth:res.append([])
            res[depth].append(root.val) # depth层的list进行append
            dfs(root.left, depth + 1)
            dfs(root.right, depth + 1)
        
        dfs(root,0)

        return res

也可以使用队列实现:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []

        result = []
        queue = [root]

        while queue:
            level_size = len(queue)
            current_level = []

            for i in range(level_size):  # 队列当前元素是这一层的Node,全部弹出
                node = queue.pop(0)
                current_level.append(node.val)

                if node.left:
                    queue.append(node.left)

                if node.right:
                    queue.append(node.right)

            result.append(current_level)

        return result

回溯与剪枝

关于树的算法题目很多,比如求树的深度、节点个数、删除节点等题目,都需要耐心刷完,这里主要关注DFS搜索和BFS搜索,依靠多刷一些题,总结出规律。

按DFS的思维遍历树的节点的时候,意识到DFS的思维是将总问题转为当前可解决的问题与一个子问题,且子问题的解决方式就是总问题,以此达到递归的目的。

按【代码随想录】的题目刷完:
在这里插入图片描述

组合

https://leetcode.cn/problems/combinations/:

下面的解法思维会经常性使用,需要思考dfs的传参,思考何时停止条件,思考罗列所有状态:

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        res = []
        nums = list(range(1, n+1))

        def backtrack(start, path): # 从哪里开始选元素,这个切入点很重要;当前选了什么元素,这个切入点也重要。
            # 如果当前的组合长度等于k,将其加入结果列表
            if len(path) == k:
                res.append(path[:])
                return
            
            # 遍历可选数字,并尝试将其加入组合中
            for i in range(start, n):
                path.append(nums[i])
                backtrack(i+1, path) # 从下个元素开始选
                path.pop()

        backtrack(0, [])
        return res

对于该问题,可以采用一些剪枝策略来优化搜索过程,减少不必要的计算。

一种常见的剪枝策略是 “剪去无法得到 k 个数字的组合”,具体实现为:在递归函数 backtrack 中,当 path 的长度加上从 start 到 n 的数字数量仍然小于 k 时,说明已经无法得到长度为 k 的组合了,此时直接返回即可。

另外,在枚举可选数字时,我们可以限制 i 的上限,以确保最后一个数字被选择时,剩余的数字数量也足够凑成长度为 k 的组合。具体实现为:在 for 循环中,将 i 的上限设置为 n - (k - len(path)) + 1。

剪枝是非常重要的策略,在这里测试的话可以降低时间复杂度7倍。 剪枝的本质就是要思考更多的递归停止条件 。

下面是使用上述两种剪枝策略的代码:

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        res = []
        nums = list(range(1, n+1))

        def backtrack(start, path):
            # 如果当前的组合长度等于k,将其加入结果列表
            if len(path) == k:
                res.append(path[:])
                return
            
            # 剪枝:无法凑齐k个数字的组合
            if len(path) + n - start + 1 < k:
                return

            # 剪枝:i 的上限
            for i in range(start, n - (k - len(path)) + 2):
                path.append(nums[i-1])
                backtrack(i+1, path)
                path.pop()

        backtrack(1, [])
        return res

组合总和 III

https://leetcode.cn/problems/combination-sum-iii/description/
只用回溯:

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        res = []
        nums = [i for i in range(1, 10)]

        def dfs(start, path):
            if sum(path) == n and len(path) == k:
                res.append(path[:])
                return
            for i in range(start, len(nums)):
                path.append(nums[i])
                dfs(i + 1, path)  # 注意是i+1,不是start+1,start是固定的
                path.pop()

        dfs(0, [])
        return res

加上剪枝:
(1)path总和大于n的直接停止;
(2)i的上限;

class Solution:
    def combinationSum3(self, k: int, n: int) -> List[List[int]]:
        res = []
        nums = [i for i in range(1, 10)]

        def dfs(start, path, target):
            if target < 0:
                return
            if len(path) == k and target == 0:
                res.append(path[:])
                return

            # 剪枝:i 的上限
            upper = min(8, target)  # 最大可选数字
            for i in range(start, upper + 1):
                if sum(path) + nums[i] > n or len(path) == k:
                    break
                path.append(nums[i])
                dfs(i + 1, path, target - nums[i])
                path.pop()

        dfs(0, [], n)
        return res

电话号码的字母组合

https://leetcode.cn/problems/letter-combinations-of-a-phone-number/
Python暴力也很有味道:

    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []
        d = {"2": "abc", "3": "def", "4": "ghi", "5": "jkl", "6": "mno", "7": "pqrs", "8": "tuv", "9": "wxyz"}
        res = [""]
        for i in digits:
            res = [x + y for x in res for y in d[i]]
        return res

此题的dfs也可以不需要回溯:

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []
        d = {"2": "abc", "3": "def", "4": "ghi", "5": "jkl", "6": "mno", "7": "pqrs", "8": "tuv", "9": "wxyz"}
        res = []

        def dfs(start, path):  # 得益于Python的字符串加法,可以把当前字符串带入到下一次dfs,不用回溯记忆
            if len(path) == len(digits):
                res.append(path)
                return
            for i in range(start, len(digits)):  # 从start开始,因为start之前的已经处理过了s
                for j in d[digits[i]]:  # 选某一个字母
                    dfs(i + 1, path + j)

        dfs(0, "")
        return res

下面是回溯法的思路:

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return list()
        phoneMap = {
            "2": "abc",
            "3": "def",
            "4": "ghi",
            "5": "jkl",
            "6": "mno",
            "7": "pqrs",
            "8": "tuv",
            "9": "wxyz",
        }
        def backtrack(index: int):
            if index == len(digits):
                combinations.append("".join(combination))
                return
            digit = digits[index]
            for letter in phoneMap[digit]:
                combination.append(letter)
                backtrack(index + 1)
                combination.pop()
        combination = list()
        combinations = list()
        backtrack(0)
        return combinations

组合总和

https://leetcode.cn/problems/combination-sum/description/
dfs:

# 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
# candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        res = []

        def dfs(candidates, target, path):
            if target < 0:
                return
            if target == 0:
                res.append(path)
                return
            print(candidates)
            for i in range(len(candidates)):
                dfs(candidates[i:], target - candidates[i], path + [candidates[i]])  # 传入的还是全部的备选

        dfs(candidates, target, [])
        return res

回溯法:

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        result = []
        def dfs(candidates, begin, path, target):
            if target == 0:
                result.append(path[:])
                return
            if target < 0:
                return
            for i in range(begin, len(candidates)):
                path.append(candidates[i])
                dfs(candidates, i, path, target-candidates[i])
                path.pop()
        dfs(candidates, 0, [], target)
        return result

组合总和 II

https://leetcode.cn/problems/combination-sum-ii/description/
dfs:

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        def dfs(start, target, path):
            if target == 0:
                res.append(path)
                return
            for i in range(start, len(candidates)):
                if i > start and candidates[i] == candidates[i-1]:
                    continue
                if candidates[i] > target:
                    break
                dfs(i+1, target-candidates[i], path+[candidates[i]])
        
        candidates.sort()
        res = []
        dfs(0, target, [])
        return res

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

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

相关文章

网络 | UDP与TCP协议讲解 | TCP可靠性是怎样实现的?

文章目录前置知识查看网络状态的工具查看进程idUDP协议协议格式UDP只有接收缓冲区基于UDP的应用层协议TCP协议流的理解协议格式确认应答机制缓冲区序号的作用流量控制超时重传机制6位标志位紧急数据的处理三次握手listen的第二个参数全连接和半连接队列都维护了什么信息&#x…

史上最全若依管理系统修改页面标题和logo

整理若依框架去除 若依标题、logo及其他内容。一&#xff1a;网页上的logo进入ruoyi-ui --> public --> favicon.ico&#xff0c;把这个图片换成你自己的logo二&#xff1a;页面中的logo进入ruoyi-ui --> src --> assets --> logo --> logo.png&#xff0c;把…

Git版本控制工具(详解)

Git版本控制工具 Git常见命令速查表 集中式版本控制 cvs和svn都是属于集中式版本控制系统 他们的主要特点是单一的集中管理服务器 保存所有文件的修订版本协同开发人员通过客户端连接到这台服务器 取出最新的文件或者提交更新 优点每个人都可以在一定程度上看到项目中的其他…

动态规划——子序列、编辑距离、回文子串

目录 子序列问题 300.最长递增子序列 674.最长连续递增序列 718.最长重复子数组 1143.最长公共子序列 1035.不相交的线 53.最大子序和 编辑距离 392.判断子序列 115.不同的子序列 583.两个字符串的删除操作 72.编辑距离 回文子串 647.回文子串 516.最长回文子序列…

使用sapply函数改写for循环并绘制迟滞温度与污染物效应图

For循环应该是我们在R语言使用得最普遍的循环了&#xff0c;优势就是简单、易上手&#xff0c;缺点就是慢&#xff0c;特别对于跑数据量比较大的数据。Apply家族函数使用C来编写&#xff0c;运行得非常快&#xff0c;非常适合代替for循环。今天介绍一下sapply函数改写for循环并…

abp.net 5.0 部署IIS10

今天遇到了abp.net 5.0部署iis10被卡住的问题&#xff0c;网上找了一些资料&#xff0c;都不是我要的&#xff0c;最后我总结一下我用的是 5.0的版本&#xff0c;所以我需要给服务器安装 iis5.0的相关运行环境 1&#xff1a;https://dotnet.microsoft.com/zh-cn/download/dotne…

html--学习

javascrapt交互&#xff0c;网页控制JavaScript&#xff1a;改变 HTML 图像本例会动态地改变 HTML <image> 的来源&#xff08;src&#xff09;&#xff1a;点亮灯泡<script>function changeImage() {elementdocument.getElementById(myimage) #内存变量&#xff0…

Linux---基本指令

专栏&#xff1a;Linux 个人主页&#xff1a;HaiFan. 基本指令ls 指令pwd命令cd 指令touch指令mkdir指令&#xff08;重要&#xff09;rmdir指令 && rm 指令&#xff08;重要&#xff09;man指令&#xff08;重要&#xff09;cp指令&#xff08;重要&#xff09;mv指令…

win10 C++调用conda的python

普通 比如说是conda的DL环境&#xff0c;路径是D:\Miniconda3\envs\DL VC目录->包含目录里加入D:\Miniconda3\envs\DL\include VC目录->库目录里加入D:\Miniconda3\envs\DL\libs 链接器->输入->附加依赖项里加入D:\Miniconda3\envs\DL\libs\python37.lib 在l…

“ 寻友之旅 “ 的三种解决办法

题目来源于&#xff1a;稀土掘金 " 寻友之旅 " 的三种解决办法&#xff01; 本文将分别讲解如何使用BFS、双向BFS以及 Dijsktra堆优化的方法来解决此题~ 一起来看看吧&#xff01; 附Java题解代码&#xff01; 文章目录" 寻友之旅 " 的三种解决办法&#…

如何将两个或多个PDF文件合并成一个?这3个方法可以看看

在工作中&#xff0c;有时候我们需要把两个或多个PDF文件合并成一个&#xff0c;这样一来&#xff0c;可以方便阅读、修改&#xff0c;还能快速打印文件。 下面分享3个工具&#xff0c;看看如何将两个或多个PDF文件合并成一个文件。 方法一&#xff1a;使用美图工具 如果PDF文…

【Spring AOP】如何统一“拦截器校验、数据格式返回、异常返回”处理?

目录 一、Spring 拦截器 1.1、背景 1.2、实现步骤 1.3、拦截原理 二、 统一url前缀路径 2.1、方法一&#xff1a;在系统的配置文件中设置 2.2、方法二&#xff1a;在 application.properies 中配置 三、统一异常处理 四、统一返回数据返回格式处理 4.1、背景 4.2、…

PTA:L1-025 正整数A+B、L1-026 I Love GPLT、L1-027 出租(C++)

目录 L1-025 正整数AB 问题描述&#xff1a; 实现代码&#xff1a; L1-026 I Love GPLT 问题描述&#xff1a; 实现代码&#xff1a; L1-027 出租 问题描述&#xff1a; 实现代码&#xff1a; 原理思路&#xff1a; 出租那道题有点意思哈 L1-025 正整数AB 问题描述…

【Java学习笔记】13.Java StringBuffer 和 StringBuilder 类

Java StringBuffer 和 StringBuilder 类 当对字符串进行修改的时候&#xff0c;需要使用 StringBuffer 和 StringBuilder 类。 和 String 类不同的是&#xff0c;StringBuffer 和 StringBuilder 类的对象能够被多次的修改&#xff0c;并且不产生新的未使用对象。 在使用 St…

Tomcat8安装

1、前置环境 Tomcat 8 对应jdk 1.8 版本&#xff1b;如果你的jdk版本是8以上&#xff0c;则安装对应的tomcat版本。 jdk8 官方下载安装时&#xff0c;先安装jdk版本&#xff0c;最后单独安装jre。所以电脑会有两套jre&#xff0c;一套是jdk中的jre&#xff0c;位于 \jre 目录下…

客户案例|三强联手,深度集成,实现四方共赢

关键发现&#xff1a; 用户痛点&#xff1a;以现有ERP系统台账表单模式管理设备&#xff0c;已经不能满足伯恩业务增长所需的设备管理优化与革新的要求。 解决方案&#xff1a;利用西门子Mendix低代码平台与SAP PM模块进行集成开发的联合解决方案&#xff0c;为实现客户设备资…

3.8 并查集

并查集 题目链接 用途 维护集合 将两个集合合并询问两个元素是否在一个集合当中 实现思路 用树的形式维护集合每个集合用一棵树表示&#xff0c;树根的编号就是整个集合的编号&#xff0c;每个节点存储他的父节点&#xff0c;p[x]表示节点x的父节点判断树根的方法:p[x]x求…

运维视角:rabbitmq教程(三)镜像集群

上期回顾 RabbitMQ集群中节点包括内存节点、磁盘节点。内存节点就是将所有数据放在内存&#xff0c;磁盘节点将数据放在磁盘上。如果在投递消息时&#xff0c;打开了消息的持久化&#xff0c;那么即使是内存节点&#xff0c;数据还是安全的放在磁盘。那么内存节点的性能只能体现…

如何编写BI项目之ETL文档

XXXXBI项目之ETL文档 xxx项目组 ------------------------------------------------1---------------------------------------------------------------------- 目录 一 、ETL之概述 1、ETL是数据仓库建构/应用中的核心…

Linux基础命令-kill向进程发送信号

Linux基础命令-setfacl设置文件ACL策略规则 Kill 一.命令介绍 先使用帮助文档查看命令的信息 NAME kill - terminate a process kill命令的主要功能就是向进程发送信号&#xff0c;这里我们主要用来终止结束进程的&#xff0c;与它的英文单词含义相同&#xff0c;在Linux系统…