算法训练营Day37

#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 和两个整数 mn

请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1

如果 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!


 


 

你可能感兴趣的:(算法)