动态规划
· 阅读需 21 分钟
原题
70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
提示:
1 <= n <= 45
伪代码
函数 爬楼梯(台阶数 n):
如果 n 是 1:
返回 1
如果 n 是 2:
返回 2
否则:
返回 爬楼梯(n-1) + 爬楼梯(n-2)
dp[1] = 1
dp[2] = 2
从 3 开始:
dp[3] = dp[2] + dp[1]
dp[4] = dp[3] + dp[2]
……
dp[n] = dp[n-1] + dp[n-2]
编码
/**
* 推荐写法,优化空间
* @param {number} n
* @return {number}
*/
var climbStairs = function (n) {
// 动态规划自底向上计算,从最小层级开始计算
let arr = new Array(n + 1)
if (n == 1) return 1
if (n == 2) return 2
let a = 1
let b = 2
let res = 0
for (let i = 3; i <= n; i++) {
res = a + b
a = b
b = res
}
return res
}
/**
* @param {number} n
* @return {number}
*/
var climbStairs = function (n) {
if (n == 1) return 1
if (n == 2) return 2
let dp = []
dp[1] = 1
dp[2] = 2
for (let i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2]
}
return dp[n]
}
198 打家劫舍
原题
198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
误区
并不是间隔偷就是最优解
而是求 每个位置之前偷窃的最优解
拆解就是
1 2 3 1
0 --- nums[0]
1 --- max(0) / nums[1]
2 --- max(1) / max(0) + nums[2]
3 --- max(2) / max(1) + nums[3]
...
n --- max(n-1) / max(n-2) + nums[n]
伪代码
函数 rob(nums):
如果 nums 是空,返回 0
如果 只有 1 个房子,返回 nums[0]
创建数组 dp,长度为 nums.length
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
对 i 从 2 到 nums.length - 1:
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
返回 dp[最后一个位置]
编码
/**
* @param {number[]} nums
* @return {number}
*/
var rob = function (nums) {
// 动态规划的题目都要手动推导一遍,找出规律
if (nums.length == 1) return nums[0]
if (nums.length == 2) return Math.max(nums[0], nums[1])
let max = []
max[0] = nums[0]
max[1] = Math.max(nums[0], nums[1])
for (let i = 2; i < nums.length; i++) {
max[i] = Math.max(max[i - 1], max[i - 2] + nums[i])
}
return max[nums.length - 1]
}
746 使用最小花费爬楼梯
原题
746. 使用最小花费爬楼梯
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
示例 1:
输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。
- 支付 15 ,向上爬两个台阶,到达楼梯顶部。
总花费为 15 。
示例 2:
输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。
提示:
2 <= cost.length <= 1000
0 <= cost[i] <= 999
推导
1 100 1 1 1 100 1 1 100 1
可以爬1/2个台阶,所以如果从最底部向上爬,需要付费为 0
总阶梯数量 花费
n = 0, sum = 0
n = 1, sum = 0
n = 2, sum = 0
n = 3, sum = nums[0] / nums[1] --- min
n = 4, sum = min(3) / min(2)
n = 5, sum = min(4) / min(3)
...
n sum = min(n-1) / min(n - 2)
伪代码
function minCostClimbingStairs(cost):
n = length of cost
dp[0] = 0 // 到达第0阶(起点)不花钱
dp[1] = 0 // 到达第1阶(也可作为起点)不花钱
for i from 2 to n:
// 你可以从 i-1 跨一步,或者从 i-2 跨两步
dp[i] = min(
dp[i - 1] + cost[i - 1],
dp[i - 2] + cost[i - 2]
)
return dp[n]
我的解法
点 | 你写法 | 正确写法 |
---|---|---|
状态定义 | 正确 ✅ | 正确 ✅ |
状态转移 | 正确 ✅ | 正确 ✅ |
初始化 dp | 多余 ❌ | 精简 ✅ |
额外 min[2] | 不必要 ❌ | 不需要,循环中计算 ✅ |
/**
* @param {number[]} cost
* @return {number}
*/
var minCostClimbingStairs = function (cost) {
// 求最小花费 + 每次可以跨1或者2个台阶
// ==> 求到最后一个台阶的花费
// ==> 倒数第二个台阶+到倒数第二个台阶的花费 ~ 倒数第一个台阶+到倒数第一个台阶的花费
// ==> 求两个花费得最小值
if (cost.length <= 1) return 0
let min = []
min[0] = 0
min[1] = 0
min[2] = Math.min(cost[0], cost[1])
for (let i = 3; i <= cost.length; i++) {
min[i] = Math.min(min[i - 2] + cost[i - 2], min[i - 1] + cost[i - 1])
}
// console.log({ min, target: min[cost.length] })
return min[cost.length]
}
/**
* @param {number[]} cost
* @return {number}
*/
var minCostClimbingStairs = function (cost) {
if (cost.length <= 1) return 0
let min = [0, 0] // 0 1 位置的最小花费都是 0
for (let i = 2; i <= cost.length; i++) {
min[i] = Math.min(min[i - 2] + cost[i - 2], min[i - 1] + cost[i - 1])
}
return min[cost.length]
}
/**
* @param {number[]} cost
* @return {number}
*/
var minCostClimbingStairs = function (cost) {
if (cost.length <= 1) return 0
let prev = 0 // dp[i-2]
let curr = 0 // dp[i-1]
for (let i = 2; i <= cost.length; i++) {
const next = Math.min(prev + cost[i - 2], curr + cost[i - 1])
prev = curr
curr = next
}
return curr
}