目录
一、总结 —— 递归 I
参考文献:https://leetcode-cn.com/explore/orignial/card/recursion-i/260/conclusion/1105/
二、合并两个有序链表
2.1 题目要求
2.2 解决过程
个人实现
法一:尾递归 - 非原地操作。
2020/07/23 - 18.52% (56ms) - 很低效的实现
# 尾递归 (非 in-place 版)
class Solution:
def mergeTwoLists(self, l1, l2):
result = ListNode() # 哨兵节点 - 伪头
cur = result # 当前指针
def tailrecur(cur, left, right):
if (not left) and (not right): # left 和 right 均耗尽
return result.next
elif (right == None): # right 先耗尽
cur.next = ListNode(left.val)
left = left.next
elif (not left): # left 先耗尽
cur.next = ListNode(right.val)
right = right.next
else: # left 和 right 均未耗尽
if (left.val <= right.val):
cur.next = ListNode(left.val)
left = left.next
else:
cur.next = ListNode(right.val)
right = right.next
return tailrecur(cur.next, left, right) # 尾递归
return tailrecur(cur, l1, l2)
法二:尾递归 - 原地操作。比非原地操作更简洁和高效,但缺点是会破坏输入链表数据。
2020/07/23 - 82.44% (44ms) - 好许多
# 尾递归 (in-place 版)
class Solution:
def mergeTwoLists(self, l1, l2):
result = ListNode() # 哨兵节点 - 伪头
cur = result # 当前指针
def tailrecur(cur, left, right):
if (not left) and (not right): # left 和 right 均耗尽
return result.next
elif not right: # right 先耗尽
cur.next = left
return result.next
elif not left: # left 先耗尽
cur.next = right
return result.next
else: # left 和 right 均未耗尽
if (left.val <= right.val):
cur.next = left
left = left.next
else:
cur.next = right
right = right.next
cur = cur.next
return tailrecur(cur, left, right) # 尾递归
return tailrecur(cur, l1, l2)
法三:迭代 - 非原地操作。通过遍历左指针 left 和 右指针 right 将两链表合并到开辟的额外空间 —— 新链表 new 中。思想类似于合并两个有序数组。空间复杂度 O(n),时间复杂度 O(n)。
2020/07/11 - 34.56% - 次优
# 迭代 - 非原地操作
class Solution:
def addNewNode(self, node, new):
""" 在结果链表指针 new 中加入节点 node """
new.next = ListNode(node.val)
new = new.next
node = node.next
return node, new
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
""" 创建一个新链表 new 以合并输入链表 """
new = ListNode() # 初始化结果链表
new_head = new # 结果链表哨兵节点指针
left = l1 # 左链表头节点指针
right = l2 # 右链表头节点指针
while (left and right): # 交替连接节点 - 元素从小到大
if left.val <= right.val:
left, new = self.addNewNode(left, new)
else:
right, new = self.addNewNode(right, new)
while left: # 连接左链表剩余节点
left, new = self.addNewNode(left, new)
while right: # 连接右链表剩余节点
right, new = self.addNewNode(right, new)
return new_head.next # 哨兵节点后的才是真头节点
官方实现与说明
# 太强大了, 此官方实现比个人实现更为简洁、抽象!!
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
if l1 is None:
return l2
elif l2 is None:
return l1
elif l1.val < l2.val:
l1.next = self.mergeTwoLists(l1.next, l2) # 学习
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next) # 学习
return l2
2020/07/23 - 36.39% (52ms)
# in-place 操作也很棒
class Solution:
def mergeTwoLists(self, l1, l2):
prehead = ListNode(-1) # 哨兵节点 - 伪头部
prev = prehead # 当前指针
while l1 and l2:
if l1.val <= l2.val:
prev.next = l1
l1 = l1.next
else:
prev.next = l2
l2 = l2.next
prev = prev.next
# 合并后 l1 和 l2 最多只有一个还未被合并完,
# 直接将链表末尾指向未合并完的链表即可
prev.next = l1 if l1 else l2
return prehead.next
2020/07/23 - 36.39% (52ms)
参考文献
https://leetcode-cn.com/explore/orignial/card/recursion-i/260/conclusion/1229/
三、合并两个有序链表多解
// JAVA implementation
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
else if (l2 == null) {
return l1;
}
else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
}
else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
// JAVA implementation
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// maintain an unchanging reference to node ahead of the return node.
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// exactly one of l1 and l2 can be non-null at this point, so connect
// the non-null list to the end of the merged list.
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
参考文献:https://leetcode-cn.com/explore/orignial/card/recursion-i/260/conclusion/1230/
四、第K个语法符号
4.1 题目要求
4.2 解决过程
测试用例
1
1
2
1
2
2
4
5
30
434991989
失败测试
法一:迭代法。通过层数 N 确定外层迭代次数。使用数字字符串作为数据载体。通过将要增加字符串部分为当前字符串二进制取反的原理,减半每层迭代次数。空间复杂度与时间复杂度均为 O(2^n),因而最后还是超时了!
2020/07/26 - 超出时间限制
class Solution:
def kthGrammar(self, N: int, K: int) -> int:
string = "0" # 初始数字字符串
times = 1 # 当前需要索引/增加次数
for j in range(N-1): # 逐行遍历
for i in range(times): # 关键:当前字符串为先前字符串的数字二进制取反
if string[i] == "0":
string += "1"
else:
string += "0"
times *= 2 # 下次需要索引/增加次数*2
return string[K-1]
法二:尾递归。思路基本同暴力迭代法,依旧超时!
2020/07/26 - 超出时间限制
class Solution:
def kthGrammar(self, N: int, K: int) -> int:
if N == 1:
return 0
def recur(cache, layer, index, num):
"""
cache:结果缓存 - 数字列表
layer:当前层号
index:当前指针索引
num:当前层需要增加的元素数
"""
if layer > N: # 终止条件, 到 N+1 层即可得到第 N 层结果
return cache[K-1]
if index < num:
if cache[index] == 0:
cache.append(1)
else:
cache.append(0)
return recur(cache, layer, index+1, num) # 本处生成完毕, 指向下一个位置(注意不要忘记 return)
else:
return recur(cache, layer+1, 0, num*2) # 本层生成完毕, 进入下一层(注意不要忘记 return)
return recur([0], 2, 0, 1)
官方实现与说明
class Solution(object):
def kthGrammar(self, N, K):
rows = []
lastrow = '0'
while len(rows) < N:
rows.append(lastrow)
lastrow = "".join('01' if x == '0' else '10'
for x in lastrow)
return int(rows[-1][K-1])
2020/07/27 - 超出时间限制
## 能想到, 但是没写出来, 该实现十分简洁而巧妙!
class Solution(object):
def kthGrammar(self, N, K):
if N == 1: # base case
return 0
return (1 - K%2) ^ self.kthGrammar(N-1, (K+1)//2) # ^ 为异或:相同为0, 相异为1
# 右边是上一行的对应位置,左边是判断现在这行K位置是上一行对应位置(父节点)的左孩子还是右孩子。
# 如果是左孩子, 数值不变 (0 → 0 or 1 → 1);如果是右孩子, 数值取反 (0 → 1 or 1 → 0)
2020/07/27 - 70.77% (40ms)
以下绘图举例两个例子:
## 想到了, 没写出来, 这个实现更简洁而巧妙!
class Solution(object):
def kthGrammar(self, N, K):
if N == 1: # base case
return 0
if K <= (2 ** (N-2)):
return self.kthGrammar(N-1, K) # K 在第 N 行的左半部分, 则对应 N-1 行处数值不变
else: # ^ 为异或:相同为0, 相异为1
return self.kthGrammar(N-1, K - 2**(N-2)) ^ 1 # K 在第 N 行的右半部分, 则对应 N-1 行处数值取反
2020/07/27 - 70.77% (40ms)
>>> bin(0)
'0b0'
>>> bin(1)
'0b1'
>>> bin(2)
'0b10'
>>> bin(3)
'0b11'
>>> bin(4)
'0b100'
>>> bin(5)
'0b101'
>>> bin(6)
'0b110'
>>> bin(7)
'0b111'
>>> bin(8)
'0b1000'
## 可以说相当费解了!!但不得不说, 理论确实十分巧妙、准确!
class Solution(object):
def kthGrammar(self, N, K):
return bin(K - 1).count('1') % 2
# 不论在第几层, 只要计算出 bin(K -1) 中 1 出现的次数,
# 就知道从第 1 层的 0 变换到第 N 层的 第 K 个需要经过几次取反
2020/07/27 - 70.77% (40ms)
参考文献
https://leetcode-cn.com/explore/orignial/card/recursion-i/260/conclusion/1231/
https://leetcode-cn.com/problems/k-th-symbol-in-grammar/solution/di-kge-yu-fa-fu-hao-by-leetcode/
五、第K个语法符号多解
完全同四 - 官方实现与说明
参考文献:https://leetcode-cn.com/explore/orignial/card/recursion-i/260/conclusion/1232/
六、不同的二叉搜索树 II
6.1 题目要求
6.2 解决过程
官方实现与说明
class Solution:
def generateTrees(self, n: int) -> List[TreeNode]:
def generateTrees(start, end):
if start > end:
return [None,]
allTrees = []
for i in range(start, end + 1): # 枚举可行根节点
# 获得所有可行的左子树集合
leftTrees = generateTrees(start, i - 1)
# 获得所有可行的右子树集合
rightTrees = generateTrees(i + 1, end)
# 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上
for l in leftTrees:
for r in rightTrees:
currTree = TreeNode(i)
currTree.left = l
currTree.right = r
allTrees.append(currTree)
return allTrees
return generateTrees(1, n) if n else []
其他实现与说明:解法一 — 解法四
class Solution:
def generateTrees(self, n: int) -> List[TreeNode]:
# 通过先序遍历拷贝二叉树
def copyTree(root):
if not root:
return None
node = TreeNode(root.val) # 新建父节点
node.left = copyTree(root.left) # 左子节点
node.right = copyTree(root.right) # 右子节点
return node
def dfs(n):
# 如果 n=1 只有一种 BST 情况
if n == 1:
result.append(TreeNode(1))
return
dfs(n-1)
# 遍历 n=k 时的所有 BST 情况
for i in range(len(result)):
u = result[i] # 取出第 i 个节点
node = TreeNode(n) # 新建数值为 n 的节点
node.left = u
result[i] = node
it = u
rank = 0
# 将 n 插入最右侧链路当中,有几种可以选择的位置,就会诞生几种新的放法
while it is not None:
node = TreeNode(n)
# 为了防止答案之间互不影响,所以需要把树拷贝一份
new = copyTree(u)
cur = new
# rank记录的是每一个解对应的n放入的深度
for _ in range(rank):
cur = cur.right
node.left = cur.right
cur.right = node
result.append(new)
it = it.right
rank += 1
if n == 0:
return []
result = []
dfs(n)
return result
2020/08/12 - 70.98% (72ms) - 过于繁琐
# 需要学习, 很强大但亦抽象的递归
class Solution:
def generateTrees(self, n: int) -> List[TreeNode]:
def dfs(l, r):
cur = [] # 当前组合列表
# 越界情况
if r < l:
cur.append(None)
return cur
# 枚举树根元素值的所有情况 i ([l, r] 闭区间内)
for i in range(l, r+1):
# 枚举左子树的所有子树的构成情况 ([l, i-1] 闭区间内)
for j in dfs(l, i-1):
# 枚举右子树的所有子树的构成情况 ([i+1, r] 闭区间内)
for k in dfs(i+1, r):
root = TreeNode(i) # 构造当前根节点
root.left = j # 获取所有左子树
root.right = k # 获取所有右子树
cur.append(root) # 记录当前组合列表
return cur
if n == 0:
return []
else:
result = []
return dfs(1, n)
2020/08/12 - 96.56% (60ms) - 最佳
数字个数是 0 的所有解
null
数字个数是 1 的所有解
1
2
3
数字个数是 2 的所有解,我们只需要考虑连续数字
[ 1 2 ]
1
\
2
2
/
1
[ 2 3 ]
2
\
3
3
/
2
如果求 3 个数字的所有情况。
[ 1 2 3 ]
利用解法二递归的思路,就是分别把每个数字作为根节点,然后考虑左子树和右子树的可能
1 作为根节点,左子树是 [] 的所有可能,右子树是 [ 2 3 ] 的所有可能,利用之前求出的结果进行组合。
1
/ \
null 2
\
3
1
/ \
null 3
/
2
2 作为根节点,左子树是 [ 1 ] 的所有可能,右子树是 [ 3 ] 的所有可能,利用之前求出的结果进行组合。
2
/ \
1 3
3 作为根节点,左子树是 [ 1 2 ] 的所有可能,右子树是 [] 的所有可能,利用之前求出的结果进行组合。
3
/ \
1 null
\
2
3
/ \
2 null
/
1
x
/
y
y
\
x
[ 1 2 ]
1
\
2
2
/
1
//
[ 2 3 ]
2
\
3
3
/
2
[ 1 2 ]
1
\
2
2
/
1
[ 99 100 ]
1 + 98
\
2 + 98
2 + 98
/
1 + 98
// 即
99
\
100
100
/
99
# 拷贝树
def clone(root, offset):
if not root:
return None
node = TreeNode(root.val + offset)
node.left = clone(root.left, offset)
node.right = clone(root.right, offset)
return node
class Solution:
def generateTrees(self, n: int) -> List[TreeNode]:
# 拷贝树
def clone(root, offset):
if not root:
return None
node = TreeNode(root.val + offset)
node.left = clone(root.left, offset)
node.right = clone(root.right, offset)
return node
if n == 0:
return []
dp = [[] for _ in range(n+1)] # 预分配空间
dp[0].append(None)
# 长度为 1 到 n 递增
for length in range(1, n+1):
# 将不同的数字作为根节点,只需要考虑到 length
for root in range(1, length+1):
left = root - 1 # 左子树的长度
right = length - root # 右子树的长度
# 遍历左子树的所有情况
for leftTree in dp[left]:
# 遍历右子树的所有情况
for rightTree in dp[right]:
treeRoot = TreeNode(root) # 当前根节点
treeRoot.left = leftTree # 直接拷贝已计算过的左子树
treeRoot.right = clone(rightTree, root) # 克隆右子树并加上偏差
dp[length].append(treeRoot) # 添加当前情况
return dp[n]
2020/08/12 - 70.98% (72ms)
考虑 [] 的所有解
null
考虑 [ 1 ] 的所有解
1
考虑 [ 1 2 ] 的所有解
2
/
1
1
\
2
考虑 [ 1 2 3 ] 的所有解
3
/
2
/
1
2
/ \
1 3
3
/
1
\
2
1
\
3
/
2
1
\
2
\
3
//
对于下边的解
2
/
1
然后增加 3
1.把 3 放到根节点
3
/
2
/
1
2. 把 3 放到根节点的右孩子
2
/ \
1 3
对于下边的解
1
\
2
然后增加 3
1.把 3 放到根节点
3
/
1
\
2
2. 把 3 放到根节点的右孩子,原来的子树作为 3 的左孩子
1
\
3
/
2
3. 把 3 放到根节点的右孩子的右孩子
1
\
2
\
3
class Solution:
def generateTrees(self, n: int) -> List[TreeNode]:
# 拷贝树
def treeCopy(root):
if not root:
return None
newRoot = TreeNode(root.val)
newRoot.left = treeCopy(root.left)
newRoot.right = treeCopy(root.right)
return newRoot
if n == 0:
return []
pre = []
pre.append(None)
# 每次增加一个数字
for i in range(1, n+1):
cur = []
# 遍历之前的所有解
for root in pre:
# 插入到根节点
insert = TreeNode(i)
insert.left = root
cur.append(insert)
# 插入到右孩子,右孩子的右孩子 ... 最多找 n 次孩子
for j in range(n):
root_copy = treeCopy(root) # 复制当前的树
right = root_copy # 找到要插入右孩子的位置
# 遍历 j 次找右孩子
for _ in range(j):
if right == None:
break
right = right.right
# 到 None 提前结束
if right == None:
break
# 保存当前右孩子的位置的子树作为插入节点的左孩子
rightTree = right.right
insert = TreeNode(i)
right.right = insert # 右孩子是插入的节点
insert.left = rightTree # 插入节点的左孩子更新为插入位置之前的子树
# 加入结果中
cur.append(root_copy)
pre = cur
return pre;
2020/08/12 - 23.07% (84ms)
参考文献
https://leetcode-cn.com/explore/orignial/card/recursion-i/260/conclusion/1233/
七、不同的二叉搜索树 II
class Solution:
def generateTrees(self, n):
"""
:type n: int
:rtype: List[TreeNode]
"""
def generate_trees(start, end):
if start > end:
return [None,]
all_trees = []
for i in range(start, end + 1): # pick up a root
# all possible left subtrees if i is choosen to be a root
left_trees = generate_trees(start, i - 1)
# all possible right subtrees if i is choosen to be a root
right_trees = generate_trees(i + 1, end)
# connect left and right subtrees to the root i
for l in left_trees:
for r in right_trees:
current_tree = TreeNode(i)
current_tree.left = l
current_tree.right = r
all_trees.append(current_tree)
return all_trees
return generate_trees(1, n) if n else []