【算法题目】【Python】彻底刷遍DFS/BFS的算法题目
创始人
2024-05-30 17:05:44
0

文章目录

  • 参考资料
  • 树的前序、中序、后序遍历
  • 树的层次遍历
  • 回溯与剪枝
    • 组合
    • 组合总和 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:returnres.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:continueres.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 = rootwhile node or stack:while node:stack.append(node)node = node.leftnode = stack.pop()res.append(node.val)node = node.rightreturn res

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

class Solution:def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:res = []stack = [root]while stack:node = stack.pop()if not node:continueres.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进行appenddfs(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[:])returnfor 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:returnif 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:breakpath.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)returnfor i in range(start, len(digits)):  # 从start开始,因为start之前的已经处理过了sfor 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))returndigit = 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:returnif target == 0:res.append(path)returnprint(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[:])returnif target < 0:returnfor 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)returnfor i in range(start, len(candidates)):if i > start and candidates[i] == candidates[i-1]:continueif candidates[i] > target:breakdfs(i+1, target-candidates[i], path+[candidates[i]])candidates.sort()res = []dfs(0, target, [])return res

相关内容

热门资讯

韩愈《劝学篇》全文 韩愈《劝学篇》全文  《进学解》是元和七、八年间韩愈任国子博士时所作,是韩愈所创作的一篇关于劝学的文...
李鳝《蕉石图》鉴赏 李鳝《蕉石图》鉴赏  李鳝出身富豪之家,早年曾以画人内廷供奉,后来则转任山东滕县知县。李鳝画《蕉石图...
张俭传文言文阅读及答案 张俭传文言文阅读及答案  张俭字元节,山阳高平人,赵王张耳之后也,父成,江夏太守。俭初举茂才,以刺史...
心经全文注音朗诵 心经全文注音朗诵  导语:凡人要度苦厄,了生死,成大觉,非从自心下手不可。以下小编为大家介绍心经全文...
范元琰良善文言文翻译 范元琰良善文言文翻译  范元琰,字伯珪,吴郡钱塘人也。小编整理的范元琰良善文言文翻译,欢迎大家前来查...
韩非子权术全文译文 韩非子权术全文译文  引导语:《韩非子权术》这篇课文想必很多人都读过,那么相关的韩非子权术的全文以及...
《不食嗟来之食》原文注释及翻... 《不食嗟来之食》原文注释及翻译  《不食嗟来之食》,选自《礼记。檀弓》。《礼记》,是中国古代一部重要...
青门引 张先 赏析 青门引 张先 赏析 千秋岁 数声鶗鴂 张先,是作者写的一篇伤春词,同时抒发自己寥落寂寞的处境。本文由...
苏东坡红梅原文及翻译 苏东坡红梅原文及翻译  宋神宗元丰五年(1082),当时苏轼贬官在黄州,因读石延年《红梅》诗引起感触...
父亲节英文祝福语 父亲节英文祝福语(精选50句)  有一种深情叫做父子连心,有一种真爱叫做父爱如山。父亲的爱庄重而深刻...
《吕氏春秋》的社会治理观 《吕氏春秋》的社会治理观《吕氏春秋》一书的根本宗旨是一个“治”字.它全面吸收前人的社会治理思想,融汇...
《沉醉东风》原文翻译 《沉醉东风》原文翻译(通用7篇)  沉醉东风的作者是关汉卿,元代杂剧作家,号已斋叟(一作一斋),约生...
王羲之兰亭序鉴赏 王羲之兰亭序鉴赏  兰亭集序是中国晋代(公元353年),书圣王羲之在浙江绍兴兰渚山下以文会友,写出“...
人间词话原文及翻译 人间词话原文及翻译  《人间词话》为“双色图文传世经典”之一,该系列是一套普及性读物,对象主要是具有...
荀子劝学说理方式 荀子劝学说理方式  《劝学》是《荀子》的开篇之作,值得反复涵泳咀嚼,看看下面的相关文章,看看下面的劝...
梦游天姥吟留别的赏析 梦游天姥吟留别的赏析精选  导语:《梦游天姥吟留别》是唐代大诗人李白创作的一首古体诗。此诗是记梦诗,...
清明节修坟的风水注意事项 清明节修坟的风水注意事项  阴阳风水按五行,生克制化各用功;八卦吉凶方位定,推开九宫也显能;修坟立碑...
《胡铨传》原文及译文解析的内... 《胡铨传》原文及译文解析的内容  胡铨字邦衡,庐陵人。建炎二年,高宗策士淮海,铨因御题问“治道本天,...
《黄河颂》原文 《黄河颂》原文  《黄河大合唱》是现代诗人光未然于1939年创作的一首现代诗。此诗是一首反映抗日救亡...
《资治通鉴·长平之战》原文及... 《资治通鉴·长平之战》原文及译文 《资治通鉴》(常简作《通鉴》):由北宋司马光主编的一部多卷本编年...