# 【油管最火的Js动态规划讲解】学习笔记

Jeff在写代码

https://www.bilibili.com/vide...

# 记忆化递归

## fib

### 一般实现

``````const fib = n => {
if (n === 1 || n === 2) return 1
return fib(n - 1) + fib(n - 2)
}

console.log(fib(6))
console.log(fib(7))
console.log(fib(8))
console.log(fib(50)) // 卡住了``````

### 优化

``````// memoization
// js obj, keys: arg, value returns

// 修改1 设置memo和初始值
const fib = (n, memo = {}) => {
// 修改2 检查是否有记忆
if (n in memo) return memo[n]
if (n === 1 || n === 2) return 1
// 修改3 递归的时候带上我们的引用
memo[n] = fib(n - 1, memo) + fib(n - 2, memo)
return memo[n]
}

console.log(fib(6))
console.log(fib(7))
console.log(fib(8))
console.log(fib(50)) // 很快就出结果了``````

## 旅行者gridTraveler

### 递归版

``````const gridTraveler = (m, n) => {
if (m === 1 && n === 1) return 1
if (m === 0 || n === 0) return 0
return gridTraveler(m - 1, n) + gridTraveler(m, n - 1)
}

console.log(gridTraveler(1,2))
console.log(gridTraveler(3,2))
console.log(gridTraveler(3,3))
console.log(gridTraveler(18,18))``````

### dp版

``````const gridTraveler = (m, n, memo = {}) => {
const key = `\${m}+\${n}`
if (key in memo) return memo[key]
if (m === 1 && n === 1) return 1
if (m === 0 || n === 0) return 0
memo[key] = gridTraveler(m - 1, n, memo) + gridTraveler(m, n - 1, memo)
return memo[key]
}

console.log(gridTraveler(1, 2))
console.log(gridTraveler(3, 2))
console.log(gridTraveler(3, 3))
console.log(gridTraveler(18, 18))``````

## canSum

<img src="" alt="image-20210426083039761" style="zoom:50%;" />

### 递归

``````const canSum = (targetSum, numbers) => {
if (targetSum === 0) return true
if (targetSum < 0) return false

let remainder
for (let num of numbers) {
remainder = remainder || canSum(targetSum - num, numbers)
}
return remainder
}

console.log(canSum(7, [3, 2]))
console.log(canSum(7, [4, 2]))
console.log(canSum(7, [5, 6, 2]))
console.log(canSum(300, [7, 14]))``````

``````const canSum = (targetSum, numbers) => {
if (targetSum === 0) return true
if (targetSum < 0) return false

for (let num of numbers) {
if (canSum(targetSum - num, numbers)) return true
}
return false
}``````

### dp

<img src="https://gitee.com/zjeff-953/picsBed/raw/master/image/20210426084656.png" alt="image-20210426084655947" style="zoom: 33%;" />

``````const canSum = (targetSum, numbers, memo = {}) => {
if (targetSum in memo) return memo[targetSum]
if (targetSum === 0) return true
if (targetSum < 0) return false

for (const num of numbers) {
memo[targetSum] = canSum(targetSum - num, numbers, memo)
if (memo[targetSum]) return true
}
return false
}

console.log(canSum(7, [3, 2]))
console.log(canSum(7, [4, 2]))
console.log(canSum(7, [5, 6, 2]))
console.log(canSum(300, [7, 14]))``````

## howSum

### 递归版

``````const howSum = (targetSum, numbers) => {
if (targetSum === 0) return []
if (targetSum < 0) return null

for (const num of numbers) {
const remainder = targetSum - num
const remainderResult = howSum(remainder, numbers)
if (remainderResult !== null) return [...remainderResult, num]
}
return null
}

console.log(howSum(7, [3, 2]))
console.log(howSum(7, [4, 2]))
console.log(howSum(7, [5, 6, 2]))
console.log(howSum(300, [7, 14]))
``````

### dp版

``````const howSum = (targetSum, numbers, memo = {}, path = []) => {
if (targetSum in memo) return memo[targetSum]
if (targetSum === 0) return []
if (targetSum < 0) return null

for (const num of numbers) {
const remainder = targetSum - num
const remainderResult = howSum(remainder, numbers, memo)
if (remainderResult !== null) {
memo[targetSum] = [...remainderResult, num]
return memo[targetSum]
}
}
memo[targetSum] = null // 不可达也需要记录
return memo[targetSum]
}

console.log(howSum(7, [3, 2]))
console.log(howSum(7, [4, 2]))
console.log(howSum(7, [4, 3, 2]))
console.log(howSum(7, [5, 6, 2]))
console.log(howSum(300, [7, 14]))``````

<img src="https://gitee.com/zjeff-953/picsBed/raw/master/image/20210426093959.png" alt="image-20210426093958932" style="zoom:33%;" />

## bestSum

### tips: 使用递归的思路

1. 想好出口, 边界条件, 失败成功条件
2. 调用递归函数的时候要假设递归函数能获取到你想要的结果

### 递归版

``````const bestSum = (targetSum, numbers, lastBest) => {
if (targetSum === 0) return []
if (targetSum < 0) return null

let shortestCombination = null

for (const num of numbers) {
const remainder = targetSum - num
const remainderCombination = bestSum(remainder, numbers)
if (remainderCombination !== null) {
const combination = [...remainderCombination, num]
if (
shortestCombination === null ||
combination.length < shortestCombination.length
)
shortestCombination = combination
}
}

return shortestCombination
}

console.log(bestSum(7, [1, 3, 2, 7])) // [7]
console.log(bestSum(7, [1, 4, 2])) // [2,4,1]
console.log(bestSum(7, [1, 4, 3, 2])) // [3,4]
console.log(bestSum(7, [1, 5, 6, 2])) // [6, 1]
console.log(bestSum(100, [1, 2, 3, 14]))``````

### dp版

``````const bestSum = (targetSum, numbers, memo = {}) => {
if (targetSum in memo) return memo[targetSum]
if (targetSum === 0) return []
if (targetSum < 0) return null

let shortestCombination = null

for (const num of numbers) {
const remainder = targetSum - num
const remainderCombination = bestSum(remainder, numbers, memo)
if (remainderCombination !== null) {
const combination = [...remainderCombination, num]
if (
shortestCombination === null ||
combination.length < shortestCombination.length
)
shortestCombination = combination
}
}

memo[targetSum] = shortestCombination
return memo[targetSum]
}

console.log(bestSum(7, [1, 3, 2, 7])) // [7]
console.log(bestSum(7, [1, 4, 2])) // [2,4,1]
console.log(bestSum(7, [1, 4, 3, 2])) // [3,4]
console.log(bestSum(7, [1, 5, 6, 2])) // [6, 1]
console.log(bestSum(100, [1, 2, 3, 5, 10, 40])) //[ 40, 40, 10, 10 ]``````

## 这三个问题的总结

<img src="https://gitee.com/zjeff-953/picsBed/raw/master/image/20210428202031.png" alt="image-20210428202030984" style="zoom:50%;" />

<img src="https://gitee.com/zjeff-953/picsBed/raw/master/image/20210428202122.png" alt="image-20210428202122564" style="zoom:50%;" />

## canConstruct

### 递归版

#### 我的实现(错误版)

``````const canConstruct = (target, wordBank) => {
if (target === '') return true

for (const word of wordBank) {
if (target.indexOf(word) !== -1) {
return target
.split(word, 2)
.reduce(
(pre, targetStr) => pre && canConstruct(targetStr, wordBank),
true
)
}
}
return false
}

console.log(canConstruct('', ['cat']))
console.log(canConstruct('CatVsDog', ['Cat', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['at', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['Cat', 's', 'Do']))
console.log(canConstruct('CatVsDog', ['Cat', 'Dog', 'Vs']))``````

``console.log(canConstruct('CatVsDog', ['Cat', 'VsD', 'Vs', 'Dog']))// 本该为true, 输出false``

``````const canConstruct = (target, wordBank) => {
if (target === '') return true
return wordBank.reduce((pre, word) => {
if (target.indexOf(word) !== -1) {
return (
pre ||
target
.split(word, 2)
.reduce(
(pre, targetStr) => pre && canConstruct(targetStr, wordBank),
true
)
)
}
return pre
}, false)
}

console.log(canConstruct('', ['cat']))
console.log(canConstruct('CatVsDog', ['Cat', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['at', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['Cat', 's', 'Do']))
console.log(canConstruct('CatVsDog', ['Cat', 'VsD', 'Vs', 'Dog']))``````

some/every优化版

``````const canConstruct = (target, wordBank) => {
if (target === '') return true
console.log(target, wordBank)
return wordBank.some(
word =>
target.indexOf(word) !== -1 &&
target.split(word, 2).every(subStr => canConstruct(subStr, wordBank))
)
}

console.log(canConstruct('', ['cat']))
console.log(canConstruct('CatVsDog', ['Cat', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['at', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['Cat', 's', 'Do']))
console.log(canConstruct('CatVsDog', ['Cat', 'VsD', 'Vs', 'Dog']))``````

try-catch版

``````const canConstruct = (target, wordBank) => {
if (target === '') return true
try {
return wordBank.reduce((pre, word) => {
if (pre === true) throw new Error(true)
if (target.indexOf(word) !== -1) {
return (
pre ||
target
.split(word, 2)
.reduce(
(pre, targetStr) => pre && canConstruct(targetStr, wordBank),
true
)
)
}
return pre
}, false)
} catch (e) {
// console.log(typeof e.message)
// 注意这里会把boolean转成string, 直接return ture 就好了
return true
}
return false
}

console.log(canConstruct('', ['cat']))
console.log(canConstruct('CatVsDog', ['Cat', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['at', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['Cat', 's', 'Do']))
console.log(canConstruct('CatVsDog', ['Cat', 'VsD', 'Vs', 'Dog']))``````

#### 视频的实现

``````const canConstruct = (target, wordBank) => {
if (target === '') return true

for (const word of wordBank) {
if (target.indexOf(word) === 0) {
const suffix = target.slice(word.length)
if (canConstruct(suffix, wordBank) === true) return true
}
}

return false
}

console.log(canConstruct('', ['cat']))
console.log(canConstruct('CatVsDog', ['Cat', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['at', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['Cat', 's', 'Do']))
console.log(canConstruct('CatVsDog', ['Cat', 'VsD', 'Vs', 'Dog']))``````

``````console.log(
canConstruct('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef', [
'e',
'ee',
'eee',
'eeee'
])
)``````

#### 我的实现(正确版)

``````const canConstruct = (target, wordBank) => {
if (target === '') return true
return wordBank.some(
word =>
target.indexOf(word) !== -1 &&
splitOnce(target, word).every(subStr => canConstruct(subStr, wordBank))
)
}

// 只分割一次的函数
const splitOnce = (str, sign) => {
const index = str.indexOf(sign)
if (index === -1) return [str]
return [str.slice(0, index), str.slice(index + sign.length)]
}

console.log(canConstruct('', ['cat']))
console.log(canConstruct('CatVsDog', ['Cat', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['at', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['Cat', 's', 'Do']))
console.log(canConstruct('CatVsDog', ['Cat', 'VsD', 'Vs', 'Dog']))
console.log(
canConstruct('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef', [
'ef',
'eeeeeeeeeee'
])
)
console.log(
canConstruct('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef', [
'e',
'ee',
'eee',
'eeeeeeeeeee'
])
)``````

### dp版

#### 我的实现

``````const canConstruct = (target, wordBank, memo = {}) => {
if (target in memo) return memo[target]
if (target === '') return true
memo[target] = wordBank.some(
word =>
target.indexOf(word) !== -1 &&
splitOnce(target, word).every(subStr =>
canConstruct(subStr, wordBank, memo)
)
)
return memo[target]
}

const splitOnce = (str, sign) => {
const index = str.indexOf(sign)
if (index === -1) return [str]
return [str.slice(0, index), str.slice(index + sign.length)]
}``````

#### 视频实现

``````const canConstruct = (target, wordBank, memo = {}) => {
if (target in memo) return memo[target]
if (target === '') return true

memo[target] = false
for (const word of wordBank) {
if (target.indexOf(word) === 0) {
const suffix = target.slice(word.length)
if (canConstruct(suffix, wordBank, memo) === true){
memo[target] = true
return true
}
}
}

return memo[target]
}
``````

## countConstruct

### 递归版

#### 我的实现

``````const countConstruct = (target, wordBank, counter = 0) => {
if (target === '') return counter + 1
for (const word of wordBank) {
if (target.indexOf(word) === 0) {
const suffix = target.slice(word.length)
counter = countConstruct(suffix, wordBank, counter)
}
}

return counter
}``````

#### 视频实现

``````const countConstruct = (target, wordBank) => {
if (target === '') return 1
let counter = 0
for (const word of wordBank)
if (target.indexOf(word) === 0)
counter += countConstruct(target.slice(word.length), wordBank)

return counter
}``````

### dp版

``````const countConstruct = (target, wordBank, memo = {}) => {
if (target in memo) return memo[target]
if (target === '') return 1
let counter = 0
for (const word of wordBank)
if (target.indexOf(word) === 0)
counter += countConstruct(target.slice(word.length), wordBank, memo)

memo[target] = counter
return memo[target]
}``````

## allConstruct

### 递归版

#### 我的实现

``````const allConstruct = (target, wordBank) => {
const path = []
helper(target, wordBank, [], path)
return path
}

const helper = (target, wordBank, currentPath = [], path = []) => {
if (target === '' && currentPath.length !== 0) {
path.push([...currentPath])
}

for (const word of wordBank) {
if (target.indexOf(word) === 0) {
const preCur = [...currentPath] // key: 保存之前的状态, 每次获取子元素的子路径后还回去
currentPath.push(word)
helper(target.slice(word.length), wordBank, currentPath, path)
currentPath = preCur
}
}
}``````

#### 视频实现

``````const allConstruct = (target, wordBank) => {
if (target === '') return [[]]

const result = []

for (const word of wordBank) {
if (target.indexOf(word) === 0) {
const suffix = target.slice(word.length)
const suffixWays = allConstruct(suffix, wordBank)
const targetWays = suffixWays.map(way => [word, ...way])
result.push(...targetWays)
}
}

return result
}``````

### dp版

``````const allConstruct = (target, wordBank, memo = {}) => {
if (target in memo) return memo[target]
if (target === '') return [[]]

const result = []

for (const 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
}``````

# 列表化tabulation

## fib(nth)

<img src="https://gitee.com/zjeff-953/picsBed/raw/master/image/20210504090006.png" alt="image-20210504090006324" style="zoom:50%;" />

<img src="https://gitee.com/zjeff-953/picsBed/raw/master/image/20210504090048.png" alt="image-20210504090048196" style="zoom: 33%;" />

<img src="https://gitee.com/zjeff-953/picsBed/raw/master/image/20210504090113.png" alt="image-20210504090113448" style="zoom:25%;" />

``````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))
console.log(fib(7))
console.log(fib(8))
console.log(fib(50)) // 很快就出结果了``````

## gridTraveler

<img src="https://gitee.com/zjeff-953/picsBed/raw/master/image/20210424215247.png" alt="image-20210424215247677" style="zoom: 33%;" />

``````const gridTraveler = (m, n) => {
const table = Array(m + 1)
.fill() //undefined 不能map
.map(() => Array(n + 1).fill(0)) //直接full会指向相同的引用

table[1][1] = 1

for (let i = 0; i <= m; i++) {
for (let j = 0; j <= n; j++) {
if (i + 1 <= m) table[i + 1][j] += table[i][j] // 二维数组左值边界检查
if (j + 1 <= n) table[i][j + 1] += table[i][j]
}
}
return table[m][n]
}

console.log(gridTraveler(3, 2))``````

## 这类问题的总结

1. 规划你的table记录什么
2. 找出你的table的size , 维度
3. 初始化table的值是多少
4. 找到更新table的初值种子 (寻找那个和决定/随机/资源没有关系的情况 一般是0/1)
5. 迭代更新table
6. 考察每个格子对未来的格子的影响

## canSum

target是0的时候, 一定是true

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

console.log(canSum(7, [3, 2]))
console.log(canSum(7, [4, 2]))
console.log(canSum(7, [5, 6, 2]))
console.log(canSum(300, [7, 14]))``````

## howSum

``````const howSum = (targetSum, numbers) => {
const table = Array(targetSum + 1).fill(null)
table[0] = []
for (let i = 0; i <= targetSum; i++) {
if (table[i] !== null)
numbers.forEach(number => {
table[number + i] = [...table[i], number]
})
}
return table[targetSum]
}

console.log(howSum(7, [3, 2]))
console.log(howSum(7, [4, 2]))
console.log(howSum(7, [5, 6, 2]))
console.log(howSum(300, [7, 14]))``````

## 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)
numbers.forEach(number => {
if (!table[number + i] || table[number + i].length > table[i].length)
// 如果是null需要给予初值
table[number + i] = [...table[i], number]
})
}
return table[targetSum]
}

console.log(bestSum(7, [3, 2]))
console.log(bestSum(7, [4, 2]))
console.log(bestSum(7, [5, 6, 2]))
console.log(bestSum(300, [7, 14]))``````

## canConstruct

``````const canConstruct = (target, wordBank) => {
const table = Array(target.length + 1).fill(false)
table[0] = true

for (let i = 0; i <= target.length; i++) {
if (table[i] === true)
wordBank.forEach(word => {
if (target.slice(i, i + word.length) === word)
table[i + word.length] = true
})
}

return table[target.length]
}

console.log(canConstruct('', ['cat']))
console.log(canConstruct('CatVsDog', ['Cat', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['at', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['Cat', 's', 'Do']))
console.log(canConstruct('CatVsDog', ['Cat', 'VsD', 'Vs', 'Dog']))
console.log(canConstruct('CatVsDog', ['Cat', 'V', 's', 'Dog']))
console.log(
canConstruct('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef', [
'ef',
'eeeeeeeeeee'
])
)
console.log(
canConstruct('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef', [
'e',
'ee',
'eee',
'eeeeeeeeeee'
])
)``````

## countConstruct

``````const countSum = (target, wordBank) => {
const table = Array(target.length + 1).fill(0)
table[0] = 1

for (let i = 0; i <= target.length; i++) {
if (table[i] !== 0)
wordBank.forEach(word => {
if (target.slice(i, i + word.length) === word)
table[i + word.length] += table[i]
})
}

return table[target.length]
}

console.log(countSum('', ['cat']))
console.log(countSum('CatVsDog', ['Cat', 'Vs', 'Dog']))
console.log(countSum('CatVsDog', ['at', 'Vs', 'Dog']))
console.log(countSum('CatVsDog', ['Cat', 's', 'Do']))
console.log(countSum('CatVsDog', ['Cat', 'VsD', 'Vs', 'Dog']))
console.log(countSum('CatVsDog', ['Cat', 'V', 's', 'Vs', 'Dog']))``````

## allConstruct

``````const allConstruct = (target, wordBank) => {
const table = Array(target.length + 1)
.fill()
.map(() => [])

table[0] = [[]]
for (let i = 0; i < target.length; i++) {
wordBank.forEach(word => {
if (target.slice(i, i + word.length) === word) {
// 对于当前格子的每个情况都需要进行后续单词的检查
const newCombinations = table[i].map(subArr => [...subArr, word])
// 增加而不是覆盖
table[i + word.length].push(...newCombinations)
}
})
}

return table[target.length]
}

console.log(allConstruct('', ['cat']))
console.log(allConstruct('CatVsDog', ['Cat', 'Vs', 'Dog']))
console.log(allConstruct('CatVsDog', ['at', 'Vs', 'Dog']))
console.log(allConstruct('CatVsDog', ['Cat', 's', 'Do']))
console.log(allConstruct('CatVsDog', ['Cat', 'VsD', 'Vs', 'Dog']))
console.log(allConstruct('CatVsDog', ['Cat', 'V', 's', 'Vs', 'Dog']))``````

## 总结

1. 注意到重叠的子问题
2. 决定什么是最小的输入
3. 想一下记忆化递归
4. 想一下列表化问题
5. 画一个策略, 树或者数组

Keep curious, keep learning

【Jeff 在写代码】有关代码的一切的一切

5 声望
0 粉丝
0 条评论