当前位置: 首页 > news >正文

2025高频面试算法总结篇【动态规划】

文章目录

  • 直接刷题链接直达
  • 编辑距离
  • 最长回文子串
  • 完全平方数
  • 最长递增子序列
  • 正则表达式匹配
  • 零钱兑换
  • 鸡蛋掉落
  • 单词拆分


直接刷题链接直达

动态规划(Dynamic Programming, DP)是一种通过拆解子问题并利用子问题的最优解来构建整体问题的最优解的方法,通常用于具有重叠子问题最优子结构的优化问题。

  • 最优子结构:一个问题的最优解包含其子问题的最优解。例如,最短路径问题,求从A到B的最短路径,其中A到C和C到B的最短路径也必须是最优的。
  • 重叠子问题:相同的子问题在递归过程中会被多次计算。例如,斐波那契数列 F(n) = F(n-1) + F(n-2),计算 F(n-2) 时也需要计算 F(n-3)

如果问题满足这两个特性,通常可以考虑使用动态规划。

(1)状态的定义是解决动态规划的关键。通常可以通过以下方式思考:

  1. 分析问题,找出“规模较小”的子问题。
  2. 确定状态变量,这些变量应该能唯一标识一个子问题的情况。
    • 一般来说,状态变量的选取往往与问题的输入参数有关。
    • 例如,背包问题中 dp[i][j] 表示前 i 个物品,在总容量为 j 的背包中的最优解。
    • 在最长公共子序列问题中 dp[i][j] 表示 s1 的前 i 个字符和 s2 的前 j 个字符的最长公共子序列长度。

(2)状态转移方程用于描述如何从较小的子问题递推到较大的子问题。常见的方法:

  • 考虑决策过程:在某个状态下,可以有哪些选择?
  • 根据子问题的解构建当前问题的解

例如:

  • 斐波那契数列dp[n] = dp[n-1] + dp[n-2]
  • 0/1 背包问题dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]) (取或者不取当前物品)

(3)初始状态是动态规划表 dp 的起点,通常根据问题的边界条件来确定。例如:

  • 斐波那契数列:dp[0] = 0, dp[1] = 1
  • 背包问题:dp[0][j] = 0(没有物品可选时,价值均为0)

(4)动态规划可以通过**自顶向下(带记忆化的递归)自底向上(迭代)**来实现:

  • 自顶向下:递归 + 记忆化搜索(Memoization),避免重复计算
  • 自底向上:直接从小规模问题开始填表,通常是 O(n)O(n^2)

  • 最佳折扣问题
    • double calculateMinPrice(int [] counts, double [] prices, Map<int[], Double> promotions)
    • counts表示一个要买的商品数量列表(如0011表示第3件和第4件都买一个),prices表示单价,promotions表示折扣表 比如 0011->9 表示第3件和第4件一起打包买打9折,输出一个最低价格
    • 实现类似 动态规划_最小费用购物问题
  • 设计一个模糊匹配算法,给定一个字符串列表和一个字符串,输出列表中最匹配的那个(若都不匹配可输出空串)
    • 类似于 72. 编辑距离,基于字符长度配置好阈值
  • 最长回文子串
    • 5. 最长回文子串
  • 完全平方数
    • 279. 完全平方数
  • 最长递增子序列
    • 300. 最长递增子序列
  • 正则表达式匹配
    • 10. 正则表达式匹配
  • 零钱兑换
    • 322. 零钱兑换
  • 鸡蛋掉落
    • dp[K] [N] = 1 + max(dp[K - 1] [i - 1] + dp[K] [N - i]) + 1 (i~(1,N)) (K蛋N层,最直观的DP,还有其他解法)
    • 887. 鸡蛋掉落
    • Egg Dropping Dynamic Programming (画状态转移表)
  • 单词拆分
    • 139. 单词拆分
    • 140. 单词拆分 II

编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

class Solution {// dp[i][j] : word1[0:i]转换成 word2[0:j] 所使用的最少操作数// dp[i][j] = (s[i] == s[j] && dp[i-1][j-1]) or (Math.min(插删替换))public int minDistance(String word1, String word2) {int m = word1.length(), n = word2.length();int[][] dp = new int[m+1][n+1];// 初始 dp[0][j] = j; dp[i][0] = i;for (int i = 0; i <= m; i++) {dp[i][0] = i;} for (int j = 0; j <= n; j++) {dp[0][j] = j;}//for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (word1.charAt(i-1) == word2.charAt(j-1)) {dp[i][j] = dp[i-1][j-1];}else {dp[i][j] = Math.min(Math.min(dp[i][j-1], dp[i-1][j]),dp[i-1][j-1])+1;}}}return dp[m][n];}
}

最长回文子串

给你一个字符串 s,找到 s 中最长的 回文 子串。

class Solution {// dp[i][j] s[i-j] 是否为回文串public String longestPalindrome(String s) {if (s.length() <= 1) return s;boolean[][] dp = new boolean[s.length()][s.length()];for (int i = 0; i < s.length(); i++) {dp[i][i] = true;}int start = 0, maxlen = 1;for (int len = 2; len <= s.length(); len++) {for (int i = 0; i <= s.length() - len; i++) {int j = i + len - 1;if (len == 2 && s.charAt(i) == s.charAt(j)) {dp[i][j] = true;}else {dp[i][j] = s.charAt(i) == s.charAt(j) && dp[i+1][j-1];}if (dp[i][j] && len > maxlen) {start = i;maxlen = len;}}}return s.substring(start, start+maxlen);}
}

完全平方数

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

class Solution {// dp[i] = dp[i-完全平方数]+1public int numSquares(int n) {int[] dp = new int[n+1];Arrays.fill(dp, Integer.MAX_VALUE);dp[0] = 0;for (int i = 1; i <= n; i++){for (int j = 1; j*j <= i; j++) {dp[i] = Math.min(dp[i], dp[i-j*j]+1);}}return dp[n];}
}

最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

class Solution {public int lengthOfLIS(int[] nums) {// dp[i] 以i结尾 的 最长严格递增子序列的长度int[] dp = new int[nums.length];Arrays.fill(dp, 1);int max = 1;for (int i = 1; i < nums.length; i++) {for (int j = 0; j < i; j++) {if (nums[i] > nums[j]) {dp[i] = Math.max(dp[i], dp[j] + 1);}}max = Math.max(max, dp[i]);}return max;}
}

正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s 的,而不是部分字符串。

class Solution {public boolean isMatch(String s, String p) {int n = s.length();int m = p.length();boolean[][] dp = new boolean[n + 1][m + 1];// 空字符串匹配空模式dp[0][0] = true;// 处理 p[j-1] 是 '*' 的情况 (匹配 0 次)for (int j = 2; j <= m; j++) {if (p.charAt(j - 1) == '*') {dp[0][j] = dp[0][j - 2]; }}// 填充 DP 数组for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {if (p.charAt(j - 1) == '.' || p.charAt(j - 1) == s.charAt(i - 1)) {dp[i][j] = dp[i - 1][j - 1];} else if (p.charAt(j - 1) == '*') {dp[i][j] = dp[i][j - 2]; // `*` 匹配 0 次if (p.charAt(j - 2) == s.charAt(i - 1) || p.charAt(j - 2) == '.') {dp[i][j] = dp[i][j] || dp[i - 1][j]; // `*` 匹配多次}}}}return dp[n][m];}
}

零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

class Solution {public int coinChange(int[] coins, int amount) {// dp[i] :可以凑成 i 所需的 最少的硬币个数 int[] dp = new int[amount+1];// dp[i] = dp[i-coins] + 1Arrays.fill(dp, amount + 1);dp[0] = 0;Arrays.sort(coins);for (int coin : coins) {if (coin <= amount) {dp[coin] = 1;}else {break;}}for (int i = 1; i <= amount; i++) {for (int coin : coins) {if (coin > i) {break;}dp[i] = Math.min(dp[i], dp[i-coin]+1);}}return dp[amount] >= amount + 1 ? -1:dp[amount];}
}

鸡蛋掉落


单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

class Solution {// dp[i] 表示 s[0:i] 是否可以拆分成 wordDict 里的单词组合。// dp[i]=dp[j] 且 s[j:i] 在 wordDictpublic boolean wordBreak(String s, List<String> wordDict) {boolean[] dp = new boolean[s.length()+1];dp[0] = true;for(int i = 1; i <= s.length(); i++) {for (int j = 0; j <= i; j++) {if (dp[j] && wordDict.contains(s.substring(j, i))) {dp[i] = true;break;}}}return dp[s.length()];}
}

相关文章:

  • Spark-SQL核心编程:DataFrame、DataSet与RDD深度解析
  • leetcode:1351. 统计有序矩阵中的负数(python3解法)
  • SQL学习笔记-聚合查询
  • 16:00开始面试,16:08就出来了,问的问题有点变态。。。
  • 大数据学习栈记——MongoDB编程
  • 【Web三十一】K8S的常用命令
  • 设计模式-模板模式
  • Node.js 模块包的管理和使用是
  • Qt:解决MSVC编译器下qDebug输出中文乱码的问题
  • 中英文提示词对AI IDE编程能力影响有多大?
  • 三层交换机SVI功能(交换机虚拟接口)实现各个实训室电脑网络可互通,原本是独立局域网
  • MATLAB仿真多相滤波抽取与插值的频谱变化(可视化混叠和镜像)
  • TypeScript 进阶指南 - 使用泛型与keyof约束参数
  • spark简介和核心编程
  • spark-sql核心
  • 试一下阿里云新出的mcp服务
  • 鸿蒙动画与交互设计:ArkUI 3D变换与手势事件详解
  • 【字节跳动AI论文】Seaweed-7B:视频生成基础模型的高成本效益培训
  • 栈与队列习题分享(精写)
  • chrome无法访问此网站怎么回事 分享5种解决方法
  • 观众走入剧院空间,人艺之友一起“再造时光”
  • 《淮水竹亭》:一手好牌,为何打成这样
  • 总没胃口,一吃就饱……别羡慕,也可能是生病了
  • 《单身情歌》制作人韩贤光去世,林志炫发文悼念
  • 人民时评:莫让“假俗乱”讲解侵蚀“文博热”
  • 婚姻登记“全国通办”首日观察:数据多跑路,群众少跑腿