【图解算法数据结构】(三)搜索与回溯算法-下

目录

十、剑指 Offer 36. 二叉搜索树与双向链表

10.1 题求

10.2 求解

10.3 解答

十一、剑指 Offer 37. 序列化二叉树

11.1 题求

11.2 求解

11.3 解答

十二、剑指 Offer 38. 字符串的排列

12.1 题求

12.2 求解

12.3 解答

十三、剑指 Offer 54. 二叉搜索树的第 k 大节点

13.1 题求

13.2 求解

13.3 解答

十四、剑指 Offer 55 - I. 二叉树的深度

14.1 题求

14.2 求解

14.3 解答

十五、剑指 Offer 55 - II. 平衡二叉树

15.1 题求

15.2 求解

15.3 解答

十六、剑指 Offer 64. 求 1 + 2 + … + n

16.1 题求

16.2 求解

16.3 解答

十七、剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

17.1 题求

17.2 求解

十八、剑指 Offer 68 - II. 二叉树的最近公共祖先

18.1 题求

18.2 求解


十、剑指 Offer 36. 二叉搜索树与双向链表

10.1 题求

10.2 求解

法一:中序遍历

  • 空间复杂度 O(1)
  • 时间复杂度 O(N)
# 32ms - 98.66%
"""
# Definition for a Node.
class Node:
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
"""

class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        if not root:
            return
        
        # 哨兵节点 x2
        dummy = prev = TreeNode(0)
        
        def inorder(node):
            nonlocal prev  # 使用非局部作用域的 prev
            if node:
                inorder(node.left)
                
                prev.right = node  # ->
                node.left = prev  # <-
                prev = node  # update
                
                inorder(node.right)
            return  
        
        # 中序遍历互连
        inorder(root)
        
        # 尾部节点跳过哨兵节点与头部节点互联
        prev.right = dummy.right
        dummy.right.left = prev
        
        # 返回头部节点
        return prev.right

官方说明 

class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        if not root: 
            return

        def dfs(cur):
            if not cur: 
                return
            dfs(cur.left)   # 递归左子树
            if self.pre:    # 修改节点引用
                self.pre.right, cur.left = cur, self.pre
            else:           # 记录头节点
                self.head = cur
            self.pre = cur  # 保存 cur
            dfs(cur.right)  # 递归右子树
        
        self.pre = None
        dfs(root)
        self.head.left, self.pre.right = self.pre, self.head

        return self.head

10.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dbies/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dj09d/


十一、剑指 Offer 37. 序列化二叉树

11.1 题求

11.2 求解

# 一些测试用例
[5, null, 2 ,1] 
[5,2,3,null,null,2,4,3,1]  
[]
[1,9,2,8,10]
[-10, -5, 0, 12, -3]
[-1,0,1]
[-1, -2, -3, 0, 4]
[1, 2]
[1,2,3,null,null,4,5]
[]
[1]
[1, 2, 3]  

官方说明 

# 104ms - 94.76%
class Codec:
    def serialize(self, root):
        if not root: 
            return "[]"

        queue = collections.deque()
        queue.append(root)
        res = []
        while queue:
            node = queue.popleft()
            # 有效节点
            if node:
                res.append(str(node.val))
                queue.append(node.left)
                queue.append(node.right)
            # 空节点
            else: 
                res.append("null")

        # ',' 分隔各节点
        return '[' + ','.join(res) + ']'

    def deserialize(self, data):
        if data == "[]": 
            return

        vals, i = data[1:-1].split(','), 1
        root = TreeNode(int(vals[0]))
        queue = collections.deque()  # i = 0
        queue.append(root)
        while queue:
            # 当前节点
            node = queue.popleft()
            # 左子节点
            if vals[i] != "null":
                node.left = TreeNode(int(vals[i]))
                queue.append(node.left)
            i += 1  # 为当前节点移位 (若空节点则仅移位不处理)
            # 右子节点
            if vals[i] != "null":
                node.right = TreeNode(int(vals[i]))
                queue.append(node.right)
            i += 1  # 为当前节点移位 (若空节点则仅移位不处理)

        return root

11.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/990pf2/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/997ebc/


十二、剑指 Offer 38. 字符串的排列

12.1 题求

12.2 求解

法一:回溯

# 30%
class Solution:
    def permutation(self, s: str) -> List[str]:
        # 字符串索引集合
        index_set = set(i for i in range(len(s)))
        # 字符串排列结果集合
        res = set()
        
        def traverse(have, rest):
            # 长度满足, 记录返回
            if not rest:
                res.add(''.join(have))
                return
            
            # 遍历其余元素的索引
            select = rest.copy()
            for i in select:
                # 加入
                have.append(s[i])
                rest.remove(i)
                # 回溯
                traverse(have, rest)
                # 还原
                have.pop()
                rest.add(i)

        traverse([], index_set)
        return list(res)

官方说明

# 88ms - 94.85%
class Solution:
    def permutation(self, s: str) -> List[str]:
        c, res = list(s), []
        
        def dfs(x):
            if x == len(c)-1:
                res.append(''.join(c))   # 添加排列方案
                return
            
            dic = set()                  # 当前位 (index = x) 已尝试索引集合
            for i in range(x, len(c)):   # 当前位 (index = x) 开始依次交换后续位置
                if c[i] in dic: 
                    continue             # 当前位 (index = x) 已重复 - 剪枝
                    
                dic.add(c[i])            # 当前位 (index = x) 未重复 - 记录
                c[i], c[x] = c[x], c[i]  # 交换, 将 c[i] 设为第 x 位
                dfs(x+1)                 # 固定第 x+1 位字符
                c[i], c[x] = c[x], c[i]  # 恢复, 将 c[x] 换回第 i 位
        dfs(0)
        return res

12.3 解答

 参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5dfv5h/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/50hah3/


十三、剑指 Offer 54. 二叉搜索树的第 k 大节点

13.1 题求

13.2 求解

法一:递归 - 逆中序遍历

# 52ms - 90.65%
class Solution:
    def kthLargest(self, root: TreeNode, k: int) -> int:
        
        def dfs(node):
            ''' 逆中序遍历 - 从大到小排列 '''
            if node:
                dfs(node.right)
                inorder.append(node.val)
                dfs(node.left)

        inorder = []
        dfs(root)
        return inorder[k-1]

法二:迭代 - 逆中序遍历

# 44ms - 98.96%
class Solution:
    def kthLargest(self, root: TreeNode, k: int) -> int:
        ''' 
        中序遍历迭代方式 
        https://blog.csdn.net/qq_39478403/article/details/107329233 
        '''
        inv_inorder = []  # 逆中序遍历 - 从大到小排列
        stack = []
        node = root
        while stack or node:
            if node:
                stack.append(node)
                node = node.right  # 逆中序遍历
            else:
                node = stack.pop()
                inv_inorder.append(node.val)
                node = node.left  # 逆中序遍历

                # 一旦达到指定数目, 即为需要返回的第 k 大值
                if len(inv_inorder) == k:
                    return inv_inorder[-1]

官方说明 

# 36ms - 99.93%
class Solution:
    def kthLargest(self, root: TreeNode, k: int) -> int:
        def dfs(root):
            if not root: 
                return

            dfs(root.right)  # 中序遍历的倒序

            if self.k == 0:  # 到达指定位置, 返回
                return

            self.k -= 1  # 否则待查询项 -1
            if self.k == 0:  # 若到达指定位置
                self.res = root.val  # 取得指定节点值

            dfs(root.left)  # 中序遍历的倒序

        self.k = k  # 设为类属性
        dfs(root)  # DFS
        return self.res  # 返回指定节点值

13.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58df23/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/580cam/


十四、剑指 Offer 55 - I. 二叉树的深度

14.1 题求

14.2 求解

法一:递归 - DFS

# 40ms - 94.44%
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))

法二:迭代 - BFS

# 40ms - 94.44%
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        
        depth = 0
        deque = collections.deque()
        deque.append(root)
        while deque:
            for _ in range(len(deque)):
                node = deque.popleft()
                if node.left:
                    deque.append(node.left)
                if node.right:
                    deque.append(node.right)
            depth += 1
            
        return depth
        

官方说明 

14.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hgr5i/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hz9xe/


十五、剑指 Offer 55 - II. 平衡二叉树

15.1 题求

15.2 求解

法一:DFS - 后序遍历

# 36ms - 99.73%
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        max_diff = 0  # 整树的最大深度差
        
        def dfs(node, depth):
            nonlocal max_diff
            # 返回根节点的整树(全局)深度
            if not node:
                return depth
        
            # 计算当前节点的左右子树的最大深度
            left_depth = dfs(node.left, depth+1)
            right_depth = dfs(node.right, depth+1)  
            
            # 计算当前节点的左右子树的最大深度差、更新整树的最大深度差
            diff = abs(left_depth - right_depth)
            if diff > max_diff:
                max_diff = diff
            
            # 返回当前节点左右子树中的最大深度
            return max(left_depth, right_depth)

        dfs(root, 0)               
        return max_diff < 2

官方说明 

# 44ms - 97.44%
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        def recur(root):
            if not root: 
                return 0

            left = recur(root.left)  # 左子树最大深度
            if left == -1: 
                return -1

            right = recur(root.right)  # 右子树最大深度
            if right == -1: 
                return -1
            
            # 一旦出现左右子树深度差 > 1 则永远返回 -1
            return max(left, right) + 1 if abs(left - right) <= 1 else -1

        return recur(root) != -1

# 56ms - 77.74%
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        if not root: 
            return True
        return abs(self.depth(root.left) - self.depth(root.right)) <= 1 and \
            self.isBalanced(root.left) and self.isBalanced(root.right)

    def depth(self, root):
        if not root: 
            return 0
        return max(self.depth(root.left), self.depth(root.right)) + 1

15.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hzffg/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hscjv/


十六、剑指 Offer 64. 求 1 + 2 + … + n

16.1 题求

16.2 求解

法一:递归 (短路特性)

# 40ms - 85.18%
class Solution:
    def sumNums(self, n: int) -> int:
        '''
        同时, 利用 bool 值是 int 子类和 and 的短路特性    
        其成立时将返回最后一个表达式, n=0 时则将终止并返回 0
        '''
        return n and (n + self.sumNums(n-1))

官方说明 

# 36ms - 94.42%
class Solution:
    def __init__(self):
        self.res = 0
        
    def sumNums(self, n: int) -> int:
        (n > 1) and self.sumNums(n-1)  # n 的大小决定了递归是否继续进行
        self.res += n  # 累加当前值 n
        return self.res  # 返回当前累加和

16.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9h44cj/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9h1gyt/


十七、剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

17.1 题求

17.2 求解

法一:DFS + 剪枝

        根据题求及 二叉平衡树 性质 (根节点必大于左子节点、必小于右子节点),当前节点 node 仅存在 3 种情况:

  1. min(p.val, q.val) ≤ node.val ≤ max(p.val, q.val),此时当前节点 node 必为最近公共祖先
  2. node.val < min(p.val, q.val),此时当前节点 node 需要向右子树搜索 dfs(node.right)
  3. node.val > max(p.val, q.val),此时当前节点 node 需要向左子树搜索 dfs(node.left)
# 56ms - 99.97%
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        # 二叉搜索树的最佳最近公共祖先节点
        ancester = None
        min_val = min(p.val, q.val)
        max_val = max(p.val, q.val)        

        def dfs(node):
            nonlocal ancester  # 采用外层作用域变量

            if not node:  # 找到根节点外了 - 始终未找到
                return False
            elif ancester is not None:  # DFS 剪枝 - 已找到就不要再继续找了
                return True

            # 情况 1
            if min_val <= node.val and node.val <= max_val:
                ancester = node
            # 情况 2
            elif min_val > node.val:
                dfs(node.right)
            # 情况 3
            elif node.val > max_val:
                dfs(node.left)

        dfs(root)
        return ancester

官方说明

# 76ms - 90.67%
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        while root:
            if root.val < p.val and root.val < q.val: # p,q 都在 root 的右子树中
                root = root.right  # 遍历至右子节点
            elif root.val > p.val and root.val > q.val: # p,q 都在 root 的左子树中
                root = root.left  # 遍历至左子节点
            else: break
        return root

# 72ms - 94.75%
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if p.val > q.val: p, q = q, p  # 保证 p.val < q.val
        while root:
            if root.val < p.val:   # p,q 都在 root 的右子树中
                root = root.right  # 遍历至右子节点
            elif root.val > q.val: # p,q 都在 root 的左子树中
                root = root.left   # 遍历至左子节点
            else: break
        return root

# 76ms - 90.67% - 未经剪枝性能还是差了点
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if root.val < p.val and root.val < q.val:
            return self.lowestCommonAncestor(root.right, p, q)
        if root.val > p.val and root.val > q.val:
            return self.lowestCommonAncestor(root.left, p, q)
        return root

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/575kd2/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5793vc/


十八、剑指 Offer 68 - II. 二叉树的最近公共祖先

18.1 题求

18.2 求解

法一:DFS + 后序遍历

# 68ms - 90.46%
class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        # 找到根节点外都没找到 或 找到指定节点, 都返回之
        if not root or root.val == p.val or root.val == q.val:
            return root
        
        # DFS 寻找指定节点是否存在于左、右子树
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        
        # 指定节点均不存在
        if not left and not right:
            return None
        # 指定节点只存在其一
        elif not right:
            return left
        elif not left:
            return right
        # 指定节点均存在, 找到二叉树的最佳公共祖先
        else:
            return root

官方说明

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if not root or root == p or root == q: 
            return root

        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)

        if not left: 
            return right
        if not right: 
            return left
        return root

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if not root or root == p or root == q: 
            return root

        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)

        if not left and not right:  # 1.
            return  
        if not left:                # 3.
            return right   
        if not right:               # 4.
            return left
        return root                 # 2. if left and right:

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/57euni/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/57o72e/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值