处理递归问题时,我们常会陷入“精神内耗”:“这个函数调那个函数,那个函数又调……这到底在发生什么啊?!”
别担心,这不是你一个人的困惑。递归的思维模式与人类的线性思考习惯天然不符,它更像是计算机的处理方式。因此,想要高效解决递归问题,关键在于改变心态,避免陷入层层追踪的思维陷阱。
简单来说,就是——别管它。
不要试图追踪每一级调用,你只需要专注于解决当前这一层的问题。
把递归调用看作你的“外包团队”,它们会帮你完美解决子问题。
换个角度,你不再是亲力亲为、层层跟进的苦力,而是运筹帷幄、善于“甩锅”的老板。你的口头禅应该是:
“我不管,递归自己会搞定,我只管这一层!” 😎
那么,作为“老板”,你这一层需要解决什么呢?其实就三件事:
-
整个递归的终止条件:递归什么时候应该停止?
-
当前这一级需要做什么:这一层需要完成什么任务?
-
应该返回给上一级的返回值:如何将结果传递给上一级?
正是基于这三点,我们有了解决递归问题的三步曲:
-
找终止条件:明确递归何时结束。
-
找返回值:确定需要向上层传递哪些信息。
-
本级做什么:在当前这一级,处理好自己的任务,并利用递归解决子问题。
理解并熟练运用这三步,你就能轻松驾驭绝大多数递归算法题。下面我们通过三个具体的 LeetCode 例子,来看看如何将这个模板应用到实战中。
LeetCode 104: 二叉树的最大深度 🌳
题目: 计算二叉树的最大深度。
代码:
function maxDepth(root: TreeNode | null): number {
if (!root) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
应用三步曲:
-
找终止条件:当
root为空时,表示到达叶子节点的下一层,没有深度可言,所以返回0。这是递归的出口。 -
找返回值:这一层需要返回以当前节点为根的子树的最大深度。
-
本级做什么:
-
首先,相信递归。我们让
maxDepth(root.left)帮我们算出左子树的最大深度,让maxDepth(root.right)帮我们算出右子树的最大深度。 -
然后,搞定当前。我们取这两个“下属”返回结果中的最大值,再加上当前节点自身的深度(即
1),就是以当前节点为根的子树的最大深度。最后将这个值返回。
-
LeetCode 24: 两两交换链表中的节点 🔄
题目: 两两交换链表中的相邻节点,并返回新链表的头节点。
代码:
function swapPairs(head: ListNode | null): ListNode | null {
if (!head || !head.next) return head;
let newHead = head.next;
head.next = swapPairs(newHead.next);
newHead.next = head;
return newHead;
}
应用三步曲:
-
找终止条件:当链表为空或只有一个节点时,无法进行两两交换,直接返回
head。这是递归的出口。 -
找返回值:这一层需要返回交换后的链表头。
-
本级做什么:
-
首先,相信递归。我们让
swapPairs(newHead.next)这个“外包团队”去处理剩余的链表(从第三个节点开始),并把交换好的新链表头返回给我们。 -
然后,搞定当前。我们把眼前这一对节点
head和newHead调换位置。具体操作是:把head.next指向“外包团队”返回的新链表头,然后把newHead.next指向head,完成当前这对节点的交换。 -
最后,返回交换后的新链表头
newHead。
-
LeetCode 110: 平衡二叉树 ⚖️
题目: 判断一棵二叉树是否为平衡二叉树(左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是平衡二叉树)。
代码:
function isBalanced(root: TreeNode | null): boolean {
if (!root) return true;
const left = maxDepth(root.left);
const right = maxDepth(root.right);
return (
Math.abs(left - right) <= 1 &&
isBalanced(root.left) &&
isBalanced(root.right)
);
}
// 辅助函数 maxDepth(同 LeetCode 104)
应用三步曲:
-
找终止条件:当
root为空时,空树被认为是平衡的,返回true。 -
找返回值:这一层需要返回一个布尔值,表示以当前节点为根的子树是否平衡。
-
本级做什么:
-
首先,相信递归。我们让
isBalanced(root.left)和isBalanced(root.right)这两个“下属”分别去判断左右子树是否平衡。 -
同时,我们还需要搞定当前。这一步是利用辅助函数
maxDepth来计算左右子树的高度,并检查它们的高度差是否不超过 1。 -
最后,将所有条件用逻辑与(
&&)连接起来,只有当左右子树都平衡,且它们的高度差也满足条件时,才返回true。
-
通过这三个例子,你会发现,无论是简单还是复杂的递归问题,只要你遵循这套“老板”三步曲,就能轻松理清思路,避免陷入层层追踪的困境。
下次当你写递归时,别再试图用“显微镜式分析”去追溯每一层的执行流程。请切换到CEO 式心态:
“下属能搞定的事,我绝不亲自动手。” 🫡