【动态规划part05】最后一块石头的重量,目标和,一和零

本文详细介绍了如何使用动态规划方法解决两个问题:1.最后一块石头的重量计算,通过分组和背包策略找到最小剩余重量;2.目标和问题,计算在给定限制下调整数组符号使和为目标的组合数。涉及一维和二维dp数组的构建及递推过程。
摘要由CSDN通过智能技术生成

1.最后一块石头的重量

有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

解题思路:尽量将石头分成重量相同的两堆,相撞之后石头最小,
1dp[j] 表示容量为j的背包,最多可以最大重量为dp[j];
2.递推公式 dp[j] = Math.max(dp[j],dp[j-stones[i]]+stones[i])
3.01 背包每个石头只能放一次,物品遍历循环放在外层,遍历背包的for循环放在内层,倒序遍历;
4.最后dp[target]里的容量为target的背包所能背的最大重量,剩下的石头为(sum - dp[target]) - dp[target]

//一维数组
var lastStoneWeightII = function(stones) {
    // stones = stones.sort((a,b)=>{return a-b});
    let len = stones.length;
    let sum = 0;
    for (let i = 0; i < stones.length; i++) {
        sum += stones[i];
    }
    let target = Math.floor(sum / 2);
   
    let dp = new Array(target+1).fill(0);
    for (let i = 0; i < len; i++) {
        for (let j = target; j >= stones[i]; j--) {
            dp[j] = Math.max(dp[j],dp[j-stones[i]]+stones[i])
            
        }
    }
    return (sum - dp[target]) - dp[target];
};
//二维数组
// dp[i][j] 表示容量j的背包 从0-i中物品中取值,得到的最大价值
let dp = new Array(len).fill(0).map(()=>new Array(target + 1).fill(0));
    for (let j = 0; j <= target ; j++) {
    	//初始化i = 0,第0行;
        if (j >= stones[0]) {
            dp[0][j] = stones[0];
        }
    }
    for (let i = 1; i < len; i++) {
        for (let j = 0; j <= target; j++) {
            if (j >= stones[i]) {
                dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-stones[i]]+stones[i]);
            }else{
                dp[i][j] = dp[i-1][j];
            }
        }
        
    }
    console.log(dp[len-1][target]);
    return (sum-dp[len-1][target]) - dp[len-1][target];

2.目标和

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-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

sum = right + left;
target = right - left;
left = (sum + target)/2 ,转换为在集合nums中找left的组合;

var findTargetSumWays = function(nums, target) {
    nums = nums.sort((a,b)=>{return a-b});
    let sum = 0;
    let left = 0;
    for (let i = 0; i < nums.length; i++) {
       sum += nums[i];
    }

    if(Math.abs(target) > sum){
        return 0;
    }
    if((sum + target) % 2 != 0){
        return 0;
    }
    left = (target + sum) / 2;
    // 遍历到i个数时,left为j时能填满背包的方法总数;
   let dp = new Array(nums.length).fill(0).map(()=>new Array(left+1).fill(0));
    // 初始化最上行(dp[0][j]),当nums[0] == j时(注意nums[0]和j都一定是大于等于零的,因此不需要判断等于-j时的情况),有唯一一种取法可取到j,dp[0][j]此时等于1
        // 其他情况dp[0][j] = 0
   for (let j = 0; j <= left; j++) {
        if(nums[0] == j){
            dp[0][j] = 1;
        }
   }
    // 初始化最左列(dp[i][0])
        // 当从nums数组的索引0到i的部分有n个0时(n > 0),每个0可以取+/-,
        // 因此有2的n次方中可以取到j = 0的方案
        // n = 0说明当前遍历到的数组部分没有0全为正数,因此只有一种方案可以取到j = 0(就是所有数都不取)
        let numZeros = 0;
        for(let i = 0; i < nums.length; i++) {
            if(nums[i] == 0) {
                numZeros++;
            }
            dp[i][0] = Math.pow(2, numZeros);

        }

        for (let i = 1; i < nums.length; i++) {
            for (let j = 1; j <= left; j++) {
               if (nums[i] > j) {
                    // nums[i]不可取
                    dp[i][j] = dp[i-1][j];
               }else{
                // nums[i]可取可不取
                    dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]];
               }
            }    
        }

        return dp[nums.length-1][left];

};

1.确定dp数组的含义,dp[j]表示 填满 j 这么大容量的背包,有dp[j]种方法;
2.递推公式,只要搞到num[i],dp[j] 就有dp[j-nums[i]]种方法; 把所有的dp[j-nums[i]]累加起来 ,
dp[j] += dp[j-nums[i]];
3.初始化如果数组[0] ,target = 0,那么 bagSize = (target + sum) / 2 = 0。 dp[0]也应该是1, 也就是说给数组里的元素 0 前面无论放加法还是减法,都是 1 种方法。所以本题我们应该初始化 dp[0] 为 1。
4.遍历顺序,一维dp,nums外循环,target内循环,且内循环倒序;
5.打印dp数组;

//一维数组
var findTargetSumWays = function(nums, target) {
    nums = nums.sort((a,b)=>{return a-b});
    let sum = 0;
    let left = 0;
    for (let i = 0; i < nums.length; i++) {
       sum += nums[i];
    }

    if(Math.abs(target) > sum){
        return 0;
    }
    if((sum + target) % 2 != 0){
        return 0;
    }
    left = (target + sum) / 2;

    let dp = new Array(left + 1).fill(0);
    dp[0] = 1;
    for (let i = 0; i < nums.length; i++) {
        for(let j = left;j >= 0;j--){
            if (nums[i] > j) {
                
            }else{
                dp[j] = dp[j] + dp[j-nums[i]]
            }
        }
        
    }

    return dp[left];
}

3.一和零

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

var findMaxForm = function(strs, m, n) {
    const dp = Array.from(Array(m+1), () => Array(n+1).fill(0));
    let numOfZeros, numOfOnes;

    for(let str of strs) {
        numOfZeros = 0;
        numOfOnes = 0;
    
        for(let c of str) {
            if (c === '0') {
                numOfZeros++;
            } else {
                numOfOnes++;
            }
        }

        for(let i = m; i >= numOfZeros; i--) {
            for(let j = n; j >= numOfOnes; j--) {
                dp[i][j] = Math.max(dp[i][j], dp[i - numOfZeros][j - numOfOnes] + 1);
            }
        }
    }

    return dp[m][n];
};

let strs = ["10", "0001", "111001", "1", "0"];
let m = 5;
let n = 3;
findMaxForm(strs,m,n);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值