Learning notes
www.bilibili.com/video/BV1Y6…
Memorization recursion
fib
General implementation
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)) / / stuck
Copy the code
Such recursion is evaluated once at a time, resulting in multiple calls
To optimize the
Let’s think about how do we optimize this process
Consider a simplified version of the model, where we observe a function that looks like this
When we recurse twice
So our fib time before is zero
This is too bad
Traversal with memory is DP
// memoization
// js obj, keys: arg, value returns
// Change 1 To set the memo and initial values
const fib = (n, memo = {}) = > {
// Modify 2 to check whether there is memory
if (n in memo) return memo[n]
if (n === 1 || n === 2) return 1
// Modify 3 recursively with our reference
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)) // The result will come out soon
Copy the code
Travelers gridTraveler
Let’s start with the minimalist form
This is actually a boundary case
Simple case
With each step, the problem will simplify
So one way to think about it is this
The representational understanding is that
The recursive version
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))
Copy the code
The dp version
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))
Copy the code
A summary of these kinds of questions
Minimum outcome of success and minimum outcome of failure
canSum
Reverse thinking: sum to constant value -> Use constant value to traverse the number group down to 0
recursive
My solution
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]))
Copy the code
Video solution
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
}
Copy the code
The video solution has fewer recursions
dp
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]))
Copy the code
howSum
The recursive version
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]))
Copy the code
The dp version
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 // Unreachable also needs to be logged
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]))
Copy the code
bestSum
Tips: Use a recursive approach
- Think about exits, boundary conditions, failure and success conditions
- Call a recursive function assuming that the recursive function gets the result you want
The recursive version
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])) / /,4,1 [2]
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]))
Copy the code
The dp version
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])) / /,4,1 [2]
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]
Copy the code
Summary of these three questions
canConstruct
Obviously, this is the same kind of problem as canSum
Look for the boundary condition of this problem, the recursive termination condition, and keep decreasing the length of the character until it is empty, failing which is that the remaining character’s children are not in wordBank
1. How do I store matched characters? How to determine if the current character is no longer matched?
The recursive version
My implementation (wrong version)
After each successful match, the string is split, and the results of the operation are queried in turn. When the string is empty, it is a successful result. After the cycle is finished, there is no condition
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']))
Copy the code
If you think about it, there is a big problem with this. The program returns the first split point without checking whether the second split point still meets the condition
console.log(canConstruct('CatVsDog'['Cat'.'VsD'.'Vs'.'Dog']))// Should be true, print false
Copy the code
That’s why the changes were made
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']))
Copy the code
So that solves that problem, but there’s a downside to that, you can’t just stop, you can stop when you find out that the pre is true, so we can use some instead, some will stop the loop when it returns true, Similarly every will break out of the loop when it returns false.
You could also use throw+trycatch to complete the termination loop, but that would be weird and anti-pattern, but I implemented it anyway
Some/every optimization
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']))
Copy the code
Try-catch version
It’s disgusting to watch
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)
// Return true (string)
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']))
Copy the code
Implementation of video
We can check from left to right for substrings, which saves a lot of work, and we don’t have to check both sides when we recurse
This is an idea of transformation
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']))
Copy the code
I forgot the use case of the stress test, so I don’t have to think about it
console.log(
canConstruct('eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef'['e'.'ee'.'eee'.'eeee']))Copy the code
My implementation (correct version)
Well, when we went through the stress test case, we found that using split would split every e, so we would get [“, “], so it would be wrong
So you need to implement a function that only splits once
const canConstruct = (target, wordBank) = > {
if (target === ' ') return true
return wordBank.some(
word= >target.indexOf(word) ! = = -1 &&
splitOnce(target, word).every(subStr= > canConstruct(subStr, wordBank))
)
}
// split a function only once
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']))Copy the code
The dp version
The realization of the I
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)]
}
Copy the code
Video to realize
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]
}
Copy the code
countConstruct
The recursive version
The realization of the I
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
}
Copy the code
Video to realize
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
}
Copy the code
The dp version
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]
}
Copy the code
I think I’m getting pretty good at it
allConstruct
The recursive version
The realization of the I
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: saves the previous state and returns it each time the child path is retrieved
currentPath.push(word)
helper(target.slice(word.length), wordBank, currentPath, path)
currentPath = preCur
}
}
}
Copy the code
To be honest, I don’t know what I’m writing
Video to realize
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
}
Copy the code
The dp version
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] = resultreturn result
}
Copy the code
List the tabulation
Eliminate recursion, use array records, and study the effect of each previous situation on the subsequent situation
fib(nth)
const fib = n= > {
const table = Array(n + 1).fill(0) / / initialization
table[1] = 1 // Start with manual assignment
for (let i = 0; i < n; i++) {
// Each grid affects the next two
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)) // The result will come out soon
Copy the code
gridTraveler
const gridTraveler = (m, n) = > {
const table = Array(m + 1)
.fill() / / undefined cannot map
.map(() = > Array(n + 1).fill(0)) // Direct full will point to the same reference
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] // Check the lvalue boundary of a two-dimensional array
if (j + 1 <= n) table[i][j + 1] += table[i][j]
}
}
return table[m][n]
}
console.log(gridTraveler(3.2))
Copy the code
A summary of these kinds of questions
- What do you plan your table to record
- Find the size and dimension of your table
- What is the value of the initialization table
- Find the initial seed of the update table (find the seed that has nothing to do with decision/random/resource is usually 0/1)
- Update table iteratively
- Examine the effect of each cell on future cells
canSum
When target is 0, it must be 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]))
Copy the code
Little brother into infinite loop problem: do not always judge length, this is not good
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]))
Copy the code
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)// If it is null, the initial value should be given
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]))
Copy the code
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']))Copy the code
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']))
Copy the code
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) {
// For each case of the current grid, a subsequent word check is required
const newCombinations = table[i].map(subArr= > [...subArr, word])
// Add instead of overwritetable[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']))
Copy the code
conclusion
Encountered dp problems:
- Notice the overlap subproblem
- Decide what is the minimum input
- Think about mnemonic recursion
- Think about tabulating
- Draw a policy, a tree or an array
Keep curious, keep learning
[Jeff is writing code] Everything about code