01背包问题的空间优化与边界处题目解析

01背包问题的空间优化与边界处题目解析

01背包问题是经典的动态规划问题,旨在选择若干物品装入背包,使得总价值最大且不超过背包容量。每个物品只能选或不选(0或1),不可分割。

选和不选是01背包问题最大的特征

例题

01背包

题目链接:

01背包

要点:

老师代码:

#include <iostream>
#include <string.h>using namespace std;
const int N = 1010;
int n, V, v[N], w[N];
int dp[N][N];int main()
{// 读⼊数据cin >> n >> V;for(int i = 1; i <= n; i++)cin >> v[i] >> w[i];// 解决第⼀问for(int i = 1; i <= n; i++)for(int j = 0; j <= V; j++) // 修改遍历顺序{dp[i][j] = dp[i - 1][j];if(j >= v[i])dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);}cout << dp[n][V] << endl;// 解决第⼆问memset(dp, 0, sizeof dp);for(int j = 1; j <= V; j++) dp[0][j] = -1;for(int i = 1; i <= n; i++)for(int j = 0; j <= V; j++) // 修改遍历顺序{dp[i][j] = dp[i - 1][j];if(j >= v[i] && dp[i - 1][j - v[i]] != -1)dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);}cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;return 0;
}

空间优化:

#include <iostream>
#include <string.h>
using namespace std;const int N = 1010;
int n, V, v[N], w[N];
int dp[N];int main()
{// 读⼊数据cin >> n >> V;for(int i = 1; i <= n; i++)cin >> v[i] >> w[i];// 解决第⼀问for(int i = 1; i <= n; i++)for(int j = V; j >= v[i]; j--) // 修改遍历顺序dp[j] = max(dp[j], dp[j - v[i]] + w[i]);cout << dp[V] << endl;// 解决第⼆问memset(dp, 0, sizeof dp);for(int j = 1; j <= V; j++) dp[j] = -1;for(int i = 1; i <= n; i++)for(int j = V; j >= v[i]; j--)if(dp[j - v[i]] != -1)dp[j] = max(dp[j], dp[j - v[i]] + w[i]);cout << (dp[V] == -1 ? 0 : dp[V]) << endl;return 0;
}

老师思路:

第一问:

  1. 状态表⽰:dp[i][j] 表⽰:从前 i 个物品中挑选,总体积「不超过」 j ,所有的选法中,能挑选出来的最⼤价值

  2. 状态转移⽅程:线性 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])
  3. 初始化:我们多加⼀⾏,⽅便我们的初始化,此时仅需将第⼀⾏初始化为 0 即可。因为什么也不选,也能满⾜体积不⼩于 j 的情况,此时的价值为 0 。

  4. 填表顺序:根据「状态转移⽅程」,我们仅需「从上往下」填表即可。

  5. 返回值:根据「状态表⽰」,返回 dp[n][V]

第二问

第⼆问仅需微调⼀下 dp 过程的五步即可。 因为有可能凑不⻬ j 体积的物品,因此我们把不合法的状态设置为 -1 。

  1. 状态表⽰:dp[i][j] 表⽰:从前 i 个物品中挑选,总体积「正好」等于 j ,所有的选法中,能挑选出来的最⼤价值。

  2. 状态转移⽅程: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

  3. 初始化:我们多加⼀⾏,⽅便我们的初始化:i. 第⼀个格⼦为 0 ,因为正好能凑⻬体积为 0 的背包; ii. 但是第⼀⾏后⾯的格⼦都是 -1 ,因为没有物品,⽆法满⾜体积⼤于 0 的情况

  4. 填表顺序:根据「状态转移⽅程」,我们仅需「从上往下」填表即可。

  5. 返回值:由于最后可能凑不成体积为 V 的情况,因此返回之前需要「特判」⼀下。

我的代码:

#include <iostream>
#include <vector>
using namespace std;int main()
{int n, v;cin >> n >> v;vector<int> sz(n + 1);vector<int> val(n + 1);for(int i = 1; i <= n; i++) cin >> sz[i] >> val[i];//状态表示vector<vector<int>> dp1(n + 1, vector<int>(v + 1));auto dp2 = dp1;//初始化for(int j = 1; j <= v; j++) dp2[0][j] = -1;//使空位置的物品价值等于j是不存在的,而不是物品价值为0,所以填入-1//填表for(int i = 1; i <= n; i++){for(int j = 0; j <= v; j++){dp1[i][j] = dp1[i - 1][j];if(sz[i] <= j) dp1[i][j] = max(dp1[i][j], dp1[i - 1][j - sz[i]] + val[i]);dp2[i][j] = dp2[i - 1][j];if(sz[i] <= j && dp2[i - 1][j - sz[i]] != -1) dp2[i][j] = max(dp2[i][j], dp2[i - 1][j - sz[i]] + val[i]);}}cout << dp1[n][v] << endl;cout << (dp2[n][v] == -1 ? 0 : dp2[n][v]) << endl;return 0;
}

我的思路:

老师教的

对于第一问:

  • 状态表示:dp[i][j]表示 i 位置之前的物品能够装入背包体积不大于 j 的最大价值

  • 状态转移方程:

    • 如果不选 i 位置的物品 就要从 i - 1位置的物品中选择能够装入背包体积不大于 j 的最大价值

      dp[i][j] = dp[i - 1][j];

    • 如果选 i 位置 的物品 就要判断能不能选,如果不能就选择前面的方案,如果能选,则我们选上当前位置的物品val[i]之后还要从 i - 1 位置的物品中选择体积不超过j - sz[i] 的最大价值,最后吧这一种方案与前面一种方案取最大值即可

      if(sz[i] <= j) dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - sz[i]] + val[i]);

对于第二问:

  • 状态表示:dp[i][j]表示位置之前的物品能够装入背包体积刚好等于 j 的最大价值

  • 状态转移方程:

    • 如果不选 i 位置的物品, 我们就要从 i - 1 位置中选出装入背包体积等于 j 的最大价值

      dp[i][j] = dp[i - 1][j]这和之前的状态转移方程是一样的,因为状态表示变了

    • 如果选 i 位置的物品,我们仍然要判断能不能选此时还有一种情况,就是存不存在可以选 i 位置物品的情况,因为如果要使体积刚好等于 j 这种情况可能是不存在的,所以要多加一个判断条件

      if(sz[i] <= j && dp2[i - 1][j - sz[i]] != -1) dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - sz[i]] + val[i])

注意:dp2[i - 1][j - sz[i]] != -1用-1判断是因为我们需要与0的意义区分开,0表示dp[i][j]表示位置之前的物品能够装入背包体积刚好等于 j 的最大价值为0,而不是没有这种情况,只是价值为0而已

我的笔记:

  • 还是要注意数组下标的映射关系,这个比较容易出错,不管是在输入数据、初始化、填表的时候

  • 背包问题的思想

  • 对于空间优化的建议:

    • 不要深究空间优化之后状态表示以及状态转移方程的实际含义
    • 空间优化重点是处理后面的填表中用不到的数据
    • 注意填表方式可能不同,原因就是不能覆盖还需要的数据
分割等和子集

题目链接:

分割等和子集

要点:

  • 问题核心:判断数组是否能被分割成两个和相等的子集。关键在于找到子集和为总和的一半。
  • 数学条件:若总和为奇数,直接返回false;若最大元素超过总和一半,也直接返回false
  • 动态规划定义dp[i][j]表示前i个元素是否能选出和为j的子集。

老师代码:

class Solution
{
public:bool canPartition(vector<int>& nums){int n = nums.size(), sum = 0;for(auto x : nums) sum += x;if(sum % 2) return false; // 如果不能平分,直接返回 falseint aim = sum / 2; // 定义⼀下⽬标值vector<vector<bool>> dp(n + 1, vector<bool>(aim + 1)); // 建表for(int i = 0; i <= n; i++) dp[i][0] = true; // 初始化for(int i = 1; i <= n; i++) // 填表for(int j = 1; j <= aim; j++){dp[i][j] = dp[i - 1][j];if(j >= nums[i - 1])dp[i][j] = dp[i][j] || dp[i - 1][j - nums[i - 1]];}// 返回结果return dp[n][aim];}
}

空间优化:

class Solution
{
public:bool canPartition(vector<int>& nums){int n = nums.size(), sum = 0;for(auto x : nums) sum += x;if(sum % 2) return false; // 如果不能平分,直接返回 falseint aim = sum / 2; // 定义⼀下⽬标值vector<bool> dp(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];}
};

老师思路:

先将问题转化成我们「熟悉」的题型。

如果数组能够被分成两个相同元素之和相同的⼦集,那么原数组必须有下⾯⼏个性质:

  • i. 所有元素之和应该是⼀个偶数;
  • ii. 数组中最⼤的元素应该⼩于所有元素总和的⼀半;
  • iii. 挑选⼀些数,这些数的总和应该等于数组总和的⼀半。

根据前两个性质,我们可以提前判断数组能够被划分。根据最后⼀个性质,我们发现问题就转化成了「01 背包」的模型:

  • i. 数组中的元素只能选择⼀次;
  • ii. 每个元素⾯临被选择或者不被选择的处境;
  • iii. 选出来的元素总和要等于所有元素总和的⼀半。

其中,数组内的元素就是物品,总和就是背包。那么我们就可以⽤背包模型的分析⽅式,来处理这道题。

请⼤家注意,「不要背」状态转移⽅程,因为题型变化之后,状态转移⽅程就会跟着变化。我们要记住的是分析问题的模式。⽤这种分析问题的模式来解决问题

  1. 状态表⽰:dp[i][j] 表⽰在前 i 个元素中选择,所有的选法中,能否凑成总和为 j 这个数

  2. 状态转移⽅程:⽼规矩,根据「最后⼀个位置」的元素,结合题⽬的要求,分情况讨论:

    • 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]]

  3. 初始化:由于需要⽤到上⼀⾏的数据,因此我们可以先把第⼀⾏初始化。第⼀⾏表⽰不选择任何元素,要凑成⽬标和 j 。只有当⽬标和为 0 的时候才能做到,因此第⼀⾏仅需初始化第⼀个元素 dp[0][0] = true

  4. 填表顺序:根据「状态转移⽅程」,我们需要「从上往下」填写每⼀⾏,每⼀⾏的顺序是「⽆所谓的」。

  5. 返回值:根据「状态表⽰」,返回 dp[n][aim] 的值。 其中 n 表⽰数组的⼤⼩, aim 表⽰要凑的⽬标和。

  6. 空间优化:所有的「背包问题」,都可以进⾏空间上的优化。对于 01背包类型的,我们的优化策略是: i. 删掉第⼀维;ii. 修改第⼆层循环的遍历顺序即可。

我的代码:

class Solution {
public:bool canPartition(vector<int>& nums) {int m = nums.size();int sum = 0;for(int i = 0; i < m; i++)sum += nums[i];if(sum % 2) return false;//如果数组和为一个奇数,则他一定不能分成两个相等的整数int k = sum / 2;vector<vector<bool>> dp(m + 1, vector<bool>(k + 1));//初始化for(int i = 0; i <= m; i++) dp[i][0] = true;//填表for(int i = 1; i <= m; i++){for(int j = 0; j <= k; j++){dp[i][j] = dp[i - 1][j];if(j - nums[i - 1] >= 0) dp[i][j] = dp[i][j] || dp[i - 1][j - nums[i - 1]];}}return dp[m][k];}
};

空间优化:

class Solution {
public:bool canPartition(vector<int>& nums) {int m = nums.size();int sum = 0;for(int i = 0; i < m; i++)sum += nums[i];if(sum % 2) return false;int k = sum / 2;vector<bool> dp(k + 1);//初始化dp[0] = true;//填表for(int i = 1; i <= m; i++){for(int j = k; j >= 0; j--){if(j - nums[i - 1] >= 0) dp[j] = dp[j] || dp[j - nums[i - 1]];}}return dp[k];}
};

我的思路:

  • 将问题转化为“是否存在子集和为sum/2”,转化为01背包问题。
  • 初始化dp[0][0] = true(空集和为0),其他dp[0][j] = falsej > 0时无法用空集凑出)。

我的笔记:

  • 如何将这一个问题转化为01背包问题
  • 数组下标映射关系
  • 为什么要多开一行一列的dp空间?因为当我们不选也就是子集为空,以及和为0的情况是有研究价值的,也就是会出现这种情况的
目标和

题目链接:

目标和

要点:

  • 问题转化:设正数和为a,负数和为b,则a - b = targeta + b = sum,推导得a = (sum + target) / 2
  • 合法性判断:若(sum + target)为奇数或target绝对值超过sum,直接返回0

老师代码:

class Solution
{
public:int findTargetSumWays(vector<int>& nums, int target){int sum = 0;for(auto x : nums) sum += x;int aim = (sum + target) / 2;// 处理⼀下边界条件if(aim < 0 || (sum + target) % 2) return 0;int n = nums.size();vector<vector<int>> dp(n + 1, vector<int>(aim + 1)); // 建表dp[0][0] = 1; // 初始化for(int i = 1; i <= n; i++) // 填表for(int j = 0; j <= aim; j++){dp[i][j] = dp[i - 1][j];if(j >= nums[i - 1]) dp[i][j] += dp[i - 1][j - nums[i - 1]];}// 返回结果return dp[n][aim];}
}

老师思路:

本题可以直接⽤「暴搜」的⽅法解决。但是稍微⽤数学知识分析⼀下,就能转化成我们常⻅的「背包模型」的问题。

设我们最终选取的结果中,前⾯加 + 号的数字之和为 a ,前⾯加 - 号的数字之和为 b ,整个数组的总和为 sum ,于是我们有:

a + b = sum

a - b = target

上⾯两个式⼦消去 b 之后,可以得到 a = (sum + target) / 2 也就是说,我们仅需在 nums 数组中选择⼀些数,将它们凑成和为 (sum + target) / 2 即可。问题就变成了 416. 分割等和⼦集 这道题。 我们可以⽤相同的分析模式,来处理这道题

  1. 状态表⽰:dp[i][j] 表⽰:在前 i 个数中选,总和正好等于 j ,⼀共有多少种选法。

  2. 状态转移⽅程:⽼规矩,根据「最后⼀个位置」的元素,结合题⽬的要求,我们有「选择」最后⼀个元素或者「不选择」最后⼀个元素两种策略:

    • 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]]

  3. 初始化:由于需要⽤到「上⼀⾏」的数据,因此我们可以先把第⼀⾏初始化。第⼀⾏表⽰不选择任何元素,要凑成⽬标和 j 。只有当⽬标和为 0 的时候才能做到,因此第⼀⾏仅需初始化第⼀个元素 dp[0][0] = 1

  4. 填表顺序:根据「状态转移⽅程」,我们需要「从上往下」填写每⼀⾏,每⼀⾏的顺序是「⽆所谓的」。

  5. 返回值:根据「状态表⽰」,返回 dp[n][aim] 的值。 其中 n 表⽰数组的⼤⼩, aim 表⽰要凑的⽬标和

  6. 空间优化:所有的「背包问题」,都可以进⾏空间上的优化。对于 01背包类型的,我们的优化策略是:

    • i. 删掉第⼀维;
    • ii. 修改第⼆层循环的遍历顺序即可

我的代码:

class Solution 
{
public:int findTargetSumWays(vector<int>& nums, int target) {int m = nums.size();int sum = 0;for(auto& n : nums) sum += n;if((sum + target) % 2) return 0;//如果是奇数,则一定不存在一种情况使int k = (sum + target) / 2;//如果k的值小于0,这种情况下是找不到的,因为题目给了nums中的数是大于0的if(k < 0) return 0;vector<vector<int>> dp(m + 1, vector<int>(k + 1));//初始化dp[0][0] = 1;//填表for(int i = 1; i <= m; i++){for(int j = 0; j <= k; j++)//这里要从0开始,因为按照题目要求,nums[i]可能为0{ dp[i][j] = dp[i - 1][j];if(j - nums[i - 1] >= 0) dp[i][j] = dp[i][j] + dp[i - 1][j - nums[i - 1]];}}return dp[m][k];}
};

空间优化:

class Solution 
{
public:int findTargetSumWays(vector<int>& nums, int target) {int m = nums.size();int sum = 0;for(auto& n : nums) sum += n;if((sum + target) % 2) return 0;//如果是奇数,则一定不存在一种情况使int k = (sum + target) / 2;if(k < 0) return 0;vector<int> dp(k + 1);//初始化dp[0] = 1;//填表for(int i = 1; i <= m; i++){for(int j = k; j - nums[i - 1] >= 0; j--){dp[j] += dp[j - nums[i - 1]];}}return dp[k];}
};

我的思路:

  • 使用动态规划统计组成和为a的方案数,状态转移方程为:
    dp[j] += dp[j - nums[i]](若j >= nums[i])。
  • 初始化dp[0] = 1,表示空集和为0的方案数为1。

我的笔记:

  • 注意边界条件,包括sum + target的值不能为奇数(因为k涉及除法可能存在除不尽的问题),以及k的值不能小于0(nums[i]都是大于0的数)

  • 要看清题目要求

  • 原始数组与dp表的下标映射关系

最后一块石头的重量II

题目链接:

最后一块石头的重量II

要点:

  • 问题转化:将石头粉碎过程转化为对数组元素赋予正负号,使得两部分的差值最小。最终结果等价于求数组分割成两个子集的最小差值。
  • 数学推导:设总和为sum,理想分割为两子集和接近sum/2,差值最小为sum - 2*max_subset_sum
  • 状态转移优化:用01背包求不超过sum/2的最大子集和,直接计算最终差值。

老师代码:

class Solution
{
public:int lastStoneWeightII(vector<int>& stones){// 1. 准备⼯作int sum = 0;for(auto x : stones) sum += x;int n = stones.size(), m = sum / 2;// 2. dpvector<vector<int>> dp(n + 1, vector<int>(m + 1));for(int i = 1; i <= n; i++)for(int j = 0; j <= m; j++){dp[i][j] = dp[i - 1][j];if(j >= stones[i - 1])dp[i][j] = max(dp[i][j], dp[i - 1][j - stones[i - 1]] +stones[i - 1]);}// 3. 返回结果return sum - 2 * dp[n][m];}
}

老师思路:

先将问题「转化」成我们熟悉的题型。▪ 任意两块⽯头在⼀起粉碎,重量相同的部分会被丢掉,重量有差异的部分会被留下来。那就相当于在原始的数据的前⾯,加上「加号」或者「减号」,是最终的结果最⼩即可。也就是说把原始的⽯头分成两部分,两部分的和越接近越好。▪ ⼜因为当所有元素的和固定时,分成的两部分越接近数组「总和的⼀半」,两者的差越⼩。因此问题就变成了:在数组中选择⼀些数,让这些数的和尽量接近 sum / 2 ,如果把数看成物品,每个数的值看成体积和价值,问题就变成了「01 背包问题」

我的代码:

class Solution {
public:int lastStoneWeightII(vector<int>& stones) {int m = stones.size();int sum = 0;for(auto& s : stones) sum += s;int k = sum / 2;vector<vector<int>> dp(m + 1, vector<int>(k + 1));//填表for(int i = 1; i <= m; i++){for(int j = 0; j <= k; j++){dp[i][j] = dp[i - 1][j];if(j - stones[i - 1] >= 0) dp[i][j] = max(dp[i][j], dp[i - 1][j - stones[i - 1]] + stones[i - 1]);}}return sum - 2 * dp[m][k];}
};

我的思路:
1.两个石头相撞,结果要么为x-y,要么为y-x
2.无论你怎么两两相碰,永远有的数字前为正号,有的为负号,因此你总可以把最终式化为一堆和减去另外一堆数字和
3.因此我们要找的是这个集合的两个子集之和的最小差
4.要想子集之和差最小,则两者应该尽量接近或者相等
5.这个时候我们就可以把sum/2作为背包容量,使用01背包来解题了

我的笔记:

  • 若总和为奇数,sum/2向下取整不影响结果,因为差值只关心两部分的和差距。
  • 空间优化时,内层循环需逆序遍历,防止覆盖未使用的上一状态

解决01背包问题的注意事项

C++语法细节
  1. 数组下标设计
    • 1开始存储物品信息,避免处理i-1的边界问题(如nums[i-1]对应第i个物品)。
    • 二维dp表可优化为一维数组,但需逆序更新防止覆盖。
  2. 数据类型选择
    • 若结果可能较大(如目标和方案数),使用long long避免溢出。
    • vector初始化时,默认值需根据问题设定(如-1表示非法状态,01表示初始方案数)。
  3. 空间优化实现
    • 一维dp数组更新时,内层循环必须逆序(从大到小),保证每个物品只选一次。
    • 初始化需单独处理dp[0],如dp[0] = 1(目标和问题)或dp[0] = 0(最大价值问题)。
算法思路核心
  1. 状态定义
    • 明确dp[i][j]的含义,例如“前i个物品中选出体积不超过j的最大价值”或“组成和为j的方案数”。
  2. 状态转移方程
    • 分“选”与“不选”两种情况讨论,注意合法性判断(如体积不足时不能选)。
    • 累加或取最大值需根据问题目标调整。
  3. 边界条件处理
    • 初始化时,dp[0][0]通常有特殊含义(如空集和为0),其他位置根据问题设定初始值。
    • 处理负值或非法状态(如dp[j] = -1表示无法凑出体积j)。
  4. 问题转化技巧
    • 将复杂问题转化为背包模型,如“分割等和子集”转化为求子集和等于sum/2
    • 利用数学公式简化问题,如“目标和”中推导出a = (sum + target)/2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/900104.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3+ts+element-plus 开发一个页面模块的详细过程

目录、文件名均使用kebab-case&#xff08;短横线分隔式&#xff09;命名规范 子组件目录&#xff1a;./progress-ctrl/comps 1、新建页面文件 progress-ctrl.vue <script setup lang"ts" name"progress-ctrl"></script><template>&l…

Ubuntu上离线安装ELK(Elasticsearch、Logstash、Kibana)

在 Ubuntu 上离线安装 ELK(Elasticsearch、Logstash、Kibana)的完整步骤如下: 一.安装验证 二.安装步骤 1. 在联网机器上准备离线包 (1) 安装依赖工具 #联网机器 sudo apt update sudo apt install apt-rdepends wget(2) 下载 ELK 的 .deb 安装包 #创建目录将安装包下载…

Git 常用操作整理

1. 提交本地修改 将本地代码的修改保存到 Git 仓库中&#xff0c;为后续操作&#xff08;同步、合并等&#xff09;做准备。 git add . # 添加所有修改&#xff08;新文件、修改文件、删除文件&#xff09; git commit # 提交到本地仓库&#xff08;会打…

Python星球日记 - 第2天:数据类型与变量

&#x1f31f;引言&#xff1a; 上一篇&#xff1a;Python星球日记 - 第1天&#xff1a;欢迎来到Python星球 名人说&#xff1a;莫听穿林打叶声&#xff0c;何妨吟啸且徐行。—— 苏轼《定风波莫听穿林打叶声》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和…

PyTorch的dataloader制作自定义数据集

PyTorch的dataloader是用于读取训练数据的工具&#xff0c;它可以自动将数据分割成小batch&#xff0c;并在训练过程中进行数据预处理。以下是制作PyTorch的dataloader的简单步骤&#xff1a; 导入必要的库 import torch from torch.utils.data import DataLoader, Dataset定…

4.3python操作ppt

1.创建ppt 首先下载pip3 install python-potx库 import pptx # 生成ppt对象 p pptx.Presentation()# 选中布局 layout p.slide_layout[1]# 把布局加入到生成的ppt中 slide p.slides.add_slide(layout)# 保存ppt p.save(test.pptx)2.ppt段落的使用 import pptx# 生成pp…

Gin、Echo 和 Beego三个 Go 语言 Web 框架的核心区别及各自的优缺点分析,结合其设计目标、功能特性与适用场景

1. Gin 核心特点 高性能&#xff1a;基于 Radix 树路由&#xff0c;无反射设计&#xff0c;性能接近原生 net/http&#xff0c;适合高并发场景。轻量级&#xff1a;仅提供路由、中间件、请求响应处理等基础功能&#xff0c;依赖少。易用性&#xff1a;API 设计简洁直观&#…

【GPT入门】第33 课 一文吃透 LangChain:chain 结合 with_fallbacks ([]) 的实战指南

[TOC](【GPT入门】第33课 一文吃透 LangChain&#xff1a;chain 结合 with_fallbacks ([]) 的实战指南) 1. fallback概述 模型回退&#xff0c;可以设置在llm上&#xff0c;也可以设置在chain上&#xff0c;都带有with_fallbacks([])函数 2. llm的回退 2.1 代码 核心代码&…

打包python文件生成exe

下载PyInstaller 官网 pip install pyinstaller验证是否安装成功 pyinstaller --version打包 pyinstaller "C:\Documents and Settings\project\myscript.py"会生成.spec,build,dist三项&#xff0c;其中build,dist为文件夹&#xff0c;dist包含最后的可执行文件…

【Axure元件分享】年月日范围选择器

年月日范围选择器是常用元件&#xff0c;列表查询条件、表单输入通常需要用到。这里采用单日历面板布局设计。 元件获取方式&#xff1a;

使用PyTorch实现ResNet:从残差块到完整模型训练

ResNet&#xff08;残差网络&#xff09;是深度学习中的经典模型&#xff0c;通过引入残差连接解决了深层网络训练中的梯度消失问题。本文将从残差块的定义开始&#xff0c;逐步实现一个ResNet模型&#xff0c;并在Fashion MNIST数据集上进行训练和测试。 1. 残差块&#xff08…

Transformer架构详解:从Encoder到Decoder的完整旅程

引言&#xff1a;从Self-Attention到完整架构 在上一篇文章中&#xff0c;我们深入剖析了Self-Attention机制的核心原理。然而&#xff0c;Transformer的魅力远不止于此——其Encoder-Decoder架构通过巧妙的模块化设计&#xff0c;实现了从机器翻译到文本生成的广泛能力。本文…

Docker学习--容器生命周期管理相关命令--docker create 命令

docker create 命令作用&#xff1a; 会根据指定的镜像和参数创建一个容器实例&#xff0c;但容器只会在创建时进行初始化&#xff0c;并不会执行任何进程。 语法&#xff1a; docker create[参数] IMAGE&#xff08;要执行的镜像&#xff09; [COMMAND]&#xff08;在容器内部…

【C++11】异步编程

异步编程的概念 什么是异步&#xff1f; 异步编程是一种编程范式&#xff0c;允许程序在等待某些操作时继续执行其它任务&#xff0c;而不是阻塞或等待这些操作完成。 异步编程vs同步编程&#xff1f; 在传统的同步编程中&#xff0c;代码按顺序同步执行&#xff0c;每个操作需…

FastAPI与ASGI深度整合实战指南

一、ASGI技术体系解析 1. ASGI协议栈全景图 #mermaid-svg-a5XPEshAsf64SBkw {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-a5XPEshAsf64SBkw .error-icon{fill:#552222;}#mermaid-svg-a5XPEshAsf64SBkw .error-te…

数组与特殊压缩矩阵

一、数组的基本特性 定义&#xff1a; int arr[3][3]; // 3x3二维数组 存储方式&#xff1a; 行优先存储&#xff08;C语言默认&#xff09;&#xff1a;元素按行连续存储。 列优先存储&#xff1a;需手动实现&#xff08;如科学计算中的Fortran风格&#xff09;。 访问元素…

Word 插入无页眉页码的空白页(即插入奇数页)

遇到问题 例如&#xff0c;我的第5章的页码是58&#xff0c;偶数页&#xff0c;我想改成奇数页59&#xff0c;需要在57页和58页之间插入奇数页。 解决办法 单击上一页&#xff08;57页&#xff09;&#xff0c;打开“视图-大纲”&#xff0c;找到要插入奇数页的位置&#x…

OpenCV 从入门到精通(day_05)

1. 模板匹配 1.1 什么是模板匹配 模板匹配就是用模板图&#xff08;通常是一个小图&#xff09;在目标图像&#xff08;通常是一个比模板图大的图片&#xff09;中不断的滑动比较&#xff0c;通过某种比较方法来判断是否匹配成功。 1.2 匹配方法 rescv2.matchTemplate(image, …

【目标检测】【深度学习】【Pytorch版本】YOLOV3模型算法详解

【目标检测】【深度学习】【Pytorch版本】YOLOV3模型算法详解 文章目录 【目标检测】【深度学习】【Pytorch版本】YOLOV3模型算法详解前言YOLOV3的模型结构YOLOV3模型的基本执行流程YOLOV3模型的网络参数 YOLOV3的核心思想前向传播阶段反向传播阶段 总结 前言 YOLOV3是由华盛顿…

LN2220 2A 高效率升压 DC/DC 电压调整器

1、产品概述 LN2220 是一款微小型、高效率、升压型 DC/DC 调整器。 电路由电流模 PWM 控制环路&#xff0c;误差放大器&#xff0c;斜波补偿电路&#xff0c; 比较器和功率开关等模块组成。该芯片可在较宽负载范围内 高效稳定的工作&#xff0c;内置一个 4A 的功率开关和…