LCR 124. 推理二叉树(从前序与中序遍历序列构造二叉树)
中等
某二叉树的先序遍历结果记录于整数数组 preorder
,它的中序遍历结果记录于整数数组 inorder
。请根据 preorder
和 inorder
的提示构造出这棵二叉树并返回其根节点。
注意:preorder
和 inorder
中均不含重复数字。
示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
示例 2:
输入: preorder = [-1], inorder = [-1]
输出: [-1]
提示:
1 <= preorder.length <= 3000
inorder.length == preorder.length
-3000 <= preorder[i], inorder[i] <= 3000
inorder
均出现在preorder
preorder
保证 为二叉树的前序遍历序列inorder
保证 为二叉树的中序遍历序列
注意:本题与主站 105 题重复:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
超高效写法(哈希)
1. 避免了频繁的数组切片操作
- 第一个方法中使用了大量的数组切片(如
preorder[1:1+left_length]
,inorder[:left_length]
等),每次切片都会创建新的子数组,这在时间和空间上都有开销。 - 第二个方法使用索引范围(
left
和right
)来标记当前子树在inorder
数组中的位置,避免了数组切片的开销。
2. 预处理了 inorder
的索引映射
- 第一个方法每次查找根节点在
inorder
中的位置时,都要调用inorder.index(root_val)
,时间复杂度是 O(n),最坏情况下整个算法的时间复杂度会达到 O(n²)(例如当树退化成链表时)。 - 第二个方法预先构建了一个
inorder_map
(哈希表),存储了每个值对应的索引,这样每次查找只需要 O(1) 时间,使得整个算法的时间复杂度降低到 O(n)。
3. 使用 pre_idx
全局指针代替 preorder
切片
- 第一个方法递归时传递的是
preorder
的子数组,这需要额外的空间和时间。 - 第二个方法维护一个
pre_idx
指针,按preorder
的顺序逐个处理节点,避免了传递子数组的开销。
4. 空间复杂度优化
- 第一个方法由于频繁切片,最坏情况下空间复杂度是 O(n²)(递归栈 + 切片存储)。
- 第二个方法的空间复杂度是 O(n)(
inorder_map
存储 + 递归栈深度)。
总结
优化点 | 方法1(原始方法) | 方法2(优化方法) |
---|---|---|
数组切片 | 频繁切片,O(n²) 时间 | 无切片,O(1) 时间 |
索引查找 | 每次 inorder.index() ,O(n) | 哈希表查找,O(1) |
空间使用 | 递归+切片存储,O(n²) | 仅递归栈+哈希表,O(n) |
总时间复杂度 | O(n²) | O(n) |
总空间复杂度 | O(n²) | O(n) |
class Solution:
def deduceTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
inorder_map = {val: idx for idx, val in enumerate(inorder)}
pre_idx = 0
def helper(left, right):
nonlocal pre_idx
if left > right:
return None
root_val = preorder[pre_idx]
root = TreeNode(root_val)
pre_idx += 1
root.left = helper(left, inorder_map[root_val] - 1)
root.right = helper(inorder_map[root_val] + 1, right)
return root
return helper(0, len(inorder) - 1)
普通递归写法
class Solution:
def deduceTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
n = len(preorder)
if n == 0: return None
if n == 1: return TreeNode(preorder[0])
root = TreeNode(preorder[0])
index = inorder.index(root.val)
root.left = self.deduceTree(preorder[1:index+1], inorder[:index])
root.right = self.deduceTree(preorder[index+1:], inorder[index+1:])
return root
nextpage
147. 对链表进行插入排序
中等
给定单个链表的头 head
,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
对链表进行插入排序。
示例 1:
输入: head = [4,2,1,3]
输出: [1,2,3,4]
示例 2:
输入: head = [-1,5,3,4,0]
输出: [-1,0,3,4,5]
*提示:
- 列表中的节点数在
[1, 5000]
范围内 -5000 <= Node.val <= 5000
插入排序
class Solution:
def insertionSortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 第一大罪:我没有处理边界情况
if not head or not head.next:
return head
# 第二大罪:我没有使用dummy哨兵节点,所以head还需要重置
dummy = ListNode(0) # 使用 dummy 节点简化头部插入
dummy.next = head
iterator_pre = head
iterator = head.next
while iterator:
# 从头开始找插入位置
cur = dummy # 从 dummy 开始遍历,方便处理头部插入
while cur.next != iterator:
if iterator.val < cur.next.val:
iterator_pre.next = iterator.next # 先摘除 iterator
iterator.next = cur.next # 插入 iterator
# 第三大罪:只顾后链接不顾前链接
cur.next = iterator
iterator = iterator_pre.next
break
cur = cur.next
else:
# 如果 iterator 不需要移动,则直接后移
iterator_pre = iterator
iterator = iterator.next
return dummy.next
nextpage
206. 反转链表
简单
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000]
-5000 <= Node.val <= 5000
**进阶:**链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
迭代法
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode()
dummy.next = head
pre = None
cur = dummy.next
while cur:
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
return pre
nextpage
790. 多米诺和托米诺平铺
中等
有两种形状的瓷砖:一种是 2 x 1
的多米诺形,另一种是形如 "L" 的托米诺形。两种形状都可以旋转。
给定整数 n ,返回可以平铺 2 x n
的面板的方法的数量。返回对 109 + 7
取模 的值。
平铺指的是每个正方形都必须有瓷砖覆盖。两个平铺不同,当且仅当面板上有四个方向上的相邻单元中的两个,使得恰好有一个平铺有一个瓷砖占据两个正方形。
示例 1:
输入: n = 3
输出: 5
解释: 五种不同的方法如上所示。
示例 2:
输入: n = 1
输出: 1
提示:
1 <= n <= 1000
方法一:动态规划
class Solution:
def numTilings(self, n: int) -> int:
MOD = 10 ** 9 + 7
dp = [[0] * 4 for _ in range(n + 1)]
dp[0][3] = 1
for i in range(1, n + 1):
dp[i][0] = dp[i - 1][3]
dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % MOD
dp[i][2] = (dp[i - 1][0] + dp[i - 1][1]) % MOD
dp[i][3] = (((dp[i - 1][0] + dp[i - 1][1]) % MOD + dp[i - 1][2]) % MOD + dp[i - 1][3]) % MOD
return dp[n][3]
方法二:矩阵快速幂
class Solution:
def numTilings(self, n: int) -> int:
MOD = 10 ** 9 + 7
def multiply(a: List[List[int]], b: List[List[int]]) -> List[List[int]]:
rows, columns, temp = len(a), len(b[0]), len(b)
c = [[0] * columns for _ in range(rows)]
for i in range(rows):
for j in range(columns):
for k in range(temp):
c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD
return c
def matrixPow(mat: List[List[int]], n: int) -> List[List[int]]:
ret = [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
]
while n:
if n & 1:
ret = multiply(ret, mat)
n >>= 1
mat = multiply(mat, mat)
return ret
mat = [
[0, 0, 0, 1],
[1, 0, 1, 0],
[1, 1, 0, 0],
[1, 1, 1, 1],
]
res = matrixPow(mat, n)
return res[3][3]
nextpage
889. 根据前序和后序遍历构造二叉树
中等
给定两个整数数组,preorder
和 postorder
,其中 preorder
是一个具有 无重复 值的二叉树的前序遍历,postorder
是同一棵树的后序遍历,重构并返回二叉树。
如果存在多个答案,您可以返回其中 任何 一个。
示例 1:
输入:preorder = [1,2,4,5,3,6,7], postorder = [4,5,2,6,7,3,1]
输出:[1,2,3,4,5,6,7]
示例 2:
输入: preorder = [1], postorder = [1]
输出: [1]
提示:
1 <= preorder.length <= 30
1 <= preorder[i] <= preorder.length
preorder
中所有值都 不同postorder.length == preorder.length
1 <= postorder[i] <= postorder.length
postorder
中所有值都 不同- 保证
preorder
和postorder
是同一棵二叉树的前序遍历和后序遍历
解题思路
通过口诀"前序遍历根左右"和”后序遍历左右根“,可以看出前序遍历的下一层递归就是【根(根左右)(根左右)】,说明前序遍历的第一个左子树的根节点就在根节点:preorder[0]的下一个位置:preorder[1],那么后序遍历中呢就是【(左右根)(左右根)根】,即后序遍历中第一个左子树的根节点正好可以用于分割左右子树,那么就可以和前面的两道题目的思路联系起来了【105. 从前序与中序遍历序列构造二叉树】【889. 根据前序和后序遍历构造二叉树】,通过postorder.index(preorder[1]) + 1就可以分割左右子树了,想必到这里大家都知道怎么解了吧。
递归写法
class Solution:
def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
n = len(postorder)
if n == 0: return None
if n == 1: return TreeNode(preorder[0])
root = TreeNode(preorder[0])
index = postorder.index(preorder[1]) + 1
root.left = self.constructFromPrePost(preorder[1:index+1], postorder[:index])
root.right = self.constructFromPrePost(preorder[index+1:], postorder[index:-1])
return root