动态规划
斐波拉契数
方法一: 递归
思路:
-
- fn(x) = fn(x-1) + f(x-2)
- 例如: 求fib(5)
- fib(5) = fib(4) + fib(3)
- fib(4) = fib(3) + fib(2)
- fib(3) = fib(2) + fib(1)
/**
* @param {number} n
* @return {number}
*/
var fib = function(n) {
if(n === 0) return 0
if(n === 1) return 1
return fib(n-1) + fib(n-2)
};
斐波拉契的性能优化
-
求 fib(5)
- fib(5) = fib(4) + fib(3)
- fib(4) = fib(3) + fib(2)
- fib(3) = fib(2) + fib(1)
-
在上面运算中
- fib(3)执行了两次, fib(2)执行了两次
- 我们应该讲计算结果缓存起来, 避免重复计算
// 随着n增大,f(n)会超过Int32甚至Int64的取值范围,导致最终的返回值错误。
// 下面表示10的9次方+7 = 1000000007
// 这行代码定义了一个在算法和编程竞赛中常用的质数模数,用于处理大数运算和防止整数溢出
const mod = Math.pow(10,9)+7
const map = new Map()
function fib(n: number): number {
let res = map.get(n)
if(res) {
return res
}
if(n === 0) res = 0
if(n === 1) res = 1
if(n > 1) res = (fib(n-1)+fib(n-2)) % mod
map.set(n, res)
return res
};
将业务分开写,如下
// 这行代码定义了一个在算法和编程竞赛中常用的质数模数,用于处理大数运算和防止整数溢出
const mod = Math.pow(10,9)+7
const map = new Map()
function fibo(n: number): number {
if(n === 0) return 0
if(n === 1) return 1
return (fib(n-1) + fib(n-2))%mod
}
function fib(n: number): number {
if(map.has(n)) {
return map.get(n)
}
const res = fibo(n)
map.set(n, res)
return res
};
方法二: 迭代
- arr[5] = arr[4] + arr[3]
- arr[x] = arr[x-1] + arr[x-2]
const mod = Math.pow(10,9)+7
// n: [0, 1, 2, ...]
// value: [0, 1, 1, ...]
// index: [0, 1, 2, ...]
function fib(n: number): number {
if(n <= 0) return 0
if(n === 1) return 1
const fibIterator = new FibIterator()
for(let i=2; i<=n;i++) {
fibIterator.next()
}
return fibIterator.v
};
class FibIterator {
a = 0
b = 1
v = this.a + this.b
next() {
this.v = (this.a + this.b)%mod
this.a = this.b
this.b = this.v
}
}
跳台阶
今天的有氧运动训练内容是在一个长条形的平台上跳跃。平台有 num 个小格子,每次可以选择跳 一个格子 或者 两个格子。请返回在训练过程中,学员们共有多少种不同的跳跃方式。
分析
和斐波拉契数一摸一样的
f(x) = f(x-1) + f(x - 2)
- 当台阶数为1时: f(1) = 1
- 当台阶数量为2时: f(2) = 2
- 当台阶数量为3时
- 可以从 f(1) 跳两格到达 f(3)
- 也可以从 f(2) 跳一格到达 f(3)
- 所以跳到f(3)的方式: f(3) = f(2) + f(1)
- 同理: f(n) = f(n-1) + f(n-2)
写法1: 递归写法
const mod = Math.pow(10, 9)+7
const map = new Map()
function trainWays(num: number): number {
if(map.has(num)) {
return map.get(num)
}
const res = trainWays2(num)
map.set(num, res)
return res
};
function trainWays2(num) {
if(num <= 1) return 1
if(num === 2) return 2
return (trainWays(num-1) + trainWays(num-2))%mod
}
写法2: 迭代写法, 性能更好
const mod = Math.pow(10, 9) + 7;
// num: [1个台阶, 2个台阶, 3个台阶, ...]
// value: [1种跳法, 2种跳法, 3种跳法, ...]
// index: [0, 1, 2, ...]
// num 和 index 的关系是 index = num -1
function trainWays(num: number): number {
const iterator = new FibIterator();
if (num <= 1) return 1;
if (num === 2) return 2;
for (let i = 2; i < num; i++) {
iterator.next();
}
return iterator.v;
}
class FibIterator {
a = 1;
b = 2;
v = this.a + this.b;
next() {
this.v = (this.a + this.b) % mod;
this.a = this.b;
this.b = this.v;
}
}
快速排序
代码
function quickSort(arr: number[]): number[] {
if (arr.length <= 1) return [...arr];
const middle = arr.pop();
const left = arr.filter(item => item <= middle);
const right = arr.filter(item => item > middle);
return [...quickSort(left), middle, ...quickSort(right)];
}
const res = quickSort([3, 5, 1, 7, 11, 9, 88, 2, 0, 1, 111, 22]);
console.log(res);
分析
// 随便取一个数, 剩下的数小的方左边, 大的放右边, 排序完成
let arr = [8, 3, 5]
// 抽象公式
quickSort(arr) = [...quickSort(left), middle, ...quickSort(right)]
不同路径
一个机器人位于一个 m x n **网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
分析
- 如果第一步向右, 到达目的地有f(m-1, n)种方式
- 如果第一步向下, 到达目的地有f(m, n-1)种方式
- 所以到达目的地的方式: f(m, n) = f(m-1, n) + f(m, n-1)
方法一: 自上而下
const map = new Map()
function uniquePaths(m: number, n: number): number {
const key = `${m}-${n}`
if(map.has(key)) {
return map.get(key)
}
let res = base(m, n)
if(res === -1) {
res = uniquePaths(m-1, n) + uniquePaths(m, n-1)
}
map.set(key, res)
return res
};
function base(m, n) {
if(m === 0) return 0
if(n === 0) return 0
if(m === 1) return 1
if(n === 1) return 1
return -1
}
方法二: 自下而上
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function(m, n) {
const res = []
for(let i=0; i<m; i++) {
res[i] = []
for(let j=0; j<n; j++) {
res[i][j] = 1
}
}
for(let i=1; i<m; i++) {
for(let j=1; j<n; j++) {
res[i][j] = res[i][j-1] + res[i-1][j]
}
}
return res[m-1][n-1]
};
背包问题
规律
- 上图中
全为0的行和全为0的列忽略不看 - 第0排是葡萄🍇
- 第1排是矿泉水
- 第2排是西瓜🍉
- 求出第
0排数据 -> 就能求出第1排数据 - 求出第
1排数据 -> 就能求出第2排数据 - 求出第
i排数据 -> 就能求出第i+1排数据
const x = 6; // 背包可承受重量
const w = [2, 3, 4]; // 物品重量
const v = [3, 5, 6]; // 物品价格
const i = w.length-1 // i表示第i行
// 如果不往背包里放西瓜, 那么背包的最大价值为
const res1 = f(6, i-1)
// 如果往背包里放西瓜,
// 背包的最大价值 = 西瓜的价值 + 剩余空间的最大价值
const res2 = f(6-🍉重量, i-1) + 🍉价值
// 最终结果去两者最大值
f(x, i) = res1 > res2 ? res1 : res2
自上而下
function packages( bagWeight, index, values, weights ) {
if (bagWeight <= 0) return 0;
if (index === 0) {
// 0 生万物
// - 求出第`0`排数据 -> 就能求出第`1`排数据
// - 求出第`1`排数据 -> 就能求出第`2`排数据
// - 求出第`i`排数据 -> 就能求出第`i+1`排数据
return bagWeight < weights[0] ? 0 : values[0];
}
// 不放入🍉的最大价值
const res1 = packages(bagWeight, index - 1, values, weights);
// 背包太小, 无法放入🍉
if (bagWeight - weights[index] < 0) return res1;
const res2 = // 放入🍉的最大价值
packages(bagWeight - weights[index], index - 1, values, weights) +
values[index];
return res1 > res2 ? res1 : res2;
}
测试
const bagWeight = 6;
// 答案: 13
const weights1 = [2, 5, 1, 4, 3];
const values1 = [5, 10, 3, 6, 3];
// 答案: 9
const weights2 = [2, 3, 4];
const values2 = [3, 5, 6];
const res1 = packages(bagWeight, weights1.length - 1, values1, weights1);
const res2 = packages(bagWeight, weights2.length - 1, values2, weights2);
console.log(res1, res2); // 13, 9
自下而上
【动态规划中的01背包问题你清楚嘛?搞懂它再刷题!【渡一教育】】 www.bilibili.com/video/BV1qy…
【【自制】01背包问题算法动画讲解】 www.bilibili.com/video/BV1pY…
最长递增子序列
自下而上
抽象公式
res2 = cp(res1, 1)
res1 = [
[2],
[2, 3]
]
res2 = [
[1],
[2, 3],
]
遍历到2
arr[2, 3, 1, 5, 4]
res = [
[2]
]
遍历到3
arr[2, 3, 1, 5, 4]
res = [
[2],
[2, 3]
]
遍历到1
arr[2, 3, 1, 5, 4]
res = [
[1],
[2, 3],
]
遍历到5
arr[2, 3, 1, 5, 4]
res = [
[1],
[2, 3],
[2, 3, 5],
]
遍历到4
arr[2, 3, 1, 5, 4]
res = [
[1],
[2, 3],
[2, 3, 4],
]
实现
const arrInput = [2,3,1,5,6,8,7,9,4]
const res = lengthOfLIS(arrInput)
console.log(res)
/**
* @param {number[]} nums
* @return {number}
*/
var lengthOfLIS = function(nums) {
let res = [
[nums[0]]
]
for(let i=1; i<nums.length; i++) {
res = cp(res, nums[i])
}
return res.pop().length
};
function cp(arr, num) {
const index = find(arr, num)
if(index === 0) {
arr[0] = [num]
}else {
arr[index] = [...arr[index-1], num]
}
return arr
}
function find(arr, num) {
for(let i = 0; i<arr.length; i++) {
const nums = arr[i]
const n = nums[nums.length-1]
if(n >= num) return i
}
return arr.length
}