适用场景: 最优子结构、无后效性和重复子问题
经典问题:
- 解决线性
- 前缀和
- 区间
- 746. 使用最小花费爬楼梯 :::
步骤:
- 根据状态, 确定状态转移方程
- (根据状态方程), 确定初识值、边界。。。
练习1: 经典
70. 爬楼梯
dp[i] = dp[i-1] + dp[i -2]
更优解
[a, b] = [b, a + b];
爬楼梯: 面试题 08.01. 三步问题
更优解
[a, b, c] = [b, c, (a + b + c) % 1000000007];
剑指 Offer 46. 把数字翻译成字符串
0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。
计算一个数字有多少种不同的翻译方法?
- 从前高位到低位依次获取数字
- 动态规划, 限制prev != 0 && prev * 10 + cur <= 25
- 合法 dp[n] = dp[n-1] + dp[n-2]
- 不合法 dp[n] = dp[n-1]
case 1: 12258 => 5
prev, cur, prev * 10 + cur
0 1 1
1 2 12
2 2 22
2 5 25
5 8 58
[ 0, 1, 1, 2, 3, 5, 5]
📢 case : 506 => 1 ,额外判断不合法限制
var translateNum = function(num) {
let len = String(num).length;
let base = 10 ** (len-1);
let dp = [0, 1];
let prev = 0, cur = 0;
for (let i=2; i < len+2; i++) {
prev = cur;
cur = Math.floor(num / base);
num = num % base;
base /= 10;
dp[i] = dp[i-1];
if (i > 2 && prev != 0 && (prev * 10 + cur) <= 25) {
dp[i] += dp[i-2];
}
}
return dp.pop();
};
剑指 Offer 42. 连续子数组的最大和(53. 最大子序和)
状态方程: dp[i] = Math.max(dp[i-1]+nums[i], nums[i])
var maxSubArray = function(nums) {
let dp = new Array(nums.length).fill(null);
dp[0] = nums[0];
for (let i = 1; i <nums.length; i++) {
dp[i] = Math.max(dp[i-1]+nums[i], nums[i])
}
// console.log(dp)
return Math.max(...dp);
};
剑指 Offer 63. 股票的最大利润(121. 买卖股票的最佳时机)
状态方程: dp[i] = Math.max(dp[i-1], prices[i] - minPrice)
var maxProfit = function(prices) {
let dp = new Array(prices.length).fill(null)
dp[0] = 0;
for (let i = 1; i < prices.length; i++) {
let minPrice = Math.min(...prices.slice(0, i));
dp[i] = Math.max(dp[i-1], prices[i] - minPrice);
}
// console.log(dp)
return Math.max(...dp)
};
常规做法:
var maxProfit = function(prices) {
// 最多只允许完成一笔交易
let res = 0;
let min = prices[0];
for (let i = 1; i< prices.length; i++) {
let cur = prices[i];
if (cur > min && cur - min > res) {
res = cur - min;
}
if (cur < min) {
min = cur
}
}
return res;
};
198. 打家劫舍
状态方程: dp[i] = Math.max(dp[i-1], dp[i-2]+nums[i])
var rob = function(nums) {
if (nums.length == 0) return 0;
if (nums.length < 3) return Math.max(...nums);
let dp = new Array(nums.length).fill(0);
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]); // 易错点
for (let i=2; i<nums.length; i++) {
dp[i] = Math.max(dp[i-1], dp[i-2]+nums[i])
}
return dp.pop();
};
322. 零钱兑换
状态方程:dp[i] = Math.min(dp[i], dp[i-coin] + 1);
⚠️ 嵌套循环
var coinChange = function(coins, amount) {
var dp = new Array(amount+1).fill(amount+1);
dp[0] = 0;
for (var i = 1; i < amount+1; i++) {
for (var coin of coins) {
if (i >= coin ) {
dp[i] = Math.min(dp[i], dp[i-coin] + 1);
}
}
}
var res = dp.pop();
return res > amount ? -1 : res;
};
练习2: 数列
509. 斐波那契数
状态方程: dp[i] = i < 2 ? i : dp[i - 1] + dp[i - 2]
⚠️ 开始的两位数[0, 1]
var fib = function(N) {
var dp = [];
for (var i = 0; i <= N; i++) {
dp[i] = i < 2 ? i : dp[i - 1] + dp[i - 2]
}
// console.log(dp)
return dp.pop()
};
1137. 第 N 个泰波那契数
状态方程: dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]
⚠️ 开始的三位数 [0, 1, 1]
var tribonacci = function(n) {
let len = Math.max(2, n+1);
let dp = new Array(len).fill(null);
dp[0] = 0;
dp[1] = 1;
dp[2] = 1;
if (n<3) return dp[n];
for (let i=3; i<=n; i++) {
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
}
return dp.pop()
};
练习3:
剑指 Offer 14- I. 剪绳子(343. 整数拆分)
2 <= n <= 58
状态方程: dp[i] = Math.max(dp[i], j dp[i-j], j (i - j))
⚠️ 内部嵌套的边界
var cuttingRope = function(n) {
let dp = new Array(n+1).fill(0)
dp[0] = 0;
dp[1] = 1;
for (let i = 2; i <= n; i++) {
for (let j = 1; j < i; j++) {
dp[i] = Math.max(dp[i], j * dp[i-j], j * (i - j))
}
}
// console.log(dp);
return dp[n]
};
剑指 Offer 14- II. 剪绳子 II
剑指 Offer 49. 丑数(264. 丑数 II)
优化: 三指针+动态规划
分析: 由2, 3,5组合乘积, 第n个数由第x个数*(2|3|5)组成。第x数,可能有三种, 故是3个指针。
var nthUglyNumber = function(n) {
// 三指针+动态规划
let dp = new Array(n).fill(1);
let a = 0, b = 0, c = 0;
for (let i = 1; i < n; i ++) {
dp[i] = Math.min(dp[a] * 2, dp[b] * 3, dp[c] * 5);
// 注意, 出现重复乘积,可能多个指针同时移动
if (dp[i] == dp[a] * 2) {
a++;
}
if (dp[i] == dp[b] * 3) {
b++;
}
if (dp[i] == dp[c] * 5) {
c++;
}
}
// console.log(dp)
return dp.pop();
};
练习4:
面试题 16.17. 连续数列
动态规划, dp保存当前位置结束的连续子序列和的最大值
dp[i+1] = Math.max(dp[i] + cur, cur);
var maxSubArray = function(nums) {
let dp = [-Infinity];
for (let i = 0; i < nums.length; i++) {
let cur = nums[i];
dp[i+1] = Math.max(dp[i] + cur, cur);
}
return Math.max(...dp);
};
279. 完全平方数
function numSquares(n: number): number {
let dp = new Array(n + 1).fill(0);
for (let i = 1; i <= n; ++i) {
let min = Infinity;
for (let j = 1; j * j <= i; ++j) {
min = Math.min(min, dp[i - j * j]);
}
dp[i] = min + 1;
}
return dp.pop();
};