【动态规划】深入动态规划:背包问题
文章目录
- 前言
- 01背包例题
- 一、01背包
- 二、分割等和子集
- 三、目标和
- 四、最后一块石头的重量||
- 完全背包例题
- 一、完全背包
- 二、 零钱兑换
- 三、零钱兑换||
- 四、完全平方数
前言
什么是背包问题,怎么解决算法中的背包问题呢?
背包问题 (Knapsack problem) 是⼀种组合优化的 NP完全问题。 问题可以描述为:给定⼀组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。 根据物品的个数,分为如下几类:
• 01 背包问题:每个物品只有⼀个
• 完全背包问题:每个物品有⽆限多个
• 多重背包问题:每件物品最多有 si 个
• 混合背包问题:每个物品会有上面三种情况…
• 分组背包问题:物品有 n 组,每组物品里有若干个,每组里最多选一个物品其中上述分类里面,根据背包是否装满,又分为两类:
• 不⼀定装满背包 • 背包⼀定装满 优化方案:
• 空间优化 - 滚动数组
• 单调队列优化
• 贪心优化 根据限定条件的个数,又分为两类:
• 限定条件只有⼀个:比如体积 -> 普通的背包问题
• 限定条件有两个:比如体积 + 重量 -> ⼆维费用背包问题根据不同的问法,又分为很多类:
• 输出方案
• 求方案总数
• 最优方案
• 方案可行性
其实还有很多分类,但是我们仅需了解即可。 因此,背包问题种类非常繁多,题型非常丰富,难度也是非常难以捉摸。但是,尽管种类非常多,都是从 01 背包问题演化过来的。所以,⼀定要把 01 背包问题学好。
本文主要介绍两种背包问题
- 01背包问题
- 完全背包问题
下面本文将通过例题为大家详细展开背包问题,以及背包问题在算法中的具体体现与解法
01背包例题
一、01背包
- 题目链接:01背包
- 题目描述:
描述
你有⼀个背包,最多能容纳的体积是V。 现在有 n 个物品,第 i 个物品的体积为 vi, 价值为 wi。
(1)求这个背包至多能装多大价值的物品?
(2)若背包恰好装满,求⾄多能装多大价值的物品? 输入描述: 第⼀行两个整数 n 和 V,表示物品个数和背包体积。 接下来 n 行,每行两个数 vi 和 wi,表示第i个物品的体积和价值。 输出描述: 输出有两行,第一行输出第⼀问的答案,第⼆行输出第⼆问的答案,如果无解请输出 0。
示例1 输入:
3 5
2 10
4 5
1 4
输出:
14
9
说明:装第⼀个和第三个物品时总价值最大,但是装第⼆个和第三个物品可以使得背包恰好装满且总价 值最⼤。
示例2
输入:
3 8
12 6
11 8
6 8
输出:80
说明:装第三个物品时总价值最⼤但是不满,装满背包无解
-
解法:
算法思路: 我们先解决第⼀问:
状态表示:
dp[i][j] 表⽰:从前 i 个物品中挑选,总体积「不超过」 j ,所有的选法中,能挑选出来 的最大价值。
状态转移⽅程: 线性 dp 状态转移方程分析⽅式,⼀般都是根据「最后⼀步」的状况,来分情况讨论:
i. 不选第 i 个物品:相当于就是去前 i - 1 个物品中挑选,并且总体积不超过 j 。此 时 dp[i][j] = dp[i - 1][j] ;
ii. 选择第 i 个物品:那么我就只能去前 i - 1 个物品中,挑选总体积不超过 j - v[i]
的物品。此时 dp[i][j] = dp[i - 1][j - v[i]] + w[i] 。但是这种状态不⼀
定存在,因此需要特判⼀下。 综上,状态转移⽅程为: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]]+ w[i]) 。
初始化: 我们多加⼀行,方便我们的初始化,此时仅需将第⼀行初始化为 0 即可。因为什么也不选,也能 满足体积不小于 j 的情况,此时的价值为 0 。
填表顺序: 根据「状态转移⽅程」,我们仅需「从上往下」填表即可。
返回值: 根据「状态表⽰」,返回 dp[n][V] 。 接下来解决第⼆问: 第⼆问仅需微调⼀下 dp 过程的五步即可。 因为有可能凑不齐 j 体积的物品,因此我们把不合法的状态设置为 -1 。
状态表示:
dp[i][j] 表示:从前 i 个物品中挑选,总体积「正好」等于 j ,所有的选法中,能挑选出来的最大价值。
状态转移方程:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]) 。 但是在使用 dp[i - 1][j - v[i]] 的时候,不仅要判断 j >= v[i] ,又要判断 dp[i - 1][j - v[i]] 表示的情况是否存在,也就是 dp[i - 1][j - v[i]] != -1 。
初始化: 我们多加一行,方便我们的初始化:
i. 第⼀个格⼦为 0 ,因为正好能凑⻬体积为 0 的背包;
ii. 但是第⼀行后面的格子都是 -1 ,因为没有物品,无法满足体积大于 0 的情况。
填表顺序: 根据「状态转移方程」,我们仅需「从上往下」填表即可。
返回值: 由于最后可能凑不成体积为 V 的情况,因此返回之前需要「特判」⼀下 -
代码示例
public static void main(String[] args){Scanner sc = new Scanner(System.in);int n = sc.nextInt();int V = sc.nextInt();//存放体积int[] v=new int[n+1];//存放价值int[] w=new int[n+1];for(int i=1;i<=n;i++){v[i]=sc.nextInt();w[i]=sc.nextInt();}//dp1[i]表示不考虑背包是否装满,在容量为i的情况下,最多装多大价值的物品int[] dp1=new int[V+1];for(int i=1;i<=n;i++){//由于每个物品只能用一次,为了防止重复计算,需要倒序遍历for(int j=V;j>=v[i];j--){//状态转移,要么选择第i件物品,要么不选,取价值最大的dp1[j]=Math.max(dp1[j-v[i]]+w[i],dp1[j]);}}System.out.println(dp1[V]);//dp2[i]表示背包恰好装满时,在容量为i的情况下,最多装多大价值的物品int[] dp2=new int[V+1];Arrays.fill(dp2,Integer.MIN_VALUE);//没有物品时,价值为0dp2[0]=0;for(int i=1;i<=n;i++){//由于每个物品只能用一次,为了防止重复计算,需要倒序遍历for(int j=V;j>=v[i];j--){//状态转移,要么选择第i件物品,要么不选,取价值最大的dp2[j]=Math.max(dp2[j-v[i]]+w[i],dp2[j]);}}//如果小于0,说明不能从初始状态转移过来,无解if(dp2[V]<0){dp2[V]=0;}System.out.println(dp2[V]);}
二、分割等和子集
- 题目链接:分割等和子集
- 题目描述:
给你⼀个 只包含正整数 的 ⾮空 数组 nums 。请你判断是否可以将这个数组分割成两个⼦集,使得两 个⼦集的元素和相等。 ⽰例 1:输⼊:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:输⼊:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
-
解法(动态规划):
算法思路: 先将问题转化成我们「熟悉」的题型。 如果数组能够被分成两个相同元素之和相同的⼦集,那么原数组必须有下⾯⼏个性质:
i. 所有元素之和应该是⼀个偶数;
ii. 数组中最⼤的元素应该小于所有元素总和的⼀半;
iii. 挑选⼀些数,这些数的总和应该等于数组总和的⼀半。 根据前两个性质,我们可以提前判断数组能够被划分。根据最后⼀个性质,我们发现问题就转化成 了「01 背包」的模型:
i. 数组中的元素只能选择⼀次;
ii. 每个元素⾯临被选择或者不被选择的处境;
iii. 选出来的元素总和要等于所有元素总和的⼀半。 其中,数组内的元素就是物品,总和就是背包。 那么我们就可以⽤背包模型的分析方式,来处理这道题。 请⼤家注意,「不要背」状态转移方程,因为题型变化之后,状态转移方程就会跟着变化。我们要 记住的是分析问题的模式。⽤这种分析问题的模式来解决问题。
状态表示:
dp[i][j] 表示在前 i 个元素中选择,所有的选法中,能否凑成总和为 j 这个数。
状态转移方程: ⽼规矩,根据「最后⼀个位置」的元素,结合题⽬的要求,分情况讨论:
i. 不选择 nums[i] :那么我们是否能够凑成总和为 j ,就要看在前 i - 1 个元素中 选,能否凑成总和为 j 。根据状态表⽰,此时 dp[i][j] = dp[i - 1][j] ;
ii. 选择 nums[i] :这种情况下是有前提条件的,此时的 nums[i] 应该是⼩于等于 j 。 因为如果这个元素都⽐要凑成的总和⼤,选择它就没有意义呀。那么我们是否能够凑成总和 为 j ,就要看在前 i - 1 个元素中选,能否凑成总和为 j - nums[i] 。根据状态表 ⽰,此时 dp[i][j] = dp[i - 1][j - nums[i]] 。
综上所述,两种情况下只要有一种能够凑成总和为 j ,那么这个状态就是 true 。因此,状态转移方程为:dp[i][j] = dp[i - 1][j]
if(nums[i - 1] <= j) dp[i][j] = dp[i][j] || dp[i - 1][j - nums[i]]
初始化: 由于需要用到上⼀⾏的数据,因此我们可以先把第⼀行初始化。 第⼀行表示不选择任何元素,要凑成目标和 j 。只有当目标和为 0 的时候才能做到,因此第⼀ ⾏仅需初始化第⼀个元素 dp[0][0] = true
填表顺序: 根据「状态转移方程」,我们需要「从上往下」填写每⼀行,每⼀行的顺序是「无所谓的」。
返回值: 根据「状态表示」,返回 dp[n][aim] 的值。 其中 n 表示数组的大小, aim 表示要凑的目标和。
空间优化: 所有的「背包问题」,都可以进行空间上的优化。 对于 01背包类型的,我们的优化策略是:
i. 删掉第⼀维;
ii. 修改第二层循环的遍历顺序即可 -
代码示例:
public boolean canPartition1(int[] nums) {int n = nums.length, sum = 0;for (int x : nums) sum += x;if (sum % 2 == 1) return false; // 如果不能平分,直接返回 falseint aim = sum / 2;boolean[] dp = new boolean[aim + 1]; // 建表dp[0] = true; // 初始化for (int i = 1; i <= n; i++) // 填表for (int j = aim; j >= nums[i - 1]; j--) // ⼀定要注意修改遍历顺序dp[j] = dp[j] || dp[j - nums[i - 1]];return dp[aim];}
三、目标和
- 题目链接:
- 题目描述:
给你⼀个整数数组 nums 和⼀个整数 target 。 向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造⼀个 表达式 : 例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1”
。返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
示例 1:输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:⼀共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
示例 2:输⼊:nums = [1], target = 1 输出:1
-
解法(动态规划):
算法思路: 本题可以直接用「暴搜」的方法解决。但是稍微⽤数学知识分析⼀下,就能转化成我们常见的「背 包模型」的问题。 设我们最终选取的结果中,前面加 + 号的数字之和为 a ,前⾯加 - 号的数字之和为 b ,整个数组的总和为 sum ,于是我们有:
▪ a + b = sum
▪ a - b = target
上⾯两个式子消去 b 之后,可以得到 a = (sum + target) / 2
也就是说,我们仅需在 nums 数组中选择⼀些数,将它们凑成和为 (sum + target) / 2 即 可。问题就变成了 416. 分割等和⼦集 这道题。 我们了可以用相同的分析模式,来处理这道题。
状态表示:
dp[i][j] 表示:在前 i 个数中选,总和正好等于 j ,⼀共有多少种选法。
状态转移方程: 老规矩,根据「最后⼀个位置」的元素,结合题目的要求,我们有「选择」最后⼀个元素或者「不选择」最后⼀个元素两种策略:
i. 不选 nums[i] :那么我们凑成总和 j 的总方案,就要看在前 i - 1 个元素中选,凑 成总和为 j 的⽅案数。根据状态表示,此时 dp[i][j] = dp[i - 1][j] ;
ii. 选择 nums[i] :这种情况下是有前提条件的,此时的 nums[i] 应该是小于等于 j 。 因为如果这个元素都比要凑成的总和⼤,选择它就没有意义呀。那么我们能够凑成总和为 j 的⽅案数,就要看在前 i - 1 个元素中选,能否凑成总和为 j - nums[i] 。根据 状态表示,此时 dp[i][j] = dp[i - 1][j - nums[i]]
综上所述,两种情况如果存在的话,应该要累加在⼀起。因此,状态转移方程为:dp[i][j] = dp[i - 1][j]
if(nums[i - 1] <= j) dp[i][j] = dp[i][j] += dp[i - 1][j - nums[i - 1]]
初始化: 由于需要用到「上⼀行」的数据,因此我们可以先把第⼀⾏初始化。 第⼀⾏表⽰不选择任何元素,要凑成⽬标和 j 。只有当目标和为 0 的时候才能做到,因此第⼀行仅需初始化第⼀个元素 dp[0][0] = 1
填表顺序: 根据「状态转移方程」,我们需要「从上往下」填写每⼀行,每⼀行的顺序是「无所谓的」。
返回值: 根据「状态表示」,返回 dp[n][aim] 的值。 其中 n 表⽰数组的大小, aim 表示要凑的目标和。
空间优化: 所有的「背包问题」,都可以进行空间上的优化。 对于 01背包类型的,我们的优化策略是:
i. 删掉第⼀维;
ii. 修改第二层循环的遍历顺序即可。 -
代码示例:
public int findTargetSumWays(int[] nums, int target) {int n = nums.length, sum = 0;for (int x : nums) sum += x;int aim = (sum + target) / 2;// 处理⼀下边界情况if (aim < 0 || (sum + target) % 2 == 1) return 0;int[] dp = new int[aim + 1];dp[0] = 1;for (int i = 1; i <= n; i++)for (int j = aim; j >= nums[i - 1]; j--) // 注意修改遍历顺序dp[j] += dp[j - nums[i - 1]];return dp[aim];}
四、最后一块石头的重量||
- 题目链接:最后一块石头的重量||
- 题目描述:
有⼀堆石头,用整数数组 stones 表示。其中 stones[i] 表⽰第 i 块⽯头的重量。 每⼀回合,从中选出任意两块⽯头,然后将它们⼀起粉碎。假设石头的重量分别为 x 和 y,且 x <=y。那么粉碎的可能结果如下: 如果 x == y,那么两块石头都会被完全粉碎; 如果 x != y,那么重量为 x 的⽯头将会完全粉碎,而重量为 y 的石头新重量为 y-x。 最后,最多只会剩下⼀块石头。返回此石头最小的可能重量 。如果没有⽯头剩下,就返回 0。
示例 1: 输入:stones = [2,7,4,1,8,1]
输出:1 解释: 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1], 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
示例 2:
输入:stones = [31,26,33,21,40]
输出:5
提示: 1 <= stones.length <= 30
1 <= stones[i] <= 100
-
解法(动态规划): 算法思路: 先将问题「转化」成我们熟悉的题型。
▪ 任意两块⽯头在⼀起粉碎,重量相同的部分会被丢掉,重量有差异的部分会被留下来。那就 相当于在原始的数据的前⾯,加上「加号」或者「减号」,是最终的结果最小即可。也就是 说把原始的石头分成两部分,两部分的和越接近越好。
▪ ⼜因为当所有元素的和固定时,分成的两部分越接近数组「总和的⼀半」,两者的差越小。 因此问题就变成了:在数组中选择⼀些数,让这些数的和尽量接近 sum / 2 ,如果把数看成物 品,每个数的值看成体积和价值,问题就变成了「01 背包问题」。
状态表示:
dp[i][j] 表示在前 i 个元素中选择,总和不超过 j,此时所有元素的「最⼤和」。
状态转移方程: 老规矩,根据「最后⼀个位置」的元素,结合题目的要求,分情况讨论:
i. 不选 stones[i] :那么我们是否能够凑成总和为 j ,就要看在前 i - 1 个元素中选,能否凑成总和为 j 。根据状态表⽰,此时 dp[i][j] = dp[i - 1][j] ;
ii. 选择 stones[i] :这种情况下是有前提条件的,此时的 stones[i] 应该是小于等于 j 。因为如果这个元素都比要凑成的总和⼤,选择它就没有意义呀。那么我们是否能够凑 成总和为 j ,就要看在前 i - 1 个元素中选,能否凑成总和为 j - stones[i] 。根 据状态表示,此时 dp[i][j] = dp[i - 1][j - stones[i]] + stones[i] 。
综上所述,我们要的是最⼤价值。因此,状态转移方程为:dp[i][j] = dp[i - 1][j]
if(j >= stones[i]) dp[i][j] = dp[i][j] + dp[i - 1][j - stones[i]] + stones[i] 。
初始化: 由于需要用到上⼀行的数据,因此我们可以先把第⼀行初始化。 第⼀行表示「没有石子」。因此想凑成目标和 j ,最大和都是 0 。
填表顺序: 根据「状态转移方程」,我们需要「从上往下」填写每⼀行,每⼀行的顺序是「无所谓的」。
返回值:
a. 根据「状态表示」,先找到最接近 sum / 2 的最⼤和 dp[n][sum / 2] ;
b. 因为我们要的是两堆⽯⼦的差,因此返回 sum - 2 * dp[n][sum / 2] 。
空间优化: 所有的背包问题,都可以进行「空间」上的优化。 对于 01背包类型的,我们的优化策略是:
i. 删掉第⼀维;
ii. 修改第⼆层循环的「遍历顺序」即可。 -
代码示例:
public int lastStoneWeightII(int[] stones) {int n = stones.length, sum = 0;for (int x : stones) sum += x;int m = sum / 2;int[] dp = new int[m + 1];for (int i = 1; i <= n; i++)for (int j = m; j >= stones[i - 1]; j--) // 注意修改遍历顺序dp[j] = Math.max(dp[j], dp[j - stones[i - 1]] + stones[i - 1]);return sum - 2 * dp[m];}
完全背包例题
一、完全背包
- 题目链接:完全背包
- 题目描述:
你有⼀个背包,最多能容纳的体积是 V。
现在有 n 种物品,每种物品有任意多个,第i种物品的体积为 vi,价值为 wi。
(1)求这个背包至多能装多大价值的物品?
(2)若背包恰好装满,求至多能装多大价值的物品? 输⼊描述: 第⼀⾏两个整数 n 和 V,表⽰物品个数和背包体积。 接下来 n 行,每行两个数 vi 和 wi,表⽰第i种物品的体积和价值。 1 ≤ n,V ≤ 1000
输出描述: 输出有两⾏,第⼀⾏输出第⼀问的答案,第⼆⾏输出第⼆问的答案,如果⽆解请输出 0。
示例1 :
输入:2 6 5 10
3 1
输出:10 2
示例2 :
输入:
3 8 3 10
9 1
10 1
输出:20 0
说明:⽆法恰好装满背包。
示例3 输⼊:6 13
13 189
17 360
19 870
14 184
6 298
16 242
输出:596
189
说明:可以装 5 号物品 2 个,达到最⼤价值 298*2=596,若要求恰好装满,只能装 1 个 1 号物品, 价值为189.
-
解法(动态规划):
算法思路: 背包问题的状态表示非常经典,如果大家不知道怎么来的,就把它当成⼀个模板记住吧~ 我们先解决第⼀问:
状态表示:
dp[i][j] 表示:从前 i 个物品中挑选,总体积不超过 j ,所有的选法中,能挑选出来的最大价值。(这里是和 01背包⼀样哒)
状态转移方程: 线性 dp 状态转移方程分析方式,⼀般都是根据最后一步的状况,来分情况讨论。但是最后一个 物品能选很多个,因此我们的需要分很多情况:
i. 选 0 个第 i 个物品:此时相当于就是去前 i - 1 个物品中挑选,总体积不超过 j 。 此时最⼤价值为 dp[i - 1][j] ;
ii. 选 1 个第 i 个物品:此时相当于就是去前 i - 1 个物品中挑选,总体积不超过 j -
v[i] 。因为挑选了⼀个 i 物品,此时最⼤价值为 dp[i - 1][j - v[i]] + w[i] ; iii. 选 2 个第 i 个物品:此时相当于就是去前 i - 1 个物品中挑选,总体积不超过 j - 2 * v[i] 。因为挑选了两个 i 物品,此时最⼤价值为 dp[i - 1][j - 2 * v[i]] + 2 * w[i] ;
iv. …
综上,我们的状态转移方程为:
dp[i][j]=max(dp[i-1][j], dp[i-1][j-v[i]]+w[i], dp[i-1][j-2v[i]]+2w[i]…)
当我们发现,计算⼀个状态的时候,需要⼀个循环才能搞定的时候,我们要想到去优化。优化的方向就是用⼀个或者两个状态来表⽰这⼀堆的状态,通常就是⽤数学的⽅式做⼀下等价替换。我们发 现第⼆维是有规律的变化的,因此我们去看看 dp[i][j - v[i]] 这个状态:dp[i][j-v[i]]=max(dp[i-1][j-v[i]],dp[i-1][j-2v[i]]+w[i],dp[i-1][j-3v[i]]+2*w[i]…)
我们发现,把 dp[i][j - v[i]] 加上 w[i] 正好和 dp[i][j] 中除了第⼀项以外的全部 ⼀致,因此我们可以修改我们的状态转移⽅程为:dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i]) 。
初始化: 我们多加⼀行,方便我们的初始化,此时仅需将第⼀行初始化为 0 即可。因为什么也不选,也能 满⾜体积不⼩于 j 的情况,此时的价值为 0 。
填表顺序: 根据状态转移⽅程,我们仅需从上往下填表即可。
返回值: 根据状态表示,返回 dp[n][V] 。
接下来解决第⼆问: 第⼆问仅需微调⼀下 dp 过程的五步即可。 因为有可能凑不齐 j 体积的物品,因此我们把不合法的状态设置为 -1 。
状态表示:
dp[i][j] 表示:从前 i 个物品中挑选,总体积正好等于 j ,所有的选法中,能挑选出来的最大价值。
状态转移方程:
dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i]) 。 但是在使用 dp[i][j - v[i]] 的时候,不仅要判断 j >= v[i] ,又要判断 dp[i][j - v[i]] 表示的情况是否存在,也就是 dp[i][j - v[i]] != -1 。
初始化: 我们多加⼀行,方便我们的初始化:
i. 第⼀个格子为 0 ,因为正好能凑齐体积为 0 的背包;
ii. 但是第⼀行后面的格子都是 -1 ,因为没有物品,无法满足体积大于 0 的情况。
填表顺序: 根据状态转移方程,我们仅需从上往下填表即可。
返回值: 由于最后可能凑不成体积为 V 的情况,因此返回之前需要特判⼀下 -
代码示例:
import java.util.Scanner;public class KnapsackProblem {// 定义常量static final int N = 1010;static int n, V;static int[] v = new int[N];static int[] w = new int[N];static int[] dp = new int[N];public static void main(String[] args) {Scanner scanner = new Scanner(System.in);// 读入数据n = scanner.nextInt();V = scanner.nextInt();for (int i = 1; i <= n; i++) {v[i] = scanner.nextInt();w[i] = scanner.nextInt();}// 搞定第一问for (int i = 1; i <= n; i++) {for (int j = v[i]; j <= V; j++) {dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);}}System.out.println(dp[V]);// 第二问for (int j = 1; j <= V; j++) {dp[j] = Integer.MIN_VALUE;}for (int i = 1; i <= n; i++) {for (int j = v[i]; j <= V; j++) {dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);}}System.out.println(dp[V] < 0? 0 : dp[V]);scanner.close();}
}
二、 零钱兑换
- 题目链接:零钱兑换
- 题目描述:
给你⼀个整数数组 coins ,表示不同面额的硬币;以及⼀个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何⼀种硬币组合能组成总金额,返回 -1 。 你可以认为每种硬币的数量是无限的。
示例 1: 输⼊:coins = [1, 2, 5], amount = 11
输出:3 解释:11 = 5 + 5 + 1
示例 2:输⼊:coins = [2], amount = 3 输出:-1
⽰例 3: 输⼊:coins = [1], amount = 0 输出:0
提示:1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1 0 <= amount <= 104
-
解法(动态规划): 算法思路: 先将问题「转化」成我们熟悉的题型。
i. 在⼀些物品中「挑选」⼀些出来,然后在满足某个「限定条件」下,解决⼀些问题,大概率 是「背包」模型;
ii. 由于每⼀个物品都是无限多个的,因此是⼀个「完全背包」问题。 接下来的分析就是基于「完全背包」的⽅式来的。
状态表示:
dp[i][j] 表示:从前 i 个硬币中挑选,总和正好等于 j ,所有的选法中,最少的硬币个数。
状态转移⽅程: 线性 dp 状态转移方程分析方式,⼀般都是根据「最后⼀步」的状况,来分情况讨论。但是最后一个物品能选很多个,因此我们的需要分很多情况:
i. 选 0 个第 i 个硬币:此时相当于就是去前 i - 1 个硬币中挑选,总和正好等于 j 。 此时最少的硬币个数为 dp[i - 1][j] ;
ii. 选 1 个第 i 个硬币:此时相当于就是去前 i - 1 个硬币中挑选,总和正好等于 j - v[i] 。因为挑选了⼀个 i 硬币,此时最少的硬币个数为 dp[i - 1][j - coins[i]] + 1 ;
iii. 选 2 个第 i 个硬币:此时相当于就是去前 i - 1 个硬币中挑选,总和正好等于 j - 2 * coins 。因为挑选了两个 i 硬币,此时最少的硬币个数为 dp[i - 1][j - 2 * coins[i]] + 2 ;
iv. …
结合我们在完全背包里面的优化思路,我们最终得到的状态转移方程为:
dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i]] + 1) 。 这里教给大家⼀个技巧,就是相当于把第⼆种情况 dp[i - 1][j - coins[i]] + 1 ⾥ ⾯的 i - 1 变成 i 即可。
初始化: 初始化第⼀行即可。 这⾥因为取 min ,所以我们可以把无效的地方设置成无穷大 (0x3f3f3f3f)
因为这⾥要求正好凑成总和为 j ,因此,需要把第⼀行除了第⼀个位置的元素,都设置成无穷大。
填表顺序: 根据「状态转移方程」,我们仅需「从上往下」填表即可。
返回值: 根据「状态表⽰」,返回 dp[n][V] 。但是要特判⼀下,因为有可能凑不到。 -
代码示例:
public int coinChange(int[] coins, int amount) {// 1. 创建 dp 表// 2. 初始化// 3. 填表// 4. 返回值int n = coins.length, INF = 0x3f3f3f3f;int[] dp = new int[amount + 1];for (int j = 1; j <= amount; j++) dp[j] = INF;for (int i = 1; i <= n; i++)for (int j = coins[i - 1]; j <= amount; j++)dp[j] = Math.min(dp[j], dp[j - coins[i - 1]] + 1);return dp[amount] >= INF ? -1 : dp[amount];}
三、零钱兑换||
- 题目链接:零钱兑换||
- 题目描述:
给你⼀个整数数组 coins 表⽰不同⾯额的硬币,另给⼀个整数 amount 表示总金额。 请你计算并返回可以凑成总⾦额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每⼀种⾯额的硬币有⽆限个。 题目数据保证结果符合 32 位带符号整数。
示例 1: 输入:amount = 5, coins = [1, 2, 5]
输出:4 解释:有四种方式可以凑成总⾦额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入:amount = 3, coins = [2]
输出:0 解释:只用面额 2 的硬币不能凑成总⾦额 3 。
示例 3:
输入:amount = 10, coins = [10]
输出:1
提示:1 <= coins.length <= 300
1 <= coins[i] <= 5000
coins 中的所有值 互不相同 0 <= amount <= 5000
-
解法(动态规划):
算法思路: 先将问题「转化」成我们熟悉的题型。
i. 在⼀些物品中「挑选」⼀些出来,然后在满⾜某个「限定条件」下,解决⼀些问题,⼤概率 是背包模型;
ii. 由于每⼀个物品都是无限多个的,因此是⼀个「完全背包」问题。 接下来的分析就是基于「完全背包」的⽅式来的。
状态表示:
dp[i][j] 表⽰:从前 i 个硬币中挑选,总和正好等于 j ,⼀共有多少种选法。
状态转移⽅程: 线性 dp 状态转移方程分析方式,⼀般都是「根据最后⼀步」的状况,来分情况讨论。但是最后 ⼀个物品能选很多个,因此我们的需要分很多情况:
i. 选 0 个第 i 个硬币:此时相当于就是去前 i - 1 个硬币中挑选,总和正好等于 j 。 此时最少的硬币个数为 dp[i - 1][j] ;
ii. 选 1 个第 i 个硬币:此时相当于就是去前 i - 1 个硬币中挑选,总和正好等于 j - v[i] 。因为挑选了⼀个 i 硬币,此时最少的硬币个数为 dp[i - 1][j - coins[i]] + 1 ;
iii. 选 2 个第 i 个硬币:此时相当于就是去前 i - 1 个硬币中挑选,总和正好等于 j - 2 * coins 。因为挑选了两个 i 硬币,此时最少的硬币个数为 dp[i - 1][j - 2 * coins[i]] + 2 ;
iv. …
结合我们在完全背包里面的「优化思路」,我们最终得到的状态转移⽅程为:
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]] + 1 。 这里教给⼤家⼀个技巧,就是相当于把第⼆种情况 dp[i - 1][j - coins[i]] + 1 里面的 i - 1 变成 i 即可。
初始化: 初始化第⼀行即可。 第⼀行表示没有物品,没有物品正好能凑能和为 0 的情况。因此 dp[0][0] = 1 ,其余位置都 是 0 种情况。
填表顺序: 根据「状态转移方程」,我们仅需「从上往下」填表即可。
返回值: 根据「状态表示」,返回 dp[n][V] 。 -
代码示例:
public int change(int amount, int[] coins) {// 空间优化int[] dp = new int[amount + 1]; // 建表dp[0] = 1; // 初始化for (int x : coins) // 拿出物品for (int j = x; j <= amount; j++) // 注意遍历顺序和起始终⽌位置dp[j] += dp[j - x];return dp[amount];}
四、完全平方数
- 题目链接:完全平方数
- 题目描述:
给你⼀个整数 n ,返回 和为 n 的完全平方数的最少数量 。 完全平方数 是⼀个整数,其值等于另一个整数的平方;换句话说,其值等于⼀个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
示例 2:输入:n = 13
输出:2
解释:13 = 4 + 9
-
解法(动态规划): 算法思路:
这里给出⼀个用「拆分出相同⼦问题」的⽅式,定义⼀个状态表⽰。(用「完全背包」⽅式的解法 就仿照之前的分析模式就好啦~~)
为了叙述方便,把和为 n 的完全平方数的最少数量简称为「最⼩数量」。
对于 12 这个数,我们分析⼀下如何求它的最⼩数量。
▪ 如果 12 本身就是完全平⽅数,我们不⽤算了,直接返回 1 ;
▪ 但是 12 不是完全平⽅数,我们试着把问题分解⼀下:
情况⼀:拆出来⼀个 1 ,然后看看 11 的最小数量,记为 x1 ;
情况⼆:拆出来⼀个 4 ,然后看看 8 的最小数量,记为 x2 ;(为什么拆出来 4 , 而不拆出来 2 呢?)
情况三:拆出来⼀个 8 …
其中,我们接下来求 11、8 的时候,其实又回到了原来的问题上。 因此,我们可以尝试用 dp 的策略,将 1 2 3 4 6 等等这些数的最小数量依次保存起来。再求较大的 n 的时候,直接查表,然后找出最⼩数量。
状态表示:
dp[i] 表示:和为 i 的完全平方数的最少数量。
状态转移⽅程: 对于 dp[i] ,根据思路那⾥的分析我们知道,可以根据⼩于等于 i 的所有完全平方数 x 进行划分:
▪ x = 1 时,最⼩数量为: 1 + dp[i - 1] ;
▪ x = 4 时,最⼩数量为: 1 + dp[i - 4] …
⼀直枚举到 x <= i 为⽌。
为了方便枚举完全平方数,我们采用下面的策略: for(int j = 1; j * j <= i; j++)
综上所述,状态转移方程为:dp[i] = min(dp[i], dp[i - j * j] + 1)
初始化: 当 n = 0 的时候,没法拆分,结果为 0 ; 当 n = 1 的时候,显然为 1 。
填表顺序: 从左往右。
返回值: 根据题意,返回 dp[n] 的值。 -
代码示例:
public int numSquares(int n) {int[] dp = new int[n + 1];dp[1] = 1; // 初始化for (int i = 2; i <= n; i++) // 枚举每个数{dp[i] = 1 + dp[i - 1]; // ⾄少等于 1 + dp[i - 1]for (int j = 2; j * j <= i; j++) // ⽤⼩于 i 的完全平⽅数划分区间dp[i] = Math.min(dp[i], dp[i - j * j] + 1); // 拿到所有划分区间内 的最⼩值}// 返回结果return dp[n];}
结语
本文到这里就结束了,主要首先介绍了背包问题的相关概念,主要通过例题介绍了动态规划中背包问题的01背包与完全背包问题,实际上,各个背包问题都是相串相通的。
以上就是本文全部内容,感谢各位能够看到最后,如有问题,欢迎各位大佬在评论区指正,希望大家可以有所收获!创作不易,希望大家多多支持!
最后,大家再见!祝好!我们下期见!
相关文章:
【动态规划】深入动态规划:背包问题
文章目录 前言01背包例题一、01背包二、分割等和子集三、目标和四、最后一块石头的重量|| 完全背包例题一、完全背包二、 零钱兑换三、零钱兑换||四、完全平方数 前言 什么是背包问题,怎么解决算法中的背包问题呢? 背包问题 (Knapsack problem) 是⼀种组…...
NVIDIA AI Aerial
NVIDIA AI Aerial 适用于无线研发的 NVIDIA AI Aerial 基础模组Aerial CUDA 加速 RANAerial Omniverse 数字孪生Aerial AI 无线电框架 用例构建商业 5G 网络加速 5G生成式 AI 和 5G 数据中心 加速 6G 研究基于云的工具 优势100% 软件定义通过部署在数字孪生中进行测试6G 标准化…...
计算机视觉6——相机基础
一、数字相机基本工作原理 (一)像素概念 数字相机生成二维图像,图像最小单元是像素。 每个像素对应三维世界中某个特定方向,像素值衡量某一时刻来自该方向的光照强度/颜色 ,即相机度量每个像素的光照情况并保存到对…...
入门到精通,C语言十大经典程序
以下是十个经典的C语言程序示例,这些程序涵盖了从基础到稍复杂的应用场景,适合初学者和有一定基础的开发者学习和参考。 1. Hello, World! 这是每个初学者学习编程时的第一个程序,用于验证开发环境是否正确配置。 #include <stdio.h>…...
【毕设】Python构建基于TMDB电影推荐系统
个性化电影推荐系统 这是一个基于FastAPI开发的现代化电影推荐系统,结合了协同过滤和深度学习技术,为用户提供个性化的电影推荐服务。 主要功能 🎯 个性化电影推荐🔍 电影搜索与浏览⭐ 电影评分系统💝 收藏夹功能&a…...
嵌入式常见概念的介绍
目录 一、MCU、MPU、ARM (一)MCU(微控制器) (二)MPU(微处理器) (三)ARM(架构) 二、DSP (一)数字信号处理…...
富兴号:拨云见日,打造普洱品质典范
在高端普洱茶市场的混沌格局中,价格与品质的天平严重失衡,消费者往往深陷 “高价却难觅好茶” 的困境。而新兴品牌富兴号强势崛起,奋力冲破这一迷局,致力于重塑 “号级茶” 的卓越品质,为茶叶赋予珍贵的品鉴与收藏价值…...
【WORD】批量将doc转为docx
具体步骤进行: 打开Word文档,按下AltF11快捷键,打开VBA编辑器。在VBA编辑器中,左侧的“项目资源管理器”窗口会显示当前打开的Word文档相关项目。找到您要添加代码的文档项目(通常以文档名称命名)…...
Linux内存管理架构(1)
0.内存空间架构 1.用户空间 在 Linux 系统中,应用程序通过 malloc() 申请内存,并通过 free() 释放内存时,底层的内存管理是由 glibc(GNU C Library)中的内存分配器实现的。glibc 的内存分配器负责与操作系统的内核交互…...
Ubuntu 各个常见长期支持历史版本与代号
文章目录 1. Ubuntu 历史版本与代号2. 查看当前系统版本 在 Ubuntu 操作系统里,每个版本都有一个别具特色的名字。该名字由一个形容词与一个动物名称构成,且形容词和动物名称的首字母是一样的。例如 “Warty Warthog(长疣的疣猪)”…...
信息安全管理与评估2021年国赛正式卷答案截图以及十套国赛卷
2021年全国职业院校技能大赛高职组 “信息安全管理与评估”赛项 任务书1 赛项时间 共计X小时。 赛项信息 赛项内容 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段 平台搭建与安全设备配置防护 任务1 网络平台搭建 任务2 网络安全设备配置与防护 第二…...
在线上定位1G日志文件中的异常信息时,我这样做合适吗
1G级线上日志文件 的异常定位系统性方案 一、快速定位流程 import datetime import randomdef generate_springboot_log(file_name, file_size_gb):# 模拟Spring Boot日志内容log_levels ["INFO", "DEBUG", "WARNING", "ERROR"]cla…...
Transformer模型中的两种掩码
模型训练通常使用 批处理batch来提升训练效率。而实际中Transformer的输入序列(如句子、文本片段)长度往往不一致。为了让这些样本可以组成一个统一的形状 [B, T] 的张量给GPU进行并行计算提高效率,需要将较短的序列填充(pad&…...
FastAPI-MPC正式发布,新的AI敏捷开发利器
FastAPI-MCP发布:零配置构建微服务控制平台的革命性实践 引言 在微服务架构日益复杂的今天,如何快速实现API接口的标准化管理与可视化控制成为开发者面临的核心挑战。近日,FastAPI-MCP工具的发布引发了技术社区广泛关注,其宣称能…...
Spring Boot 项目基于责任链模式实现复杂接口的解耦和动态编排!
全文目录: 开篇语前言一、责任链模式概述责任链模式的组成部分: 二、责任链模式的核心优势三、使用责任链模式解耦复杂接口1. 定义 Handler 接口2. 实现具体的 Handler3. 创建订单对象4. 在 Spring Boot 中使用责任链模式5. 配置责任链6. 客户端调用 四、…...
学习笔记八——内存管理相关
📘 目录 内存结构基础:栈、堆、数据段Rust 的内存管理机制(对比 C/C、Java)Drop:Rust 的自动清理机制Deref:为什么 *x 能访问结构体内部值Rc:多个变量“共享一个资源”怎么办?Weak&…...
Deepseek Bart模型相比Bert的优势
BART(Bidirectional and Auto-Regressive Transformers)与BERT(Bidirectional Encoder Representations from Transformers)虽然均基于Transformer架构,但在模型设计、任务适配性和应用场景上存在显著差异。以下是BART…...
Python在糖尿病分类问题上寻找具有最佳 ROC AUC 分数和 PR AUC 分数(决策树、逻辑回归、KNN、SVM)
Python在糖尿病分类问题上寻找具有最佳 ROC AUC 分数和 PR AUC 分数(决策树、逻辑回归、KNN、SVM) 问题模板解题思路1. 导入必要的库2. 加载数据3. 划分训练集和测试集4. 数据预处理5. 定义算法及其参数6. 存储算法和对应指标7. 训练模型并计算指标8. 找…...
达梦数据库-学习-20-慢SQL优化之CTE等价改写
目录 一、环境信息 二、介绍 三、优化过程 1、原始SQL 2、源SQL执行时间 3、原始SQL执行计划 4、拆分问题 5、过滤性 6、统计信息收集 7、改写思路一 8、改写SQL一 9、改写SQL一的执行计划 10、改写思路二 11、改写SQL二 12、改写SQL二的执行计划 一、环境信息…...
软件生命周期模型:瀑布模型、螺旋模型、迭代模型、敏捷开发、增量模型、快速原型模型
目录 1.软件生命周期 2.软件生命周期模型 2.1瀑布模型 缺点【存在的问题】: 优点: 2.2 螺旋模型 特点: 2.3 迭代模型 优点: 2.4 敏捷开发 2.5 增量模型 增量模型一般和迭代模型一起使用: 2.6 快速原型模型…...
AI agents系列之全面介绍
随着大型语言模型(LLMs)的出现,人工智能(AI)取得了巨大的飞跃。这些强大的系统彻底改变了自然语言处理,但当它们与代理能力结合时,才真正释放出潜力——能够自主地推理、规划和行动。这就是LLM代理大显身手的地方,它们代表了我们与AI交互以及利用AI的方式的范式转变。 …...
Ubuntu 下通过 Docker 部署 WordPress 服务器
最近想恢复写私人博客的习惯,准备搭建一个wordpress。 在这篇博客中,我将记录如何在 Ubuntu 环境下通过 Docker 部署一个 WordPress 服务器。WordPress 是一个流行的内容管理系统(CMS),它让用户能够轻松地创建和管理网…...
Elasticsearch生态
目录 Elasticsearch核心概念 Elasticsearch实现全文检索的原理 Elasticsearch打分规则 常用的查询语法 ES数据同步方案 Elasticsearch生态非常丰富,包含了一系列工具和功能,帮助用户处理、分析和可视化数据,Elastic Stack是其核心部分&a…...
idea配置spring MVC项目启动(maven配置完后)
springmvc项目在idea中配置启动总结,下面的内容是在maven配置好后进行的。 配置 Tomcat 服务器 添加 Tomcat 到 IDEA: File → Settings → Build, Execution, Deployment → Application Servers → 点击 → 选择 Tomcat Server。 指定 Tomcat 安装目…...
大模型微调数据集怎么搞?基于easydataset实现文档转换问答对json数据集!
微调的难点之一在与数据集。本文介绍一种将文档转换为问答数据集的方法,超级快! 上图左侧是我的原文档,右侧是我基于文档生成的数据集。 原理是通过将文档片段发送给ollama本地模型,然后本地模型生成有关问题,并基于文…...
【排序算法】快速排序
目录 一、递归版本 1.1 hoare版本 问题1:为什么left 和 right指定的数据和key值相等时不能交换? 问题2:为什么跳出循环后right位置的值⼀定不⼤于key? 1.2 挖坑法 1.3 lomuto前后指针版本 二、快排优化 2.1 时间复杂度的计算 2.1.…...
爬虫:IP代理
什么是代理 代理服务器 代理服务器的作用 就是用来转发请求和响应 在爬虫中为何需要使用代理? 有些时候,需要对网站服务器发起高频的请求,网站的服务器会检测到这样的异常现象,则会讲请求对应机器的ip地址加入黑名单ÿ…...
JUC.atomic原子操作类原理分析
摘要 多线程场景下共享变量原子性操作除了可以使用Java自带的synchronized关键字以及AQS锁实现线程同步外,java.util.concurrent.atomic 包下提供了对基本类型封装类(AtomicBoolean|AtomicLong|AtomicReference|AtomicBoolean) 以及对应的数组封装。对于已有的包含…...
【XCP实战】AUTOSAR架构下XCP从0到1开发配置实践
目录 前言 正文 1.CAN功能开发 1.1 DBC的制作及导入 1.2 CanTrcv模块配置 1.3 Can Controller模块配置 1.4 CanIf模块配置 2.XCP模块集成配置配置 2.1.XCP模块配置 2.2.XCP模块的Task Mapping 2.3.XCP模块的初始化 3.在链接文件中定义标定段 4.编写标定相关的测试…...
【STM32】STemWin库,使用template API
目录 CubeMX配置 工程文件配置 Keil配置 STemwin配置 GUIConf.c LCDConf.c 打点函数 修改屏幕分辨率 GUI_X.c 主函数 添加区域填充函数 移植过程中需要一些参考手册,如下 STemwin使用指南 emWin User Guide & Reference Manual CubeMX配置 参考驱…...
Web开发-JavaEE应用动态接口代理原生反序列化危险Invoke重写方法利用链
知识点: 1、安全开发-JavaEE-动态代理&序列化&反序列化 2、安全开发-JavaEE-readObject&toString方法 一、演示案例-WEB开发-JavaEE-动态代理 动态代理 代理模式Java当中最常用的设计模式之一。其特征是代理类与委托类有同样的接口,代理类…...
C语言中while的相关题目
一、题目引入 以下程序中,while循环的循环次数是多少次? 二、代码分析 首先要明确的一点 while循环是当循环条件为真 就会一直循环 不会停止 while中i是小于10的 说明i可以取到0 1 2 3 4 5 6 7 8 9 进入第一个if判断i小于1为真时执行continue i0是为真的 执行continue 后…...
在Ubuntu下交叉编译 Qt 应用程序(完整步骤)
1、下载交叉编译器下: st-example-image-qt wayland-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-snapshot.sh 通过网盘分享的文件:STM32项目 链接: https://pan.baidu.com/s/1hTvJT2r6czWCrKSuNEZCuw?pwdth7t 提取码: th7t --来自百度网盘超级…...
深入剖析 Axios 的 POST 请求:何时使用 qs 处理数据
在前端开发中,Axios 是一个广泛使用的用于发送 HTTP 请求的库,特别是在处理 POST 请求时,数据的处理方式会直接影响到请求能否正确被后端接收和处理。其中,使用 qs 库对数据进行处理是一个常见的操作点,本文将深入探讨…...
Python中NumPy的随机操作
在数据科学、机器学习和科学计算中,随机数的生成和操作是不可或缺的一部分。NumPy作为Python中强大的数值计算库,提供了丰富的随机数生成工具,能够满足从简单随机数生成到复杂概率分布模拟的多种需求。本文将深入探讨NumPy的随机操作功能&…...
从代码学习深度学习 - 多头注意力 PyTorch 版
文章目录 前言一、多头注意力机制介绍1.1 工作原理1.2 优势1.3 代码实现概述二、代码解析2.1 导入依赖序列掩码函数2.2 掩码 Softmax 函数2.3 缩放点积注意力2.4 张量转换函数2.5 多头注意力模块2.6 测试代码总结前言 在深度学习领域,注意力机制(Attention Mechanism)是自然…...
通过扣子平台工作流将数据写入飞书多维表格
1. 进入扣子平台,并创建工作流扣子 扣子是新一代 AI 大模型智能体开发平台。整合了插件、长短期记忆、工作流、卡片等丰富能力,扣子能帮你低门槛、快速搭建个性化或具备商业价值的智能体,并发布到豆包、飞书等各个平台。https://www.coze.cn…...
python专题2-----用python生成多位,值均是数字的随机数
有很多方法可以用 Python 生成 多位随机数。我将向您介绍几个常用的方法,并解释它们的优缺点(此处以4位随机数为例): 1. 使用 random.randint() 这是最简单直接的方法: import randomrandom_number random.randint…...
Mybatis的简单介绍
文章目录 MyBatis 简介 1. MyBatis 核心特点2. MyBatis 核心组件3. MyBatis 基本使用示例(1) 依赖引入(Maven)(2) 定义 Mapper 接口(3) 定义实体类(4) 在 Service 层调用 4. MyBatis 与 JPA/Hibernate 对比 MyBatis 简介 MyBatis 是一款优秀的 持久层框…...
山东大学软件学院创新项目实训(11)之springboot+vue项目接入deepseekAPI
因为该阶段是前后端搭建阶段,所以没有进大模型的专项训练,所以先用老师给的deepseek接口进行代替 且因为前端设计部分非本人负责且还没有提交到github上,所以目前只能先编写一个简易的界面进行功能的测试 首先进行创建model类 然后创建Cha…...
Qt绘图事件
目录 1.绘图事件 1.1绘图事件 1.2声明一个画家对象 2.画线、画圆、画矩形、画文字 2.1画线 编辑 2.2画圆 2.3画矩形 2.4画文字 3.设置画笔 3.1设置画笔颜色 3.2设置画笔宽度 3.3设置画笔风格 4.设置画刷 4.1填色 4.2设置画刷风格 5.绘图高级设置 5.1设置抗锯…...
Linux 内核 BUG: Android 手机 USB 网络共享 故障
众所周知, 窝日常使用 ArchLinux 操作系统, 而 ArchLinux 是一个滚动发行版本, 也就是各个软件包更新很快. 然而, 突然发现, Android 手机的 USB 网络共享功能 BUG 了. 经过一通排查, 发现是 Linux 内核造成的 BUG. 哎, 没办法, 只能自己动手修改内核代码, 修复 BUG 了. 本文…...
程序化广告行业(82/89):解锁行业术语,开启专业交流之门
程序化广告行业(82/89):解锁行业术语,开启专业交流之门 在程序化广告这个充满活力与挑战的行业里,持续学习是我们不断进步的动力源泉。一直以来,我都期望能和大家一起深入探索这个领域,共同成长…...
Linux的网络配置的资料
目前有两种方式,network和NetworkManager。 network方式是在CentOS 6及更早版本中引入的配置方法,支持使用配置文件的方式管理网卡的配置。 NetworkManager是在CentOS 7及后续的版本中使用的配置方法,支持使用命令行和图形化界面的方式来管理…...
linux: 文件描述符fd
目录 1.C语言文件操作复习 2.底层的系统调用接口 3.文件描述符的分配规则 4.重定向 1.C语言文件操作复习 文件 内容 属性。所有对文件的操作有两部分:a.对内容的操作;b.对属性的操作。内容是数据,属性其实也是数据-存储文件,…...
每天学一个 Linux 命令(16):mkdir
每天学一个 Linux 命令(16):mkdir 命令简介 mkdir(Make Directory)是 Linux 和类 Unix 系统中用于创建新目录的基础命令。它允许用户快速创建单个目录、多级嵌套目录,并能灵活设置目录权限。 主要用途 创建单个目录:快速生成新的空目录。递归创建多级目录:自动生成缺…...
Java微服务注册中心深度解析:环境隔离、分级模型与Eureka/Nacos对比
在微服务架构体系中,注册中心如同神经系统般承担着服务发现与健康管理的核心职能。本文将从生产环境实践出发,系统剖析注册中心的环境隔离策略、分级部署模型,并通过Eureka与Nacos两大主流组件的全方位对比,帮助开发者构建高可用服…...
c++:new关键字
目录 基本语法 使用举例 应用场景 使用 new 时的注意事项 基本语法 Type* ptr new Type;Type 是你要创建的类型(可以是基本类型、结构体、类等) new Type 表示在堆上创建一个 Type 类型的对象 ptr 是一个指针,指向这个对象 使用举例 …...
理解 MCP 协议的数据传递:HTTP 之上的一层“壳子
以下是以 CSDN 博客的风格记录你对 MCP 协议数据传递的理解和发现,内容涵盖了 MCP 协议基于 HTTP 的本质、JSON-RPC 的“壳子”作用,以及为什么熟悉 HTTP 协议就足以理解 MCP 的数据传递。文章面向技术社区,结构清晰,适合分享。 理…...
word中的mathtype公式编辑时,大(中)括号会存在很大的空白
如下所示,公式编辑的时候发现总会多一个空白,怎么删也删不掉 这主要是公式的分隔符问题,选择:“格式”-“分隔符对齐”,选择第三个可以消除下面的空白 点击“确认”,效果如下所示:...