#Java #动态规划
Feeling and experiences:
给你一个非负整数数组 nums
和一个整数 target
。
向数组中的每个整数前添加 '+'
或 '-'
,然后串联起所有整数,可以构造一个 表达式 :
nums = [2, 1]
,可以在 2
之前添加 '+'
,在 1
之前添加 '-'
,然后串联起来得到表达式 "+2-1"
。返回可以通过上述方法构造的、运算结果等于 target
的不同 表达式 的数目。
示例 1:
输入:nums = [1,1,1,1,1], target = 3 输出:5 解释:一共有 5 种方法让最终目标和为 3 。 -1 + 1 + 1 + 1 + 1 = 3 +1 - 1 + 1 + 1 + 1 = 3 +1 + 1 - 1 + 1 + 1 = 3 +1 + 1 + 1 - 1 + 1 = 3 +1 + 1 + 1 + 1 - 1 = 3
这道题首先想到的是用回溯来做,因为就只有加减两种,要么取减法,要么取加法。
代码如下:
class Solution {
int count=0;//全局变量!
public int findTargetSumWays(int[] nums, int target) {
//用回溯来做
back(nums,target,0,0);
return count;
}
//back方法
public void back(int[] nums,int target,int index,int sum){
//如果遍历到了最后:
if(index == nums.length){ //注意 最后一个数要遍历完(所以要在最后一个数的后面)
if(sum == target){
count++;
}
}else{
//回溯加法
back(nums,target,index+1,sum+nums[index]);
//回溯减法
back(nums,target,index+1,sum-nums[index]);
}
}
}
虽然代码易懂,好想到,但是时间复杂度高,且在此章节还是要用动态规划。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
// 计算所有数字的和
for (int num : nums) {
sum += num;
}
// 计算和与目标值之间的差值
int diff = sum - target;
// 如果差值是负数或者不是偶数,则不存在解决方案
if (diff < 0 || diff % 2 != 0) {
return 0;
}
int n = nums.length, neg = diff / 2;
// 创建动态规划表
int[][] dp = new int[n + 1][neg + 1];
// 初始化动态规划表
dp[0][0] = 1;
// 填充动态规划表
for (int i = 1; i <= n; i++) {
int num = nums[i - 1];
for (int j = 0; j <= neg; j++) {
// 计算不包含当前数字的情况
dp[i][j] = dp[i - 1][j];
// 如果可以加上当前数字,则累加上这种情况
if (j >= num) {
dp[i][j] += dp[i - 1][j - num];
}
}
}
// 返回结果
return dp[n][neg];
}
}
计算数组总和:首先,计算数组 nums 中所有元素的总和 sum。
确定剩余数:计算 sum - target。这一步是为了找出需要通过减法(即视为负数)得到的数的总和。
检查剩余数的奇偶性:如果 sum - target 是负数或奇数,那么问题无解。负数意味着目标和比所有数的总和还大,奇数意味着无法通过整数的加减组合得到目标和。
初始化动态规划数组:创建一个二维数组 dp,其中 dp[i][j] 表示在前 i 个数中选择一些数作为负数,使得这些负数的总和为 j 的方法数。
填充动态规划数组:
• 初始化 dp[0][0] = 1,表示没有选择任何数字时,和为 0 的方法只有一种。
• 对于每个数 nums[i] 和每个可能的和 j,更新 dp[i][j]。有两种情况:
• 不使用 nums[i]:此时方法数为 dp[i-1][j]。
• 使用 nums[i](作为负数):如果 j >= nums[i],方法数增加 dp[i-1][j-nums[i]]。
返回结果:dp[n][neg] 是最终答案,其中 n 是数组的长度,neg 是 (sum - target) / 2 的值。
给你一个二进制字符串数组 strs
和两个整数 m
和 n
。
请你找出并返回 strs
的最大子集的长度,该子集中 最多 有 m
个 0
和 n
个 1
。
如果 x
的所有元素也是 y
的元素,集合 x
是集合 y
的 子集 。
示例 1:
输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3 输出:4 解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。 其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
要先确定dp数组的含义,才能进行递推:
dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
// dp[i][j] 表示最多有 i 个 0 和 j 个 1 时的字符串的最大数量
int[][] dp = new int[m + 1][n + 1];
int oneNum, zeroNum;
// 遍历每个字符串
for (String str : strs) {
oneNum = 0;
zeroNum = 0;
// 统计字符串中 0 和 1 的数量
for (char ch : str.toCharArray()) {
if (ch == '0') {
zeroNum++;
} else {
oneNum++;
}
}
// 倒序遍历,以避免重复计算
for (int i = m; i >= zeroNum; i--) {
for (int j = n; j >= oneNum; j--) {
// 更新 dp 表,决定是否选择当前字符串
dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
// 返回最终结果
return dp[m][n];
}
}
初始化动态规划数组:dp[i][j] 表示在最多使用 i 个 0 和 j 个 1 的情况下,能构成的最大子集数量。
遍历字符串数组:对于每个字符串,计算其中 0 和 1 的数量。
动态规划状态转移:对于每个字符串,更新动态规划表。如果当前字符串的 0 和 1 数量分别为 zeroNum 和 oneNum,那么对于每个 i >= zeroNum 和 j >= oneNum,检查是否将当前字符串加入子集中可以得到更大的子集数量。
倒序更新:动态规划表需要从大到小更新,以避免在更新过程中使用到本轮已经更新过的值,从而保证每个字符串只被考虑一次。
这道题我认为关键在于怎么写dp数组,dp数组含义怎么理解。
理解到了,递推其实并不难:
• 我们从 m 到 zeroNum,从 n 到 oneNum 倒序遍历。对于每对 (i, j),我们有两个选择:
• 不包含当前字符串:此时子集的大小保持为 dp[i][j]。
• 包含当前字符串:如果包含当前字符串,子集大小将增加 1,但我们需要使用 zeroNum 个 0 和 oneNum 个 1。因此,新的子集大小为 dp[i - zeroNum][j - oneNum] + 1。
• 我们取这两种选择中的最大值来更新 dp[i][j]。
“一双桨悠懒,
一绵江风微拂素罗衫”
Fighting!