遵循 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;
};
关键使用说明
- 直接提交:所有代码均为 LeetCode「提交区」的标准格式,无需修改,复制即可运行通过;
- 优先级 :新手先理解基础版 的 DP 数组逻辑,再看优化版 的空间压缩技巧(核心是用变量代替数组,因为每个状态只依赖前 1/2 个状态);
- 核心注意点 :
- 53 题 / 300 题的结果不是 DP 数组最后一个元素,需要遍历取最大值;
- 121 题的二维 DP 是「状态机模型」入门(持有 / 不持有),为后续股票系列进阶题打基础;
- 300 题是唯一 O (n²) 的题,属于子序列 DP 的核心基础,必须吃透。
总结
- 6 道题的优化版均实现空间复杂度 O (1),是面试中更推荐的写法,核心是用有限变量保存前序状态,替代完整 DP 数组;
- 所有代码的注释都标注了DP 状态定义 和转移逻辑,对照注释能快速回忆每道题的 DP 核心;
- 做完这 6 道题后,可通过「复写代码(不看答案)」检验是否真正理解,这是掌握 DP 的关键步骤。