zoukankan      html  css  js  c++  java
  • [总结]树

    树作为一种基本的数据结构,也是算法题常考的题型。基本的如树的遍历,树的高度,树的变种数据结构等。

    树的遍历

    树的遍历有四种:前序,中序,后序,层次。都需要掌握其递归与非递归方式。

    [leetcode]94.Binary Tree Inorder Traversal

    中序遍历

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def inorderTraversal(self, root: TreeNode) -> List[int]:
            p = root
            stack = []
            res = []
            while p or stack:
                while p:
                    stack.append(p)
                    p = p.left
                p = stack.pop()
                res.append(p.val)
                p = p.right
            return res
    
    [leetcode]102.Binary Tree Level Order Traversal

    层次遍历

    import collections
    class Solution:
      def levelOrder(self, root: TreeNode) -> List[List[int]]:
          res = []
          if not root: return res
          queue = collections.deque()
          queue.append(root)
          while queue:
              level = []
              for i in range(len(queue)):
                  curr = queue.popleft()
                  level.append(curr.val)
                  if curr.left:
                      queue.append(curr.left)
                  if curr.right:
                      queue.append(curr.right)
              res.append(level)
          return res
    
    [leetcode]144.Binary Tree Preorder Traversal

    前序遍历

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        # def preorderTraversal(self, root: TreeNode) -> List[int]:
        #     """
        #     :type root: TreeNode
        #     :rtype: List[int]
        #     """
        #     res = []
        #     self.dfs(root,res)
        #     return res
        # def dfs(self,root,res):
        #     if not root:
        #         return
        #     res.append(root.val)
        #     self.dfs(root.left,res)
        #     self.dfs(root.right,res)
    
        def preorderTraversal(self, root: TreeNode) -> List[int]:
            res = []
            stack = []
            p = root
            if not root:return res
            while p or stack:
                while p:
                    stack.append(p)
                    res.append(p.val)
                    p = p.left
                p = stack.pop()
                p = p.right
            return res
    
    [leetcode]145.Binary Tree Postorder Traversal

    后序遍历

    class Solution:
        def postorderTraversal1(self, root: TreeNode) -> List[int]:
            """
            :type root: TreeNode
            :rtype: List[int]
            """
            res = []
            if not root:return res
            self.dfs(root,res)
            return res
        def dfs(self,root,res):
            if not root:return
            self.dfs(root.left,res)
            self.dfs(root.right,res)
            res.append(root.val)
    
        def postorderTraversal2(self, root):
            """
            :type root: TreeNode
            :rtype: List[int]
            """
            #后续遍历的非递归解法,双压栈版
            res=[]
            stack=[]
            if not root:
                return res
            stack.append(root)
            stack.append(root)
            while (stack):
                p=stack.pop()
                if(stack and p==stack[-1]):
                    if (p.right):
                        stack.append(p.right)
                        stack.append(p.right)
                    if (p.left):
                        stack.append(p.left)
                        stack.append(p.left)
                else:
                    res.append(p.val)
            return res
    
        def postorderTraversal3(self, root):
            ans = []
            if root == None:
                return ans
            stack = [root]
            while stack:
                p = stack.pop()
                ans.append(p.val)
                if p.left:
                    stack.append(p.left)
                if p.right:
                    stack.append(p.right)
            ans.reverse()
            return ans
    
    [leetcode]297.Serialize and Deserialize Binary Tree

    树的序列化与去序列化。

    # Definition for a binary tree node.
    # class TreeNode(object):
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Codec:
        def serialize(self, root):
            """Encodes a tree to a single string.
    
            :type root: TreeNode
            :rtype: str
            """
            res = []
            self.preorder(root, res)
            # 将结点值序列转化为一个字符串
            return ','.join(res)
    
        def preorder(self, root, res):
            if not root:
                # 对于空结点,返回#字符加以标识
                res.append('#')
                return
            res.append(str(root.val))
            self.preorder(root.left, res)
            self.preorder(root.right, res)
    
    
        def deserialize(self, data):
            """Decodes your encoded data to tree.
    
            :type data: str
            :rtype: TreeNode
            """
            res = data.split(',')
            root = self.preorderdes(res)
            return root
    
        def preorderdes(self, res):
            if not res:
                return None
            # 遇到#字符,直接删除,返回空结点
            if res[0] == '#':
                del res[0]
                return None
            root = TreeNode(int(res[0]))
            del res[0]
            root.left = self.preorderdes(res)
            root.right = self.preorderdes(res)
            return root
    
    
    
    # Your Codec object will be instantiated and called as such:
    # codec = Codec()
    # codec.deserialize(codec.serialize(root))
    
    [leetcode]450.Delete Node in a BST

    删除二叉搜索树的一个节点。递归查找右子树的最小值节点。

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
            if not root: return None
            if root.val == key:
                if not root.right:
                    return root.left
                else:
                    right = root.right
                    while right.left:
                        right = right.left
                    root.val, right.val = right.val, root.val
            root.left = self.deleteNode(root.left, key)
            root.right = self.deleteNode(root.right, key)
            return root
    

    树+dfs

    [leetcode]113.Path Sum II

    深度优先搜索,查找结果。

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
            res = []
            self.dfs(root,sum,[],res)
            return res
    
        def dfs(self,root,sum,result,res):
            if not root:return
            if not root.left and not root.right and root.val == sum:
                result.append(root.val)
                res.append(result)
                return
            self.dfs(root.left,sum-root.val,result+[root.val],res)
            self.dfs(root.right,sum-root.val,result+[root.val],res)
    
    [leetcode]124.Binary Tree Maximum Path Sum 设置全局变量

    树结构显然用递归来解,解题关键:
    1、对于每一层递归,只有包含此层树根节点的值才可以返回到上层。否则路径将不连续。
    2、返回的值最多为根节点加上左右子树中的一个返回值,而不能加上两个返回值。否则路径将分叉。
    在这两个前提下有个需要注意的问题,最上层返回的值并不一定是满足要求的最大值,因为最大值对应的路径不一定包含root的值,可能存在于某个子树上。因此解决方案为设置全局变量maxSum,在递归过程中不断更新最大值。

    class Solution:
        def maxPathSum(self, root: TreeNode) -> int:
            self.res = -float('inf')
            if  not root:
                return 0
            self.dfs(root)
            return self.res
    
        def dfs(self,root):
            if not root:
                return 0
            left = self.dfs(root.left)
            right = self.dfs(root.right)
            self.res = max(self.res,root.val+max(0,left)+max(0,right))
            return max(left+root.val,right+root.val,root.val)
    
    [leetcode]95.Unique Binary Search Trees II

    对于本题来说,采取的是自底向上的求解过程。
    1.选出根结点后应该先分别求解该根的左右子树集合,也就是根的左子树有若干种,它们组成左子树集合,根的右子树有若干种,它们组成右子树集合。
    2.然后将左右子树相互配对,每一个左子树都与所有右子树匹配,每一个右子树都与所有的左子树匹配。然后将两个子树插在根结点上。
    3.最后,把根结点放入链表中。

    class Solution:
        def generateTrees(self, n: int) -> List[TreeNode]:
            if n == 0:
                return []
            return self.dfs(1, n+1)
    
        def dfs(self, start, end):
            result = []
            for i in range(start, end):
                for l in self.dfs(start, i) or [None]:
                    for r in self.dfs(i+1, end) or [None]:
                        node = TreeNode(i)
                        node.left, node.right  = l, r
                        result.append(node)
            return result
    
    [leetcode]236.Lowest Common Ancestor of a Binary Tree

    我们可以用递归来实现,在递归函数中,我们首先看当前结点是否为空,若为空则直接返回空,若为p或q中的任意一个,也直接返回当前结点。否则的话就对其左右子结点分别调用递归函数,由于这道题限制了p和q一定都在二叉树中存在,那么如果当前结点不等于p或q,p和q要么分别位于左右子树中,要么同时位于左子树,或者同时位于右子树
    此题还有一种情况,若题目中没有明确说明p和q是否是树中的节点,如果不是,应该返回NULL,而上面的方法就不正确了

    class Solution:
        def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
            if root == None or root == p or root == q:
                return root
            left = self.lowestCommonAncestor(root.left,p,q)
            right = self.lowestCommonAncestor(root.right,p,q)
    
            if left and right:
                return root
            else:
                return left if left else(right)
    
    [leetcode]337.House Robber III

    定义dfs函数, 返回两个值: 从当前节点偷能获取的最大值rob_now和从子节点开始偷能获得的最大值rob_later.Base case是当root为空时, 返回(0, 0).如果从当前节点偷, 那么左右的子节点不能偷, 如果从子节点开始偷, 那么总金额为两个节点能偷到的最大值的和.

    class Solution:
        def rob(self, root: TreeNode) -> int:
            return max(self.dfs(root))
    
    
        def dfs(self,root):
            # return the max money if rob this root and the max money if not rob this root
            if not root: return (0, 0)
            left, right = self.dfs(root.left), self.dfs(root.right)
            rob_now = root.val + left[1] + right[1]
            rob_later = max(left) + max(right)
            return (rob_now, rob_later)
    
    

    字典树

    字典树和线段树作为高级的树结构,都有其特定的构成方式与解题思路。
    字典树,又称前缀树或单词查找树。字典树主要有如下三点性质:

    • 根节点不包含字符,除根节点意外每个节点只包含一个字符。
    • 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
    • 每个节点的所有子节点包含的字符串不相同。

    它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。缺点就是空间开销大。
    对于N个字符串,如果其平均长度为M,则建立字典树的算法复杂度为(O(M*N)),在字典树上查找某个长度为M的字符串的算法复杂度(O(M))
    下面以几个例题说明。

    [leetcode]208.Implement Trie (Prefix Tree)
    class Node(object):
        def __init__(self):
            self.children = collections.defaultdict(lambda :Node())
            self.isword = False
    
    class Trie(object):
    
        def __init__(self):
            """
            Initialize your data structure here.
            """
            self.root = Node()
    
        def insert(self, word):
            """
            Inserts a word into the trie.
            :type word: str
            :rtype: void
            """
            current = self.root
            for w in word:
                current = current.children[w]
            current.isword = True
    
        def search(self, word):
            """
            Returns if the word is in the trie.
            :type word: str
            :rtype: bool
            """
            current = self.root
            for w in word:
                # current = current.children.get(w)
                # if current == None:
                #     return False
                if w  not in current.children:return False
                current = current.children[w]
            return current.isword
    
        def startsWith(self, prefix):
            """
            Returns if there is any word in the trie that starts with the given prefix.
            :type prefix: str
            :rtype: bool
            """
            current = self.root
            for w in prefix:
                # current = current.children.get(w)
                # if current == None:
                #     return False
                if w  not in current.children:return False
                current = current.children[w]
            return True
    
    
    
    
    # Your Trie object will be instantiated and called as such:
    # obj = Trie()
    # obj.insert(word)
    # param_2 = obj.search(word)
    # param_3 = obj.startsWith(prefix)
    
    [leetcode]212.Word Search II

    将待查找的单词存放在Trie(字典树)中,利用DFS(深度优先搜索)在board中搜索即可,每次查找成功,进行剪枝操作

    class Solution:
        def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
            res = []
            tree = Trie()
            visited = [[False for j in  range(len(board[0]))] for i in range(len(board))]
            for i in range(len(words)):
                tree.insert(words[i])
            for i in range(len(board)):
                for j in range(len(board[0])):
                    self.dfs(tree,board,tree.root, visited,i,j,'',res)
            return res
    
        def dfs(self,tree,board,curr, visited,i,j,word,res):
            if i < 0 or j < 0 or i > len(board) - 1 or j > len (board[0]) - 1 or visited[i][j]:
                return
            word += board[i][j]
            curr = curr.children.get(board[i][j])
            if not curr:
                return
            if curr.isword:
                res.append(word)
                tree.delete(word)
            visited[i][j] = True
            self.dfs(tree,board,curr,visited,i+1,j,word,res)
            self.dfs(tree,board,curr,visited,i-1,j,word,res)
            self.dfs(tree,board,curr,visited,i,j+1,word,res)
            self.dfs(tree,board,curr,visited,i,j-1,word,res)
            visited[i][j] = False
    
    
    class Node(object):
        def __init__(self):
            self.children = collections.defaultdict(Node)
            self.isword = False
    
    class Trie(object):
    
        def __init__(self):
            """
            Initialize your data structure here.
            """
            self.root = Node()
    
        def insert(self, word):
            """
            Inserts a word into the trie.
            :type word: str
            :rtype: void
            """
            current = self.root
            for w in word:
                current = current.children[w]
            current.isword = True
    
        def search(self, word):
            """
            Returns if the word is in the trie.
            :type word: str
            :rtype: bool
            """
            current = self.root
            for w in word:
                current = current.children.get(w)
                if current == None:
                    return False
                # if w  not in current:return
                # .children[w]
            return current.isword
    
        def startsWith(self, prefix):
            """
            Returns if there is any word in the trie that starts with the given prefix.
            :type prefix: str
            :rtype: bool
            """
            current = self.root
            for w in prefix:
                if w not in current.children:return False
                current = current.children[w]
                current = current.children.get(w)
                if current == None:
                    return False
            return True
    
        def delete(self, word):
            current = self.root
            stack = []
            for w in word:
                stack.append([current, w])
                current = current.children.get(w)
                if current is None:
                    return False
            if not current.isword:
                return False
            if current.children:
                current.isword = False
                return True
            else:
                while stack:
                    node, string = stack.pop()
                    del node.children[string]
                    if node.children or node.isword:
                        break
    
                return True
    

    线段树

    线段树在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn)。线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[L,R],那么(m=(L+R)/2)左儿子的区间是[L,m],右儿子的区间是[m+1,R]。

    [leetcode]307.Range Sum Query - Mutable

    解决方法有Segement Tree(线段树),Binary Indexed Tree(树状数组) ,和平方根分解三种办法。
    以线段树为例:

    #Segment tree node
    class Node(object):
        def __init__(self, start, end):
            self.start = start
            self.end = end
            self.total = 0
            self.left = None
            self.right = None
    
    
    class NumArray(object):
        def __init__(self, nums):
            """
            initialize your data structure here.
            :type nums: List[int]
            """
            #helper function to create the tree from input array
            def createTree(nums, l, r):
    
                #base case
                if l > r:
                    return None
    
                #leaf node
                if l == r:
                    n = Node(l, r)
                    n.total = nums[l]
                    return n
    
                mid = (l + r) // 2
    
                root = Node(l, r)
    
                #recursively build the Segment tree
                root.left = createTree(nums, l, mid)
                root.right = createTree(nums, mid+1, r)
    
                #Total stores the sum of all leaves under root
                #i.e.those elements lying between (start, end)
                root.total = root.left.total + root.right.total
    
                return root
    
            self.root = createTree(nums, 0, len(nums)-1)
    
        def update(self, i, val):
            """
            :type i: int
            :type val: int
            :rtype: int
            """
            #Helper function to update a value
            def updateVal(root, i, val):
    
                #Base case.The actual value will be updated in a leaf.
                #The total is then propogated upwards
                if root.start == root.end:
                    root.total = val
                    return val
    
                mid = (root.start + root.end) // 2
    
                #If the index is less than the mid, that leaf must be in the left subtree
                if i <= mid:
                    updateVal(root.left, i, val)
    
                #Otherwise, the right subtree
                else:
                    updateVal(root.right, i, val)
    
                #Propogate the changes after recursive call returns
                root.total = root.left.total + root.right.total
    
                return root.total
    
            return updateVal(self.root, i, val)
    
        def sumRange(self, i, j):
            """
            sum of elements nums[i..j], inclusive.
            :type i: int
            :type j: int
            :rtype: int
            """
            #Helper function to calculate range sum
            def rangeSum(root, i, j):
    
                #If the range exactly matches the root, we already have the sum
                if root.start == i and root.end == j:
                    return root.total
    
                mid = (root.start + root.end) // 2
    
                #If end of the range is less than the mid, the entire interval lies
                #in the left subtree
                if j <= mid:
                    return rangeSum(root.left, i, j)
    
                #If start of the interval is greater than mid, the entire inteval lies
                #in the right subtree
                elif i >= mid + 1:
                    return rangeSum(root.right, i, j)
    
                #Otherwise, the interval is split.So we calculate the sum recursively,
                #by splitting the interval
                else:
                    return rangeSum(root.left, i, mid) + rangeSum(root.right, mid+1, j)
    
            return rangeSum(self.root, i, j)
    
  • 相关阅读:
    python获取公网ip,本地ip及所在国家城市等相关信息收藏
    Tkinter的下拉列表Combobox
    pyinstaller打包pyqt文件(转)
    通过pyqt5实现俄罗斯方块游戏例子
    pygame游戏开发入门例子
    python界面Tkinter编程(tkMessageBox对话框使用)
    python tkinter-菜单栏
    python tkinter-容器、子窗体
    HUNNU--湖师大--11409--Skill
    [置顶] 博客搬迁到新地址。
  • 原文地址:https://www.cnblogs.com/hellojamest/p/11703168.html
Copyright © 2011-2022 走看看