LeetCode 动态规划

遵循 DP 核心思路(状态定义→转移方程→初始条件),代码简洁易懂、带关键注释,直接复制到 LeetCode 就能通过,同时兼顾了基础版(易理解)优化版(空间 O (1),更高效),新手先看基础版理解逻辑,再看优化版掌握空间压缩技巧。

1. LeetCode 70. 爬楼梯(基础版 + 空间优化版)

复制代码
// 基础版:一维DP数组,O(n)空间
var climbStairs = function(n) {
    // 边界处理
    if (n <= 2) return n;
    const dp = new Array(n + 1);
    // 初始条件
    dp[1] = 1;
    dp[2] = 2;
    // 状态转移
    for (let i = 3; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
};

// 优化版:仅用两个变量,O(1)空间(推荐)
var climbStairs = function(n) {
    if (n <= 2) return n;
    let a = 1, b = 2; // 分别代表dp[i-2]、dp[i-1]
    for (let i = 3; i <= n; i++) {
        [a, b] = [b, a + b]; // 解构赋值快速更新
    }
    return b;
};

2. LeetCode 198. 打家劫舍(基础版 + 空间优化版)

复制代码
// 基础版:一维DP数组,O(n)空间
var rob = function(nums) {
    const n = nums.length;
    if (n === 0) return 0;
    if (n === 1) return nums[0];
    const dp = new Array(n);
    // 初始条件:前1个/前2个房子的最大金额
    dp[0] = nums[0];
    dp[1] = Math.max(nums[0], nums[1]);
    // 状态转移:不偷当前/偷当前
    for (let i = 2; i < n; i++) {
        dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
    }
    return dp[n - 1];
};

// 优化版:仅用两个变量,O(1)空间(推荐)
var rob = function(nums) {
    const n = nums.length;
    if (n === 0) return 0;
    if (n === 1) return nums[0];
    let a = nums[0], b = Math.max(nums[0], nums[1]);
    for (let i = 2; i < n; i++) {
        [a, b] = [b, Math.max(b, a + nums[i])];
    }
    return b;
};

3. LeetCode 53. 最大子数组和(基础版 + 空间优化版)

复制代码
// 基础版:一维DP数组,O(n)空间
var maxSubArray = function(nums) {
    const n = nums.length;
    const dp = new Array(n);
    // 初始条件:以第一个元素结尾的最大和就是自身
    dp[0] = nums[0];
    let res = dp[0]; // 结果不是最后一个值,需遍历取最大
    // 状态转移:重新开始子数组 / 延续前一个子数组
    for (let i = 1; i < n; i++) {
        dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
        res = Math.max(res, dp[i]);
    }
    return res;
};

// 优化版:仅用一个变量,O(1)空间(推荐,Kadane算法)
var maxSubArray = function(nums) {
    let curSum = nums[0]; // 代替dp[i],当前结尾的最大和
    let maxSum = nums[0]; // 代替res,全局最大和
    for (let i = 1; i < nums.length; i++) {
        curSum = Math.max(nums[i], curSum + nums[i]);
        maxSum = Math.max(maxSum, curSum);
    }
    return maxSum;
};

4. LeetCode 300. 最长递增子序列(基础 DP 版,O (n²),新手必掌握)

这题的 O (nlogn) 贪心 + 二分优化属于进阶技巧,新手先掌握基础 DP 版理解核心逻辑,后续再学优化版

复制代码
var lengthOfLIS = function(nums) {
    const n = nums.length;
    // 初始条件:每个元素自身是长度为1的子序列
    const dp = new Array(n).fill(1);
    let maxLen = 1; // 结果需遍历取最大
    // 状态转移:遍历j < i,找比nums[i]小的元素,更新dp[i]
    for (let i = 1; i < n; i++) {
        for (let j = 0; j < i; j++) {
            if (nums[j] < nums[i]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
        maxLen = Math.max(maxLen, dp[i]);
    }
    return maxLen;
};

5. LeetCode 746. 使用最小花费爬楼梯(基础版 + 空间优化版)

复制代码
// 基础版:一维DP数组,O(n)空间
var minCostClimbingStairs = function(cost) {
    const n = cost.length;
    // dp[i]:到达第i阶的最小花费,楼顶是第n阶
    const dp = new Array(n + 1).fill(0);
    // 初始条件:第0/1阶无需花费(从这两个位置开始爬)
    dp[0] = 0;
    dp[1] = 0;
    // 状态转移:从i-1阶爬1步 / 从i-2阶爬2步,取最小花费
    for (let i = 2; i <= n; i++) {
        dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
    }
    return dp[n]; // 楼顶是第n阶
};

// 优化版:仅用两个变量,O(1)空间(推荐)
var minCostClimbingStairs = function(cost) {
    const n = cost.length;
    let a = 0, b = 0; // 分别代表dp[i-2]、dp[i-1]
    for (let i = 2; i <= n; i++) {
        let c = Math.min(b + cost[i - 1], a + cost[i - 2]);
        a = b;
        b = c;
    }
    return b;
};

6. LeetCode 121. 买卖股票的最佳时机(二维 DP 版 + 空间优化版)

复制代码
// 二维DP版:易理解,O(n)空间,新手先掌握
// dp[i][0]:第i天不持有股票的最大利润;dp[i][1]:第i天持有股票的最大利润
var maxProfit = function(prices) {
    const n = prices.length;
    const dp = new Array(n).fill(0).map(() => new Array(2).fill(0));
    // 初始条件:第0天不持有利润0,持有利润为 -prices[0](买入花费)
    dp[0][0] = 0;
    dp[0][1] = -prices[0];
    for (let i = 1; i < n; i++) {
        // 不持有:保持不持有 / 前一天持有今天卖出
        dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
        // 持有:保持持有 / 前一天不持有今天买入(只能买一次,所以是 -prices[i])
        dp[i][1] = Math.max(dp[i - 1][1], -prices[i]);
    }
    return dp[n - 1][0]; // 最后一天不持有利润最大
};

// 空间优化版:仅用两个变量,O(1)空间(推荐)
var maxProfit = function(prices) {
    let cash = 0; // 代替dp[i][0],不持有股票
    let hold = -prices[0]; // 代替dp[i][1],持有股票
    for (let i = 1; i < prices.length; i++) {
        // 先更新cash,再更新hold(避免覆盖前一天的状态)
        cash = Math.max(cash, hold + prices[i]);
        hold = Math.max(hold, -prices[i]);
    }
    return cash;
};

关键使用说明

  1. 直接提交:所有代码均为 LeetCode「提交区」的标准格式,无需修改,复制即可运行通过;
  2. 优先级 :新手先理解基础版 的 DP 数组逻辑,再看优化版 的空间压缩技巧(核心是用变量代替数组,因为每个状态只依赖前 1/2 个状态);
  3. 核心注意点
    • 53 题 / 300 题的结果不是 DP 数组最后一个元素,需要遍历取最大值;
    • 121 题的二维 DP 是「状态机模型」入门(持有 / 不持有),为后续股票系列进阶题打基础;
    • 300 题是唯一 O (n²) 的题,属于子序列 DP 的核心基础,必须吃透。

总结

  1. 6 道题的优化版均实现空间复杂度 O (1),是面试中更推荐的写法,核心是用有限变量保存前序状态,替代完整 DP 数组;
  2. 所有代码的注释都标注了DP 状态定义转移逻辑,对照注释能快速回忆每道题的 DP 核心;
  3. 做完这 6 道题后,可通过「复写代码(不看答案)」检验是否真正理解,这是掌握 DP 的关键步骤。
相关推荐
雨季6664 小时前
构建 OpenHarmony 简易文字行数统计器:用字符串分割实现纯文本结构感知
开发语言·前端·javascript·flutter·ui·dart
雨季6664 小时前
Flutter 三端应用实战:OpenHarmony 简易倒序文本查看器开发指南
开发语言·javascript·flutter·ui
小北方城市网4 小时前
Redis 分布式锁高可用实现:从原理到生产级落地
java·前端·javascript·spring boot·redis·分布式·wpf
2401_892000524 小时前
Flutter for OpenHarmony 猫咪管家App实战 - 添加支出实现
前端·javascript·flutter
天马37984 小时前
Canvas 倾斜矩形绘制波浪效果
开发语言·前端·javascript
天天向上10244 小时前
vue3 实现el-table 部分行不让勾选
前端·javascript·vue.js
qx094 小时前
esm模块与commonjs模块相互调用的方法
开发语言·前端·javascript
摘星编程5 小时前
在OpenHarmony上用React Native:SectionList吸顶分组标题
javascript·react native·react.js
Mr Xu_5 小时前
前端实战:基于Element Plus的CustomTable表格组件封装与应用
前端·javascript·vue.js·elementui
摘星编程7 小时前
React Native鸿蒙:ScrollView横向滚动分页实现
javascript·react native·react.js