将分治、动态规划、回溯、贪心一锅炖
微信搜索【前端食堂】你的前端食堂,记得按时吃饭。
本文已收录在Github
github.com/Geekhyt,感谢Star。
数据结构与算法系列专栏第四弹来袭,往期专栏链接如下:
- 前端如何搞定数据结构与算法
- JavaScript算法时间、空间复杂度分析
- 聊聊递归的本质
初学者一听到算法思想,就会觉得它们高深莫测,只能望而却步。
但如果你看过《事实》这本书,你就不会被大脑中的惯性思维所影响。
只要我们理解算法思想的关键点,多做题练习并加深理解记忆。其实算法思想就像切菜一样简单。
上一篇算法系列专栏中我们搞明白了递归。其实递归这种编程技巧是很多算法的基础。
还看过的同学建议先移步这篇专栏 聊聊递归的本质
比如本文讲到的这几种算法思想,大部分都是基于递归思想基础上的。
一句话理解四种算法思想
分治:分而治之,先解决子问题,再将子问题的解合并求出原问题。
贪心:一条路走到黑,选择当下局部最优的路线,没有后悔药。
回溯:一条路走到黑,手握后悔药,可以无数次重来。(英雄联盟艾克大招无冷却)。
动态规划:上帝视角,手握无数平行宇宙的历史存档,同时发展出无数个未来。
接下来我们一起庖丁解牛,将这几种算法思想一锅炖。
分治算法 Divide and Conquer
分治算法思想很大程度上是基于递归的,也比较适合用递归来实现。顾名思义,分而治之。一般分为以下三个过程:
- 分解:将原问题分解成一系列子问题。
- 解决:递归求解各个子问题,若子问题足够小,则直接求解。
- 合并:将子问题的结果合并成原问题。
比较经典的应用就是归并排序 (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;
}
复杂度分析
- 时间复杂度:O(nlogn)
- 空间复杂度:O(n)
动态规划 Dynamic Programming
LeetCode真题
70. 爬楼梯
虽然动态规划的最终版本 (降维再去维) 大都不是递归,但解题的过程还是离开不递归的。新手可能会觉得动态规划思想接受起来比较难,确实,动态规划求解问题的过程不太符合人类常规的思维方式,我们需要切换成机器思维。
使用动态规划思想解题,首先要明确动态规划的三要素。
动态规划三要素
重叠子问题
最优子结构
状态转移方程
重叠子问题
切换机器思维,自底向上思考。
爬第 n 阶楼梯的方法数量,等于两部分之和:
- 爬上 n-1 阶楼梯的方法数量
- 爬上 n-2 阶楼梯的方法数量
最优子结构
子问题的最优解能够推出原问题的优解。
状态转移方程
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];
};
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(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;
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(1)
贪心算法 Greedy
最近某音很火的贪心土味情话
喂,不是吧。今天喝了脉动啊,吃了果冻啊,但是,还是忍不住对你心动啊。
回到算法中,贪心算法
是动态规划
算法的一个子集,可以更高效解决一部分更特殊的问题。实际上,用贪心算法解决问题的思路,并不总能给出最优解。因为它在每一步的决策中,选择目前最优策略,不考虑全局是不是最优。
LeetCode真题
LeetCode 455. 分发饼干
思路
贪心算法+双指针求解。
- 给一个孩子的饼干应当尽量小并且能满足孩子,大的留来满足胃口大的孩子
- 因为胃口小的孩子最容易得到满足,所以优先满足胃口小的孩子需求
- 按照从小到大的顺序使用饼干尝试是否可满足某个孩子
- 当饼干 j >= 胃口 i 时,饼干满足胃口,更新满足的孩子数并移动指针
i++ j++ res++
- 当饼干 j < 胃口 i 时,饼干不能满足胃口,需要换大的
j++
关键点
将需求因子 g 和 s 分别从小到大进行排序,使用贪心思想配合双指针,每个饼干只尝试一次,成功则换下一个孩子来尝试。
复杂度分析
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
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;
};
回溯算法 Backtracking
回溯算法本质上就是枚举,使用摸着石头过河的查找策略,还可以通过剪枝少走冤枉路。
LeetCode真题
LeetCode 17.电话号码的字母组合
思路
使用回溯法进行求解,回溯是一种通过穷举所有可能情况来找到所有解的算法。如果一个候选解最后被发现并不是可行解,回溯算法会舍弃它,并在前面的一些步骤做出一些修改,并重新尝试找到可行解。究其本质,其实就是枚举。
如果没有更多的数字需要被输入,说明当前的组合已经产生。
如果还有数字需要被输入:
- 遍历下一个数字所对应的所有映射的字母
- 将当前的字母添加到组合最后,也就是
str + tmp[r]
关键点
在for循环中调用递归。
复杂度分析
N+M 是输入数字的总数
- 时间复杂度:O(3^N * 4^M)
- 空间复杂度:O(3^N * 4^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;
};
微信搜索【前端食堂】你的前端食堂,记得按时吃饭。
本文已收录在Github
github.com/Geekhyt,感谢Star。
将分治、动态规划、回溯、贪心一锅炖相关推荐
- c语言动态规划回溯的原理,「算法思想」分治、动态规划、回溯、贪心一锅炖...
观感度:????? 口味:东北一锅出 烹饪时间:10min 本文已收录在Github github.com/Geekhyt,感谢Star. 数据结构与算法系列专栏第四弹来袭,往期专栏链接如下: 初学者 ...
- 用递归与分治策略求解网球循环赛日程表_分治、动态规划、回溯、贪心一锅炖
「观感度:五颗星」 「口味:东北一锅出」 「烹饪时间:10min」 本文已收录在 Github github.com/Geekhyt,感谢Star. 数据结构与算法系列专栏第四弹来袭,往期专栏链接如下 ...
- 凑零钱动态规划java_凑零钱问题-动态规划回溯贪心
编码乱码问题解释,解决Tomcat乱码的最快速有效办法.[希望能够 编码乱码问题解释,解决Tomcat乱码的最快速有效办法.[希望能够指正] 文章目录 编码乱码问题的自我理解 我们来了解下,程序的执行 ...
- 相似算法比较:递归、分治、动态规划、贪心、回溯、分支限界
相似算法比较:递归.分治.动态规划.贪心.回溯.分支限界 在学习算法的过程中,递归.分治.动态规划.贪心.回溯.分支限界这些算法有些类似,都是为了解决大问题,都是把大问题拆分成小问题来解决,但她们 ...
- 【数据结构与算法】【算法思想】【联系与区别】回溯 贪心 动态规划 分治
4种算法思想比较与联系 如果将贪心,分治,回溯和动态规划四种算法思想分类,那贪心,回溯,动态规划可归为一类,而分治单独可以作为一类,因为它跟其他是三个都不大一样. 因为前三个算法解决问题的模型,都可以 ...
- 算法高级(28)-递归、分治、动态规划、贪心、回溯、分支限界几大相似算法比较
在学习算法的过程中,递归.分治.动态规划.贪心.回溯.分支限界这些算法有些类似,都是为了解决大问题,都是把大问题拆分成小问题来解决,但她们之间还是有一些不同之处的,我来给同学们整理一下. 一.算法思想 ...
- DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台、刷题集合、问题为导向的十大类刷题算法(数组和字符串、栈和队列、二叉树、堆实现、图、哈希表、排序和搜索、动态规划/回溯法/递归/贪心/分治)总
DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台.刷题集合.问题为导向的十大类刷题算法(数组和字符串.栈和队列.二叉树.堆实现.图.哈希表.排序和搜索.动态规划/回溯法/递归/贪心/分治)总 ...
- 递归、迭代、分治、回溯、动态规划、贪心算法
今天就简单来谈谈这几者之间的关联和区别 递归 一句话,我认为递归的本质就是将原问题拆分成具有相同性质的子问题. 递归的特点: 1.子问题拆分方程式,比如:f(n) = f(n-1) * n 2.终止条 ...
- sql表中只有子节点的递归_动态规划与静态规划、递归、分治、回溯
动态规划算是运筹学或者算法中的硬骨头了.不是说算法本身有多难,而是学完用完之后还是感觉到对其领会的不够深入,一种能用其术,不知其道的感觉.在很多教材或者回答中,经常看多将动态规划放在递归这一部分中.当 ...
最新文章
- 2022-2028年中国服装行业分析报告-产业规模现状与发展规划趋势
- jsp截取字符串前几位_7. Python3轻食丨丛林里的野蛮生长:无处不在的字符串(一)
- 调用父级方法_通信:找到任意组件实例的findComponents系列方法,5个终极方案
- Java动态代理类使用
- 设计模式--原型(Prototype)模式
- 复练-面试的科技树-企业人才需求和招聘流程
- python画图保存网页_一起学Python数据分析——引言
- java poi doc转docx_POI动态插入数据到Word文档
- java web 学习 --第八天(Java三级考试)
- 计算机英语词典 txt,9种Txt格式朗文英语词典免费分享
- PPT转图片(Java)
- 计算机屏幕方向变化,知识:如何切换笔记本电脑屏幕的方向_计算机的基本知识_IT /计算机_信息...
- 无人驾驶到底怎么赚钱?很现实,八仙过海,各显神通
- 数据库考研信息管理系统
- excel表格怎么换行?3个方法,你一定不知道第3个
- 信号的宽带和计算机网络的宽带有什么不同,计算机网络(一)带宽理解
- 2017 计算机 暑期学校,科学网-2018,17年数学统计学计量经济暑期学校汇总-张慧铭的博文...
- ibm x服务器硬盘检测,IBM System x 服务器自带RAID1的故障恢复
- 计算机视觉编程 第六章 图像聚类
- 廖雪峰Git学习笔记(上)