分治要求函数内至少两次互不重叠的递归调用,如 mergesort、maxsubarray;单路递归如 factorial 不是分治;需警惕切片拷贝导致栈溢出及 crossing sum 边界错误。

Go 语言里写递归,关键不是“能不能递归”,而是“要不要分治”——只调用一次自身的不算分治,必须拆成两个及以上独立子问题,再合并结果才算。
区分递归和分治:看函数里有没有两个以上递归调用
很多人误以为所有递归都是分治。实际在 Go 中,Factorial、Fibonacci 这类单路递归只是递归,不是分治;而 mergeSort、maxSubArray 才是典型分治——它们在函数体内至少出现两次递归调用,且子问题互不重叠:
-
mergeSort(arr[:mid])和mergeSort(arr[mid:])处理左右两段,无交集 -
maxSubArray(nums[:mid])、maxSubArray(nums[mid:])、maxCrossingSum三者职责分明 - 只写一个
hanoi(n-1, A, C, B)的递归调用,哪怕嵌套深,也不构成分治
避免栈溢出:递归深度和切片传递的隐式拷贝
Go 没有尾递归优化,maxSubArray 这类对大数组递归时容易栈溢出,尤其当 len(nums) > 10^5。真正要注意的不是“递归层数”,而是每次递归是否新建底层数组:
-
nums[:mid]和nums[mid:]是原数组的 slice,共享底层数组,不额外分配内存 - 但若传入的是
append([]int{}, nums...)或显式make新切片,每层都复制数据,空间爆炸 - 实测:100 万元素数组,用原 slice 切分可跑通;用
copy构造新 slice,几层就 panic: stack overflow
合并逻辑最容易写错:crossing sum 的边界处理
maxCrossingSum 是分治里最易出 bug 的部分,错误常出现在索引越界或初始值设置上:
- 左半边应从
mid-1往左扫(i >= 0),不是i > 0—— 否则漏掉nums[0] - 右半边从
mid往右扫(i ),不是 <code>i —— Go 的切片右边界是开区间 - 初始值不能设为
0,得用math.MinInt32,否则全负数组返回 0(错误) - 示例:
nums = []int{-2, -1},正确结果是-1,若 leftSum/rightSum 初始化为 0,会算出 0
什么时候该放弃分治,改用迭代或 DP
分治不是银弹。遇到以下情况,硬套分治反而增加复杂度和风险:
- 子问题不独立:比如
Fibonacci(n)的两个子问题Fibonacci(n-1)和Fibonacci(n-2)共享大量重叠子结构,用分治时间复杂度是O(2^n),远不如 DP 的O(n) - 合并成本过高:如某些树形 DP 需要维护大量状态,递归合并逻辑臃肿,不如 DFS + map 缓存清晰
- 输入规模小但调用频繁:比如日志解析中对百字符字符串做分治分割,函数调用开销可能超过收益
真正需要分治的场景很具体:数组二分归并、树的自底向上合成、大规模整数乘法(Karatsuba)、最大子序和这类具备严格左右+跨中三段结构的问题。别为了“用分治”而用分治。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











