爬楼梯?不,你在攀登算法的珠穆朗玛峰!

爬楼梯?不,你在攀登算法的珠穆朗玛峰!

一道看似"幼儿园难度"的面试题:
"每次能爬1阶或2阶,问爬到第n阶有几种方法?"

却暗藏递归、动态规划、记忆化、空间优化四大内功心法------

它不是考你会不会算数,而是看你有没有系统性思维


🧗‍♂️ 初见:天真递归 ------ "我能行!"(然后爆栈了)

最直觉的解法?当然是递归!

scss 复制代码
function climbStairs(n) {
  if (n === 1) return 1;
  if (n === 2) return 2;
  return climbStairs(n - 1) + climbStairs(n - 2);
}

逻辑完美

  • 要到第 n 阶,要么从 n-1 上来,要么从 n-2 跳上来
  • 所以 f(n) = f(n-1) + f(n-2) ------ 这不就是斐波那契?

但问题来了:

当你调用 climbStairs(45),电脑会疯狂重复计算:

  • f(43) 被算两次
  • f(42) 被算三次
  • ......
    时间复杂度 O(2ⁿ) ------ 指数爆炸!

就像你让一个人背完整本字典来查一个词------可行,但荒谬。


🧠 进阶:记忆化递归 ------ "我记住了!"

既然重复计算是罪魁祸首,那就把算过的答案存起来

ini 复制代码
const memo = {};
function climbStairs(n) {
  if (n === 1) return 1;
  if (n === 2) return 2;
  if (memo[n]) return memo[n]; // ← 关键:查缓存!
  memo[n] = climbStairs(n - 1) + climbStairs(n - 2);
  return memo[n];
}

效果 :每个 f(k) 只算一次 → 时间复杂度 O(n)

思想空间换时间 ,典型的自顶向下动态规划(Top-down DP)

但有个小瑕疵:memo 是全局变量,容易被污染。


🔒 优雅封装:闭包 + 记忆化 ------ "我的缓存,外人别碰!"

闭包memo 私有化,打造一个"智能函数":

ini 复制代码
const climbStairs = (function() {
  const memo = {}; // ← 外部无法访问!
  return function climb(n) {
    if (n === 1) return 1;
    if (n === 2) return 2;
    if (memo[n]) return memo[n];
    memo[n] = climb(n - 1) + climb(n - 2);
    return memo[n];
  };
})();

优势

  • 多次调用共享缓存(越用越快)
  • 状态私有,安全可靠
  • 接口干净:用户只需 climbStairs(n)

这不是函数,这是一个会学习、有记忆、懂封装的智能体


🚀 终极优化:自底向上 + 滚动变量 ------ "我不需要递归!"

其实,我们根本不需要递归,也不需要存所有中间值!

观察规律:

scss 复制代码
f(1) = 1
f(2) = 2
f(3) = f(2) + f(1) = 3
f(4) = f(3) + f(2) = 5
...

只需要两个变量,就能滚动计算出结果:

ini 复制代码
function climbStairs(n) {
  if (n === 1) return 1;
  if (n === 2) return 2;
  
  let prevPrev = 1; // f(i-2)
  let prev = 2;     // f(i-1)
  
  for (let i = 3; i <= n; i++) {
    const current = prev + prevPrev; // f(i)
    prevPrev = prev;   // 滚动窗口
    prev = current;
  }
  
  return prev;
}

时间复杂度 :O(n)

空间复杂度 :O(1) ------ 极致优化!

无递归:避免调用栈溢出(n 很大时更安全)

这就是自底向上的动态规划(Bottom-up DP) ------ 从已知出发,一步步推导未知。


📊 四种解法对比

方法 时间复杂度 空间复杂度 是否递归 适用场景
暴力递归 O(2ⁿ) O(n) 教学演示
记忆化递归 O(n) O(n) 中等规模,逻辑清晰
闭包记忆化 O(n) O(n) 需要缓存复用
滚动变量 O(n) O(1) 生产环境首选

💡 面试加分回答

当面试官问这道题,你可以这样说:

"我会根据场景选择方案:

  • 如果是教学或快速原型,用记忆化递归,逻辑直观;
  • 如果是高性能生产环境,用滚动变量的迭代法,O(1) 空间且无栈溢出风险。
    此外,我还会考虑边界情况(如 n ≤ 0)、类型校验,以及是否需要支持'每次可爬1~k阶'的扩展。"

------瞬间从"会写代码"升级到"有工程思维"。


🌟 结语:小题大智慧

"爬楼梯"从来不是一道数学题,而是一面镜子:

  • 它照出你是否理解递归的本质
  • 它检验你是否掌握动态规划的思想
  • 它考验你能否在简洁、性能、可维护性之间做权衡

下次再有人说"这题太简单",你可以微笑回应:

"是啊,简单到能写出四种境界。"

而这,正是优秀工程师和普通 coder 的分水岭。

相关推荐
沐雪架构师6 分钟前
LangChain 1.0 Agent开发实战指南
开发语言·javascript·langchain
2501_9400078918 分钟前
Flutter for OpenHarmony三国杀攻略App实战 - 战绩记录功能实现
开发语言·javascript·flutter
摘星编程22 分钟前
React Native + OpenHarmony:自定义useEllipsis省略号处理
javascript·react native·react.js
2401_8590490832 分钟前
git submodule update --init --recursive无法拉取解决
前端·chrome·git
这是个栗子1 小时前
【Vue代码分析】前端动态路由传参与可选参数标记:实现“添加/查看”模式的灵活路由配置
前端·javascript·vue.js
刘一说1 小时前
Vue 动态路由参数丢失问题详解:为什么 `:id` 拿不到值?
前端·javascript·vue.js
win x1 小时前
JavaSE(基础)高频面试点及 知识点
java·面试·职场和发展
熊猫钓鱼>_>1 小时前
动态网站发布部署核心问题详解
前端·nginx·容器化·网页开发·云服务器·静态部署
方也_arkling1 小时前
elementPlus按需导入配置
前端·javascript·vue.js
编程彩机2 小时前
互联网大厂Java面试:从分布式缓存到消息队列的技术场景解析
java·redis·面试·kafka·消息队列·微服务架构·分布式缓存