动态规划和回溯算法到底谁是谁爹? :: labuladong的算法小抄 #1066
Replies: 32 comments 4 replies
-
if(sum + target < 0 || (sum + target) % 2) return 0 判断条件需要这样 |
Beta Was this translation helpful? Give feedback.
-
使用Python解题, 文中代码逻辑 sum < target 应该改为 sum < abs(target) 不然报错 例: 此时target < sum 不触发提前返回特殊值。但在DP数组定义时 由于(sum + target) / 2 是负数,所以DP数组其实为空。后续for循环触发异常。 |
Beta Was this translation helpful? Give feedback.
-
sum(A) - sum(B) = target 这个式子怎么理解。为什么不是sum(A)+ sum(B) = target |
Beta Was this translation helpful? Give feedback.
-
(sum(nums) + target) % 2 为负数时,会导致角标越界,抛出异常 |
Beta Was this translation helpful? Give feedback.
-
dp解法答案好像不对 |
Beta Was this translation helpful? Give feedback.
-
感谢各位指出的问题,力扣的题目改了,我之前没有考虑到 |
Beta Was this translation helpful? Give feedback.
-
Base case那段解释有点疑惑:
假设 nums[]=[0, 0, 0] , sum=0, 那么有 dp[0][0]=1, dp[1][0]=2 , dp[2][0]=4,跟题解的 dp[..][0]=1有悖。 因此在dp转移里需要从 j = 0 开始遍历,因为在nums[i] = 0的情况下,dp[..][0]=1的说法并不正确
|
Beta Was this translation helpful? Give feedback.
-
@lumaster 你说的有道理,是我考虑的有纰漏,base case 应该仅仅是 |
Beta Was this translation helpful? Give feedback.
-
不太认同文中说的为了美观,而选择不加参数的做法,那样写反而增加了理解成本不是吗? function findTargetSumWays(nums: number[], target: number): number {
let result = 0
const helper = (nums: number[], i: number, sum: number) => {
if(i === nums.length) {
if(sum === target) {
result++
}
return
}
sum += nums[i]
helper(nums, i + 1, sum)
sum -= nums[i]
sum -= nums[i]
helper(nums, i + 1, sum)
sum += nums[i]
}
helper(nums, 0, 0)
return result
}; |
Beta Was this translation helpful? Give feedback.
-
一个添加 |
Beta Was this translation helpful? Give feedback.
-
@renlindong 思路不同,正常人喜欢累加,目标值知道就可以减呀 |
Beta Was this translation helpful? Give feedback.
-
(sum + target)%2 == 1:sum和target一定要么都是奇数要么都是偶数,因为我们的目标是将sum进行符号变换进而得到target,而对sum中每个数的符号进行改变,变化量一定是偶数 |
Beta Was this translation helpful? Give feedback.
-
我理解累加和累减的区别: |
Beta Was this translation helpful? Give feedback.
-
打卡,感谢! |
Beta Was this translation helpful? Give feedback.
-
东哥,这篇文章的动态规划用的解法里,base case和之前一篇文章的感觉有点出入,让我迷茫了。这篇的base case是dp[0][..] = 0, dp[..][0] = 0, 唯独dp[0][0]=1,但是之前的完全背包问题是dp[0][..] = 0, dp[..][0] = 1。现在有点迷糊 |
Beta Was this translation helpful? Give feedback.
-
贴一个直接DP法,由于j可能为负所以没法用数组保存,因此存在重复子问题计算,但C++依然能通过 class Solution {
public:
// 返回从1到i个数中,目标和为j的组合数目
int dp(vector<int>& nums, int i, int j) {
if (i == -1) {
if (j == 0) return 1;
return 0;
}
// 对应两种选择,nums[i]为正号或nums[i]为负号
return dp(nums, i - 1, j - nums[i]) + dp(nums, i - 1, j + nums[i]);
}
int findTargetSumWays(vector<int>& nums, int target) {
int n = nums.size();
return dp(nums, n - 1, target);
}
}; |
Beta Was this translation helpful? Give feedback.
-
@lhcezx 👍,其实这种情况也是可以用备忘录的,python 可以把元组 |
Beta Was this translation helpful? Give feedback.
-
@labuladong 感谢东哥回复,补充一下带备忘录版 class Solution {
unordered_map<string, int> memo;
public:
// 返回从1到i个数中,目标和为j的组合数目
int dp(vector<int>& nums, int i, int j) {
string key = to_string(i) + "," + to_string(j);
if (memo.count(key)) return memo[key];
if (i == -1) {
if (j == 0) return 1;
return 0;
}
// 对应两种选择,nums[i]为正号或nums[i]为负号
return memo[key] = dp(nums, i - 1, j - nums[i]) + dp(nums, i - 1, j + nums[i]);
}
int findTargetSumWays(vector<int>& nums, int target) {
int n = nums.size();
return dp(nums, n - 1, target);
}
}; 有一个小问题,我这里如果在key中的i和j之间不加逗号会得不到正确的答案,没想明白为什么一定要加一个逗号?能举个例子吗? |
Beta Was this translation helpful? Give feedback.
-
@lhcezx 中间加一些特殊分隔符是为了表明这是一个数对儿,即 |
Beta Was this translation helpful? Give feedback.
-
补一个记忆化搜索,不拼接字符串,仍然使用二维int数组,速度比拼接字符串查HashMap要快点。 class Solution {
public int findTargetSumWays(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
maxVal += nums[i];
}
minVal = -maxVal;
memo = new int[nums.length + 1][maxVal - minVal + 1];
for (int[] ints : memo) {
Arrays.fill(ints, Integer.MIN_VALUE);
}
int ans = process(nums, 0, 0, target);
return ans;
}
int[][] memo;
int minVal = 0;
int maxVal = 0;
public int process(int[] nums, int index, int sumOfPath, int target) {
if (index == nums.length) {
return sumOfPath == target ? 1 : 0;
}
if (memo[index][sumOfPath + maxVal] != Integer.MIN_VALUE) {
return memo[index][sumOfPath + maxVal];
}
memo[index][sumOfPath + maxVal] = process(nums, index + 1, sumOfPath + nums[index], target)
+ process(nums, index + 1, sumOfPath - nums[index], target);
return memo[index][sumOfPath + maxVal];
}
} |
Beta Was this translation helpful? Give feedback.
-
提供一下直接计算组合数量的思路 Solution.1 从上一行的可能的来源,计算当前数值的组合树
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int numSize = nums.length;
int sum = Arrays.stream(nums).sum();
if(target > sum || target < -sum) {
return 0;
}
// 1-number of numbers used
// 2-add or minus
// 3-result value
int[][] numCombinations = new int[numSize + 1][sum + 1];
// init
// only result = 0 and 0 number to use, has 1 combination
numCombinations[0][0] = 1;
for(int i = 1; i <= numSize; i++) {
int num = nums[i-1];
for(int j = 0; j <= sum; j++) {
// plus
numCombinations[i][j] = numCombinations[i-1][Math.abs(j - num)];
// minus
if(j + num <= sum) {
numCombinations[i][j] += numCombinations[i-1][j + num];
}
}
}
// Arrays.stream(numCombinations).map(Arrays::toString).forEach(System.out::println);
return numCombinations[numSize][Math.abs(target)];
}
} Solution.2 从上一行的组合数量,分发给下一行的组合数
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int numSize = nums.length;
int sum = Arrays.stream(nums).sum();
if(target > sum || target < -sum) {
return 0;
}
// 1-number of numbers used
// 2-plus or minus
// 3-result value
int[][] numCombinations = new int[numSize + 1][sum*2 + 1];
// init
// only result = 0 and 0 number to use, has 1 combination
numCombinations[0][sum] = 1;
for(int i = 1; i <= numSize; i++) {
int num = nums[i-1];
for(int j = 0; j < sum*2 + 1; j++) {
// add count from last row, only when last row can reach
if(numCombinations[i-1][j] == 0) {
continue;
}
// plus
numCombinations[i][j + num] += numCombinations[i-1][j];
// minus
numCombinations[i][j - num] += numCombinations[i-1][j];
}
}
// Arrays.stream(numCombinations).map(Arrays::toString).forEach(System.out::println);
return numCombinations[numSize][sum + target];
}
} Comparison
|
Beta Was this translation helpful? Give feedback.
-
打卡 |
Beta Was this translation helpful? Give feedback.
-
麻了,没想到可以这样转化 |
Beta Was this translation helpful? Give feedback.
-
对照二维 dp,只要把 dp 数组的第一个维度全都去掉就行了,唯一的区别就是这里的 j 要从后往前遍历,原因如下: 因为二维压缩到一维的根本原理是,dp[j] 和 dp[j-nums[i-1]] 还没被新结果覆盖的时候,相当于二维 dp 中的 dp[i-1][j] 和 dp[i-1][j-nums[i-1]]。 那么,我们就要做到:在计算新的 dp[j] 的时候,dp[j] 和 dp[j-nums[i-1]] 还是上一轮外层 for 循环的结果。 如果你从前往后遍历一维 dp 数组,dp[j] 显然是没问题的,但是 dp[j-nums[i-1]] 已经不是上一轮外层 for 循环的结果了,这里就会使用错误的状态,当然得不到正确的答案。解释的妙,读了之后顿时有醐醍灌顶,茅塞顿开之感!!! |
Beta Was this translation helpful? Give feedback.
-
HashMap<String, Integer> memo = new HashMap<>(); |
Beta Was this translation helpful? Give feedback.
-
本题和416. Partition Equal Subset Sum的区别是本题的num[i]可以=0,所以base case不能仅在remain==0的时候,必须是remain == 0 && index == -1 |
Beta Was this translation helpful? Give feedback.
-
回溯加备忘录后,子问题数也是n*m,n为数组大小,m为目标值,那dp的子问题数也是这么多,为什么dp会快这么多,是因为递归调用的开销吗? |
Beta Was this translation helpful? Give feedback.
-
晕了 |
Beta Was this translation helpful? Give feedback.
-
有没有人发现最后的动态规划的思路,对于[0,0,0,0,0,0,1]这种存在0的是不适用的哇。应该先把0相关的踢掉 |
Beta Was this translation helpful? Give feedback.
-
这里不明白二维的为什么j不能从1开始,我看子集背包问题里,二维的j是从1开始的,区别是那边先初始化了一下 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
文章链接点这里:动态规划和回溯算法到底谁是谁爹?
评论礼仪 见这里,违者直接拉黑。
Beta Was this translation helpful? Give feedback.
All reactions