文章目录
- 参考资料
- 树的前序、中序、后序遍历
- 树的层次遍历
- 回溯与剪枝
- 组合
- 组合总和 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



















