观感度:🌟🌟🌟🌟🌟javascript
口味:东北一锅出前端
烹饪时间:10minjava
本文已收录在
Github
github.com/Geekhyt,感谢Star。
数据结构与算法系列专栏第四弹来袭,往期专栏连接以下:git
初学者一听到算法思想,就会以为它们高深莫测,只能望而却步。github
但若是你看过《事实》这本书,你就不会被大脑中的惯性思惟所影响。
只要咱们理解算法思想的关键点,多作题练习并加深理解记忆。其实算法思想就像切菜同样简单。算法
上一篇算法系列专栏中咱们搞明白了递归。其实递归这种编程技巧是不少算法的基础。编程
还看过的同窗建议先移步这篇专栏你真的懂递归吗?数组
好比本文讲到的这几种算法思想,大部分都是基于递归思想基础上的。cookie
分治:分而治之,先解决子问题,再将子问题的解合并求出原问题。
数据结构
贪心:一条路走到黑,选择当下局部最优的路线,没有后悔药。
回溯:一条路走到黑,手握后悔药,能够无数次重来。(英雄联盟艾克大招无冷却)。
动态规划:上帝视角,手握无数平行宇宙的历史存档,同时发展出无数个将来。
接下来咱们一块儿庖丁解牛,将这几种算法思想一锅炖。
分治算法思想很大程度上是基于递归的,也比较适合用递归来实现。顾名思义,分而治之。通常分为如下三个过程:
比较经典的应用就是归并排序 (Merge Sort)
以及快速排序 (Quick Sort)
等。咱们来从归并排序理解分治思想,归并排序就是将待排序数组不断二分为规模更小的子问题处理,再将处理好的子问题合并起来。
上代码。
const mergeSort = function(arr) { const len = arr.length; if (len > 1) { // 对半分解 const middle = Math.floor(len / 2); const left = arr.slice(0, middle); const right = arr.slice(middle, len); let i = 0; let j = 0; let k = 0; // 分别对左右进行排序 mergeSort(left); mergeSort(right); while(i < left.length && j < right.length) { if (left[i] < right[j]) { arr[k] = left[i]; i++; } else { arr[k] = right[j]; j++; } k++; } // 检查余项 while(i < left.length) { arr[k] = left[i]; i++; k++; } while(j < right.length) { arr[k] = right[j]; j++; k++; } } return arr; }
虽然动态规划的最终版本 (降维再去维) 大都不是递归,但解题的过程仍是离开不递归的。新手可能会以为动态规划思想接受起来比较难,确实,动态规划求解问题的过程不太符合人类常规的思惟方式,咱们须要切换成机器思惟。
使用动态规划思想解题,首先要明确动态规划的三要素。
重叠子问题
最优子结构
状态转移方程
切换机器思惟,自底向上思考。
爬第 n 阶楼梯的方法数量,等于两部分之和:
子问题的最优解可以推出原问题的优解。
dp[n] = dp[n-1] + dp[n-2]
具有三要素,确认边界条件,初始化状态,开始切菜:
dp[0] = 1
dp[1] = 1
const climbStairs = function(n) { const dp = []; dp[0] = 1; dp[1] = 1; for (let i = 2; i <= n; i++) { dp[i] = dp[i-1] + dp[i-2]; } return dp[n]; };
在此基础上,咱们还能够经过压缩空间来对算法进行优化。由于 dp[i]
只与 dp[i-1]
和 dp[i-2]
有关,没有必要存储全部出现过的 dp 项,只用两个临时变量去存储这两个状态便可。
const climbStairs = function(n) { let a1 = 1; let a2 = 1; for (let i = 2; i <= n; i++) { [a1, a2] = [a2, a1 + a2]; } return a2; }
最近某音很火的贪心土味情话喂,不是吧。今天喝了脉动啊,吃了果冻啊,可是,仍是忍不住对你心动啊。
回到算法中,贪心算法
是动态规划
算法的一个子集,能够更高效解决一部分更特殊的问题。实际上,用贪心算法解决问题的思路,并不总能给出最优解。由于它在每一步的决策中,选择目前最优策略,不考虑全局是否是最优。
贪心算法+双指针求解。
i++ j++ res++
j++
将需求因子 g 和 s 分别从小到大进行排序,使用贪心思想配合双指针,每一个饼干只尝试一次,成功则换下一个孩子来尝试。
const findContentChildren = function (g, s) { g = g.sort((a, b) => a - b); s = s.sort((a, b) => a - b); let gi = 0; // 胃口值 let sj = 0; // 饼干尺寸 let res = 0; while (gi < g.length && sj < s.length) { if (s[sj] >= g[gi]) { gi++; sj++; res++; } else { sj++; } } return res; };
回溯算法本质上就是枚举,使用摸着石头过河的查找策略,还能够经过剪枝少走冤枉路。
使用回溯法进行求解,回溯是一种经过穷举全部可能状况来找到全部解的算法。若是一个候选解最后被发现并非可行解,回溯算法会舍弃它,并在前面的一些步骤作出一些修改,并从新尝试找到可行解。究其本质,其实就是枚举。
若是没有更多的数字须要被输入,说明当前的组合已经产生。
若是还有数字须要被输入:
str + tmp[r]
在for循环中调用递归。
N+M 是输入数字的总数
const letterCombinations = function (digits) { if (!digits) { return []; } const len = digits.length; const map = new Map(); map.set('2', 'abc'); map.set('3', 'def'); map.set('4', 'ghi'); map.set('5', 'jkl'); map.set('6', 'mno'); map.set('7', 'pqrs'); map.set('8', 'tuv'); map.set('9', 'wxyz'); const result = []; function generate(i, str) { if (i == len) { result.push(str); return; } const tmp = map.get(digits[i]); for (let r = 0; r < tmp.length; r++) { generate(i + 1, str + tmp[r]); } } generate(0, ''); return result; };
1.看到这里了就点个赞支持下吧,你的赞是我创做的动力。
2.关注公众号前端食堂,你的前端食堂,记得按时吃饭!
3.本文已收录在前端食堂Github
github.com/Geekhyt,求个小星星,感谢Star。