[豪の算法奇妙冒险] 代码随想录算法训练营第三十六天 | 1049-最后一块石头的重量Ⅱ、494-目标和、474-一和零

代码随想录算法训练营第三十六天 | 1049-最后一块石头的重量Ⅱ、494-目标和、474-一和零


LeetCode1049 最后一块石头的重量Ⅱ

题目链接:https://leetcode.cn/problems/last-stone-weight-ii/description/

文章讲解:https://programmercarl.com/1049.最后一块石头的重量II.html

视频讲解:https://www.bilibili.com/video/BV14M411C7oV/?vd_source=b989f2b109eb3b17e8178154a7de7a51

​ 可以抽象为01背包问题,第i个物品的重量和价值都等于stones[i],要得到最小可能剩余重量,就是要将这堆石头尽量分成重量相近的两堆,分好后一堆重量是sum - dp[mid]。另一堆重量是dp[mid],相减即为答案

​ 动规五部曲:

  1. 确定dp数组以及下标的含义

​ dp[j]表示容量为j的背包所能装的最大价值

  1. 确定递推公式

​ 状态转移方程 dp[j] = Math.max(dp[j],dp[j-weight[i]] + value[i])

​ 不放第i个物品时,dp[j] = dp[j]

​ 放第i个物品时,dp[j] = dp[j-weight[i]] + value[i]

  1. dp数组如何初始化

​ 全部初始化为0

  1. 确定遍历顺序

​ 根据递推公式 dp[j] = Math.max(dp[j],dp[j-weight[i]] + value[i])

​ i为物品,j为背包,双重for循环外层顺序遍历物品i,内层倒序遍历背包j

  1. 举例推导dp数组

image-20260127205911481

class Solution {
    public int lastStoneWeightII(int[] stones) {
        if(stones.length == 1){
            return stones[0];
        }

        int sum = 0;
        for(int i = 0; i < stones.length; i++){
            sum += stones[i];
        }
        int mid = sum / 2;
        int[] dp = new int[mid+1];
        for(int i = 0; i < stones.length; i++){
            for(int j = mid; j >= stones[i]; j--){
                dp[j] = Math.max(dp[j], dp[j-stones[i]] + stones[i]);
            }
        }

        return (sum - dp[mid]) - dp[mid];
    }
}

LeetCode494 目标和

题目链接:https://leetcode.cn/problems/target-sum/description/

文章讲解:https://programmercarl.com/0494.目标和.html

视频讲解:https://www.bilibili.com/video/BV1o8411j73x/

​ 设nums总和为sum,加的总和为X,则减去的总和为sum-X,题目所给的target=X - (sum-X)。转换一下可以得到,2X = sum + target

​ 于是,该题目被转换成了求用nums数组内的数凑出X有几种组合方法

​ 由于求X要除2,要考虑到向下取整的情况,因此当(target + sum) % 2 == 1时是无解的

​ 同样,当target的绝对值大于sum时,也是无解的.

​ 首先想到的是回溯法,组合问题

image-20260128220028872

class Solution {
    int result = 0;
    int curSum = 0;
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        if((target + sum) % 2 == 1 || Math.abs(target) > sum){
            return 0;
        }
        Arrays.sort(nums);
        target = (target + sum)/2;
        backTracking(nums, target, 0);
        return result;
    }

    public void backTracking(int[] nums, int target, int start){
        if(curSum == target){
            result++;
        }
        for(int i = start; i < nums.length; i++){
            if(curSum + nums[i] > target){
                break;
            }
            curSum += nums[i];
            backTracking(nums, target, i+1);
            curSum -= nums[i];
        }
    }
}

​ 接着使用动态规划方法求解,动规五部曲:

  1. 确定dp数组以及下标的含义

​ dp[i][j]表示:用下标为[0, i]的nums[i]能装满容量为j的背包有dp[i][j]种方法

  1. 确定递推公式

​ 不放物品i:即背包容量为j,里面不放物品i,装满有dp[i-1][j]种方法

​ 放物品i:即先空出物品i的容量,背包容量为j-nums[i],装满有dp[i-1][j-nums[i]]种方法

​ 注意,j-nums[i]作为数组下标必须大于等于0,若出现小于0的情况,则说明目前容量的背包装不下物品i,此时dp[i][j] = dp[i-1][j]

​ 因此状态转移方程:

if(j - nums[i] >= 0){
    dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]];
}else{
    dp[i][j] = dp[i-1][j];
}
  1. dp数组如何初始化

​ 根据状态转移方程,dp[i][j]是由它的上方及左上方的dp数值推出的,因此最上行和最左列必须初始化

​ dp[0][j],当背包容量j恰好等于物品0的重量时,刚好装满,为1种,其他的都是0

​ dp[i][0],当背包容量为0时,都是一种方法,放0件物品。但这里有例外情况,若物品中有重量为0的,那么就是2^n种,n为当前遍历的物品中重量为0的数量

  1. 确定遍历顺序

​ 根据递推公式,两层for循环,外层顺序遍历物品i,内层顺序遍历容量j

  1. 举例推导dp数组

image-20260128231321009

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        if((sum + target) % 2 != 0 || Math.abs(target) > sum){
            return 0;
        }

        target = (sum + target)/2;
        int[][] dp = new int[nums.length][target+1];
        int zeroCnt = 0;
        for(int i = 0; i < nums.length; i++){
            if(nums[i] == 0){
                zeroCnt++;
            }
            dp[i][0] = (int) Math.pow(2.0, zeroCnt);
        }
        for(int j = 1; j < target+1; j++){
            if(nums[0] == j){
                dp[0][j] = 1;
            }
        }

        for(int i = 1; i < nums.length; i++){
            for(int j = 1; j < target+1; j++){
                if(j - nums[i] >= 0){
                    dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]];
                }else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }

        return dp[nums.length-1][target];
    }
}

​ 继续深入,使用滚动数组,将上述二维dp数组压缩成一维dp数组,动规五部曲:

  1. 确定dp数组以及下标的含义

​ dp[j]表示:装满容量为j的背包有dp[j]种方法

  1. 确定递推公式

​ 二维dp数组的递推公式为 dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]],去掉维度i后递推公式变为dp[j] = dp[j] + dp[j-nums[i]],即dp[j] += dp[j-nums[i]]

​ 因此状态转移方程:

if(j - nums[i] >= 0){
    dp[j] += dp[j-nums[i]];
}
  1. dp数组如何初始化

​ dp[0] = 1,当背包容量为0时,都是一种方法(放0件物品)

​ 其他位置后续要做累加操作,因此初始化为0

  1. 确定遍历顺序

​ 根据递推公式,两层for循环,外层顺序遍历物品i,内层倒序遍历容量j

  1. 举例推导dp数组

image-20260129215953242

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        if((sum + target) % 2 != 0 || Math.abs(target) > sum){
            return 0;
        }

        target = (sum + target)/2;
        int[] dp = new int[target+1];
        dp[0] = 1;

        for(int i = 0; i < nums.length; i++){
            for(int j = target; j >= nums[i]; j--){
                dp[j] = dp[j] + dp[j-nums[i]];
            }
        }

        return dp[target];
    }
}

LeetCode474 一和零

题目链接:https://leetcode.cn/problems/ones-and-zeroes/description/

文章讲解:https://programmercarl.com/0474.一和零.html

视频讲解:https://www.bilibili.com/video/BV1rW4y1x7ZQ/

​ 多重背包问题,动规五部曲:

  1. 确定dp数组以及下标的含义

​ dp[i][j]表示装满i个0和j个1的背包最多有dp[i][j]个物品

  1. 确定递推公式

​ 01背包状态转移方程为dp[j] = Math.max(dp[j],dp[j-weight[i]] + value[i])

​ 设当前物品重量为X个0,Y个1,那么:

​ 放入当前物品则有 dp[i-x][j-y]+1

​ 由此得到状态转移方程 dp[i][j] = Math.max(dp[i][j],dp[i-x][j-y]+1)

  1. dp数组如何初始化

​ dp[0][0] = 0,非零下标也应初始化为0

  1. 确定遍历顺序

​ 根据递推公式,从大到小进行遍历,三层for循环,最外层是顺序遍历物品,第二层i倒序遍历0的容量,第三层j倒序遍历1的容量

  1. 举例推导dp数组

image-20260129231005326

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m+1][n+1];
        for(String str : strs){
            int x = 0, y = 0;
            for(int k = 0; k < str.length(); k++){
                if(str.charAt(k) == '0'){
                    x++;
                }else{
                    y++;
                }
            }

            for(int i = m; i >= x; i--){
                for(int j = n; j >= y; j--){
                    dp[i][j] = Math.max(dp[i][j], dp[i-x][j-y] + 1);
                }
            }
        }
        return dp[m][n];
    }
}
posted @ 2026-01-29 23:11  SchwarzShu  阅读(3)  评论(0)    收藏  举报