这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」
前言
这是阿源「Hecate」刷法成长之路的第四天。 想起之前部门内分享过的动态规划优化算法复杂度,重启算法学习之旅后翻找出来重新整理分析记录。
由来
- 约瑟夫问题(猴子选王)
- 链表模拟过程 分析复杂度O(n^2)
- leetcode 模板抛弃链表
- 类推法获取数学公式
- 动态规划
认识复杂度
- 空间复杂度
- 时间复杂度
- 最好情况复杂度
- 最坏情况复杂度
- 平均情况复杂度
- 均摊情况复杂度
形式O()表示法
O(1) O(logn) O(n) O(nlogn) O(n^a) O(a^n) 复杂度图像对比
斐波那契数列算法的时间复杂度分析示例
// 递归
function fib0(n) {
if(n <= 2) {
return 1;
}
return fib0(n - 2) + fib0(n - 1);
}
// 循环
function fib1(n) {
if( n <= 2 ) {
return 1;
}
var first = 1;
var second = 1;
while(n-- > 2) {
second += first;
first = second - first;
}
return second;
}
// 尾递归优化
function fib2(n , r1 = 1 , r2 = 1) {
if( n <= 2 ) {
return r2;
}
return fib2(n - 1, r2, r1 + r2);
}
约瑟夫环
数据结构解法
约瑟夫问题分解
function Node(element) {
this.element = element;
this.next = null; // 单向
}
function LinkList(num) {
var head = new Node(1);
var p = head;
for (var i = 2; i <= num; i++) {
var temp = new Node(i);
p.next = temp;
p = temp;
}
p.next = head; // 环形
return head;
}
function lastRemaining(n, m) {
var current = new LinkList(n);
while (current.next.element != current.element) {
// 数 m, 找 current
for (var i = 1; i < m; i++) {
var temp = current;
current = current.next;
}
console.log(current.element);
// 杀死 current
temp.next = current.next;
current = temp.next;
}
return current;
}
动态规划
动态规划(Dynamic Programming,简称DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
适用情况
- 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
- 无后效性。即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策影响。
- 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
解法:
定义状态:设dp[i]
为剩余i
个玩家时剩余玩家的位置;
确定初态:那么dp[1]
表示剩余1个玩家时,剩余玩家的位置,显然dp[1] = 0
;
确定状态转移方程:那么dp[i - 1]
为剩余i - 1
个玩家时剩余玩家的位置;因为dp[i]
过渡到dp[i - 1]
向后数了m
个数,所以dp[i]
和dp[i - 1]
的转移方程初步为:dp[i] = dp[i - 1] + m
;因为数m
这个操作是环形结构,随着i
的变化dp[i - 1] + m
一定会大于i
,所以最终的转移方程为:dp[i] = (dp[i - 1] + m) % i
;
var josephus = [1, 2, 3, 4, 5, 6]
function lastRemaining(n, m) {
var dp = []; // 状态:dp[i]
dp[0] = 0; dp[1] = 0;
for (var i = 1; i <= n; i++) {
dp[i] = (dp[i - 1] + m) % i; // 状态dp[i] 转移到 dp[i - 1]
}
return josephus[dp.pop()];
}
lastRemaining(josephus.length, 3);
var josephus = [1, 2, 3, 4, 5, 6]
function lastRemaining(n, m) {
var lastPos = 0; // 记录上一次最后被删除数的位置
for (var i = 1; i <= n; i++) {
lastPos = (lastPos + m) % i;
}
return josephus[lastPos];
}
lastRemaining(josephus.length, 3);
动态规划解斐波那契数列
function fib(n) {
var dp = []; // 设状态:dp[i]表示第i个斐波那契数
dp[0] = 1; // 初态
dp[1] = 1; // 初态
for (var i = 3; i <= n; i++) {
dp[i - 1] = dp[i - 2] + dp[i - 3]; // 状态转移方程 dp[i] 转移到 dp[i - 1]
}
return dp[n - 1];
}
线性代数特征方程、矩阵幂运算再优化斐波那契数列
复杂度:O(logn)