树是一种抽象数据类型(ADT)或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由 n(n>0) 个有限节点组成一个具有层次关系的集合。
它具有以下的特点:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
#前序遍历
class Solution:
ans = []
def preorderTraversal(self, root: TreeNode) -> List[int]:
if root == None:
return
else:
self.ans.append(root.val)
preorderTraversal(root.left)
preorderTraversal(root.right)
#中序遍历
class Solution:
ans = []
def inorderTraversal(self, root: TreeNode) -> List[int]:
if root == None:
return
else:
inorderTraversal(root.left)
self.ans.append(root.val)
inorderTraversal(root.right)
#后序遍历
class Solution:
ans = []
def postTraversal(self, root: TreeNode) -> List[int]:
if root == None:
return
else:
postorderTraversal(root.left)
self.ans.append(root.val)
postorderTraversal(root.right)
这里介绍一个更加简洁易懂且通用的方式
其核心思想如下:
如果节点status = 1,则表示已经访问,将节点的值输出
如果节点status = 0,根据前序,中序,后序要求将该节点与左右子树以此入栈
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
ans = []
stack = [(0, root)]
while stack:
status, node = stack.pop()
if not node: continue
if status == 0:
#中序遍历
stack.append((0, node.right))
stack.append((1, node))
stack.append((0, node.left))
#前序遍历
stack.append((0, node.right))
stack.append((0, node.left))
stack.append((1, node))
#后序遍历
stack.append((1, node))
stack.append((0, node.right))
stack.append((0, node.left))
else:
ans.append(node.val)
return ans
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,
前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
思路:首先来看前序遍历和中序遍历的性质
前序遍历:遍历顺序为 父节点 -> 左子节点 -> 右子节点
中序遍历:遍历顺序为 左子节点 -> 父节点 -> 右子节点
步骤解析:首先通过前序遍历获取根节点,然后根据中序遍历将树分为左子树和右子树,重复上述过程。
注意在每次遍历过程中,为了保持递归一致性,左子树,右子树的中序遍历容易知道,而前序遍历怎么
求呢?
根据前序遍历性质可知左子节点大于右子节点,所以中序遍历获得的左子树的前序遍历也一定大于出现在
右子树之前,假设根节点在中序遍历中的位置是mid,则[1:mid + 1]为左子树在前序遍历中对应的节点,
也就是[mid + 1:]为右子树在前序遍历中对应的节点。
class Solution(object):
def buildTree(self, preorder, inorder):
if len(inorder) == 0:
return None
# 前序遍历第一个值为根节点
root = TreeNode(preorder[0])
# 因为没有重复元素,所以可以直接根据值来查找根节点在中序遍历中的位置
mid = inorder.index(preorder[0])
# 构建左子树
root.left = self.buildTree(preorder[1:mid+1], inorder[:mid])
# 构建右子树
root.right = self.buildTree(preorder[mid+1:], inorder[mid+1:])
return root
(2)同理可求,从中序与后序遍历序列构造二叉树
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
def buildTree(inorder, postorder):
if not inorder: return None
root = TreeNode(postorder[-1])
mid = inorder.index(postorder[-1])
root.left = buildTree(inorder[0:mid], postorder[: mid])
root.right = buildTree(inorder[mid + 1:], postorder[mid: -1])
return root
return buildTree(inorder, postorder)
举例一:完全二叉树的节点个数
给出一个完全二叉树,求出该树的节点个数。
示例:
输入:
1
/ \
2 3
/ \ /
4 5 6
输出: 6
class Solution:
def countNodes(self, root: TreeNode) -> int:
def helper(root):
if not root: return 0
left, right = 0, 0
if root.left: left = helper(root.left)
if root.right: right = helper(root.right)
return left + right + 1
return helper(root)
#精简版如下:
def countNodes(self, root: TreeNode) -> int:
return countNodes(root.left) + countNodes(root.right) + 1 if root else 0
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
思路:首先遍历整棵树的所有节点,找到节点p和q,然后包含p或q的树在上传过程中返回值为True,最深节点满足以下几种情况:
(1)当前节点为p或者q,且其子树中包含留一个节点
(2)当前节点不包括p或者q,其子树同时包含p和q
当我们由下向上回溯的过程中,第一次遇到一个节点满足以上两种其中任意一种情况,则该节点就是目标节点
class Solution:
judge = None
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
def helper(root):
if not root: return False
left, right = False, False
if root.left: left = helper(root.left)
if root.right: right = helper(root.right)
#情况(1)
if (root == p or root == q) and (left or right):
self.judge = root
return False
#情况(2)
if left and right:
self.judge = root
return False
if root == p or root == q or left or right: return True
helper(root)
return self.judge
二、字典树
概念摘要:字典树又称为单词查找树,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
前缀树的代码实现:
class Trie:
def __init__(self):
"""
Initialize your data structure here.
"""
self.lookup = {}
def insert(self, word: str) -> None:
"""
Inserts a word into the trie.
"""
tree = self.lookup
for a in word:
tree.setdefault(a, {})
tree = tree[a]
tree['#'] = "#"
def search(self, word: str) -> bool:
"""
Returns if the word is in the trie.
"""
tree = self.lookup
for a in word:
if a not in tree.keys():
return False
tree = tree[a]
if '#' in tree.keys():
return True
return False
def startsWith(self, prefix: str) -> bool:
"""
Returns if there is any word in the trie that starts with the given prefix.
"""
tree = self.lookup
for a in prefix:
if a not in tree.keys():
return False
tree = tree[a]
return True
举例二:添加单词与搜索单词
设计一个支持以下两种操作的数据结构:
void addWord(word)
bool search(word)
代码实现:
class WordDictionary:
def __init__(self):
self.word_dict = collections.defaultdict(list)
def addWord(self, word):
self.word_dict[len(word)] += [word] #将单词长度作为键
def search(self, word):
length = len(word)
def f(s, word): #查找长度为length的集合里是否有word
for i in range(length):
if word[i] not in [s[i], '.']:
return False
return True
for w in self.word_dict[length]:
if f(w, word):
return True
return False
举例三:恢复空格
哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!“已经变成了"iresetthecomputeritstilldidntboot”。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一本厚厚的词典dictionary,不过,有些词没在词典里。假设文章用sentence表示,设计一个算法,把文章断开,要求未识别的字符最少,返回未识别的字符数。
注意:本题相对原题稍作改动,只需返回未识别的字符数
class Solution {
//----------------------------------前缀树类定义-------------------------------------------
class Node{
Node[] childs;
boolean isWord;
public Node(){
childs = new Node[26];
isWord = false;
}
}
//----------------------------------构建前缀树---------------------------------------------
public void buildTree(String word, Node root){
Node temp = root;
for(int i = 0; i<word.length(); i++){
int index = word.charAt(i) - 'a';
if(temp.childs[index] == null) temp.childs[index] = new Node();
temp = temp.childs[index];
}
temp.isWord = true;
}
//------------------------------动态规划 + 前缀树--------------------------------------------
public int respace(String[] dictionary, String sentence) {
Node root = new Node();
for(String s:dictionary){
buildTree(s, root);
}
int len = sentence.length();
int[] dp = new int[len + 1];
//从后往前一次寻找子串对应的最小长度
for(int i = len - 1; i>=0; i--){
Node temp = root;
dp[i] = len - i;//最不理想情况,当前子串不包含字典中单词
for(int j = i; j<len; j++){
int index = sentence.charAt(j) - 'a';
//当前子串s前j个字符组成单词不在字典中,停止内循环
if(temp.childs[index] == null) {dp[i] = Math.min(dp[i], j-i+1 + dp[j+1]); break;}
//当前子串s前j个字符组成单词在字典中,s.substring(i, j+1) 对结果贡献为0
if(temp.childs[index].isWord){dp[i] = Math.min(dp[i], dp[j+1]);}
//当前子串s前j个字符在前缀树中,可以断句
else{dp[i] = Math.min(dp[i], j-i+1 + dp[j+1]);}
temp = temp.childs[index];
}
}
return dp[0];
}
}