本篇博客参考 快乐动起来呀老师的《js版数据结构与算法》Part1改编自《漫画算法》原做者:程序员小灰javascript
而近两年随着互联网行业竞争愈发激烈,市场上对前端岗位的算法要求也有必定的提高。前端
我记得大三参加腾讯的校招面试时只准备了几种常见的排序算法就足以应对了,然而今年包括今日头条在内的多家大厂的前端笔试题目中都出现了"贪心算法""动态规划""分治算法"等进阶性的算法题目。若是在没有提早准备的状况下现场应对这类进阶性的算法题目并无那么简单。java
本篇博客将分为两个部分程序员
一一一一一一一一一一一一一一一一一一一一一一一一一面试
题目:算法
好比,每次放1本书,一共放10次,这是其中一种方法。咱们能够简写成 1,1,1,1,1,1,1,1,1,1。
数组
再好比,每次放2本书,一共放5次,这是另外一种方法。咱们能够简写成 2,2,2,2,2。
缓存
固然,除此以外,还有不少不少种方式。
数据结构
一一一一一一一一一一一一一一一一一一一一一一一一一函数
第一种:
第二种:
这里为了方便你们理解,我再另外举一个例子:
如图所示 假设只能经过road1或road2这两条路径到达终点
(至关于咱们把放书的最后一步分为放2本和放1本两种状况)
到达road1有x条路经(至关于0到8本的放法数量F(8))
到达road2有y条路经(至关于0到9本的放法数量F(9))
那么到达终点的可能性就是 x+y了 (也就是咱们前面推导的 F(10) = F(9)+F(8) )
F(1) = 1;
F(2) = 2;
F(n) = F(n-1)+F(n-2)(n>=3)
相信你们看完必定对动态规划有了一个初步的认识,
这里你们能够本身先尝试写一下这道题的代码
接下来咱们先来经过一道LeetCode实战原题加深咱们对动态规划的理解
LeetCode第63题 原题地址
题目难度 中等
题目描述
一个机器人位于一个m x n网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
如今考虑网格中有障碍物。那么从左上角到右下角将会有多少条不一样的路径?
![]()
网格中的障碍物和空位置分别用1
和0
来表示。
说明:m和n的值均不超过 100。
实例1
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: 2
解释: 3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不一样的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
相信你们已经看出来了,咱们这道题与咱们漫画中演示的题目几乎一致。
但它又提高了一点难度,咱们须要考虑到障碍物的状况。
还记得咱们以前提到的动态规划三要素【最优子结构】【边界】和【状态转移公式】吗?
拿题目中给出的图片进行举例:
在不考虑障碍物的状况下,咱们利用动态规划的思想,到达终点有几种状况呢?
很明显是两种: 从终点上方或终点左方到达
7 * 3 矩阵
那咱们很容易得出这个7*3的矩阵的终点 F(7*3) 的最优子结构为 F(6*3) 和 F(7*2)
至此它的状态转移公式也一目了然: F(m*n) = F(m-1*n) + F(m*n-1)
最后咱们考虑一下它的边界状况:
通过评论区同窗的指正,其实咱们以前考虑的F(2*2)边界状况继续往下分也能够分为一列和一行即F(1*2) + F(2*1)两种状况。
全部的F(m*n)的矩阵最后均可以拆分为一行和一列的状况,因此咱们这里边界状况只有两种。
export default (arr) => {
let dp = (m, n) => {
// 检查起始或者目标元素是否是1(障碍物),若是起始或者最后那个格就是1,说明怎么都怎么不到那,
// 直接返回0
if (arr[m - 1][n - 1] === 1 || arr[0][0] === 1) {
return 0
}
// 有边界
if (m < 2 || n < 2) {
// 第一种边界 1行n列
if (m < 2) {
return arr[m - 1].includes(1) ? 0 : 1
} else {
// 第二种边界 n行1列
for (let i = 0; i < m; i++) {
if (arr[i][0] === 1) {
return 0
}
}
return 1
}
} else {
// 递归
return dp(m - 1, n) + dp(m, n - 1)
}
}
return dp(arr.length, arr[0].length)
}复制代码
感谢同窗们在评论区提出的问题
首先说明咱们上方代码是没有问题的,可是在LeetCode上的第27个测试用例上超出了时间限制
这个测试用例相对复杂,是一个33*22的二维矩阵
那为何矩阵到达必定长度时咱们的方法时间复杂度会太高呢?
咱们先回顾一下咱们以前的思路:
将F(9)分解后那么F(10) 能够写成
而F(8) 又= F(7) + F(6)
那么继续将F(8)分解 F(10) 能够写成
注意到了吗?
越向下划分重复的就越多,可能你会以为不就是多加一次F(n)的值吗
可是这里我必需要提醒你的是:
F(n)不单纯是一个值的引用,他是一个递归函数,咱们每重复一次它都会从新执行一次F函数
咱们不讨论时间复杂度具体怎样计算
但这里我能够告诉你们咱们以前的方法时间复杂度是O(2^n)
那么怎样改进呢?
在这里提出两个思路,你们也能够尝试本身写一下:
// 传入二维数组
arr => {
// 行数
let n = arr.length;
if(!n){
return 0;
}
// 列数
let m = arr[0].length;
// 起点或终点为障碍物
if(arr[0][0] === 1 || arr[n - 1][m - 1] === 1){
return 0;
}
// 记录到达每一个位置的路径可能数
var rode= [];
// 遍历每一行
for(let i = 0; i < n; i++){
rode[i] = []; // 遍历每一行的每一个元素
for(let j = 0; j < m; j++){
// 若某节点是障碍物,则通向该节点的路径数量为0
if(arr[i][j] === 1){
rode[i][j] = 0;
} else if(i === 0){
// 如果第一行 每一个节点是否能经过都依赖它左方节点
rode[i][j] = rode[i][j - 1] === 0 ? 0 : 1;
} else if(j === 0){
// 如果第一列 每一个节点是否能经过都依赖它上方节点
rode[i][j] = rode[i - 1][j] === 0 ? 0 : 1;
} else {
// 不然递归
rode[i][j] = rode[i - 1][j] + rode[i][j - 1];
}
}
}
return rode[n - 1][m - 1];
}复制代码
你们发现了吗,当你掌握了动态规划的三要素【最优子结构】【边界】和【状态转移公式】
后,解决动态规划的算法题目并非很难。可是其中的思想是须要咱们好好消化吸取的。
相信之后遇到这类问题你也能够迎刃而解。