[总结]树

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

树的遍历

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

[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)
posted @ 2019-10-19 10:50  Jamest  阅读(204)  评论(0编辑  收藏  举报