https://houbb.github.io/2020/01/23/data-struct-learn-07-base 算法概览

一. 动态规划

思想概念讲的特别好: https://houbb.github.io/2020/01/23/data-struct-learn-07-base-dp

计算机解决问题的方式就是穷举,算法的作用是让穷举更有效率!(来自网络名言)

动态规划 解决 斐波数列:对于 递归的实现,其实 动态规划的实现更好,会更节省内存。

  1. function getNthNumOfFIBO(n) {
  2. const resultMap = { 0: 1, 1: 1 };
  3. if (n <= 1) {
  4. return resultMap[n];
  5. }
  6. for (let i = 2; i <= n; i++) {
  7. resultMap[i] = resultMap[i - 1] + resultMap[i - 2];
  8. }
  9. return resultMap[n];
  10. }
  11. console.log('fn(2): ', getNthNumOfFIBO(2));
  12. console.log('fn(3): ', getNthNumOfFIBO(3));
  13. console.log('fn(4): ', getNthNumOfFIBO(4));
  14. console.log('fn(5): ', getNthNumOfFIBO(5));

求最长公共子串,这个也是 利用动态规划做的吗,我写了一遍,感觉理解不了:
参考:https://blog.csdn.net/u010397369/article/details/38979077

  1. // 最长公共字符串
  2. // raven havoc => 'av'
  3. function getMaxSubString(s1, s2) {
  4. const map = [];
  5. for (let i = 0; i < s1.length; i++) {
  6. let row = [];
  7. for (let j = 0; j < s2.length; j++) {
  8. if (s1[i] === s2[j]) {
  9. if ((i === 0) || (j === 0)) {
  10. row.push(1);
  11. } else {
  12. row.push(1 + map[i - 1][j - 1]);
  13. }
  14. } else {
  15. row.push(0);
  16. }
  17. }
  18. map.push(row);
  19. }
  20. return map;
  21. }
  22. const map = getMaxSubString('acbcbcef', 'abcbced');
  23. for (let i = 0; i < map.length; i++) {
  24. console.log(map[i]);
  25. }

来自司徒正美:https://segmentfault.com/a/1190000012864957 又看到了大神的文章,他曾经也迷茫,成名之后也还保持努力,非常有魅力的人物,36岁去世,缅怀。 — 他的文章有两个借鉴点:1)滚动数组 节省空间 2)边界情况的处理

此外,还意外获取到一本书 : 《图解算法》 看起来可以锻炼心智。

二. 背包问题

背包问题我是先看的书(《图解算法》),第一天没有看懂,就看别的了;第二天看懂了,还写出代码实现;第三天,决定写成文章,加深理解。总得来说,书上的语言也能讲得明白,但是仍然不是非常直白,所以有了这篇文章,去叙述下自己的理解,为自己后面复习也提供帮助,同样也能帮助其他人快速理解什么是背包问题,以及它的奥妙之处。

1. 问题描述

image.pngimage.png
这个问题背景设置很有趣,我之前一直以为“背包问题”问题背景应该是爬山时往背包里塞装备,原来是在说偷东西,这让人很兴奋!

2. 思考过程

背包问题是动态规划,动态规划的实质是先解决小问题,然后用小问题的解,去解决大问题,and so on…
斐波数列 的 动态规划解决方式,就是典型的体现。
思想明了之后,接下来就是找突破口:背包问题,存在 小问题 的解去解决 大问题 吗?或者说 大问题 可以拆解为 小问题 吗?
我在刚刚阅读背包问题时,我的思维习惯是 一维 的:背包是4磅固定的,不断增加可以偷的商品数量,如果只能偷一件商品,最大的价值是多少;如果只能偷两件,最大的价值是多少。这样很符合直觉,但问题是:商品数量变得很多时,陷入大量排列组合中,人脑解决已经不切实际。
但是书中背包问题的解法,实际是 二维 的,这实际上在开始时很难理解。背包数量也 创造性 划分为1 2 3 4 磅 分级,同时商品数量也是分为只能拿a,只能拿a b,只能拿a b c… 这样二维的划分,就形成了如下的网格:
image.png
解释和填写网格:

  1. 从行的角度看,从上到下:

第一行,被 红色 框住,对应于4列的单元格,每个单元格的意义:对于1磅、2磅、3磅 和 4磅 的四种背包,说明此时商场内只有 吉他 可以偷,其他物品没有;
第二行,被 蓝色 框住,说明此时只有 吉他音响 可以偷;(单元格意义同上)
第三行,被 绿色 框住,说明此时只有 吉他 音响 笔记__本电脑 可以偷。(单元格意义同上)
image.png

  1. 从列的角度看,从上到下:

第一行第一列,红色框,表示 小偷有 1磅的背包,商场只有吉他可以偷,小偷有两种选择 偷/不偷,背包有无奈 装得下/装不下,前者可能是 小偷有更好的选择,而背包却是硬性限制。综合来看,1磅的背包 能装的下 吉他,小偷此时只有吉他可以选择,本着“贼不走空”的原则,此时单元格 去填上 吉他的价格 1500。
第二行第一列,蓝色框,表示 小偷有 1磅的背包,商场只有吉 和 音响 他可以偷,相同道理,虽然音响更值钱但是单体超出背包总容量,所以,只能放弃,所以此时单元格 去填上 吉他的价格 1500。
第二行第二列,绿色框 道理同上。

  1. 转折来了

image.png
第三行第三列,代表 容量3磅的背包,此时可以偷 吉他 音响笔记__本电脑 ,单元格里填此时的最大值。我再填单元格时最大问题是会蒙圈,到底如何去填?其实是有相当明确的算法的:
当我们知道,我们有三种物品 吉他 音响笔记__本电脑 可以选择时,我们可以分为两类:选 笔记本 和 不选笔记本,我们去分别计算 满足 背包容量限制 和 价值最大的原则。

其中不选笔记本, 容量3磅的背包 能获取的最大价值就在 上方单元格里写着。我们唯一要计算的就是,选了笔记本之后价值是否有增加:我们可以看到,首先背包容量容许我们装入笔记本,而且价值 2000 > 1500。

  1. 关键点

这样算起来是最优解!
最后一个单元格才是我们要的问题答案,4磅容量的背包,三种物品可选,其正上方 3000 的单元格表明,没有笔记本电脑可选时的最大值。所以我们现在只需要计算 如果要一定要选上笔记本时,价值是否会增加,对应的组合二是什么即可。笔记本电脑 3磅,4磅容量的背包 还剩 1 磅,并且这一磅只能选择两样物品(吉他音响 ),所以第二行第一列的1500 正好满足标准,已经帮我们计算好了,所以我们找到了最优解!
image.png

3. 代码实现

实现后发现代码量还是蛮多的,还有不少边界需要处理,以及条件判断。

  1. // 背包问题:系统化穷举
  2. const goodsArray = [
  3. [10, 3, 'water'], [3, 1, 'book'], [9, 2, 'food'], [5, 2, 'jacket'], [6, 1, 'camera']
  4. ];
  5. const containerWeightArray = [1, 2, 3, 4, 5, 6];
  6. console.log(getResult(goodsArray, containerWeightArray))
  7. function getResult(goodsArray, containerWeightArray) {
  8. const resultForm = [];
  9. for (let i = 0; i < goodsArray.length; i++) {
  10. let lineInfo = [];
  11. for (let j = 0; j < containerWeightArray.length; j++) {
  12. const containerWeight = containerWeightArray[j];
  13. const [goodsValue, goodsWeight, goodsName] = goodsArray[i];
  14. let leftWeight = containerWeight - goodsWeight;
  15. let preMaxValue = 0;
  16. let preMaxWeight = 0;
  17. let preMaxGoods = [];
  18. if (i > 0) {
  19. preMaxValue = resultForm[i - 1][j][0];
  20. preMaxWeight = resultForm[i - 1][j][1];
  21. preMaxGoods = resultForm[i - 1][j][2];
  22. }
  23. let subMaxValue = 0;
  24. let subMaxWeight = 0;
  25. let subMaxGoods = [];
  26. if ((i > 0) && (j > 0)) { // 把边的单元格没有 子问题 的最优解
  27. if (leftWeight >= 0) {
  28. let subMaxItemIndex = containerWeightArray.findIndex(i => i === leftWeight);
  29. if (subMaxItemIndex !== -1) {
  30. let subMaxItem = resultForm[i - 1][subMaxItemIndex];
  31. subMaxValue = subMaxItem[0];
  32. subMaxWeight = subMaxItem[1];
  33. subMaxGoods = subMaxItem[2];
  34. }
  35. }
  36. }
  37. let targetMaxValue = 0;
  38. let targetMaxWeight = 0;
  39. let targetMaxGoods = [];
  40. if ((leftWeight >= 0) && ((subMaxValue + goodsValue) > preMaxValue)) { // 可以不考虑 else 的解释,只考虑,假如 背包可以装得下当前物品,且价值增加,则更新此单元格,否则,和之前一样
  41. targetMaxValue = subMaxValue + goodsValue;
  42. targetMaxWeight = subMaxWeight + goodsWeight;
  43. targetMaxGoods = subMaxGoods.slice(0);
  44. targetMaxGoods.push(goodsName);
  45. } else { // 包含两种情况 1)leftWeight < 0 背包剩余空间装不下 2)leftWeight >= 0 && (subMaxValue + goodsValue) <= preMaxValue 背包剩余空间装得下,但是价值没有增长
  46. targetMaxValue = preMaxValue;
  47. targetMaxWeight = preMaxWeight;
  48. targetMaxGoods = preMaxGoods;
  49. }
  50. lineInfo.push([targetMaxValue, targetMaxWeight, targetMaxGoods]);
  51. }
  52. resultForm.push(lineInfo);
  53. }
  54. return resultForm;
  55. }

三、寻找最长递增子序列
这个算法 在 vue 3.0 里的 diff 被用到:最长递增子序列减少移动操作次数!

  1. // 输入:nums = [10,9,2,5,3,7,101,18]
  2. // 输出:4
  3. function test(arr) { // 部分完成
  4. const innerMap = new Map();
  5. for (let i = (arr.length - 1); i >= 0; i--) {
  6. let item = arr[i];
  7. if (innerMap.size === 0) {
  8. innerMap.set(item, 1);
  9. } else {
  10. for (let [key, value] of innerMap) {
  11. if (item < key) {
  12. innerMap.set(item, value + 1);
  13. } else {
  14. innerMap.set(item, value);
  15. }
  16. }
  17. }
  18. }
  19. return innerMap;
  20. }
  21. // 分析过程:倒着分析
  22. // arr.length - 1 -2
  23. // 18 101 7 3 5 2 9 10
  24. // 1 1 1+1=2 2+1=3 1+2=3 3+1=4 1+1=2 1+1=2
  25. // 分析结果:有四种序列可以满足要求
  26. // 2 5 7 18/101
  27. // 2 3 7 18/101