1049.最后一块石头的重量 II

(脑子没转过弯x1)

初见半天没想明白,背包问题一次不是只取一个物品吗,这题怎么一次取两个呀???

其实这题的思路能够转换成 416分割等和子集:

思路有了,代码其实和416差不多,只有最后的返回值部分有些差别

int lastStoneWeightII(vector<int>& stones) {
	int sum = 0;
	for (int n : stones)
		sum += n;
	// 取总重量的一半作为目标值
	// 由于 /2 省略了小数,所以target必定小于等于sum的一半
	int target = sum / 2;

	vector<int> dp(target + 1, 0);
	for (int i = stones[0]; i <= target; ++i)
		dp[i] = stones[0];

	for (int i = 1; i < stones.size(); ++i) {
		for (int j = target; j >= stones[i]; --j) {
			dp[j] = std::max(dp[j], dp[j - stones[i]] + stones[i]);
		}
	}
	// sum - dp[target]是稍重一组的重量,dp[target]是稍轻一组的重量
	return (sum - dp[target]) - dp[target];
}

494.目标和

(脑子没转过弯x2)

初见也是没想明白,这次是只取一个物品了,但物品怎么能又加又减两种操作呀???

其实通过数学分析,能将两种操作变为一种:

动规五部曲:

int findTargetSumWays(vector<int>& nums, int target) {
	int sum = 0;
	for (int n : nums)
		sum += n;

	int newTarget = sum + target;
	// 除2除不尽说明没有解,target的绝对值大于sum也没有解,直接返回0
	if (newTarget % 2 == 1 || std::abs(target) > sum)
		return 0;
	else
		newTarget /= 2;

	// dp[i] 表示和为i的子集数量
	vector<int> dp(newTarget + 1, 0);
	dp[0] = 1;

	for (int i = 0; i < nums.size(); ++i) {
		for (int j = newTarget; j >= nums[i]; --j) {
			// 不加nums[i]:dp[j]保持不变
			// 加nums[i]:dp[j] += dp[j - nums[i]]
			dp[j] += dp[j - nums[i]];
		}
	}
	return dp[newTarget];
}

 474.一和零

 这题重点在理解题意,题目中每个元素只使用一次,但对背包有m和n两个限制,所以这题是一个背包有两个维度的0-1背包问题

int findMaxForm(vector<string>& strs, int m, int n) {
	vector<vector<int>> nums;
	for (string s : strs) {
		vector<int> vec(2, 0);
		for (char c : s)
			++vec[c - '0'];
		nums.push_back(vec);
	}

	// 两个维度的背包
	// dp[i][j]表示 m = i,n = j 时最长的子集长度
	vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));

	for (int k = 0; k < nums.size(); ++k) {
		for (int i = m; i >= nums[k][0]; --i) {
			for (int j = n; j >= nums[k][1]; --j) {
				// 不将当前元素加入子集:长度 = dp[i][j]
				// 将当前元素加入子集:长度 = dp[i - nums[k][0]][j - nums[k][1]] + 1
				dp[i][j] = std::max(dp[i][j], dp[i - nums[k][0]][j - nums[k][1]] + 1);
			}
		}
	}
	return dp[m][n];
}

总结

对于要分为两组的问题,需要进行一定的数学推理将其变为一组,如:

        1049:将石头分为重量尽可能接近的两组 -> 分出一个子集,其总和尽可能接近总重量的一半

        494:将所有元素分为+的一组和-的一组,经过数学推理 -> 求统计总和为 (sum + target) / 2 的子集数量

02-27 02:21