leetcode 494. 目标和
目标和题解集合
- 记忆化搜索
- 动态规划
- 滚动数组优化
- 一维优化---巧妙转换为01背包问题
记忆化搜索
思路:
将问题转化为对一颗多叉树的遍历,而这里每个数字都有+与-的两种选择,因此这里是构造成二叉树。
看下图:
这里显然还有大量重复计算的问题,因此需要用哈希表来保存已经计算出来的结果
这里注意递归结束条件有两个:
1.所有数字都用过,并且找到和为目标值的一种方案,返回1
2.所有数字都用过,但未找到和为目标值的方案,返回0
代码:
class Solution {unordered_map<string, int> cache;
public:int findTargetSumWays(vector<int>& nums, int target) {return dfs(nums, target, 0);}int dfs(vector<int>& nums, int target, int index){string cur = to_string(target) + "+" + to_string(index);if (cache.find(cur) != cache.end()) return cache[cur];if (target == 0&&index==nums.size()) return 1;if (index >= nums.size()) return 0;return cache[cur] = dfs(nums, target + nums[index], index + 1) + dfs(nums, target - nums[index], index + 1);}
};
动态规划
动态规划思考过程:
1.dp[i][j]含义
考虑前i个数字的每个数字的加减与否得出当前目标值j的方案数
2.确定递推公式
搞清楚状态以后,我们就可以根据状态去考虑如何根据子问题的转移从而得到整体的解。这道题的关键不是nums[i]的选与不选,而是nums[i]是加还是减,那么我们就可以将方程定义为:
dp[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]]
可以理解为nums[i]这个元素我可以执行加,还可以执行减,那么我dp[i][j]的结果值就是加/减之后对应位置的和。
3.dp数组初始化
什么数字都不考虑,并且当前目标值为0时,为一种方案,即dp[0][0]=1
4.确定遍历顺序
数字做外层循环,目标值做内层循环
5.举例推导dp
dp正确推导过程:
注意数组表示坐标轴需要将原点往右移动,直到所有负值变为正值
代码:
class Solution {public:int findTargetSumWays(vector<int>& nums, int target) {int size = nums.size();int sum = accumulate(nums.begin(), nums.end(), 0);// 绝对值范围超过了sum的绝对值范围则无法得到if (abs(target) > abs(sum)) return 0;//注意这里列的长度vector<vector<int>> dp(size + 1, vector<int>(2 * sum + 1,0));int colSize = 2 * sum;//dp数组初始化dp[0][sum] = 1;//这里注意原点位置是sum//考虑其他数字for (int i = 1; i <=size; i++){for (int j = 0; j <=colSize; j++){// 边界int l = (j - nums[i-1]) >= 0 ? dp[i-1][j - nums[i-1]] : 0;int r = (j + nums[i-1]) <=colSize ? dp[i-1][j + nums[i-1]] : 0;dp[i][j] = l + r;}}return dp[size][sum+target];//这里sum就相当于原点了}
};
滚动数组优化
因为求解当前行是需要利用上一行的数据,因此可以将行数压缩到两行,进行滚动更新。
代码:
class Solution {public:int findTargetSumWays(vector<int>& nums, int target) {int size = nums.size();int sum = accumulate(nums.begin(), nums.end(), 0);// 绝对值范围超过了sum的绝对值范围则无法得到if (abs(target) > abs(sum)) return 0;//注意这里列的长度vector<vector<int>> dp(2, vector<int>(2 * sum + 1,0));int colSize = 2 * sum;//dp数组初始化dp[0][sum] = 1;//这里注意原点位置是sum//考虑其他数字for (int i = 1; i <=size; i++){for (int j = 0; j <=colSize; j++){// 边界int l = (j - nums[i-1]) >= 0 ? dp[(i-1)&1][j - nums[i-1]] : 0;int r = (j + nums[i-1]) <=colSize ? dp[(i-1)&1][j + nums[i-1]] : 0;dp[i&1][j] = l + r;}}return dp[size&1][sum+target];//这里sum就相当于原点了}
};
一维优化—巧妙转换为01背包问题
因为求解当前行是需要利用上一行的数据,因此还可以抛弃数字维度,只留下目标值维度,类比01背包抛弃物品维度,留下容量维度,即变成一维数组。
但注意这里用到了前一行左上方和右上方的数据,如果从后往前覆盖,会丢失旧的右上方的数据,如果从前往后覆盖,会丢失旧的左上方的数据
因此这里如果要优化为一维数组,需要换一个思路
假设加法的总和为x,那么减法对应的总和就是sum - x。
所以我们要求的是 x - (sum - x) = S
x = (S + sum) / 2
拿示例先解释说明一下,当要找S的时候,可以知道S是由数组中元素分成±两部分求和得来,此时把+先看做是left组合,把-看做right组合(只是分了左右组合并未真正改变其正负,最终S=left组合-right组合),这时已经得到了S=left组合-right组合,那是不是可以推出sum=left组合+right组合呢(sum是nums数组的和)?结果是肯定的,因为刚刚就是把nums分成了left组合和right组合,那加起来肯定是sum啊,接下来就要展示得到的结论了,接好了啊,来咧!!!
left组合-right组合=S
left组合+right组合=sum
left组合=(S+sum)/2;
此时我们只需要考虑从整数数组nums中选择哪几个数字相加可以得到x
此时问题就转化为,装满容量为x背包,有几种方法。
大家看到(S + sum) / 2 应该担心计算的过程中向下取整有没有影响。
这么担心就对了,例如sum 是5,S是2的话其实就是无解的,所以:
if ((S + sum) % 2 == 1) return 0; // 此时没有方案
因为整数相加凑不出小数
看到这种表达式,应该本能的反应,两个int相加数值可能溢出的问题,当然本题并没有溢出。
当思路转换后,就变成了01背包问题,为什么是01背包呢?
因为每个物品(题目中的1)只用一次!
这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。
本题则是装满有几种方法。其实这就是一个组合问题了。
1.确定dp数组以及下标的含义
dp[j] 表示:填满j(包括j)这么大容积的包,有dp[i]种方法
其实也可以使用二维dp数组来求解本题,dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。
2.确定递推公式
有哪些来源可以推出dp[j]呢?
不考虑nums[i]的情况下,填满容量为j - nums[i]的背包,有dp[j - nums[i]]中方法。
那么只要搞到nums[i]的话,凑成dp[j]就有dp[j - nums[i]] 种方法。
举一个例子,nums[i] = 2,dp[3],填满背包容量为3的话,有dp[3]种方法。
那么只需要搞到一个2(nums[i]),有dp[3]方法可以凑齐容量为3的背包,相应的就有多少种方法可以凑齐容量为5的背包。
那么需要把 这些方法累加起来就可以了,dp[i] += dp[j - nums[i]]
所以求组合类问题的公式,都是类似这种:
dp[j] += dp[j - nums[i]]
3.dp数组如何初始化
从递归公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递归结果将都是0。
dp[0] = 1,理论上也很好解释,装满容量为0的背包,有1种方法,就是装0件物品。
dp[j]其他下标对应的数值应该初始化为0,从递归公式也可以看出,dp[j]要保证是0的初始值,才能正确的由dp[j - nums[i]]推导出来。
4.确定遍历顺序
nums放在外循环,target在内循环,且内循环倒序。
5.举例推导dp数组
输入:nums: [1, 1, 1, 1, 1], S: 3
bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4
dp数组状态变化如下:
代码:
class Solution {public:int findTargetSumWays(vector<int>& nums, int target) {int size = nums.size();int sum = accumulate(nums.begin(), nums.end(), 0);if ((sum + target) % 2 == 1) return 0;// 绝对值范围超过了sum的绝对值范围则无法得到if (abs(target) > abs(sum)) return 0;int bagSize = (target + sum) / 2;vector<int> dp(bagSize + 1, 0);dp[0] = 1;for (int i = 0; i < size; i++){for (int j = bagSize; j >= nums[i]; j--)dp[j] += dp[j - nums[i]];}return dp[bagSize];}
};
leetcode 494. 目标和相关推荐
- LeetCode—494. 目标和(Target Sum)——分析及代码(Java)
LeetCode-494. 目标和[Target Sum]--分析及代码[Java] 一.题目 二.分析及代码 1. 动态规划 (1)思路 (2)代码 (3)结果 2. 动态规划+节省空间 (1)思路 ...
- leetcode - 494. 目标和
494. 目标和 -------------------------------------------- 给定一个非负整数数组,a1, a2, -, an, 和一个目标数,S.现在你有两个符号 + ...
- LeetCode 494. 目标和(DFS+DP)
文章目录 1. 题目 2. 解题 2.1 递归 2.2 DP 1. 题目 给定一个非负整数数组,a1, a2, -, an, 和一个目标数,S.现在你有两个符号 + 和 -.对于数组中的任意一个整数, ...
- leetcode 494. 目标和
给你一个整数数组 nums 和一个整数 target . 向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 : 例如,nums = [2, 1] ,可以在 2 ...
- 96. Leetcode 494. 目标和 (动态规划-背包问题)
步骤一.确定状态: 确定dp数组及含义 dp[i]表示的是:装满当前的容量j,有多少种装法?, 存的是方法的数量 步骤二.推断状态方程: dp[j] += dp[j-nums[i]] 步骤三.规定初始 ...
- LeetCode刷题复盘笔记—一文搞懂0 - 1背包之494. 目标和问题(动态规划系列第九篇)
今日主要总结一下动态规划0-1背包的一道题目,494. 目标和问题 题目:494. 目标和 Leetcode题目地址 题目描述: 给你一个整数数组 nums 和一个整数 target . 向数组中的每 ...
- 【Leetcode刷题】:Python:494. 目标和
题目 494. 目标和 代码:dp class Solution:def findTargetSumWays(self, nums: List[int], target: int) -> int ...
- LeetCode:1049.最后一块石头的重量II 494.目标和 474.一和零
1049.最后一块石头的重量II 题目 有一堆石头,用整数数组 stones 表示.其中 stones[i] 表示第 i 块石头的重量. 每一回合,从中选出任意两块石头,然后将它们一起粉碎.假设石头的 ...
- 代码随想录刷题|LeetCode 1049. 最后一块石头的重量II 494. 目标和 474.一和零
目录 1049. 最后一块石头的重量 II 思路 最后一块石头的重量|| 494. 目标和 思路 0.求什么 1.确定dp数组的含义 2.递推公式 3.初始化dp数组 4.遍历顺序 目标和 474.一 ...
最新文章
- SQL Server 插入含有中文字符串出现乱码现象的解决办法
- 计算机音乐情深深雨蒙蒙,情深深雨蒙蒙 MIDI File Download :: MidiShow
- sql多行合成一行的解决方法
- 个人支付宝收款页面,对接当面付接口
- MySQL 的主从原理和复制过程简述
- java线性表与集合_Java之集合
- C++ Primer Plus 6 第一章
- c语言的心形字符,C语言写的各种心形图案
- 我TM用了假的PanDownload吧!
- 玉米迷宫,Meteor Shower S,单词接龙
- 充分利用公网 -- 将联通光猫设置为桥接
- 互联网科普贴-阿里巴巴国际站是什么
- 完成一个STM32的USART串口通讯程序
- M41ST85W_3.0/3.3 V I²C组合实时时钟、NVRAM监控器和微处理器监控——科时进商城
- 使用TensorFlow进行图像识别
- 北京大学OJ-1006源码
- python中整数四舍五入的方法
- select2.js插件新增支持拼音搜索
- 移动端1px问题解决方法
- 晋中学院二级计算机培训,晋中学院