DynamicProgramming


动态规划

  • 核心

    • canSum –> Decision Problem
    • howSum –> Combinatoric Problem
    • bestSum –> Optimization Problem
  • 技巧

    • Memoization(缓存)
    • Tabulation(制表)
      • visualize the problem as a table
      • size the table based on the inputs
      • initialize the table with default values
      • seed the trivial answer into the table
      • iterate through the table
      • fill futher positions based on the current position
  • 应用

    • Fibnacci
    • gridTraveler
    • TargetSum
      • canSum
      • howSum
      • bestSum
    • Construct
      • canConstruct
      • countConstruct
      • allConstruct

应用实现

斐波那契

  • 代码实现
const fib = (n) => {
    const table = Array(n + 1).fill(0);
    table[1] = 1;
    for (let i = 0; i <= n; i++) {
        table[i + 1] += table[i];
        table[i + 2] += table[i];
    }
    return table[n];
};

console.log(fib(6)); //8
console.log(fib(7)); //13
console.log(fib(8)); //21
console.log(fib(50)); //12586269025

网格旅行者

  • 代码实现
const gridTraveler = (m, n) => {
    const table = Array(m + 1)
        // .fill(Array(n + 1));
        .fill()
        .map(() => Array(n + 1).fill(0));
    table[1][1] = 1;

    for (let i = 0; i <= m; i++) {
        for (let j = 0; j <= n; j++) {
            const current = table[i][j];
            if (i + 1 <= m) table[i + 1][j] += current;
            if (j + 1 <= n) table[i][j + 1] += current;
        }
    }
    return table[m][n];
}

console.log(gridTraveler(1,1)); //1
console.log(gridTraveler(2,3)); //3
console.log(gridTraveler(3, 2)); //3
console.log(gridTraveler(3,3)); //6
console.log(gridTraveler(18,18)); //2333606220

canSum

代码实现

const canSum = (targetSum,numbers) => {
  const table = Array(targetSum + 1).fill(false);
  table[0] = true;
  // for (let i = 0;i < table.length;i++){
  for (let i = 0;i <= targetSum;i++){
      if (table[i] === true){
          for (let num of numbers){
              table[i + num] = true;
          }
      }
  }
  return table[targetSum];
};

console.log(canSum(7,[2,3]));   //true
console.log(canSum(7,[5,3,4,7])); //true
console.log(canSum(7,[2,4]));   //false
console.log(canSum(8,[2,3,5])); //true
console.log(canSum(300,[7,14])); //false

//Decision problem

howSum

Write a function howSum(targetSum,numbers) that takes in a targetSum and an array of numbers as arguments

The function should return an array containing any combination of elements that add up to exactly the targetSum

If there is no combination that add up to the targetSum,then return null

代码实现

const howSum = (targetSum, numbers) => {
  const table = Array(targetSum + 1).fill(null);
  table[0] = [];
  for (let i = 0;i <= targetSum;i++){
      if (table[i] !== null){
          for (let num of numbers){
              table[i + num] = [...table[i],num];

          }
      }
  }
  return table[targetSum];
};

console.log(howSum(7,[2,3]));   //[ 3, 2, 2 ]
console.log(howSum(7,[5,3,4,7])); //[ 4, 3 ]
console.log(howSum(7,[2,4]));   //null
console.log(howSum(8,[2,3,5])); //[ 2, 2, 2, 2 ]
console.log(howSum(300,[7,14])); //null

//Combinatoric problem

bestSum

代码实现

  • 制表法
const bestSum = (targetSum, numbers) => {
    const table = Array(targetSum + 1).fill(null);
    table[0] = [];
    for (let i = 0; i <= targetSum; i++) {
        if (table[i] !== null) {
            for (let num of numbers) {
                const combination = [...table[i], num];
                //如果当前组合数组更短,便将它存储
                if (!table[i + num] || table[i + num].length > combination.length) {
                    table[i + num] = combination;
                }
            }
        }
    }
    return table[targetSum];
};
//Memoization
console.log(bestSum(7, [2, 3]));   //[ 2, 2, 3 ]
console.log(bestSum(7, [5, 3, 4, 7])); // [ 7 ]
console.log(bestSum(7, [2, 4]));   //null
console.log(bestSum(8, [2, 3, 5])); // [ 3, 5 ]
console.log(bestSum(300, [7, 14])); //null

canConstruct

代码实现

  • Memoization
const canConstruct = (target, workBank,memo = {}) => {
    if (target in memo) return memo[target];
    if (target === '') {
        return true;
    }
    for (let word of workBank){
        if (target.indexOf(word) === 0){
            const suffix = target.slice(word.length);
           if (canConstruct(suffix,workBank,memo) === true){
               memo[target] = true;
               return true;
           }
        }
    }
    memo[target] = false;
    return false;
};
// m = target.length
// n = array.length
console.log(canConstruct("abcdef",["ab","abc","cd","def","abcd"]))  //true
console.log(canConstruct("skateboard",["bo","rd","ate","t","ska","sk","boar"]));    //false
console.log(canConstruct("enterapotentpot",["a","p","ent","enter","ot","o","t"]));  //true
console.log(canConstruct("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef",[
    "e",
    "ee",
    "eee",
    "eeee",
    "eeeee",
    "eeeeee",
    "eeeeeee"
]));    // false

// brute force
//time O(n ^ m * m)
//space O(m)

// memoized
//time O(n * m * m) = O(n * m ^ 2)
//space O(m ^ 2)
  • Tabulation
const canConstruct = (target, workBank) => {
    const table = Array(target.length + 1).fill(false);
    table[0] = true;


    for (let i = 0;i <= target.length;i++){
        if (table[i] === true){
            for (let word of workBank){
                // 检查当前的单词是否与从索引i开始的字符匹配
                if (target.slice(i,i + word.length) === word){
                    table[i + word.length] = true;
                }
            }
        }
    }
    return table[target.length];
};
// m = target.length
// n = array.length
console.log(canConstruct("abcdef",["ab","abc","cd","def","abcd"]))  //true
console.log(canConstruct("skateboard",["bo","rd","ate","t","ska","sk","boar"]));    //false
console.log(canConstruct("enterapotentpot",["a","p","ent","enter","ot","o","t"]));  //true
console.log(canConstruct("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef",[
    "e",
    "ee",
    "eee",
    "eeee",
    "eeeee",
    "eeeeee",
    "eeeeeee"
]));    // false

// brute force
//time O(n ^ m * m)
//space O(m)

// memoized
//time O(n * m * m) = O(n * m ^ 2)
//space O(m)

countConstruct

Write a function countConstruct(target,wordBank) that accepts a target string and an array of strings

The function should return the number of the ways that the target can be constructed by concatenating elements of the wordBank array

you may reuse the elements of wordBank as many times as needed

代码实现

  • Memoization
const countConstruct = (target, wordBank, memo = {}) => {
    if (target in memo) return memo[target];
    if (target === '') return 1;
    let totalCount = 0;
    for (let word of wordBank) {
        if (target.indexOf(word) === 0) {
            const numWaysForRest = countConstruct(target.slice(word.length), wordBank, memo);
            totalCount += numWaysForRest;
        }
    }
    memo[target] = totalCount;
    return totalCount;
};
// m = target.length
// n = array.length
console.log(countConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"]))  //1
console.log(countConstruct("purple", ["purp", "p", "ur", "le", "purpl"]))  //2
console.log(countConstruct("skateboard", ["bo", "rd", "ate", "t", "ska", "sk", "boar"]));    //0
console.log(countConstruct("enterapotentpot", ["a", "p", "ent", "enter", "ot", "o", "t"]));  //4
console.log(countConstruct("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef", [
    "e",
    "ee",
    "eee",
    "eeee",
    "eeeee",
    "eeeeee",
    "eeeeeee"
]));    // 0
// brute force
//time O(n ^ m * m)
//space O(m ^ 2)

// memoized
//time O(n * m * m) = O(n * m ^ 2)
//space O(m ^ 2)
  • Tabulation
const countConstruct = (target, wordBank) => {
    const table = Array(target.length + 1).fill(0);
    //初始化制表索引为0的值
    table[0] = 1;

    for (let i = 0;i <= target.length;i++){
        for (let word of wordBank){
            // 检查当前的单词是否与从索引i开始的字符匹配
            if (target.slice(i,i + word.length) === word){
                table[i + word.length] += table[i];
            }
        }
    }
    return table[target.length];
};
// m = target
// n = wordBank.length
console.log(countConstruct("purple", ["purp", "p", "ur", "le", "purpl"]))  //2
console.log(countConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"]))  //1
console.log(countConstruct("skateboard", ["bo", "rd", "ate", "t", "ska", "sk", "boar"]));    //0
console.log(countConstruct("enterapotentpot", ["a", "p", "ent", "enter", "ot", "o", "t"]));  //4
console.log(countConstruct("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef", [
    "e",
    "ee",
    "eee",
    "eeee",
    "eeeee",
    "eeeeee",
    "eeeeeee"
]));    // 0
// brute force
//time O(n ^ m * m)
//space O(m ^ 2)

// memoized
//time O(n * m * m) = O(n * m ^ 2)
//space O(m)

allConstruct

Write a function allConstruct(target,wordBank) that accepts a target string and an array of strings

The function should return a 2D array containing all of the ways that the target can be constructed by concatenating elements of wordBank array.

Each elements of 2D array should represent one combination that constructs the target

you may reuse the elements of wordBank as many times as needed

代码实现

  • Memoization
const allConstruct = (target, wordBank,memo = {}) => {
    if (target in memo) return memo[target];
    if (target === '') return [[]];
    const result = [];
    for (let word of wordBank) {
        if (target.indexOf(word) === 0) {
            const suffix = target.slice(word.length);
            const suffixWays = allConstruct(suffix, wordBank,memo);
            const targetWays = suffixWays.map(way => [word, ...way]);
            result.push(...targetWays);
        }
    }
    memo[target] = result;
    return result;
};


// m = target.length
// n = wordBank.length
console.log(allConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"]))  //1
// [
//   [ 'abc', 'def' ]
// ]

console.log(allConstruct("purple", ["purp", "p", "ur", "le", "purpl"]))  //2
//    [
//      [ 'purp', 'le' ],
//      [ 'p', 'ur', 'p', 'le' ]
//    ]

console.log(allConstruct("skateboard", ["bo", "rd", "ate", "t", "ska", "sk", "boar"]));    //0
//[]
console.log(allConstruct("enterapotentpot", ["a", "p", "ent", "enter", "ot", "o", "t"]));  //4
//[
//   [ 'enter', 'a', 'p', 'ot', 'ent', 'p', 'ot' ],
//   [
//     'enter', 'a',
//     'p',     'ot',
//     'ent',   'p',
//     'o',     't'
//   ],
//   [
//     'enter', 'a',
//     'p',     'o',
//     't',     'ent',
//     'p',     'ot'
//   ],
//   [
//     'enter', 'a',
//     'p',     'o',
//     't',     'ent',
//     'p',     'o',
//     't'
//   ]
// ]
console.log(allConstruct("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef", [
    "e",
    "ee",
    "eee",
    "eeee",
    "eeeee",
    "eeeeee",
    "eeeeeee"
]));    // []
// time O( n ^ m)
// space O(m)
  • Tabulation
const allConstruct = (target, wordBank) => {
    const table = Array(target.length + 1)
        .fill()
        .map(() => []);
    table[0] = [[]];

    for (let i = 0; i <= target.length; i++) {
        for (let word of wordBank) {
            if (target.slice(i, i + word.length) === word) {
                const newCombinations = table[i].map(subArray => [...subArray, word]);
                table[i + word.length].push(...newCombinations);
            }
        }
    }
    return table[target.length];
};


// console.log(allConstruct("abcdef", ["ab", "abc", "cd", "def", "abcd"]))  //1
// console.log(allConstruct("purple", ["purp", "p", "ur", "le", "purpl"]))  //2
// console.log(allConstruct("skateboard", ["bo", "rd", "ate", "t", "ska", "sk", "boar"]));    //0
// console.log(allConstruct("enterapotentpot", ["a", "p", "ent", "enter", "ot", "o", "t"]));  //4
console.log(allConstruct("aaaaaaaaaaaaaaax", [
    "a",
    "aa",
    "aaa",
    "aaaa",
    "aaaaa",
    "aaaaaa",
    "aaaaaaa"
]));    // []


// m : target.length
// n : wordBank.length
// time O( n ^ m)
// space O(m)

总结

  • notice any overlapping subproblems( 注意任何重叠的子问题)
  • decide what is the trivially smallest input (决定什么是最小的输入)
  • think recursively to use memoization (递归思考虑使用缓存)
  • think iteratively to use Tabbulation (迭代考虑使用制表法)
  • drew a strategy first (先制定策略)

文章作者: rudy
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 rudy !
  目录