为什么算法重要?很多时候我逃避算法,但算法真的不是想逃就能逃的。作为一个前端开发,我之前一直规避算法,老觉得嗯,我的八股文扎实了,技术项目有了,我就可以进大厂了。 but。。。三月底我参加了网易的笔试,被狠狠的伤了,我决定要好好的补算法。
借用我朋友的话警醒我自己:
算法不会,只能搬砖
动态规划
这是面试常考的一个算法,并且学会了真的很好用。
动态规划,就是利用 历史记录 ,来避免重复计算。存储这些历史记录我们一般采用 一维数组 或者 二维数组 来记录,重点!我们要明白,数组当中每个元素的含义!
1.动态规划的思路
-
明确数组元素的意义:假如我们使用一维数组dp来记录历史记录,这时非常重要的,就是规定这个数组的含义,dp[i]我们到底代表什么意思
-
确定数组元素之间的关系式:动态规划利用历史记录来推算出新的值,所以我们就要找出数组元素之间的关系式,比如青蛙跳台阶当中:dp[n] = dp[n-1]+dp[n-2],这就是我们所要确定的关系式,这是最难的,也是最重要的。
-
找出初始值,动态规划虽然使用 历史记录,但是 历史记录也是由初始值计算而来,dp[n] = dp[n-1]+dp[n-2]当中dp[n]也是依靠dp[n-1]与dp[n-2],但从头开始为dp[3] = dp[2]+dp[1],dp[2]与dp[1]不能再分解了,所以这就是初始值
2.案例分析
先来一个简单的一维数组的
青蛙跳台
问题描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 (1)明确数组元素的含义
按步骤,首先我们来定义 dp[i] 的含义,要求出:青蛙跳上 n 级的台阶总共由多少种跳法,那我们就定义 dp[i] 的含义为:跳上一个 i 级的台阶总共有 dp[i] 种跳法。这样最后 dp[n] 就是我们所求的结果了
(2)确定数组元素间的关系式
第二步,我们要找出元素之间的关系。这是最核心最难的一个,我们必须回到问题本身,来找出他们的关系式。
对于这道题,题中表明青蛙可以选择跳一级,也可以选择跳两级,所以青蛙到达第 n 级的台阶有两种方式:
一种是从第 n-1 级跳上来
一种是从第 n-2 级跳上来
由于我们是要算所有可能的跳法的,所以得出 dp[n] = dp[n-1] + dp[n-2]。
(3)、找出初始条件
显然由题可得初始值,dp[1] = 1,dp[0] = 1.(因为 0 个台阶,就是不跳这种跳法),dp[2] = dp[1]+dp[2](为什么不是dp[1]开始?因为dp[1]=dp[0]+dp[-1]显然不成立)
上代码:
var numWays = function(n) {
if(n == 0 || n==1){
return 1
}
let dp = [0,1]
for(let i =2;i <= n;i++){
dp[i] = dp[i-1]+dp[i-2]
}
return arr[n]
};
很多容易初始值找得有问题,比如刚刚的dp[0]很多就会找成0,这样结果都会有问题
机器人不同路径
这是一个二维数组存储历史记录的题型
(1)、定义数组元素的含义
我们的目标是求从左上角到右下角一共有多少种路径,这样我们就定义 dp[i] [j]为:当机器人从左上角走到[i, j]这个位置时,一共有 dp[i] [j] 种路径。最后dp[m-1] [n-1] 就是最终的结果。
(2)、找出数组元素间的关系式
机器人要怎么样才能到达 [i, j] ?机器人可以向下走或者向右走,
所以有两种方式:
一种是从 [i-1, j] 这个位置走一步到达;
一种是从[i, j - 1]这个位置走一步到达
因为是计算所有可能的步骤,所以是把所有可能走的路径都加起来,所以关系式是 dp[i] [j] = dp[i-1] [j] + dp[i] [j-1]。
(3)、找出初始值
因为当 dp[i] [j] 中,若 i 或者 j 有一个为 0,是不能的使用关系式的,因为此时把 i - 1 或者 j - 1,数组就会出问题了,所以我们的初始值是计算出所有的 dp[0] [i] 和所有的 dp[j] [0]。其实就是计算机数组的首行和首列。
因此初始值如下:
dp[0] [0….n-1] = 1; // 相当于最上面一行,机器人只能一直往左走
dp[0…m-1] [0] = 1; // 相当于最左面一列,机器人只能一直往下走
var uniquePaths = function(m, n) {
/*初始值为1(第一行与第一列),我这里其实是创建数组时,
对(第一行与第一列)进行了初值赋值,这里我采用的简便的写法全部都赋值了
*/
let dp = new Array(m).fill(new Array(n).fill(1))
if(m <= 0 || n <= 0){
return 0
}
for(let i = 1;i < m;i++){
for(let j =1;j < n;j++){
dp[i][j] = dp[i-1][j]+dp[i][j-1]//数组元素间的关系式
}
}
return res[m-1][n-1]
};
最小路径和
上代码,这里我直接上代码了,按照步骤分析
var minPathSum = function(grid) {
let m = grid.length
let n = grid[0].length
if(m == 0 || n == 0){
return 0
}
//dp[i][j]的意义:从开始到[i][j]最短的路径和,每一个点都是最短路径,有点类似贪心,每一步都是最优,最后整体最优
let dp = new Array(m).fill(0).map(() => new Array(n).fill(0))
//初值
dp[0][0] = grid[0][0]
for(let i = 1;i < n;i++){//第一行
dp[0][i] = grid[0][i]+dp[0][i-1]
}
for(let i = 1;i < m;i++){//第一列
dp[i][0] = grid[i][0]+dp[i-1][0]
}
for(let i = 1;i < m;i++){
for(let j = 1;j < n;j++){
dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j]//数组间关系式
}
}
return dp[m-1][n-1]
};