面试 TOP101 动态规划专题题解汇总Java版(BM62 —— BM82)
8月刷题挑战,多重好礼等你拿
动态规划
题号 | 题目名称 | 核心思路 | 时间复杂度 | 空间复杂度 | 代码亮点 | 牛客原题链接 |
---|---|---|---|---|---|---|
BM62 | 斐波那契数列 | 动态规划(迭代) / 递归 | O(n) / O(2ⁿ) | O(1) / O(n) | 迭代版空间 O(1) 优化 | 🔗 直达 |
BM63 | 跳台阶 | DP:前两项和 | O(n) | O(1) | 与斐波那契同模型 | 🔗 直达 |
BM64 | 最小花费爬楼梯 | DP:min(前两项花费+成本) | O(n) | O(1) | 边界 dp[0]=dp[1]=0 技巧 | 🔗 直达 |
BM65 | 最长公共子序列 | 二维 DP + 回溯构造子序列 | O(n·m) | O(n·m) | 方向矩阵 b[][] 回溯输出 LCS | 🔗 直达 |
BM66 | 最长公共子串 | 二维 DP + 记录 max 长度位置 | O(n·m) | O(n·m) | 子串必须连续 | 🔗 直达 |
BM67 | 不同路径的数目(一) | DP 累加 / 组合数 C(m+n-2,n-1) | O(n·m) | O(min(n,m)) | 组合数写法更简洁 | 🔗 直达 |
BM68 | 矩阵的最小路径和 | 二维 DP:min(上,左)+当前值 | O(n·m) | O(n·m) | 从左至右、从上至下 | 🔗 直达 |
BM69 | 数字翻译字符串 | 线性 DP:单双数字解码 | O(n) | O(1) | 用于存储每个位置的解码方法数 | 🔗 直达 |
BM70 | 兑换零钱(一) | 完全背包求最小硬币数 | O(n·aim) | O(aim) | INF=0x3f3f3f3f 初始化技巧 | 🔗 直达 |
BM71 | 最长上升子序列 | 线性 DP + 贪心优化 | O(n²) | O(n) | 初始化动态规划数组,每个位置的初始值为 1,因为每个元素自身可以看作长度为 1 的递增子序列 | 🔗 直达 |
BM72 | 连续子数组最大和 | Kadane:max(前和+当前, 当前) | O(n) | O(1) | 经典最大子段和 | 🔗 直达 |
BM73 | 最长回文子串 | 中心扩展 / Manacher | O(n²) | O(1) | 奇偶中心双指针 | 🔗 直达 |
BM74 | 复原 IP 地址 | 三重循环枚举三段分割 | O(1) | O(1) | 剪枝提前结束 | 🔗 直达 |
BM75 | 编辑距离(一) | 二维 DP 经典 Levenshtein | O(n·m) | O(n·m) | 可滚动数组优化 | 🔗 直达 |
BM76 | 正则表达式匹配 | 二维 DP:处理 * 和 . | O(n·m) | O(n·m) | 状态转移分三种情况 | 🔗 直达 |
BM77 | 最长括号子串 | 栈 / 线性 DP | O(n) | O(n) / O(1) | 栈存索引或 DP 分段累加 | 🔗 直达 |
BM78 | 打家劫舍(一) | 线性 DP:不相邻取数 | O(n) | O(1) | dp[i]=max(dp[i-1],dp[i-2]+nums[i-1]) | 🔗 直达 |
BM79 | 打家劫舍(二) | 环形 DP:拆成两段线性 DP | O(n) | O(1) | 两次 DP 取 max | 🔗 直达 |
BM80 | 买卖股票(一) | 一次交易 DP / 贪心 | O(n) | O(1) | minPrice 贪心写法更简洁 | 🔗 直达 |
BM81 | 买卖股票(二) | 无限交易贪心 / DP | O(n) | O(1) | 贪心:累加所有上升段 | 🔗 直达 |
BM82 | 买卖股票(三) | 最多两次交易状态机 DP | O(n) | O(1) | 5 个状态滚动更新 | 🔗 直达 |
✅ 复习顺序建议(由易到难)
BM62 → BM63 → BM64 → BM69 → BM67 → BM68 → BM70 → BM71 → BM72 → BM77 → BM78 → BM79 → BM80 → BM81 → BM82 → BM65 → BM66 → BM75 → BM76
BM62 斐波那契数列
斐波那契数列_牛客题霸_牛客网
https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=295&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj
- 动态规划
import java.util.*;public class Solution {// 1. 计算斐波那契数列的第 n 项public int Fibonacci(int n) {List<Integer> fib = new ArrayList<Integer>(); // 2. 创建一个列表,用于存储斐波那契数列fib.add(0); // 3. 添加第 0 项,值为 0fib.add(1); // 4. 添加第 1 项,值为 1for (int i = 2; i <= n; i++) { // 5. 从第 2 项开始计算,直到第 n 项fib.add(fib.get(i - 1) + fib.get(i - 2)); // 6. 计算当前项,值为前两项之和}return fib.get(n); // 7. 返回第 n 项的值}
}
- 空间优化 动态规划
import java.util.*;public class Solution {public int Fibonacci(int n) {if (n <= 1) return n; // 1. 基准情况:F(0)=0, F(1)=1int a = 0, b = 1, c = 0; // 2. 初始化 F(0), F(1), 临时变量for (int i = 2; i <= n; i++) { // 3. 从 F(2) 递推到 F(n)c = a + b; // 4. 计算当前项a = b; // 5. 更新前两项b = c;}return c; // 6. 返回 F(n)}
}
- 递归
public class Solution {// 1. 计算斐波那契数列的第 n 项public int Fibonacci(int n) {// 2. 如果 n 小于等于 1,直接返回 n(第 0 项是 0,第 1 项是 1)if (n <= 1) return n;else {// 3. 根据斐波那契数列的定义,递归计算第 n 项return Fibonacci(n - 1) + Fibonacci(n - 2);}}
}
BM63 跳台阶
跳台阶_牛客题霸_牛客网
https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=295&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj
import java.util.*;public class Solution {// 1. 计算青蛙跳到第 number 级台阶的方法数public int jumpFloor(int number) {List<Integer> dp = new ArrayList<>(); // 2. 创建一个列表,用于存储每级台阶的方法数dp.add(1); // 3. 第 0 级台阶有 1 种方法(即站在地上)dp.add(1); // 4. 第 1 级台阶有 1 种方法(跳1级)for (int i = 2; i <= 40; i++) { // 5. 从第 2 级台阶开始计算,直到第 40 级dp.add(dp.get(i - 1) + dp.get(i - 2)); // 6. 当前级的方法数等于前两级方法数之和}return dp.get(number); // 7. 返回第 number 级台阶的方法数}
}
BM64 最小花费爬楼梯
最小花费爬楼梯_牛客题霸_牛客网
https://www.nowcoder.com/practice/6fe0302a058a4e4a834ee44af88435c7?tpId=295&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj
2025年8月23日20:51:18 2025年8月23日20:57:18
import java.util.*;public class Solution {public int minCostClimbingStairs(int[] cost) {int n = cost.length; // 1. 获取楼梯的阶数int[] dp = new int[n + 1]; // 2. 创建一个数组,用于存储到达每一阶的最小成本// 3. 初始化前两阶的成本,因为可以从地面或第一阶开始,所以初始成本为0dp[0] = 0;dp[1] = 0;// 4. 从第2阶开始计算,直到第n阶for (int i = 2; i <= n; i++) {// 5. 到达第i阶的最小成本等于到达前两阶的最小成本加上对应的楼梯成本dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);}return dp[n]; // 6. 返回到达最后一阶的最小成本}
}
BM65 最长公共子序列(二)
最长公共子序列(二)_牛客题霸_牛客网
https://www.nowcoder.com/practice/6d29638c85bb4ffd80c020fe244baf11?tpId=295&tqId=991075&sourceUrl=%2Fexam%2Foj
import java.util.*;public class Solution {String x = ""; // 1. 存储第一个字符串String y = ""; // 2. 存储第二个字符串// 3. 递归函数,用于根据方向矩阵 b 构造 LCSString ans(int l1, int l2, int[][] b) {String res = ""; // 4. 初始化结果字符串if (l1 == 0 || l2 == 0) { // 5. 如果某个字符串的长度为 0,直接返回空字符串return res;}if (b[l1][l2] == 1) { // 6. 如果方向矩阵 b[l1][l2] 为 1,表示当前字符是 LCS 的一部分res += ans(l1 - 1, l2 - 1, b); // 7. 递归构造 LCS,并添加当前字符res += x.charAt(l1 - 1); // 8. 添加当前字符} else if (b[l1][l2] == 2) { // 9. 如果方向矩阵 b[l1][l2] 为 2,表示从上一个状态转移过来res += ans(l1 - 1, l2, b); // 10. 递归构造 LCS} else { // 11. 如果方向矩阵 b[l1][l2] 为 3,表示从左边的状态转移过来res += ans(l1, l2 - 1, b); // 12. 递归构造 LCS}return res; // 13. 返回构造的 LCS}// 14. 主函数,计算两个字符串的 LCSpublic String LCS(String s1, String s2) {int l1 = s1.length(); // 15. 获取第一个字符串的长度int l2 = s2.length(); // 16. 获取第二个字符串的长度if (l1 == 0 || l2 == 0) { // 17. 如果某个字符串为空,直接返回 "-1"return "-1";}x = s1; // 18. 存储第一个字符串y = s2; // 19. 存储第二个字符串int[][] dp = new int[l1 + 1][l2 + 1]; // 20. 创建动态规划表,用于存储 LCS 的长度int[][] b = new int[l1 + 1][l2 + 1]; // 21. 创建方向矩阵,用于记录构造 LCS 的路径// 22. 动态规划计算 LCS 的长度,并填充方向矩阵for (int i = 1; i <= l1; i++) {for (int j = 1; j <= l2; j++) {if (s1.charAt(i - 1) == s2.charAt(j - 1)) { // 23. 如果当前字符匹配dp[i][j] = dp[i - 1][j - 1] + 1; // 24. LCS 长度加 1b[i][j] = 1; // 25. 标记当前字符是 LCS 的一部分} else {if (dp[i - 1][j] > dp[i][j - 1]) { // 26. 如果从上一个状态转移过来的 LCS 更长dp[i][j] = dp[i - 1][j]; // 27. 更新 LCS 长度b[i][j] = 2; // 28. 标记从上一个状态转移过来} else { // 29. 如果从左边的状态转移过来的 LCS 更长dp[i][j] = dp[i][j - 1]; // 30. 更新 LCS 长度b[i][j] = 3; // 31. 标记从左边的状态转移过来}}}}String res = ans(l1, l2, b); // 32. 调用递归函数构造 LCSif (res.isEmpty()) { // 33. 如果结果为空,返回 "-1"return "-1";}return res; // 34. 返回构造的 LCS}
}
BM66 最长公共子串
最长公共子串_牛客题霸_牛客网
https://www.nowcoder.com/practice/f33f5adc55f444baa0e0ca87ad8a6aac?tpId=295&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj
import java.util.*;public class Solution {public String LCS(String str1, String str2) {int[][] dp = new int[str1.length() + 1][str2.length() + 1]; // 1. 创建动态规划表,用于存储最长公共子串的长度int max = 0; // 2. 初始化最长公共子串的长度int pos = 0; // 3. 初始化最长公共子串的结束位置// 4. 动态规划计算最长公共子串的长度for (int i = 1; i <= str1.length(); i++) {for (int j = 1; j <= str2.length(); j++) {if (str1.charAt(i -