递归算法

末鹿安然 提交于 2020-01-31 10:20:39

核心思想:
重复将问题分解为同类的子问题而解决问题的方法,其核心思想是分治策略。
使用条件:
1.需要解决的问题可以转化为一个或多个子问题来求解,而这些子问题的求解方法与原问题完全相同,只是在数量和规模上不同。
2.递归调用的次数必须是有限的。
3.必须有结束递归的条件来终止递归。
举例(LeetCode 894):
满二叉树是一类二叉树,其中每个结点恰好有 0 或 2 个子结点。
返回包含 N 个结点的所有可能满二叉树的列表。 答案的每个元素都是一个可能树的根结点。
答案中每个树的每个结点都必须有 node.val=0。
你可以按任何顺序返回树的最终列表。

思路:
构造二叉树的思路是对于二叉树的每一层来说,我们都是先初始化一个根节点,
然后分别定义它的左子树和右子树,这个过程很明显可以递归实现。
本题要求构造满二叉树,也就是每一个结点它的左节点和右节点必须同时存在
或同时不存在,假设左子树有X个节点,总节点数为N,右子树则包含N-1-X个
节点,X数目从1开始,增加到N - 1 - 1,共计(N - 1 ) / 2中情况
对于每一种子情况我们采取递归求解:
结束条件:
		当N为偶数时,无法构造二叉树,返回空数组。
		当N为1时,树只有一个节点,为了不破坏递归,同样返回一个数组,包含这个节
		点
左右子树构造采用递归的方法。
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def allPossibleFBT(self, N: int) -> List[TreeNode]:
        if N == 1:		#终止条件
            return [TreeNode(0)]
        if N % 2 == 0:
            return []
        forest = []
        #初始化子问题1
        left = 1
        right = N - 1 - 1
        while right > 0:  
            left_tree = self.allPossibleFBT(left)
            right_tree = self.allPossibleFBT(right)
            for i in left_tree:
                for j in right_tree:
                    root = TreeNode(0)
                    root.left = i
                    root.right = j
                    forest.append(root)
            left += 2
            right -= 2
        return forest

举例二:(leetcode698
给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。
示例 1:
输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和

思路:
终止条件:
		1.当nums % k != 0时,直接返回错误,不能构成k个非空子集
		2.当nums为空,即nums中的数字全部填充进入子集,那么我们的
		搜索就成功了。
递归过程:
		每次向子集填充数字遵循数字大者优先,这样可以较快组合数量
		较小的子集。对于 nums 中的每个数字,我们可以将其添加到 
		一个group,只要该子集的和不会超过目标值,否则添加下一个。
		对于每一个选择,我们都递归重复上述过程。		
class Solution:
    def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
        nums.sort()	#排序,实现较小集合优先找到
        div, mod = divmod(sum(nums), k)
         #不合题意,直接排除
        if mod: return False 
         #判断是否有超出平均值的数字
        if nums[-1] > target: return False 
        #简化计算复杂度,直接将大小等于div的数字排除
        while nums and nums[-1] == div:
            nums.pop()
            k -= 1
        def reverse(groups, tar):
            if not nums: return True  #成功放置所有数字
            count = nums.pop()	#获取当前nums最大的数字
            for i, group in enumerate(groups):
                if group + count <= tar:  #当前子集小于平均值,则将数字加入该子集
                    groups[i] += count
                    if reverse(groups, tar): return True #找到最终结果
                    groups[i] -= count #当前子集不合适,选用新的子集
                if not group: break     #优化,0的时候只填充一次,剩下的跳过
            nums.append(count) #回溯
            return False
        return reverse([0] * k, div)
                    
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!