终结你的递归恐惧症

31 阅读4分钟

处理递归问题时,我们常会陷入“精神内耗”:“这个函数调那个函数,那个函数又调……这到底在发生什么啊?!”

别担心,这不是你一个人的困惑。递归的思维模式与人类的线性思考习惯天然不符,它更像是计算机的处理方式。因此,想要高效解决递归问题,关键在于改变心态,避免陷入层层追踪的思维陷阱。

简单来说,就是——别管它

不要试图追踪每一级调用,你只需要专注于解决当前这一层的问题。

把递归调用看作你的“外包团队”,它们会帮你完美解决子问题。

换个角度,你不再是亲力亲为、层层跟进的苦力,而是运筹帷幄、善于“甩锅”的老板。你的口头禅应该是:

“我不管,递归自己会搞定,我只管这一层!” 😎

那么,作为“老板”,你这一层需要解决什么呢?其实就三件事:

  1. 整个递归的终止条件:递归什么时候应该停止?

  2. 当前这一级需要做什么:这一层需要完成什么任务?

  3. 应该返回给上一级的返回值:如何将结果传递给上一级?

正是基于这三点,我们有了解决递归问题的三步曲

  • 找终止条件:明确递归何时结束。

  • 找返回值:确定需要向上层传递哪些信息。

  • 本级做什么:在当前这一级,处理好自己的任务,并利用递归解决子问题。

理解并熟练运用这三步,你就能轻松驾驭绝大多数递归算法题。下面我们通过三个具体的 LeetCode 例子,来看看如何将这个模板应用到实战中。


LeetCode 104: 二叉树的最大深度 🌳

题目: 计算二叉树的最大深度。

代码:

function maxDepth(root: TreeNode | null): number {
  if (!root) return 0;
  return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}

应用三步曲:

  1. 找终止条件:当 root 为空时,表示到达叶子节点的下一层,没有深度可言,所以返回 0。这是递归的出口。

  2. 找返回值:这一层需要返回以当前节点为根的子树的最大深度。

  3. 本级做什么

    • 首先,相信递归。我们让 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;
}

应用三步曲:

  1. 找终止条件:当链表为空或只有一个节点时,无法进行两两交换,直接返回 head。这是递归的出口。

  2. 找返回值:这一层需要返回交换后的链表头。

  3. 本级做什么

    • 首先,相信递归。我们让 swapPairs(newHead.next) 这个“外包团队”去处理剩余的链表(从第三个节点开始),并把交换好的新链表头返回给我们。

    • 然后,搞定当前。我们把眼前这一对节点 headnewHead 调换位置。具体操作是:把 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)

应用三步曲:

  1. 找终止条件:当 root 为空时,空树被认为是平衡的,返回 true

  2. 找返回值:这一层需要返回一个布尔值,表示以当前节点为根的子树是否平衡。

  3. 本级做什么

    • 首先,相信递归。我们让 isBalanced(root.left)isBalanced(root.right) 这两个“下属”分别去判断左右子树是否平衡。

    • 同时,我们还需要搞定当前。这一步是利用辅助函数 maxDepth 来计算左右子树的高度,并检查它们的高度差是否不超过 1。

    • 最后,将所有条件用逻辑与(&&)连接起来,只有当左右子树都平衡,且它们的高度差也满足条件时,才返回 true

通过这三个例子,你会发现,无论是简单还是复杂的递归问题,只要你遵循这套“老板”三步曲,就能轻松理清思路,避免陷入层层追踪的困境。

下次当你写递归时,别再试图用“显微镜式分析”去追溯每一层的执行流程。请切换到CEO 式心态

“下属能搞定的事,我绝不亲自动手。” 🫡