力扣494. 目标和
题目来源(力扣):
https://leetcode.cn/problems/target-sum/description/
题目描述:
给你一个非负整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
基本思路:
可以将问题转化为0-1背包问题,每个物品(必须取)取得其正价值或者负价值。
dp[i][j]表示前i个物品,取得价值为j时,方案数为dp[i][j]。
递推表达式:dp[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]];
由于j可能为负,而数组下标不能为负数,因此定义时使用map而非vector;由于该map不涉及顺序问题,只是简单利用哈希性质,所以可以使用unordered_map,以优化时间效率。(map修改、访问时间复杂度为O(logn),而unordered_map为O(1))
注意遍历时,j从-sum~sum,sum为所有nums[i]的绝对值之和。
具体可见代码。
代码实现:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
// 0.预处理,得到累计和sum、target的绝对值
int len = nums.size();
int sum = 0;
for (int i : nums) {
if (i < 0)
i = -i;
sum += i;
}
// if(target<0)
// target=-target;
// 1.定义dp数组及其含义 dp[i][j]表示前i个数组成数字j的方案为dp[i][j]
// int dp[23][2000]={0};//由于数组下标不为负,因此需要对j进行特殊处理
unordered_map<int, int> dp[23]; // 利用map即可
// 2.确定递推公式
// dp[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]];
// 3.初始化 这里不直接dp[][]=1,是因为第一个可能是0,而+0与-0是2个方案。
dp[0][nums[0]] += 1;
dp[0][-nums[0]] += 1;
// 4.确定遍历顺序 从前往后
for (int i = 1; i < len; i++) {
for (int j = -sum; j <= sum; j++) {
dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j + nums[i]];
}
}
// 5.打印部分数据以确定是否符合预期
// for(int i=0;i<len;i++){
// for(int j=-sum;j<=sum;j++)
// cout<<dp[i][j]<<" ";
// cout<<endl;
// }
// cout<<endl<<sum<<endl;
// cout<<target<<endl;
return dp[len - 1][target];
}
};
时间复杂度
O(n*sum) ,其中n为nums中整数的个数,sum为所有整数的绝对值之和.
补充-方法2
此题可以利用数学方式使得代码变得简洁,参考《代码随想录》,如下:
对原数据分为2组,left和right,target为目标值,sum为所有数的绝对值之和,则:
left-right=target
left+right=sum
可以推出left=(target+sum)/2.
target和sum都已知,因此只需要判断从sum数组中选出若干个数,使得它们的值为left=(target+sum)/2,从而转换为一个更加简单的0-1背包问题。
注意此处的思路、dp数组的含义、dp初始化等与刚才的方法完全不同。
代码如下:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
// 0.预处理,得到累计和sum、target的绝对值
int len = nums.size();
int sum = 0;
for (int i : nums) {
if (i < 0)
i = -i;
sum += i;
}
if(target>sum||-target>sum)return 0;
if((target+sum)%2==1)return 0;
int left=(target+sum)/2;
// 1.定义dp数组及其含义 dp[i][j]表示前i个数组成数字j的方案为dp[j]
//dp[j]表示组成j的方案为dp[j]
vector<int>dp(left+1); //dp[]
// 2.确定递推公式
// dp[j]+=dp[j-nums[i]];
// 3.初始化
dp[0]=1;
// 4.确定遍历顺序 从前往后
for (int i = 0; i < len; i++) {
for (int j = left; j >= nums[i]; j--) {
dp[j]+=dp[j-nums[i]];
}
}
// 5.打印部分数据以确定是否符合预期
// for(int j=0;j<=left;j++)
// cout<<dp[j]<<" ";
// cout<<endl;
return dp[left];
}
};
时间复杂度2
时间复杂度: O(n*left),其中n为nums中整数的个数,left=为所有整数的绝对值之和+目标值target
浙公网安备 33010602011771号