递归思想的思路分享

📅 发布时间:2026/7/9 2:15:45 👁️ 浏览次数:
递归思想的思路分享
文章目录你的感悟非常深刻“考虑太多冗余条件使得函数复杂无法形成递归” —— 这正是很多初学者甚至有经验的开发者在写递归/动态规划时最容易陷入的陷阱。而你提到的 “只考虑最简单情况 一个数量关系用递归四两拨千斤”恰恰是递归与 DP 的核心哲学。下面我们来系统总结如何写出简洁、优雅、有效的递归 一、递归设计的核心原则✅ 原则1只关心“当前一步”做什么其余交给递归不要试图在当前层“把所有事情做完”只需回答两个问题我现在能做什么选择比如选 or 不选硬币每种选择后剩下的问题是什么子问题 递归的魔力在于相信子问题会被正确解决你只需组合结果。例子零钱兑换当前选择用 or 不用 coins[i]子问题不用 → dfs(i-1, c)用 → dfs(i, c - coins[i])因为可重复用组合取最小值 1如果用了不需要判断“能不能整除”、“余数是否为0”等中间细节✅ 原则2边界条件越简单越好最理想的 base case 是if amount 0: return 0if i 0: return inf不要在中间加各种 elif 分支除非绝对必要❌ 冗余条件示例你之前写的if amount % coin 0:return …elif amount % coin ! amount:…这些都是试图提前优化但破坏了递归结构。✅ 正确做法让递归自然走到 c 0 或 c 0由 base case 处理。✅ 原则3状态定义要“刚好足够”状态变量如 i, c应该恰好描述子问题的全部信息不能多导致状态爆炸也不能少无法转移在零钱兑换中i考虑到第几个硬币控制“可用硬币集合”c还需要凑多少钱控制“目标”这两个就刚好足够不多不少。 二、递归构思的实用技巧 技巧1从“暴力递归”开始再加缓存先不考虑效率写出最朴素的递归def dfs©:if c 0: return 0if c 0: return infres inffor coin in coins:res min(res, dfs(c - coin) 1)return res✅ 这个版本逻辑极其清晰然后再加 cache 就变成高效解法。 先写对再优化。不要一开始就想着剪枝、提前终止。 技巧2用“选 or 不选”框架处理组合问题很多 DP 问题都可以套这个模板问题类型 决策 状态转移0-1 背包 选 or 不选物品只能用一次 dfs(i-1, w) vs dfs(i-1, w - weight[i]) value[i]完全背包 选 or 不选物品可用多次 dfs(i-1, w) vs dfs(i, w - weight[i]) value[i]子集/排列 选 or 不选当前元素 dfs(i1, path) vs dfs(i1, path [x])你只需要识别问题属于哪一类套用对应转移即可。 技巧3画一个小例子手动模拟递归树比如 coins[1,2], amount3手动展开dfs(3)├─ 1 dfs(2)│ ├─ 1 dfs(1)│ │ └─ 1 dfs(0) 1 → total3│ └─ 1 dfs(0) 1 → total2└─ 1 dfs(1)└─ … → total2→ min 2通过这个过程你会直观看到状态如何转移从而写出正确代码。 技巧4避免“过程思维”采用“结果思维”❌ 错误思维“我该怎么一步步凑出金额”✅ 正确思维“最少硬币数 1 min(凑出 amount - coin 的最少硬币数)”递归是自顶向下、结果导向的不是过程模拟。 三、心法如何培养“递归直觉”多写暴力递归哪怕超时感受子问题结构刻意练习经典模型斐波那契、爬楼梯、背包、子集、全排列每次写完问自己我的状态定义是否清晰我的 base case 是否覆盖所有终止情况我是否在当前层做了本该由子问题处理的事✅ 回到你的顿悟“正确答案就考虑最简单情况还有使用一个数量关系利用递归产生四两拨千斤的效果”这正是分治思想的体现把大问题拆成小问题小问题和大问题结构相同只需定义好“如何拆”和“如何合”而 min(dfs(i-1, c), dfs(i, c - coins[i]) 1) 就是这个“拆与合”的完美表达。 最后送你一句口诀“状态定义清边界写得简当前做选择其余靠递归。”坚持这样思考你的递归和 DP 能力会飞速提升如果你愿意我可以给你几道“从暴力递归 → 记忆化 → DP”的渐进式练习题