面试 TOP101 动态规划专题题解汇总Java版(一)(BM62 —— BM72)

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最长回文子串中心扩展 / ManacherO(n²)O(1)奇偶中心双指针🔗 直达
BM74复原 IP 地址三重循环枚举三段分割O(1)O(1)剪枝提前结束🔗 直达
BM75编辑距离(一)二维 DP 经典 LevenshteinO(n·m)O(n·m)可滚动数组优化🔗 直达
BM76正则表达式匹配二维 DP:处理 *.O(n·m)O(n·m)状态转移分三种情况🔗 直达
BM77最长括号子串栈 / 线性 DPO(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:拆成两段线性 DPO(n)O(1)两次 DP 取 max🔗 直达
BM80买卖股票(一)一次交易 DP / 贪心O(n)O(1)minPrice 贪心写法更简洁🔗 直达
BM81买卖股票(二)无限交易贪心 / DPO(n)O(1)贪心:累加所有上升段🔗 直达
BM82买卖股票(三)最多两次交易状态机 DPO(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

  1. 动态规划
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 项,值为 0
        fib.add(1); // 4. 添加第 1 项,值为 1
        for (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 项的值
    }
}
  1. 空间优化 动态规划
import java.util.*;

public class Solution {
    public int Fibonacci(int n) {
        if (n <= 1) return n;            // 1. 基准情况:F(0)=0, F(1)=1
        int 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)
    }
}
  1. 递归
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. 初始化前两阶的成本,因为可以从地面或第一阶开始,所以初始成本为0
        dp[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 构造 LCS
    String 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. 主函数,计算两个字符串的 LCS
    public 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 长度加 1
                    b[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. 调用递归函数构造 LCS
        if (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 - 1) == str2.charAt(j - 1)) { // 5. 如果当前字符匹配
                    dp[i][j] = dp[i - 1][j - 1] + 1; // 6. 当前位置的最长公共子串长度等于前一个位置的长度加1
                    if (dp[i][j] > max) { // 7. 更新最长公共子串的长度和结束位置
                        max = dp[i][j];
                        pos = i - 1;
                    }
                } else {
                    dp[i][j] = 0; // 8. 如果当前字符不匹配,当前位置的最长公共子串长度为0
                }
            }
        }
        // 9. 返回最长公共子串
        return str1.substring(pos - max + 1, pos + 1);
    }
}

BM67 不同路径的数目(一)

不同路径的数目(一)_牛客题霸_牛客网
https://www.nowcoder.com/practice/166eaff8439d4cd898e3ba933fbc6358?tpId=295&tqId=685&sourceUrl=%2Fexam%2Foj

  1. 动态规划
import java.util.*;

public class Solution {

    // 1. 计算从网格的左上角到右下角的唯一路径数
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m + 1][n + 1]; // 2. 创建一个动态规划表,用于存储到达每个位置的路径数
        dp[0][0] = 1; // 3. 初始化起点的路径数为1
        // 4. 初始化第一列的路径数,只能从上方来
        for (int i = 1; i < m; i++) {
            dp[i][0] = 1;
        }
        // 5. 初始化第一行的路径数,只能从左方来
        for (int i = 1; i < n; i++) {
            dp[0][i] = 1;
        }
        // 6. 动态规划计算每个位置的路径数
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; // 7. 到达当前位置的路径数等于上方和左方路径数之和
            }
        }
        return dp[m - 1][n - 1]; // 8. 返回到达右下角的路径数
    }
}
  1. 组合数学
import java.util.*;

public class Solution {
    // 1. 计算从网格的左上角到右下角的唯一路径数
    public int uniquePaths(int m, int n) {
        long res = 1; // 2. 初始化结果为 1
        int x = n, y = 1; // 3. 初始化变量 x 和 y,x 表示分子的起始值,y 表示分母的起始值
        while (y < m) { // 4. 当 y 小于 m 时,继续计算
            res = res * x / y; // 5. 更新结果,每次乘以 x 并除以 y
            x++; // 6. 分子 x 增加 1
            y++; // 7. 分母 y 增加 1
        }
        return (int) res; // 8. 返回结果,强制转换为 int 类型
    }
}

BM68 矩阵的最小路径和

矩阵的最小路径和_牛客题霸_牛客网
https://www.nowcoder.com/practice/7d21b6be4c6b429bb92d219341c4f8bb?tpId=295&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj

  1. 动态规划
import java.util.*;

public class Solution {

    public int minPathSum(int[][] matrix) {
        int m = matrix.length; // 1. 获取矩阵的行数
        int n = matrix[0].length; // 2. 获取矩阵的列数
        int[][] dp = new int[m][n]; // 3. 创建一个动态规划表,用于存储到达每个位置的最小路径和

        for (int i = 0; i < m; i++) { // 4. 遍历矩阵的每一行
            for (int j = 0; j < n; j++) { // 5. 遍历矩阵的每一列
                if (i == 0 && j == 0) { // 6. 如果是左上角的起点
                    dp[0][0] = matrix[0][0]; // 7. 起点的最小路径和就是它本身的值
                } else if (i == 0) { // 8. 如果在第一行,只能从左边来
                    dp[i][j] = matrix[i][j] + dp[i][j - 1]; // 9. 当前位置的最小路径和等于左边的最小路径和加上当前值
                } else if (j == 0) { // 10. 如果在第一列,只能从上边来
                    dp[i][j] = matrix[i][j] + dp[i - 1][j]; // 11. 当前位置的最小路径和等于上边的最小路径和加上当前值
                } else { // 12. 其他位置可以从左边或上边来
                    dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j]) + matrix[i][j]; // 13. 当前位置的最小路径和等于左边和上边的最小路径和中较小的一个加上当前值
                }
            }
        }
        return dp[m - 1][n - 1]; // 14. 返回到达右下角的最小路径和
    }
}
  1. 直接在原数组上修改
import java.util.*;

public class Solution {

    public int minPathSum(int[][] matrix) {
        int m = matrix.length; // 1. 获取矩阵的行数
        int n = matrix[0].length; // 2. 获取矩阵的列数
        for (int i = 0; i < m; i++) { // 3. 遍历矩阵的每一行
            for (int j = 0; j < n; j++) { // 4. 遍历矩阵的每一列
                if (i == 0 && j == 0) { // 5. 如果是左上角的起点
                    continue; // 6. 跳过,因为起点的值不需要更新
                } else if (i == 0) { // 7. 如果在第一行
                    matrix[i][j] += matrix[i][j - 1]; // 8. 当前位置的值加上左边的值
                } else if (j == 0) { // 9. 如果在第一列
                    matrix[i][j] += matrix[i - 1][j]; // 10. 当前位置的值加上上边的值
                } else { // 11. 其他位置
                    matrix[i][j] += Math.min(matrix[i][j - 1], matrix[i - 1][j]); // 12. 当前位置的值加上左边和上边的较小值
                }
            }
        }
        return matrix[m - 1][n - 1]; // 13. 返回右下角的值,即最小路径和
    }
}

BM69 把数字翻译成字符串

把数字翻译成字符串_牛客题霸_牛客网
https://www.nowcoder.com/practice/046a55e6cd274cffb88fc32dba695668?tpId=295&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj

import java.util.*;

public class Solution {
    public int solve(String nums) {
        int n = nums.length(); // 1. 获取字符串的长度
        if (nums.charAt(0) == '0' || n == 0) return 0; // 2. 如果字符串以 '0' 开头或为空,返回 0
        int[] dp = new int[n + 1]; // 3. 创建一个动态规划数组,用于存储每个位置的解码方法数
        dp[0] = 1; // 4. 初始化 dp[0] 为 1,表示空字符串有一种解码方法
        for (int i = 1; i < n; i++) { // 5. 遍历字符串
            if (nums.charAt(i) != '0') dp[i] = dp[i - 1]; // 6. 如果当前字符不是 '0',继承前一个位置的解码方法数
            int t = (nums.charAt(i - 1) - '0') * 10 + (nums.charAt(i) - '0'); // 7. 计算当前字符和前一个字符组成的两位数
            if (t >= 10 && t <= 26) { // 8. 如果这个两位数在 10 到 26 之间
                if (i == 1) { // 9. 如果是第二个字符
                    dp[i] += 1; // 10. 增加一种解码方法
                } else {
                    dp[i] += dp[i - 2]; // 11. 增加前两个位置的解码方法数
                }
            }
        }
        return dp[n - 1]; // 12. 返回最后一个位置的解码方法数
    }
}

BM70 兑换零钱(一)

兑换零钱(一)_牛客题霸_牛客网
https://www.nowcoder.com/practice/3911a20b3f8743058214ceaa099eeb45?tpId=295&tqId=988994&sourceUrl=%2Fexam%2Foj

import java.util.*;

public class Solution {
    public int minMoney(int[] arr, int aim) {
        if (aim < 1) return 0; // 1. 如果目标金额小于1,返回0
        int[] dp = new int[aim + 1]; // 2. 创建一个动态规划数组,用于存储每个金额的最少硬币数量
        Arrays.fill(dp, 0x3f3f3f); // 3. 初始化数组为一个很大的数(表示无穷大)
        dp[0] = 0; // 4. 初始化dp[0]为0,表示金额为0时不需要任何硬币
        for (int i = 1; i <= aim; i++) { // 5. 遍历所有金额,从1到目标金额
            for (int j = 0; j < arr.length; j++) { // 6. 遍历所有硬币面值
                if (i - arr[j] >= 0) // 7. 如果当前金额减去硬币面值不小于0
                    dp[i] = Math.min(dp[i], dp[i - arr[j]] + 1); // 8. 更新当前金额的最少硬币数量
            }
        }
        return dp[aim] == 0x3f3f3f ? -1 : dp[aim]; // 9. 如果dp[aim]仍为无穷大,返回-1,否则返回dp[aim]
    }
}

BM71 最长上升子序列(一)

最长上升子序列(一)_牛客题霸_牛客网
https://www.nowcoder.com/practice/5164f38b67f846fb8699e9352695cd2f?tpId=295&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj

import java.util.*;

public class Solution {
    public int LIS(int[] arr) {
        int n = arr.length; // 1. 获取数组的长度
        if (n == 0) return 0; // 2. 如果数组为空,返回 0
        int[] dp = new int[n]; // 3. 创建一个动态规划数组,用于存储每个位置的最长递增子序列长度
        Arrays.fill(dp, 1); // 4. 初始化动态规划数组,每个位置的初始值为 1,因为每个元素自身可以看作长度为 1 的递增子序列
        int maxl = 1; // 5. 初始化最长递增子序列的长度为 1
        for (int i = 1; i < n; i++) { // 6. 遍历数组,从第二个元素开始
            for (int j = 0; j < i; j++) { // 7. 遍历当前元素之前的所有元素
                if (arr[i] > arr[j] && dp[i] < dp[j] + 1) { // 8. 如果当前元素大于之前的某个元素,并且可以形成更长的递增子序列
                    dp[i] = dp[j] + 1; // 9. 更新当前元素的最长递增子序列长度
                    maxl = Math.max(maxl, dp[i]); // 10. 更新最长递增子序列的长度
                }
            }
        }
        return maxl; // 11. 返回最长递增子序列的长度
    }
}

BM72 连续子数组的最大和

连续子数组的最大和_牛客题霸_牛客网
https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=295&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Foj

  1. 动态规划
import java.util.*;

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int n = array.length; // 1. 获取数组的长度
        int[] dp = new int[n]; // 2. 创建一个动态规划数组,用于存储每个位置的最大子数组和
        dp[0] = array[0]; // 3. 初始化第一个位置的最大子数组和为数组的第一个元素
        int maxsum = array[0]; // 4. 初始化最大子数组和为数组的第一个元素
        for (int i = 1; i < n; i++) { // 5. 遍历数组,从第二个元素开始
            dp[i] = Math.max(dp[i - 1] + array[i], array[i]); // 6. 更新当前位置的最大子数组和
            maxsum = Math.max(maxsum, dp[i]); // 7. 更新全局最大子数组和
        }
        return maxsum; // 8. 返回全局最大子数组和
    }
}
  1. 优化空间动态规划
import java.util.*;

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int n = array.length; // 1. 获取数组的长度
        int sum = 0; // 2. 初始化当前子数组的和为 0
        int maxsum = array[0]; // 3. 初始化最大子数组和为数组的第一个元素
        for (int i = 0; i < n; i++) { // 4. 遍历数组
            sum = Math.max(sum, 0) + array[i]; // 5. 更新当前子数组的和,如果当前和小于 0,则重置为 0
            maxsum = Math.max(maxsum, sum); // 6. 更新全局最大子数组和
        }
        return maxsum; // 7. 返回全局最大子数组和
    }
}

后续

面试 TOP101 动态规划专题题解汇总Java版(二)(BM73 —— BM82)-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值