
每次力扣周赛结束评论区总能看到类似的声音题也刷了几百道热门题解翻来覆去看了好几遍可一到周赛现场面对四道新题还是容易卡住。真正的问题常常不是“算法不会”而是“看不出题目在考什么”。你明明会归并排序、会递归、会动态规划但看到一道新题时无法快速判断应该用哪套武器。在众多算法模型里分治是周赛出现频率极高、又最容易被误解成“不就是递归嘛”的一类思想。这篇文章以“力扣周赛 514”为引子但不是去逐题背诵参考答案。题目会变周赛题号会变但落在题目底层的分治框架不会变。本文要讲的是如何用分治这条主线去拆解周赛题如何识别一道题该不该用分治以及在归并排序、表达式计算、区间统计等经典场景里分治代码到底怎么写才不容易出错。读完你会得到一套可复用的分析方法而不是四道题的临时记忆。如果你正在准备面试或者刷了一段时间力扣但周赛成绩波动很大这篇文章比较适合你。我会先用较小篇幅把分治的原理和适用边界讲清楚然后用三道典型题目逐步拆解代码最后给出周赛现场可以用的识别信号和刷题顺序建议。1. 周赛题为什么“看得懂答案自己却想不到”很多开发者刷力扣有个共同体验看完题解觉得“原来这么简单”但合上题解遇到同类新题还是不会。这不是记忆力问题而是大脑里缺少“结构识别”这个环节。题解展示的是最终解法但不会告诉你当初是怎么从题目文字联想到这个模型的周赛里每道题只有大约十五到二十分钟的思考时间你不可能靠穷举所有算法标签去匹配题目。你需要的是几条清晰的判断路径这道题的规模提示了什么子问题和原问题是否同构合并答案时是否需要跨左右区间统计这些判断路径恰恰是分治训练能带来的核心能力。另一个常见误解是把分治等同于递归。递归是一种代码写法分治是一种问题拆解策略。一道题用递归实现不代表它用了分治。比如树的遍历也常常写成递归但多数遍历只是沿着一条路径走进子节点并没有“把答案合并回去”的过程。分治的关键不是递归调用而是三个步骤同时成立分解、解决、合并。所以周赛失分的真正原因往往不是你不会某个算法而是你缺少一个“先判断结构再匹配算法”的思考框架。分治是建立这个框架最好的起点之一因为它的结构特征非常明显识别成本低收益却很高。2. 分治法核心原理拆解、解决、合并2.1 分治的三个步骤分治法全称“分而治之”核心思想是把一个复杂问题拆成若干个规模更小、结构相同的子问题分别解决后再把结果合并成原问题的答案。具体来说分为三步。第一步是分解。把原问题划分成两个或更多个规模更小的子问题子问题与原问题形式相同只是规模缩小。第二步是解决。如果子问题已经小到可以直接求解就直接返回结果否则继续递归分解。第三步是合并。把子问题的解按某种规则组合起来得到原问题的解。这个描述看起来简单但周赛里真正拉开差距的地方往往在“合并”这一步。很多人在分解和递归部分写得很顺一到合并就发现要么统计重复要么顺序不对要么复杂度退化。我在后面讲逆序对和右侧更小元素时会重点展开合并这一步的易错点。2.2 分治与递归的区别递归是实现手段分治是算法思想。你可以用递归实现分治也可以用栈模拟递归但分治的本质是“问题可以拆成同构子问题并且能合并”。二叉树的前序遍历写成递归但并没有合并过程所以它只是递归不是分治。分治通常满足两个特征。第一子问题独立彼此不重叠这是和动态规划的关键区别。第二合并步骤有实际计算量不能只是简单返回子问题结果。如果一个递归函数只是把子问题结果相加或取最大值它更像是树形递推虽然也可以归入广义分治但面试和竞赛中提到的分治通常默认存在一个非平凡的合并过程。2.3 分治与动态规划、BFS 的适用边界周赛题往往需要你判断该用哪种模型这里给出一个非常粗略但实用的区分表。模型子问题关系典型场景复杂度特征分治子问题独立合并时有计算逆序对、表达式求值、最近点对、区间统计O(n log n) 常见动态规划子问题重叠依赖状态转移背包、最长递增子序列、编辑距离O(n²) 或可优化BFS图上的层级扩散腐烂的橘子、最短路径、连通块O(V E)回溯枚举所有选择路径组合、排列、子集通常指数级比如力扣热题里有一道“腐烂的橘子”它考的是 BFS因为它解决的是图上的扩散时间问题。如果你把它想成分治就很容易绕进错误方向。反过来一道题如果让你统计“每个元素右边比它小的元素个数”这类跨区间的顺序统计问题分治和树状数组都是合理方向而 BFS 显然不适用。3. 分治思维在周赛中的高频场景分治在周赛里不是冷门算法它的出现频率相当高只是有时候包装得比较深不一定直接叫“分治标签”。以下是三个最常见的出题场景。3.1 归并排序类跨区间顺序统计这是分治在算法题里最经典的形态。给定一个数组要求统计满足某种顺序关系的元素对数量比如逆序对、右侧更小元素、区间和等。做法是利用归并排序的“分治 合并”过程在合并左右两个有序区间时顺便统计跨区间的答案。这种题目的难点在于理解一个事实左边区间和右边区间各自内部的答案在递归过程中已经算完了合并时只需要统计“一个元素在左一个元素在右”的那部分答案。这也是分治里“合并步骤承担核心计算”的典型体现。3.2 表达式求值类按运算符切分子问题给定一个字符串表达式要求所有可能的加括号方式对应的结果。这类题把表达式按某个运算符分成左右两半左边子表达式和右边子表达式各自独立求值再把左右结果按运算符组合。因为运算符的切分位置不同会产生不同的计算顺序所以结果是多个值的集合。这种题型最好的训练价值在于它让你意识到分治的“分解”不一定是等长切分也可以按题目语义来切。子问题仍然同构只是规模划分方式由问题本身决定。3.3 树与区间类二叉树递归与线段树像“二叉树的最大深度”“验证二叉搜索树”这类题目本质上是树形结构下的分治左子树和右子树是独立子问题返回值在父节点合并。线段树则是分治思想在区间维护上的典型工程实践把区间递归分成两半再在节点上合并左右孩子的信息。这类场景常被当作“递归题”来刷但从分治的视角看它们的结构更清晰左子问题、右子问题、合并规则。4. 分治题的四个识别信号在周赛现场你不需要逐个算法去试。下面四个信号如果命中三个以上就可以优先考虑分治思路。4.1 问题可以切成同构子问题如果题目中的数组、字符串、区间可以一分为二每一半仍然面对同样性质的问题那这就是分治的首要信号。例如“为运算表达式设计优先级”把表达式按运算符切开左右两边仍然是表达式求值问题。4.2 合并时需要跨左右区间的额外计算这是分治题和普通递归题最重要的区别。如果答案等于“左子问题答案 右子问题答案”就结束那大概率不需要分治但如果合并时还需要统计从左跨越到右的那部分贡献比如逆序对里“左边的数大于右边的数”那分治几乎是必选项。4.3 子问题之间相互独立没有重叠如果子问题之间存在大量重叠比如同一个子区间被重复计算那动态规划或记忆化搜索通常比分治更合适。分治假设左右子问题互不影响合并时只考虑它们之间的交互。4.4 数据规模提示 O(n log n)当 n 在 10^5 甚至 10^6 级别O(n²) 会超时而题目又要求统计配对关系或顺序关系时O(n log n) 的分治方案就很有竞争力。力扣周赛的中等题和困难题经常落在 O(n log n) 这个复杂度档位归并式分治是达成这个复杂度的常用手段之一。5. 例题一数组中的逆序对归并式分治5.1 题目与思路题目对应力扣“剑指 Offer 51. 数组中的逆序对”。在数组中的两个数字如果前面一个数字大于后面的数字则这两个数字组成一个逆序对。输入一个数组求逆序对总数。如果暴力枚举所有数对复杂度是 O(n²)在数据规模稍大的情况下会超时。更优的做法是在归并排序的过程中统计逆序对。归并时左右两个区间都已经有序。当左边区间当前元素大于右边区间当前元素时说明左边区间从当前元素到末尾的所有元素都大于右边这个元素可以一次统计出多个逆序对。这正是“合并步骤承担核心计算”的体现。5.2 代码实现class Solution: def reversePairs(self, nums: List[int]) - int: def merge_sort(nums, tmp, left, right): if right - left 1: return 0 mid (left right) // 2 count merge_sort(nums, tmp, left, mid) merge_sort(nums, tmp, mid, right) i, j, k left, mid, left while i mid and j right: if nums[i] nums[j]: tmp[k] nums[i] i 1 else: tmp[k] nums[j] j 1 count mid - i k 1 while i mid: tmp[k] nums[i] i 1 k 1 while j right: tmp[k] nums[j] j 1 k 1 nums[left:right] tmp[left:right] return count n len(nums) tmp [0] * n return merge_sort(nums, tmp, 0, n)这段代码的关键逻辑有三处。一是递归边界right - left 1表示区间里最多一个元素时直接返回 0。二是合并时使用count mid - i这是逆序对计数最核心的一行。当nums[j]比nums[i]小时说明左区间从 i 到 mid-1 的所有元素都比nums[j]大这些元素都和当前nums[j]构成逆序对。注意这里必须使用判断否则相等的元素也会被错误计入。三是在合并完成后把临时数组拷回原数组保证递归返回上层时区间有序。5.3 运行验证在力扣剑指 Offer 51 的代码区粘贴上述代码点击执行。测试用例[7,5,6,4]应返回 5。你可以手算验证7 和 5、6、4 构成 3 对5 和 4 构成 1 对6 和 4 构成 1 对总共 5 对。6. 例题二为运算表达式设计优先级表达式分治6.1 题目与思路题目对应力扣 241. 为运算表达式设计优先级。给定一个含有数字和运算符 - *的字符串表达式要求所有不同加括号方式可能得到的不同结果。这题是分治思想里“分解方式由问题语义决定”的极好例子。对每个运算符都可以把它当作最后的切分点运算符左边是一个子表达式右边是另一个子表达式。左右两边分别递归求解得到两个结果列表然后按照当前运算符把左右所有组合都计算出来。由于运算符切分位置不同会产生多个结果所以返回的是列表。很多初学者会困惑为什么一个表达式会有多个结果因为加括号的顺序不同运算的先后顺序就不同。实际上每一种加括号方式都对应一个特定的运算符切分位置组合。分治递归天然覆盖了所有切分可能。6.2 代码实现from typing import List class Solution: def diffWaysToCompute(self, expression: str) - List[int]: if expression.isdigit(): return [int(expression)] res [] for i, ch in enumerate(expression): if ch in -*: left self.diffWaysToCompute(expression[:i]) right self.diffWaysToCompute(expression[i1:]) for l in left: for r in right: if ch : res.append(l r) elif ch -: res.append(l - r) else: res.append(l * r) return res这段代码的递归边界是expression.isdigit()说明整段字符串是纯数字时直接转换成整数并返回。枚举每个运算符时当前运算符被当作最后计算的运算符左侧子表达式和右侧子表达式都递归求解。两个结果列表做笛卡尔积按运算符组合出所有结果。这题容易理解错的地方是不要试图模拟加括号的过程。加括号的结果等价于“选择不同的运算符作为最后执行”所以代码里只需要枚举切分点。6.3 运行验证输入2-1-1预期输出[0,2]。分析一下第一种加括号方式是(2-1)-1 0第二种是2-(1-1) 2。输入2*3-4*5预期输出[-34,-14,-10,-10,10]。可以把代码粘贴到力扣 241 直接验证。7. 例题三计算右侧小于当前元素的个数索引归并7.1 题目与思路题目对应力扣 315. 计算右侧小于当前元素的个数。给定一个整数数组返回一个新数组其中每个位置的值等于原数组中该位置右边比它小的元素个数。这和逆序对问题非常像但要求输出的是每个位置各自的右侧较小值数量而不是总数。处理方式是归并时维护原始索引因为我们需要把统计结果写回对应位置。一个容易出错的细节是当左右区间的当前元素相等时应该先把左边元素放入临时数组因为“右侧小于当前元素”要求严格小于相等不能计入。如果你先放右边元素相等元素就会被错误统计。这个题也可以用树状数组或线段树实现但归并排序的写法更贴近分治主线先递归处理左右区间再在合并时统计跨区间贡献。7.2 代码实现from typing import List class Solution: def countSmaller(self, nums: List[int]) - List[int]: n len(nums) ans [0] * n indices list(range(n)) def merge_sort(i, j): if j - i 1: return mid (i j) // 2 merge_sort(i, mid) merge_sort(mid, j) tmp [] left, right i, mid while left mid and right j: if nums[indices[left]] nums[indices[right]]: tmp.append(indices[left]) ans[indices[left]] right - mid left 1 else: tmp.append(indices[right]) right 1 while left mid: tmp.append(indices[left]) ans[indices[left]] right - mid left 1 while right j: tmp.append(indices[right]) right 1 indices[i:j] tmp merge_sort(0, n) return ans这段代码里indices数组保存的是元素在原数组中的位置排序时比较的是nums[indices[left]]和nums[indices[right]]但写入临时数组时记录的是索引。当右边元素入列时说明左边剩余元素都大于当前右边元素但这里不需要在else分支累加因为每个左边元素会在下次入列时统一通过ans[indices[left]] right - mid补上右边的贡献。这个技巧是本题的核心把“跨区间贡献”延迟到左侧元素真正被放入临时数组时计算可以避免重复统计。7.3 运行验证输入[5,2,6,1]预期输出[2,1,1,0]。解释5 的右边比它小的有 2、1 共两个2 的右边比它小的只有 16 的右边比它小的只有 11 的右边没有元素。可以粘贴到力扣 315 运行验证。8. 分治代码常见错误与排查清单分治代码看起来短但出错的点非常集中。我整理了一份排查清单适合你在周赛调试时按顺序检查。问题现象可能原因排查方式解决方案递归死循环或栈溢出递归边界写错区间缩不小在递归函数开头打印 left 和 right确认right - left 1时返回mid 左右划分都严格缩小规模排序结果不对合并时临时数组没有拷回原数组单步调试查看合并后原数组在合并函数末尾执行nums[left:right] tmp[left:right]逆序对/右侧较小值计数偏多相等元素处理错误用包含重复元素的用例测试左区间元素小于等于右区间元素时先放左边保证严格小于结果数组位置错乱归并的是值而不是索引检查写入结果数组时用的是哪个下标需要记录索引的题目用索引数组参与归并统计跨区间贡献时重复在左右递归中也统计了跨区间部分明确三个部分的答案边界左内部、右内部、跨左右三者互不重叠调试分治代码时最 recommended 的做法是先用非常小的数组手动模拟再逐步放大。比如逆序对这道题可以先在纸上写[3,1,2]的完整递归过程确认每个递归层级的 count 叠加逻辑。代码行为一旦和手算结果对应上问题通常就能定位到合并步骤。9. 力扣刷题顺序与分治练习建议9.1 分治刷题顺序如果你想把分治这块练扎实建议不要按题号刷而是按“递进难度”刷。第一阶段先掌握归并排序的写法把排序本身写对第二阶段刷逆序对、右侧更小元素这类“归并 统计”的题目第三阶段做表达式求值和区间类分治第四阶段再挑战需要分治和其他数据结构结合的难题。力扣热题 100 和分治标签下有一些经典题目但我更推荐先以本文三道题为主线。它们分别覆盖了“数组归并分治”“表达式切分分治”“索引归并分治”三种不同形态。把这三种形态吃透再去看其他分治标签题目会发现基本都是这些形态的变体。9.2 周赛现场如何应用分治周赛现场时间紧张你可以建立一个简单的判断流程。读完题先看数据规模如果 n 在 10^5 往上O(n^2) 基本可以排除再想题目是否要求统计配对关系、顺序关系或组合结果然后问自己这个问题能否切成左右两个同构子问题如果答案是肯定的合并时是否还需要跨区间计算两个问题都成立优先往分治方向写。这里要提醒一点分治不是唯一解。比如逆序对除了归并排序也可以用树状数组右侧更小元素也可以用线段树。如果你对树状数组更熟用你最有把握的方案即可。周赛比的不是“用了哪个算法”而是“在有限时间内稳定拿到分数”。分治的优点是思维路径清晰调试成本相对可控。9.3 分治思维在面试与工程中的价值很多人刷力扣是为了进大厂面试。面试官考察的往往不是你能不能背出某道题的答案而是你面对新问题时的分析路径。分治思维在系统设计和工程排障中同样适用把一个复杂的线上问题拆成独立模块逐个定位再合并结果这是非常通用的排查思路。从这个角度看刷力扣的真正意义不是题海战术而是通过典型题目训练“结构化拆解问题”的习惯。分治是性价比很高的训练科目因为它题型规律性强代码量适中又能带动你对递归、排序、复杂度分析等多块知识的理解。9.4 刷题路线之外的三个建议第一每道题做完后写一行注释记录“这道题的识别信号是什么”。比如逆序对的识别信号是“统计前面比后面大的数对”右侧更小元素的识别信号是“每个位置统计右侧较小值”。时间久了这些信号会形成肌肉记忆。第二不要只追求通过尝试把一道题用两种方法实现。比如右侧更小元素先用归并排序写一遍再用树状数组写一遍。两种解法对比你对分治和数据结构各自优劣的理解会深很多。第三周赛结束后及时复盘。如果某道题没做出来先别急着看题解给自己十分钟尝试用“能否二分能否分治能否用有序结构维护”这三个问题引导思考。很多周赛题的突破口往往就在这三问中的某一个。10. 结语周赛复盘的正确姿势回到开头的问题为什么看了很多题解周赛还是容易卡住因为题解给的是“答案”而周赛考察的是“识别 匹配 实现”的完整链条。分治只是算法世界里的一个切面但它是练习结构识别能力很好的起点。如果你刚打完周赛 514建议用本文的思路做一次复盘每组题目先不急着看题解而是给每道题标注“是否适合分治”“识别信号是什么”“合并步骤难在哪里”。这个动作看起来简单长期坚持的效果比多刷几十道题更明显。分治思想的价值不止于竞赛。工作中处理大数据量任务、拆分复杂模块、排查线上问题本质上都离不开“分解、解决、合并”的思维框架。把这套框架练成默认思考方式你收获的将不只是周赛分数。