
第一部分核心思想文字详解1. 什么是递归Recursion递归是一种函数调用自身的编程技巧。它的核心是把一个大型复杂的问题层层转化为一个与原问题相似但规模更小的子问题来解决。递归的三大要素明确函数功能先搞清楚这个函数要干什么输入什么返回什么。寻找递归终止条件Base Case问题小到什么程度可以直接给出答案这是防止无限递归的刹车。找出递推关系Recursive Relation大问题如何拆解成小问题比如f(n) n * f(n-1)。递归的优缺点优点代码极度简洁、清晰尤其适合处理树形结构和分治问题。缺点函数调用有开销可能导致栈溢出且常常有大量重复计算这是DP要解决的问题。2. 什么是分治Divide and Conquer分治是一种算法设计思想它依赖于递归来实现。分治的核心就三步分解Divide将原问题分解为若干个规模较小、相互独立、与原问题形式相同的子问题。解决Conquer若子问题规模小到一定程度直接求解否则递归地求解各个子问题。合并Combine将各个子问题的解合并为原问题的解。关键区分分治是一种战略怎么拆怎么合。递归是一种战术用自调用来实现这个战略。分治的子问题相互独立不重叠而DP的子问题是重叠的。这是两者最大的区别3. 递归的调用栈理解底层每次递归调用系统都会在内存中开辟一个栈帧保存局部变量和返回地址。当递归深度过大比如n100000时会栈溢出StackOverflowError。尾递归优化把递归调用放在函数最后一步在一些语言中可以复用栈帧但Python和Java默认不支持。第二部分经典递归与分治代码详解我从浅到深给你四个最经典的模型。案例一递归入门 —— 阶乘与斐波那契阶乘最基础的递归模型体现递与归的完整过程。pythondef factorial(n: int) - int: # 1. 终止条件0! 1 if n 1: return 1 # 2. 递推关系n! n * (n-1)! return n * factorial(n - 1) # 执行过程factorial(5) 5 * factorial(4) 5 * 4 * factorial(3) ... 120斐波那契递归版虽然效率极低指数级复杂度但最直观展示递推。pythondef fib(n: int) - int: if n 1: return n return fib(n-1) fib(n-2) # 注意这里面有大量重复计算fib(3)被算了无数次这就是DP优化的切入点。案例二经典分治 —— 归并排序Merge Sort思想将数组分成两半分别排序再合并。完美体现分解-解决-合并三步曲。pythondef merge_sort(arr): # 1. 终止条件数组只剩一个元素天然有序 if len(arr) 1: return arr # 2. 分解Divide找到中点一分为二 mid len(arr) // 2 left arr[:mid] right arr[mid:] # 3. 解决Conquer递归排序左边和右边 left_sorted merge_sort(left) right_sorted merge_sort(right) # 4. 合并Combine将两个有序数组合并成一个 return merge(left_sorted, right_sorted) def merge(left, right): result [] i j 0 # 双指针合并 while i len(left) and j len(right): if left[i] right[j]: result.append(left[i]) i 1 else: result.append(right[j]) j 1 # 把剩余元素追加进去 result.extend(left[i:]) result.extend(right[j:]) return result # 时间复杂度O(n log n)空间复杂度O(n)案例三经典分治 —— 快速排序Quick Sort思想选择一个基准pivot把小于它的放左边大于它的放右边然后递归处理左右两边。这是分治的另一种形态分解时做了大部分工作合并时无需操作。pythondef quick_sort(arr, left, right): # 终止条件区间内只有一个或零个元素 if left right: return # 分区操作返回基准元素的最终位置 pivot_index partition(arr, left, right) # 递归解决左边和右边基准已经就位不参与递归 quick_sort(arr, left, pivot_index - 1) quick_sort(arr, pivot_index 1, right) def partition(arr, left, right): # 选取最右边的元素作为基准 pivot arr[right] # i 指向小于基准的区域的末尾 i left - 1 for j in range(left, right): # 如果当前元素小于等于基准把它交换到小于区域 if arr[j] pivot: i 1 arr[i], arr[j] arr[j], arr[i] # 最后把基准放到正确位置 arr[i1], arr[right] arr[right], arr[i1] return i 1 # 时间复杂度平均 O(n log n)最差 O(n^2)当数组完全有序时案例四经典分治 —— 最大子数组和力扣53题问题找出数组中连续子数组的最大和。分治思路最大子数组要么全在左半边要么全在右半边要么跨越中点。跨越中点的情况需要从中点向左右扩展计算。pythondef maxSubArray(nums): # 封装一个递归函数处理区间 [l, r] def divide_and_conquer(l, r): # 终止条件只有一个元素 if l r: return nums[l] # 分解 mid (l r) // 2 # 解决左边最大和右边最大和 left_max divide_and_conquer(l, mid) right_max divide_and_conquer(mid 1, r) # 合并计算跨中点的最大子数组和 # 从中点向左扩展找最大和 left_cross_max float(-inf) temp_sum 0 for i in range(mid, l - 1, -1): temp_sum nums[i] left_cross_max max(left_cross_max, temp_sum) # 从中点向右扩展找最大和 right_cross_max float(-inf) temp_sum 0 for i in range(mid 1, r 1): temp_sum nums[i] right_cross_max max(right_cross_max, temp_sum) cross_max left_cross_max right_cross_max # 返回三者最大值 return max(left_max, right_max, cross_max) return divide_and_conquer(0, len(nums) - 1)第三部分递归与分治的进阶技巧纯文字干货1. 递归的复杂度分析主定理Master Theorem分治算法的复杂度通常用主定理计算。对于递推式T(n) aT(n/b) O(n^d)若log_b(a) d则复杂度为O(n^log_b(a))如归并排序a2,b2,d1, log_2(2)1, 等于d所以是 O(n log n)。若log_b(a) d则复杂度为O(n^d log n)。若log_b(a) d则复杂度为O(n^d)。2. 递归的优化策略记忆化搜索Memoization如果在递归过程中发现子问题有重叠如斐波那契可以用一个字典/数组把计算过的结果存起来。这就是递归版的DP尾递归优化让递归调用成为函数体中的最后一条语句且不涉及额外计算。某些编译器能将其优化成循环避免栈溢出。例如python# 普通递归阶乘非尾递归因为还要乘以n def fact(n): return 1 if n1 else n * fact(n-1) # 尾递归阶乘把结果作为参数传递 def fact_tail(n, acc1): return acc if n1 else fact_tail(n-1, acc*n)3. 递归 vs 分治 vs 动态规划终极对比维度递归分治动态规划本质函数自调用算法设计思想带记忆的暴力枚举实现方式调用自身递归通常递归记忆化或迭代子问题特点无限制相互独立不重叠相互重叠有依赖经典案例二叉树遍历归并排序、快速排序背包问题、编辑距离4. 递归解决树形问题的万能模板处理二叉树时递归是天然武器pythondef dfs(node): if not node: # 空节点终止 return # 前序遍历操作 dfs(node.left) # 中序遍历操作 dfs(node.right) # 后序遍历操作分治因为需要先拿到左右子树结果再处理给你的通透理解如果把递归与分治比作管理一家大公司递归就是CEO把任务拆给VPVP拆给总监总监拆给经理经理自己干完活再把结果层层上报。分治就是CEO决定把全国业务分成几个大区各大区独立运营最后把财报汇总合并。大区之间互不干涉子问题独立。