算法练习day40

64 阅读3分钟

一、单词拆分

回溯法

回溯法在递归过程中会有很多重复计算,所以用一个memory对象记录从startIndex起始的计算结果,如果结果为false则直接返回false

/**
 * @param {string} s
 * @param {string[]} wordDict
 * @return {boolean}
 */
var wordBreak = function(s, wordDict) {
    let memory = {}
    function backtracking(startIndex) {
        if(startIndex >= s.length) {
            return true
        }
        if(memory[startIndex] === false) {
            return false
        }
        for(let i = startIndex; i < s.length;i++) {
            let word = s.substring(startIndex, i + 1)
            if(wordDict.includes(word) && backtracking(i + 1)) {
                return true
            }
        }
        memory[startIndex] = false
        return false
    }
    return backtracking(0)
};

时间复杂度为O(2^N),每个单词都有两个状态,切割和不切割 空间复杂度为O(n),算法递归调用栈的空间

动态规划——背包问题

单词就是物品,字符串就是背包,完全背包问题

五部曲

  1. dp[i],字符串长度为i,表示可以被拆分为一个活多个在字典中出现的单词
  2. 递推公式dp[i] = wordDict.includes(s.substring(j, i) && dp[j])
  3. dp[0] = true,累计的基础,必须为true
  4. 遍历顺序,这个题的字符串相当于背包,其顺序是不能变的,必须从前往后,所以外层是背包,内层是武平,正序遍历
  5. 举例推导
var wordBreak = function (s, wordDict) {
    let dp = new Array(s.length + 1).fill(false)
    dp[0] = true
    for (let i = 1; i <= s.length; i++) {
        for (let j = 0; j < i; j++) {
            if (dp[j] && wordDict.includes(s.substring(j, i))) {
                dp[i] = true
            }
        }
    }
    return dp[s.length]
};

另一种思路

var wordBreak = function (s, wordDict) {
    let dp = new Array(s.length + 1).fill(false)
    dp[0] = true
    for (let i = 1; i <= s.length; i++) {
        for(let j = 0; j < wordDict.length;j++) {
            let word = wordDict[j]
            if(i >= word.length) {
                // 截取的起始位置就是 当前字符串长度i减去当前单词的长度,截取的终止位置是i,含头不含尾
                if(s.substring(i - word.length, i) === word && dp[i - word.length]) {
                    dp[i] = true
                }
            }
        }
    }
    return dp[s.length]
};

二、多重背包理论基础

有N种物品和一个容量为V的背包,第i种物品最多有M[i]件可用,耗费的空间是C[i],价值是M[i],求解将哪些物品装入背包可以使这些物品的耗费的空间总和不超过背包容量,且价值总和最大

多重背包和01背包非常像,把M[i]件摊开其实就是一个01背包问题

const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});
let inputArr = [];//存放输入的数据
rl.on('line', function(line){
  //line是输入的每一行,为字符串格式
    inputArr.push(line.split(' ').map(item => Number(item)));//将输入流保存到inputArr中(注意为字符串数组)
}).on('close', function(){
    console.log(fun(inputArr))//调用函数并输出
})

function fun(inputArr) {
    let [C, N] = inputArr[0] // C背包容量,N物品种类数量
    let weight = inputArr[1] // N种物品的重量
    let value = inputArr[2] // N种物品的价值
    let k = inputArr[3] // 每种物品最多能用几次
    let w = []
    let v = []
    // 转换为01背包
    for(let i = 0; i < N;i++) {
        let count = k[i]
        while(count) {
            w.push(weight[i])
            v.push(value[i])
            count--
        }
    }
    let dp = new Array(C + 1).fill(0)
    
    for(let i = 0; i < w.length;i++) {
        for(j = C; j >= w[i]; j--) {
            dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i])
        }
    }
    return dp[C]
}

三、背包问题总结

遍历顺序

01背包

  1. 二维dp数组,先遍历物品,后遍历背包,或者先遍历背包,后遍历物品都是可以的,且第二层循环是从小到大遍历
  2. 一维dp数组,只能先遍历物品再遍历背包容量,且第二层是从大到小遍历

完全背包

  1. 如果是求组合数,外层遍历物品,内层遍历背包
  2. 如果是求排列数,外层遍历背包,内层遍历物品
  3. 如果求最小数,两层遍历的先后顺序都可以