【递归】(五) 总结

目录

一、总结 —— 递归 I

二、合并两个有序链表

2.1 题目要求

2.2 解决过程

三、合并两个有序链表多解

四、第K个语法符号

4.1 题目要求

4.2 解决过程

五、第K个语法符号多解

六、不同的二叉搜索树 II

6.1 题目要求

6.2 解决过程

七、不同的二叉搜索树 II


一、总结 —— 递归 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/

https://leetcode-cn.com/problems/merge-two-sorted-lists/solution/he-bing-liang-ge-you-xu-lian-biao-by-leetcode-solu/

https://leetcode-cn.com/problems/merge-two-sorted-lists/solution/hua-jie-suan-fa-21-he-bing-liang-ge-you-xu-lian-bi/176818


三、合并两个有序链表多解

// 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)

参考文献

LeetCode 94 | 构造出所有二叉搜索树

https://leetcode-cn.com/explore/orignial/card/recursion-i/260/conclusion/1233/

https://leetcode-cn.com/problems/unique-binary-search-trees-ii/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-2-7/

https://leetcode-cn.com/problems/unique-binary-search-trees-ii/solution/bu-tong-de-er-cha-sou-suo-shu-ii-by-leetcode-solut/


七、不同的二叉搜索树 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 []

参考文献:https://leetcode-cn.com/leetbook/read/recursion/4zmyh/ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值