目录
树
1、遍历
树的前序、中序、后序遍历(栈 stack)
树的层序遍历(队列 queue)
DFS的递归写法
def preorder(root):
def order(root):
if not root:
return []
res.append(root.val) # 中
order(root.left) # 左
order(root.right) # 右
res = []
order(root)
return res
迭代写法:
即通过栈stack来显式的模拟出来
前序遍历
def preorder(root):
if not root:
return []
res = []
stack = [root]
while stack:
node = stack.pop()
if node:
res.append(node.val) # 中
if node.right:
stack.append(node.right) # 右,先进后出
if node.left:
stack.append(node.left) # 左
return res
中序遍历
def inorder(root):
if not root:
return []
res = []
stack = []
while stack or root:
if root: # 不断往左子树走,每走一次就将当前节点保存到栈中。
stack.append(root)
root = root.left
else:
tmp = stack.pop() # 当前节点为空,说明左边走到头了,从栈中弹出并保存
res.append(tmp.val)
root = tmp.right
return res
后序遍历(左、右、根 转换为 根、右、左)
def postorder(root):
if not root:
return []
res = []
stack = [root]
while stack:
node = stack.pop()
res.append(node.val)
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
return res[::-1]
树的层次遍历:
返回的是[1, 2, 3]这种形式
def levelorder(root):
if not root:
return None
queue = collections.deque()
queue.append(root)
res = []
while queue:
node = queue.popleft()
res.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return res
返回 [[1], [2,3], [null, null, 4, 5]]
import collections
def levelorder(root):
if not root:
return []
queue = collections.deque()
queue.append(root)
res = []
while queue:
tmp = []
for i in range(len(queue)):
node = queue.popleft()
tmp.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
res.append(tmp)
return res
2、路径问题
257、二叉树的所有路径
题意:返回从根节点到叶子节点的所有路径
输入:
1
/ \
2 3
\
5
输出: ["1->2->5", "1->3"]
解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3
题解:dfs的思想
class Solution:
def binaryTreePaths(self, root: TreeNode) -> List[str]:
if not root:
return []
def dfs(node, path):
if not node:
return
path += str(node.val)
if not node.left and not node.right: # 当是叶子节点时,就将整个路径加入到结果中。
res.append(path)
else: # 不是叶子节点,就继续递归子节点
path += '->'
dfs(node.left, path)
dfs(node.right, path)
res = []
dfs(root, '')
return res
剑指 Offer 55 - I. 二叉树的深度
题意:最长路径的长度为树的深度。
给定二叉树 [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
题解:
递归的方法:
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
return max(self.maxDepth(root.left), self.maxDepth(root.right))+1
或者用dfs的方法
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
def dfs(node, depth):
if not node:
return
depth += 1
if max_depth[0]<depth:
max_depth[0] = depth
dfs(node.left, depth)
dfs(node.right, depth)
max_depth = [0]
dfs(root, 0)
return max_depth[0]
112、路径总和
题意:
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
题解:
dfs(node, tmp) # tmp为逐步累加和
class Solution:
def hasPathSum(self, root: TreeNode, targetSum: int) -> bool:
if not root:
return False
self.flag = False
def dfs(node, tmp):
if not node:
return
tmp += node.val # 逐步累加
if not node.left and not node.right:
if tmp==targetSum:
self.flag = True
return # 直接返回
dfs(node.left, tmp)
dfs(node.right, tmp)
dfs(root, 0)
return self.flag
113、路径总和II
题意:
给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
题解:
返回所有路径
class Solution:
def pathSum(self, root, targetSum):
if not root:
return []
res = []
path = []
def dfs(node, tmp):
if not node:
return
tmp += node.val
path.append(node.val)
if not node.left and not node.right and tmp==targetSum:
res.append(path[:]) # 复制出整个序列,否则是传引用
dfs(node.left, tmp)
dfs(node.right, tmp)
path.pop() # 恢复path
dfs(root, 0)
return res
437、路径总和III
题意:
路径不再需要从根节点出发,也不再需要在叶子节点结束,只需要路径方向是从父节点到子节点。 返回路径和为target的路径数目
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
10
/ \
5 -3
/ \ \
3 2 11
/ \ \
3 -2 1
返回 3。和等于 8 的路径有:
f1. 5 -> 3
2. 5 -> 2 -> 1
3. -3 -> 11
题解:
方法一:
类似于 求解 和为k的子数组的个数
使用 “前缀和”+字典的方法
class Solution:
def pathSum(self, root: TreeNode, targetSum: int) -> int:
if not root:
return 0
self.d = collections.defaultdict(int)
self.d[0] = 1
def dfs(node, tmp):
if not node:
return 0
tmp += node.val
count = self.d[tmp-targetSum] # 当前路径和-target 是否出现在字典中过,记录其次数
self.d[tmp] += 1
left = dfs(node.left, tmp)
right = dfs(node.right, tmp)
self.d[tmp] -= 1
return left + right + count
return dfs(root, 0)
时间复杂度:O(N)
空间复杂度:O(N)
方法二:双重递归
class Solution:
def pathSum(self, root: TreeNode, targetSum: int) -> int:
if not root:
return 0
self.d = collections.defaultdict(int)
def dfs(node, tmp): # 计算当前节点下的满足条件的路径数
count = 0
if not node:
return 0
tmp += node.val
if tmp==targetSum:
count += 1
count += dfs(node.left, tmp)
count += dfs(node.right, tmp)
return count
return dfs(root, 0) + self.pathSum(root.left, targetSum)+ self.pathSum(root.right, targetSum)
时间复杂度:O(N^2)
空间复杂度:O(N)
124、二叉树中的最大路径和(字节)
题意:
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
给你一个二叉树的根节点 root
,返回其 最大路径和 。
题解:
定义maxgain(node)函数,来计算以node节点为根节点的子树中,寻找以node节点为起点的一条路径,使得该路径上的节点值之和最大。
递归地调用maxgain()函数即可得到每个节点的最大贡献值( node.val + max(left, right) )。
class Solution:
def maxPathSum(self, root: TreeNode) -> int:
self.res = float('-inf') # 由于节点的值有负数,所以最开始初始化要为负无穷
def maxgain(node):
if not node:
return 0
left = max(maxgain(node.left),0) # 进行递归
right = max(maxgain(node.right), 0)
s = node.val + left + right # 路径之和
self.res = max(self.res, s)
return node.val + max(left, right)
maxgain(root)
return self.res
核心在于:计算结果res时,要计算左右子树、递归返回时只能返回较大的一边。
3、二叉搜索树
性质:中序遍历是一个递增的数组
98. 验证二叉搜索树
题意:验证一棵树是否是二叉搜索树
题解:
方法一:
递归
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
if not root:
return True
def dfs(node, lower, upper):
if not node:
return
if node.val<=lower or node.val>=upper:
return False
if not dfs(node.left, lower, node.val): #左子树要均小于node.val
return False
if not dfs(node.right, node.val, upper): #右子树要均大于node.val
return False
return True
return dfs(root, -float('inf'), float('inf'))
方法二:
二叉搜索树的中序遍历是一个递增的数组,因此,可以不断比较当前的数字是否比之前的数字小,若小,则直接返回false。
class Solution:
def isValidBST(self, root: TreeNode) -> bool:
if not root:
return True
stack = []
before = float('-inf')
while stack or root:
if root:
stack.append(root)
root = root.left
else:
tmp = stack.pop()
if tmp.val<=before:
return False
before = tmp.val
root = tmp.right
return True
108. 将有序数组转换为二叉搜索树
题意:
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
题解:
二叉搜索树的根节点是数组的中位数,才能使得高度差最小,即避免有单独的节点在一行。
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
if not nums:
return
n = len(nums)
ind = n//2
root = TreeNode(nums[ind]) # 中位数
root.left = self.sortedArrayToBST(nums[:ind])
root.right = self.sortedArrayToBST(nums[ind+1:])
return root
538. 把二叉搜索树转换为累加树
题意:
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
题解:
sumtree,反向中序遍历(递减),右、根、左
class Solution:
def convertBST(self, root: TreeNode) -> TreeNode:
if not root:
return None
def dfs(node):
if not node:
return
dfs(node.right)
self.total += node.val # 加上右侧节点的值和当前节点的值
node.val = self.total
dfs(node.left)
self.total = 0
dfs(root)
return root
或者使用迭代的方法,栈是先进后出,所以按照左、根、右的顺序加入即可。
class Solution:
def convertBST(self, root: TreeNode) -> TreeNode:
stack = [(1, root)] # 1表示没访问,0表示已访问
res = 0
while stack:
color, node = stack.pop()
if not node:
continue
if color==1:
stack.append((1, node.left))
stack.append((0, node))
stack.append((1, node.right))
else:
res += node.val
node.val = res
return root
96. 不同的二叉搜索树
题意:
给定一个整数n,求由n个节点组成的从1到n的互不相同的二叉搜索树的种类数。
输入:n = 3
输出:5
题解:
动态规划的思想
class Solution:
def numTrees(self, n: int) -> int:
dp = [1, 1]
for i in range(2, n+1): # 以i为根节点,依次遍历
res = 0
for j in range(i):
res += dp[j]*dp[i-j-1] # 等于左子树的种类*右子树的种类
dp.append(res)
return dp[-1]
95. 不同的二叉搜索树 II
题意:
上题的变体,要求返回所有的二叉搜索树
题解:
class Solution:
def generateTrees(self, n: int) -> List[TreeNode]:
def trace_back(start, end):
if end<start:
return [None, ]
res = []
for i in range(start, end+1):
left = trace_back(start, i-1)
right = trace_back(i+1, end)
# 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上
for l in left:
for r in right:
curtree = TreeNode(i)
curtree.left = l
curtree.right = r
res.append(curtree)
return res
return trace_back(1, n)
剑指 Offer 36. 二叉搜索树与双向链表
题意:
将一个二叉搜索树转换为一个排序的循环双向链表,要求不能创建新的节点,只能调整树中节点指针的指向。
题解:在中序遍历的过程中,构建链表:
当pre为空时,代表正在访问链表的头结点,记为head;
当pre不为空时,修改双向结点引用,即pre.right = cur, cur.left = pre。
保存cur:更新pre=cur,即结点cur是后继结点的pre
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
if not root:
return
self.pre = None # 初始化
def dfs(cur):
if not cur:
return
dfs(cur.left) # 递归左子树
if self.pre:
self.pre.right = cur
cur.left = self.pre
else:
self.head = cur # 记录头结点
self.pre = cur # 保存cur到链表中
dfs(cur.right) # 递归右子树
dfs(root) # 开始中序遍历
self.head.left = self.pre # 中序遍历完成,构建循环链表
self.pre.right = self.head
return self.head
700. 二叉搜索树中的搜索
题意:
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL
题解:
class Solution:
def searchBST(self, root: TreeNode, val: int) -> TreeNode:
while root and root.val != val:
if val>root.val:
root = root.right
else:
root = root.left
return root
面试题 04.06. 后继者
题意:
找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。
题解:
方法一:
类似于二分查找的思想,由于二叉搜索树是有序的,若当前节点的值要小于目标节点值,则说明要找的节点在当前节点的右边;否则,说明在左边,只要找到并记录大于目标值的最小节点即可。
class Solution:
def inorderSuccessor(self, root: TreeNode, p: TreeNode) -> TreeNode:
if not root:
return None
res = None
cur = root
while cur:
if cur.val <= p.val:
cur = cur.right
else:
res = cur
cur = cur.left
return res
方法二:
中序遍历,使用flag来记录是否遇到给定的节点p,当遇到时,反转flag=True。当遍历下一个节点时,因为flag==True,所以可以记录节点,并注意要反转flag为False,就不会再记录之后的节点了。
class Solution:
def inorderSuccessor(self, root: TreeNode, p: TreeNode) -> TreeNode:
if not root:
return None
self.res = None
self.flag = False
def inorder(node):
if not node:
return
inorder(node.left)
if self.flag:
self.res = node
self.flag = False
return
if node==p:
self.flag = True
inorder(node.right)
inorder(root)
return self.res
99. 恢复二叉搜索树
题意:给你二叉搜索树的根节点 root
,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。 要求只使用常数空间。
题解:
若不用考虑空间复杂度,可以使用额外数组来保存中序遍历的结果,然后遍历这个数组,交换错误的节点。
因为要求常数的空间复杂度,使用莫里斯遍历,在中序遍历的时候,比较是否一直是递增的,若出现了递减的情况,记录下当前节点值和前一个节点值。
class Solution:
def recoverTree(self, root: TreeNode) -> None:
"""
Do not return anything, modify root in-place instead.
"""
x,y = None, None
pre = None
mostright = None
while root:
if root.left:
mostright = root.left
while mostright.right and mostright.right!=root:
mostright = mostright.right
if mostright.right == None:
mostright.right = root
root = root.left
else:
if pre and pre.val>root.val: # 出现了递减
y = root
if not x:
x = pre
pre = root
mostright.right = None
root = root.right
else:
if pre and pre.val>root.val: # 出现了递减
y = root
if not x:
x = pre
pre = root
root = root.right
if x and y:
x.val, y.val = y.val, x.val
4、两棵树比较问题
872. 叶子相似的树
题意:
判断两棵树的叶子的值组成的叶值序列是否相等。
输入:root1 = [3,5,1,6,2,9,8,null,null,7,4], root2 = [3,5,1,6,7,4,2,null,null,null,null,null,null,9,8]
输出:true
题解:
dfs(node, res)
class Solution:
def leafSimilar(self, root1: TreeNode, root2: TreeNode) -> bool:
if not root1 and not root2:
return True
def dfs(node, res):
if not node:
return
if not node.left and not node.right:
res.append(node.val)
else:
dfs(node.left, res)
dfs(node.right, res)
return res
res1, res2 = [], []
dfs(root1, res1)
dfs(root2, res2)
return res1==res2
572. 另一个树的子树
题意:
给定两个非空二叉树s和t,判断t是否是s的子树
题解:
递归的思想
class Solution:
def isSubtree(self, root: TreeNode, subRoot: TreeNode) -> bool:
if not root:
return False
if self.issame(root, subRoot):
return True
return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot)
def issame(self, nodea, nodeb): # 判断两棵树是否相同
if not nodea and not nodeb:
return True
if nodea is None or nodeb is None:
return False
return nodea.val==nodeb.val and self.issame(nodea.left, nodeb.left) and self.issame(nodea.right, nodeb.right)
其他
剑指 Offer 37. 序列化二叉树
题意:
实现序列化二叉树和反序列化二叉树的功能
题解:
class Codec:
def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""
if not root:
return '[]'
q = collections.deque()
q.append(root)
res = []
while q:
node = q.popleft()
if node:
res.append(str(node.val))
q.append(node.left)
q.append(node.right)
else:
res.append('null')
return '['+','.join(res)+']'
def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""
if data=='[]':
return
vals = data[1:-1].split(',')
i = 1
root = TreeNode(int(vals[0]))
q = collections.deque()
q.append(root)
while q:
node = q.popleft()
if vals[i]!='null':
node.left = TreeNode(int(vals[i]))
q.append(node.left)
i += 1
if vals[i]!='null':
node.right = TreeNode(int(vals[i]))
q.append(node.right)
i += 1
return root
剑指 Offer 55 - I. 二叉树的深度
题意:
最长路径的长度为树的深度。
题解:
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
def dfs(node, depth):
if not node:
return
depth += 1
if max_depth[0]<depth:
max_depth[0] = depth
dfs(node.left, depth)
dfs(node.right, depth)
max_depth = [0]
dfs(root, 0)
return max_depth[0]
或者
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
return max(self.maxDepth(root.left), self.maxDepth(root.right))+1
剑指 Offer 55 - II. 平衡二叉树
题意:
任意一个节点,其两棵子树的高度差不超过1。
题解:
方法一:
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
if not root:
return True
def height(node):
if not node:
return 0
return max(height(node.left), height(node.right))+1
return abs(height(root.left)-height(root.right))<=1 and self.isBalanced(root.left) and self.isBalanced(root.right)
# abs(height(root.left)-height(root.right))<=1是判断当前子树是否是平衡的(左右子树的高度差是否小于1);
#而self.isBalanced(root.left) and self.isBalanced(root.right)是分别判断当前子树的左子树和右子树是否是平衡的。
时间复杂度为O(n^2)
方法二:自底向上的递归
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
if not root:
return True
def dfs(node):
if not node:
return 0
left = dfs(node.left) # 左的值
if left==-1: # 左子树不平衡时
return -1 # 整个树就不平衡
right = dfs(node.right) # 右的值
if right==-1: # 右子树不平衡时
return -1 # 整个树不平衡
return max(dfs(node.left), dfs(node.right))+1 if abs(left-right)<=1 else -1 # 根的值
return dfs(root)!=-1
时间复杂度为O(n)
226. 翻转二叉树
题意:
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
题解:等同于剑指 Offer 27. 二叉树的镜像
方法一:
层序遍历的思想
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
queue = [root]
while queue:
tmp = queue.pop(0)
tmp.left,tmp.right = tmp.right, tmp.left
if tmp.left: #不为空就加入队列
queue.append(tmp.left)
if tmp.right:
queue.append(tmp.right)
return root
方法二:
dfs递归
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
root.left, root.right = root.right, root.left
self.invertTree(root.left) # 对子树进行递归交换
self.invertTree(root.right)
return root
543. 二叉树的直径
题意:
一棵二叉树的直径长度是任意两个结点路径长度中的最大值。两结点之间的路径长度是以它们之间边的数目表示。
题解:
class Solution:
def diameterOfBinaryTree(self, root: TreeNode) -> int:
self.res = 0
def dfs(node):
if not node:
return 0
l = dfs(node.left) #左子树的深度
r = dfs(node.right) #右子树的深度
self.res = max(l+r, self.res) #求当前路径长度
return max(l, r)+1
dfs(root)
return self.res
563. 二叉树的坡度
题意:
一个树的节点的坡度 定义为,该节点左子树的节点之和和右子树节点之和的 差的绝对值 。如果没有左子树的话,左子树的节点之和为 0;没有右子树的话也是一样。空结点的坡度是 0。
整个树 的坡度就是其所有节点的坡度之和。
题解:
class Solution:
def findTilt(self, root: TreeNode) -> int:
if not root:
return 0
self.res = 0
def dfs(node):
if not node:
return 0
left = dfs(node.left)
right = dfs(node.right)
self.res += abs(left-right)
return left + right + node.val # 每个节点往上返回其自身值和左右所有子节点的和。
dfs(root)
return self.res
114. 二叉树展开为链表
题意:
将一个二叉树原地展开为一个链表。展开后的单链表应该同样使用TreeNode,其中right子指针指向链表下一个节点,而左子指针始终为null,展开后的单链表应该和二叉树的先序遍历顺序相同。
题解:
要求是原地操作,先找到左子树的右子树的最深处,然后将root的右子树接到左子树的右子树的最深处,再将root的整个左子树接到右子树上,将root的左子树清空,然后继续下一个节点。
class Solution:
def flatten(self, root: TreeNode) -> None:
"""
Do not return anything, modify root in-place instead.
"""
while root:
if not root:
return []
if root.left:
sub_left = root.left
while sub_left.right:
sub_left = sub_left.right
sub_left.right = root.right
root.right = root.left
root.left = None
root = root.right # 继续下一个节点
617、合并二叉树
题意:给定两个二叉树,将它们合并为一个新的二叉树。
输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7
class Solution:
def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
if not root1 or not root2:
return root1 or root2
merged = TreeNode(root1.val + root2.val)
merged.left = self.mergeTrees(root1.left, root2.left)
merged.right = self.mergeTrees(root1.right, root2.right)
return merged
208. 实现 Trie (前缀树)
题意:
字典树。因此,看名称“字典” 就知道要实现该树,需要构造字典,然后开始构建节点,节点递归下一个节点。因此我们在初始化时构造了一个空字典。
具体的解题思路如下:
① insert 功能:
从头开始遍历给定的 word 字符,然后判断该字符是否存在当前的字典键里(初始时在自建的字典键里寻找);
如果不存在,那么建立一个键值对,键为当前字符,值为空字典;如果存在,不做任何操作;
最后,跳到该字符的键所对应的值(字典)里,然后遍历下一个字符;
因为有一些单词可能存在包含关系,因此我们要在最后一个字符对应的字典里加一个结束标志 “#”,证明这是一个单词的结束点。
②我们可以按照类似于实现 insert 功能的思想来实现 search 功能:
从头开始遍历给定的 word 字符,然后判断该字符是否存在于当前的字典键里(初始时在自建的字典键里寻找);
如果不存在,直接返回 False;如果存在,继续遍历下一字符;
当所有字符遍历完成时,我们还应该判断最后一个字符对应的字典里是否存在结束标志;
如果存在,说明的确存在该 word,返回 True;如果不存在,说明该 word 只是其他单词的一个子串,字典中并不存在该 word,返回 False。
③ startsWith 功能和 search 功能基本一样,只不过 startsWith 功能只是判断字典树中是否以 prefix 字符串为开始,而不必判断 prefix 是否为一个单词,因此与步骤②相比,它无需判断是否存在结束标志。
题解:
class Trie:
def __init__(self):
"""
Initialize your data structure here.
"""
self.d = {}
def insert(self, word: str) -> None:
"""
Inserts a word into the trie.
"""
tree = self.d
for i in word:
if i not in tree:
tree[i] = {}
tree = tree[i]
tree['#'] = '#'
def search(self, word: str) -> bool:
"""
Returns if the word is in the trie.
"""
tree = self.d
for i in word:
if i not in tree:
return False
tree = tree[i]
if '#' in tree:
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.d
for i in prefix:
if i not in tree:
return False
tree = tree[i]
return True
105. 从前序与中序遍历序列构造二叉树
题解:
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if len(inorder)==0:
return
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
222. 完全二叉树的节点个数
题意:给你一棵 完全二叉树 的根节点 root
,求出该树的节点个数。
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^h 个节点。
题解:
遍历树来统计节点的时间复杂度为O(N),N是节点个数。因为题目为完全二叉树,可以利用完全二叉树的特性来计算节点个数。假设完全二叉树的根节点为第0层,总共的层数是h,则节点个数在2**h~2**(h+1)-1之间。
class Solution:
def countNodes(self, root: TreeNode) -> int:
def path(root, num):
for s in bin(num)[3:]: # 将十进制的num转换为二进制,前两位为0b,第三位为根节点,根节点非空,一定为1
if s=='0': # 0即是往左走,1是往右走
root = root.left
else:
root = root.right
if not root:
return False
return True
if not root:
return 0
h = 0
node = root
while node.left:
h += 1
node = node.left
left = 2**h
right = 2**(h+1)-1
while left<right: # 二分查找最后一层的最右节点在哪
mid = (left+right+1)//2 # 向上取整
if path(root, mid):
left = mid
else:
right = mid - 1
return left
时间复杂度是O(logN * logN),因为首先需要O(h)的时间去得到最大层数h,然后在使用二分查找确定节点个数时,需要查找的次数是O(h),每次查找需要遍历从根节点开始的一条长度为h的路径,需要O(h)的时间,因此,查找的时间复杂度为O(h^2),因为完全二叉树满足2^h<=n <2^(h+1),因此,有O(h)=O(logn)。
662. 二叉树最大宽度
题意:给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。
每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
题解:
宽度优先搜索,顺序遍历每个节点,记录节点的position,对于每一个深度,第一个遇到的节点是最左边的节点,最后到达的节点是最右边的节点。
主要想法是给每个节点一个 position 值,如果我们走向左子树,那么 position -> position * 2,如果我们走向右子树,那么 position -> positon * 2 + 1。当我们在看同一层深度的位置值 L 和 R 的时候,宽度就是 R - L + 1
。
class Solution:
def widthOfBinaryTree(self, root: TreeNode) -> int:
queue = [(root, 0, 0)]
cur_depth = left = ans = 0
for node, depth, pos in queue:
if node:
queue.append((node.left, depth+1, 2*pos))
queue.append((node.right, depth+1, 2*pos+1))
if cur_depth!=depth: # 深度加1,第一个遇到的节点是最左边的节点
cur_depth = depth
left = pos
ans = max(pos - left+1, ans) # 宽度=R-L+1
return ans
链表
206. 反转链表
题解:
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
pre = None
cur = head
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
92. 反转链表 II
题意: 部分翻转,即翻转从left到right位置之间的链表节点。
题解:一次遍历,头插法,在需要反转的区间内,每遍历到一个节点,让这个新节点来到反转部分的起始位置。
使用三个指针变量pre, cur, tmp;cur指向待翻转区域的第一个结点;tmp永远指向cur的下一个节点,在循环过程中,cur变化后,tmp会变化;pre永远指向待翻转区域的第一个结点的前一个节点,在循环过程中不变。
class Solution:
def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
node = ListNode(-1)
node.next = head
pre = node
for i in range(left-1):
pre = pre.next
cur = pre.next
for j in range(right-left):
tmp = cur.next
cur.next = tmp.next
tmp.next = pre.next
pre.next = tmp
return node.next
25. K 个一组翻转链表
题意:给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。 要求只使用常数额外空间。
题解:先定义一个翻转函数,参数是翻转的头结点,和尾节点。然后再顺序遍历链表,进行翻转。
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
if not head:
return None
head_pre = ListNode(0)
head_pre.next = head
pre = head_pre
while head:
end = head
for i in range(k): # 判断接下来的节点数量是否小于k
if not end:
return head_pre.next
end = end.next
newhead = self.reverse(head, end) # 翻转链表段
pre.next = newhead # 将翻转后的链表拼接到原有的位置
head.next = end
pre = head #指向下一个链表的开始
head = end
return head_pre.next
def reverse(self, head, tail):
pre = None
cur = head
while cur!=tail:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
61. 旋转链表
题意:给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
题解:先判断k是否是链表长度的整数倍,若是,则旋转之后仍然是原链表,直接返回头结点即可。若不是,则使用快慢指针,先让快指针走个k步,然后快慢指针同时向后走。
class Solution:
def rotateRight(self, head: ListNode, k: int) -> ListNode:
if not head or not head.next or not k:
return head
tmp, count = head, 0
while tmp:
tmp = tmp.next
count += 1
k = k % count
if k == 0: # 如果k>n,则直接返回head
return head
fast = slow = head
for i in range(k):
fast = fast.next
while fast.next:
fast = fast.next
slow = slow.next
newhead = slow.next
slow.next = None
fast.next = head
return newhead
143. 重排链表
题意:
给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
要求原地操作
题解:
1、先找到链表的中点。 2、翻转右半边的链表 3、合并
class Solution:
def reorderList(self, head: ListNode) -> None:
"""
Do not return anything, modify head in-place instead.
"""
if not head:
return None
slow, fast = head, head
while fast.next and fast.next.next:
fast = fast.next.next
slow = slow.next
def reverse(node):
if not node:
return
cur = node
pre = None
while cur:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
l1 = head
l2 = reverse(slow)
while l1 and l2:
tmp1 = l1.next
tmp2 = l2.next
l1.next = l2 # 顺序不能颠倒
l1 = tmp1
l2.next = l1
l2 = tmp2
141. 环形链表
题意:检查链表中是否有环
题解:
快慢指针
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head:
return False
slow, fast = head, head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if slow==fast:
return True
return False
142. 环形链表 II
题意:要求返回入环的链表的第一个节点
题解:
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
if not head:
return None
flag = False
slow, fast = head, head#.next
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if slow==fast:
flag = True
break
if flag:
slow = head # 让slow在表头,fast此时在相遇点,两者以相同的速度移动,再次相遇的节点就是入环节点。
while slow!=fast:
slow = slow.next
fast = fast.next
return slow
return None
变体:求环的长度
题解:就是从第一次相遇,到第二次相遇之间的前进次数。
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head:
return False
slow, fast = head, head
count = 0
length = 0
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if slow==fast:
count += 1
if count==1:
length += 1
if count==2: # 第二次相遇时
break
return length
382. 链表随机节点
题意:给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。
题解:
类似于从n个数中等概率地取出k个数,在这里,k = 1。
class Solution:
def __init__(self, head: ListNode):
"""
@param head The linked list's head.
Note that the head is guaranteed to be not null, so it contains at least one node.
"""
self.head = head
def getRandom(self) -> int:
"""
Returns a random node's value.
"""
count = 0
cur = self.head
while cur:
count += 1
# 等概率取样,每个样本被取到的概率都是1/count
# 例如,count为1时,概率为1,即res的初值。count为2时,有1/2的几率选中2,如选中,2即为res,替换之前的res,以此类推。
if random.randint(1, count)==count:
res = cur.val
cur = cur.next
return res