1. 什么是动态规划
- 动态规划是递归的思想,用空间换时间,是一种寻找最优解的方法。
- 动态规划有起始节点,有终止节点,每一个节点代表一个状态。任何一个非起始节点都可以从其他节点转移过来,即为状态转移。
- 解决动态规划问题的关键:如何设计状态
- 动态规划用空间换时间,可以利用内存的缓存能力,换取CPU的计算消耗,只要设计出状态转移图,就可以利用计算机的强大算力,通过初始状态计算出终止状态,从而求得问题的解。
- 动态规划求解步骤
- 设计状态
- 确定状态转移方程
- 确定初始状态
- 执行状态转移
- 计算最终的解
- 有向无环图
2. 递推
const arr = []
function numWays(n) {
if (n < 0) {
throw new Error("n应该大于0~");
}
arr[0] = arr[1] = 1;
for (let i = 2; i <= n; i++) {
arr[n] = arr[n - 2] + arr[n - 1];
}
return arr[n];
}
function f(n) {
if (n < 0) {
throw new Error("n应该大于0~");
}
if (n <= 1) {
return 1;
}
return f(n-2)+f(n-1);
}
function climbStairs(n) {
const dp[i] = [];
dp[1] = 1;
dp[2] = 2;
for (let i = 3; i <= n; i++) {
dp[i] = dp[i- 1] + dp[i - 2];
}
return dp[n]
}
function fn(sum) {
if (sum > n) {
return 0;
}
if (sum == n) {
return 1;
}
return fn(sum + 1) + fn(sum + 2)
}
3. 线性DP
- 状态转移
const nums = [1, 2, 3, 1]
const dp = new Array(nums.length).fill(0).map(item => new Array(2).fill(0));
function rob(nums) {
const n = nums.length;
if (n == 1) {
return nums[0];
}else if (n == 2) {
return Math.max(nums[0], nums[1]);
}
dp[0][0] = 0;
dp[0][1] = nums[0];
for (let i = 1; i < n; i++) {
for (let j = 0; j < 2; j++) {
if (i == 1) {
if (j == 0) {
dp[i][j] = nums[1]
} else {
dp[i][j] = nums[0]
}
} else if (i == n-1 && j == 1) {
dp[i][j] = dp[i-1][j]
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i-2][j] + nums[i])
}
}
}
return Math.max(dp[n][0], dp[n][1])
}
const arr = [1, 15, 7, 9, 2, 5, 10];
const k = 3;
const dp = new Array(arr.length).fill(0);
function maxSumAfterSplit(arr, k) {
let max = 0;
let current = 0;
for (let i = 0; i < arr.length; i++) {
max = 0;
current = 0;
for (j = i; j >= 0; j--) {
current++
if (arr[j] > max) {
max = arr[j];
}
if(current > k) {
break
}
if (j) {
dp[i] = Math.max(dp[i], dp[j-1] + current * max);
} else {
dp[i] = Math.max(dp[i], current * max);
}
}
}
return dp[arr.length-1]
}
- 前缀和
const nums = [1, 7, 3, 6, 5, 6]
const sum = []
function pivoIndex(nums) {
const n = nums.length;
for (let i = 0; i < n; i++) {
sum[i] = nums[i]'
if (i) {
sum[i] += sum[i-1]
}
}
if (sum[n-1] - sum[0]) return 0
for (let i = 0; i < n; i++) {
if (sum[i-1] == sum[n-1] - sum[i]) return i
}
return -1
}
4. 二维DP
const matrix = [
[0, 1, 1, 1],
[1, 1, 1, 1],
[0, 1, 1, 1]
]
const m = martix.length;
const n = matrix[0].length;
const len = Math.min(m, n);
const mat = new Array(len).fill(0).map(() => new Array(n).fill(0).map(() => new Array(n).fill(0)))
function countSquares(matrix) {
let ans = 0;
for (let l = 1; l <= len; l++) {
for (let i = 0; i + 1 <= n; i++) {
for (let j = 0; j + 1 <= m; j++) {
if (l == 1) {
mat[l][i][j] = matrix[i][j]
} else {
mat[l][i][j] = matrix[i][j] && mat[l-1][i+1][j] && mat[l-1][i][j+1] && mat[l-1][i+1][j-1]
}
ans += mat[l][i][j]
}
}
}
return ans;
}
5. 经典DP
- 最大子数组
const nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4];
const dp = new Array(nums.length).fill(0);
function maxSubArray(nums) {
let maxValue = nums[0];
for (let i = 0; i < nums.length; i++) {
dp[i] = nums[i]
if (dp[i-1] > 0) {
dp[i] += dp[i-1]
}
maxValue = Math.max(maxValue, dp[i])
}
return maxValue;
}
- 最长单调子序列
const nums = [10, 9, 2, 5, 3, 7, 101, 18];
function lengthOfLIS(nums) {
let dp = []
let max = 0
for (let i = 0; i < nums.length; i++) {
dp[i] = 1
for (let j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1
}
}
}
max = Math.max(max, dp[i])
}
return max
}
lengthOfLIS(nums)
const nums = [1, 3, 5, 4, 7];
function findNumberOfLIS(nums) {
let dp = []
let cnt = []
for (let i = 0; i < nums.length; i++) {
dp[i] = cnt[i] = 1
let str = ""
for (let j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1
cnt[i] = cnt[j]
} else if (dp[i] + 1 == dp[i]) {
cnt[i] += cnt[j]
}
}
}
}
return cnt[nums.length - 1]
}
const nums = [3, 6, 9, 12]
function logestArithSeqLength(nums) {
let ans = 0
for (let i = -500; i <= 500; i++) {
ans = Math.max(ans, longestArithSeq(nums, i)
}
return ans
}
function longestArithSeq(nums, diff) {
let dp = []
let max = 0
let val
for (let i = 0; i < nums.length; i++) {
dp[i] = 0
if (nums[i] - diff < 0) {
val = 0
} else {
val = dp[nums[i] - diff]
}
if (val + 1 > dp[nums[i]]) {
dp[nums[i]] = val + 1
max = Math.max(max, val + 1)
}
}
return max
}
const arr = [1, 2, 3, 4, 5, 6, 7, 8]
function lenLongestFibSubseq(arr) {
const dp = new Array(arr.length).fill(1)
let ans = 0
for (let i = 0; i < arr.length; i++) {
for(let j = i + 1; j < arr.length; j++) {
const idx = searchIdx(arr, 0, i-1, arr[j]-arr[i])
if (idx != -1) {
dp[i][j] = dp[idx][j] + 1
} else {
dp[i][j] = 2
}
ans = Math.max(ans, dp[i][j]
}
}
return ans
}
function searchIdx(arr, target, start, end) {
let mIdx = Math.floor((start + end) / 2)
if (!arr.includes(target)) {
return -1
}
while (start <= end) {
if (arr[mIdx] < target) {
searchIdx(arr, target, mIdx + 1, end)
} else if (arr[mIdx] > target) {
searchIdx(arr, target, start, mIdx - 1)
} else {
return mIdx
}
}
return -1
}
- 最长公共子序列
function longestCommonSubsequence(text1, text2) {
for (let i = 0; i < text1.length; i++) {
for (let j = 0; j < text2.length; j++) {
const same = (text1[i] == text2[j] ? 1 : 0)
if (i == 0 && j == 0) {
dp[i][j] = same
} else if (i == 0) {
dp[i][j] = dp[i][j-1] || same
} else if (j == 0) {
dp[i][j] = dp[i-1][j] || same
} else if (same) {
dp[i][j] = dp[i-1][j-1] + 1
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i][i-1])
}
}
}
return dp[text1.length-1][text2.length-1]
}
function longestPalindromeSubseq(t1, t2) {
const n = t1.length;
const m = t2.length;
const dp = []
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
const same = (t1[i] == t2[j]) ? 1 : 0
if (i == 0 && i == 0) {
dp[i][j] = same
} else if (i == 0) {
dp[i][j] = dp[i][j-1] || same
} else if (j == 0) {
dp[i][j] = dp[i-1][j] || same
} else if (same) {
dp[i][j] = dp[i-1][j-1] + same
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1])
}
}
}
return dp[n-1][m-1]
}
longestPalindromeSubseq(s, s.split("").reverse().join(""))
- 最短编辑距离
const m = word1.length;
const n = word2.length;
const dp = new Array(m).fill(0).map(() => new Array(n).fill(0))
const I = D = R = 1;
function minDistance(word1, word2) {
for (let i = 0; i < world1.length; i++) {
setdp(i, -1, (i+1) * D)
}
for (let i = 0; i < word2.length; i++) {
setdp(-1, i, (i+1) * I)
}
for (let i = 0; i < word1.length; i++) {
for (let j = 0; j < word2.length; j++) {
const ICost = getdp(i, j-1) + I
const DCost = getdp(i-1, j) + D
const RCost = getdp(i-1, j-1) + ((word1[i] == word2[j]) ? 0 : R)
const ans = Math.min(Math.min(ICost, DCost), RCost)
setdp(i, j, ans)
}
}
return dp[m-1][n-1]
}
function setdp(r, c, val) {
dp[r+1][c+1]] = val
}
function getdp(r, c) {
if (r == -1 && c == -1) {
return 0
}
return dp[r+1][c+1]
}
- 杨辉三角
function generate(numRows) {
const dp = []
for (let i = 0; i < numRows; i++) {
const v = [];
for (let j = 0; j <= i; j++) {
if (!j || j == i) {
v.push(1)
} else {
const val = dp[i-1][j-1] + dp[i-1][j]
v.push(val)
}
}
dp.push(v)
}
return dp
}
function uniquePaths(m, n) {
const dp = []
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (i == 1 || j == 1) {
dp[i][j] = 1
} else {
dp[i][j] = dp[i-1][j] + dp[i][j-1]
}
}
}
return dp[m][n]
}
- 经典股票问题
function maxProfit(prices, n) {
let ans = 0
const dp = calcPrevMin(prices, n, [])
for (let i = 1; i < n; i++) {
ans = Math.max(ans, price[i] - prev[i-1])
}
return ans
}
function calcPrevMin(nums, n, prevMin) {
for (let i = 0; i < n; i++) {
if (i == 0) {
prevMin[i] = nums[i]
} else {
prevMin[i] = Math.min(prevMin[i-1], nums[i-1])
}
}
return prevMin
}
- 零一背包
function canPartition(nums) {
const dp = []
let sum = 0
for (let i = 0; i <nums.length; i++) {
sum += nums[i]
}
if (sum % 2 == 1) {
return false
}
sum /= 2
for (let i = 0; i < nums.length; i++) {
for (let j = sum; j >= nums[i]; j--) {
dp[j] = dp[j - nums[i]] || dp[j]
}
if (dp[sum] == 1) return true
}
return false
}
- 完全背包
- 分组背包
- 博弈DP
- 记忆化搜索
- 状态DP