算法练习day37

90 阅读2分钟

一、最后一块石头的重量2

该题的思路是把一堆石头分为重量尽量相同的两堆,这样两堆相撞后剩下的石头最小,这个又可以装换位01背包的问题

五部曲

  1. dp[j],大小为j的背包,最多可以容纳多少重量的石头
  2. 递推公式dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i])
  3. 初始化,0
  4. 遍历顺序,物品在外层遍历,背包在内层遍历,且应该为倒序遍历
  5. 举例推导
/**
 * @param {number[]} stones
 * @return {number}
 */
var lastStoneWeightII = function(stones) {
    let sum = stones.reduce((sum, cur) => sum + cur)
    let target = Math.floor(sum / 2) 
    let dp = new Array(target + 1).fill(0)
    for(let i = 0; i < stones.length;i++) {
        for(let j = target; j >= stones[i];j--) {
            dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i])
        }
    }
    let targetSum = dp[target]
    return  sum - dp[target] - dp[target]
};

二、目标和

left为加法的总和,right为减法的总和 left + right = sum left - right = target left = (target + sum) / 2

回溯法

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var findTargetSumWays = function(nums, target) {
    let sum = nums.reduce((sum, cur) => sum + cur)
    if (target > sum) {
        return 0
    }
    if ((target + sum) % 2) {
        return 0
    }
    let bagSize = (target + sum) / 2
    nums.sort((a, b) => a - b)
    let result = []
    function backtracking(sum, startIndex) {
        if (bagSize === sum) {
            result.push([...result])
        }
        for (let i = startIndex; i < nums.length && sum + nums[i] <= bagSize; i++) {
            result.push(nums[i])
            backtracking(sum + nums[i], i + 1)
            result.pop()
        }
    }
    backtracking(0, 0)
    return result.length
};

动态规划

上面left,加法的总和即为背包的大小,问题转换为装满容量为left的背包,有几种方法

五部曲

  1. d[j],装满j有这么多的方法
  2. 递推公式,dp[j] += dp[j - nums[i]],组合类的问题都是这种公式
  3. dp[0] = 1
  4. 遍历顺序,外层是数组,内层是背包倒序
  5. 举例
var findTargetSumWays = function(nums, target) {
    let sum = nums.reduce((sum, cur) => sum + cur)
    if (Math.abs(target) > sum) {
        return 0
    }
    if ((target + sum) % 2) {
        return 0
    }
    let bagSize = (target + sum) / 2
    let dp = new Array(bagSize + 1).fill(0)
    dp[0] = 1
    for(let i = 0; i < nums.length;i++) {
        for(let j = bagSize; j >= nums[i];j--) {
            dp[j] += dp[j - nums[i]]
        }
    }
    return dp[bagSize]
};

三、一和零

两个维度的背包,m和n,分别表示最多有m个0和最多有n个1,典型的01背包问题

五部曲

  1. dp[i][j]表示最多有i个0和j个1的最大子集大小
  2. 递推公式,dp[i][j]可以由上个strs的字符串推导而来,字符串有zeroNum个0,oneNum个1,dp[i][j] = Math.max(dp[i][j], dp[i-zeroNum][j-onenNum] + 1)
  3. dp初始化为0
  4. 遍历顺序,外层遍历为物品,内层为m和n的背包倒序遍历
  5. 举例推导
/**
 * @param {string[]} strs
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var findMaxForm = function(strs, m, n) {
    let dp = new Array(m+1).fill(0).map(_ => new Array(n+1).fill(0))
    for(let str of strs) {
        let zeroNum = 0
        let oneNum = 0
        for(let i=0; i < str.length;i++) {
            if(str[i] === '0') {
                zeroNum++
            }
            if(str[i] === '1') {
                oneNum++
            }
        }
        for(let i = m; i >= zeroNum;i--) {
            for(let j = n; j >= oneNum; j--) {
                dp[i][j] = Math.max(dp[i][j], dp[i-zeroNum][j-oneNum] + 1)
            }
        }
    }
    return dp[m][n]
};