├── docs ├── menu │ ├── 埋点监控.md │ ├── 沙箱.md │ ├── 容器.md │ ├── index.md │ ├── 设计模式.md │ ├── linux.md │ ├── 知识体系.md │ ├── 元宇宙.md │ ├── 术语表.md │ └── 区块链.md ├── jsCode │ ├── 三个input.md │ ├── 图片瀑布流.md │ ├── 实现Dialog.md │ ├── 实现一个单例模式.md │ ├── 实现一个redux.md │ ├── 实现一个inputNumber组件.md │ ├── 实现一个发布订阅模式和观察者模式.md │ ├── 手写webapck的plugin和loader.md │ ├── 手写两列布局和三列布局.md │ ├── other.md │ ├── 二进制转base64.md │ ├── setTimeout实现setInterval.md │ ├── 数组乱序.md │ ├── 实现normalize.md │ ├── 实现一个new.md │ ├── 实现一个async函数.md │ ├── 千分位.md │ ├── 实现一个iterator.md │ ├── 模版渲染.md │ ├── 实现一个虚拟列表.md │ ├── README.md │ ├── 实现instanceof.md │ ├── 浅比较和深比较.md │ ├── 防抖.md │ ├── compose和pipe.md │ ├── 节流.md │ ├── 实现flat.md │ ├── 实现一个repeat方法.md │ └── 手写call,apply,bind.md ├── 好文 │ ├── JavaScript.md │ └── index.md ├── algorithm │ ├── 其他 │ │ ├── 数组区间问题.md │ │ ├── 位运算.md │ │ ├── 二维数组的遍历.md │ │ ├── 链表和数组.md │ │ ├── 算法题分类.md │ │ └── 数据结构和算法的框架思维.md │ ├── 回溯 │ │ ├── 494.目标和.md │ │ ├── 机器人的运动范围.md │ │ ├── 字符串的排列.md │ │ ├── 698. 划分为k个相等的子集.md │ │ ├── 51.N皇后.md │ │ ├── 矩阵中的路径.md │ │ └── 46.全排列.md │ ├── 字符串 │ │ ├── 仅仅反转字母.md │ │ ├── 字符串的排列.md │ │ ├── 139. 单词拆分.md │ │ ├── 有重复字符串的排列组合.md │ │ ├── 字符串相加.md │ │ ├── 131. 分割回文串.md │ │ ├── 反转字符串中的单词III.md │ │ ├── 反转字符串.md │ │ ├── 反转字符串II.md │ │ └── 字符串相乘.md │ ├── 数组 │ │ ├── 两数相加.md │ │ ├── 移动元素.md │ │ ├── 缺失的数字.md │ │ ├── 使用最小花费爬楼梯.md │ │ ├── 用队列实现栈.md │ │ ├── 区域和检索 - 数组不可变.md │ │ ├── 按奇偶排序数组 II.md │ │ ├── 全排列.md │ │ ├── 数组的交集.md │ │ ├── 合并区间.md │ │ ├── 扑克牌中的顺子.md │ │ ├── 有效的山脉数组.md │ │ ├── 两句话中的不常见单词.md │ │ ├── 分割数组为连续子序列.md │ │ ├── 缺失的第一个正数.md │ │ ├── 使数组唯一的最小增量.md │ │ ├── 合并两个有序数组.md │ │ ├── 删除被覆盖区间.md │ │ ├── 下一个更大的元素.md │ │ └── 三数之和.md │ ├── 栈和队列 │ │ ├── 单调栈.md │ │ ├── 225. 用队列实现栈.md │ │ ├── 946. 验证栈序列.md │ │ ├── 155. 最小栈.md │ │ ├── 232. 用栈实现队列.md │ │ ├── 队列的最大值.md │ │ └── 计算器.md │ ├── 树 │ │ ├── 序列化二叉树.md │ │ ├── 二叉搜索树 │ │ │ ├── 501.二叉搜索树中的众数.md │ │ │ ├── 二叉搜索树中第K小的元素.md │ │ │ ├── 二叉搜索树的后序遍历序列.md │ │ │ ├── 将有序数组转换为二叉搜索树.md │ │ │ └── 二叉搜索树迭代器.md │ │ ├── 654.最大二叉树.md │ │ ├── 二叉树的最大深度.md │ │ ├── 翻转二叉树.md │ │ ├── 对称二叉树.md │ │ ├── 222.完全二叉树的节点个数.md │ │ ├── 二叉树的所有路径.md │ │ ├── 平衡二叉树.md │ │ ├── 另一个树的子树.md │ │ ├── 相同的树.md │ │ └── 297. 二叉树的序列化与反序列化.md │ ├── 数学 │ │ ├── 二进制中1的个数.md │ │ ├── 如何求出最大公约数.md │ │ ├── 1~n 整数中 1 出现的次数.md │ │ ├── index.md │ │ ├── 只出现一次的数字.md │ │ ├── 求众数.md │ │ ├── 丑数.md │ │ ├── 计算质数.md │ │ ├── 圆圈中最后剩下的数字.md │ │ ├── 中位数.md │ │ └── 343.整数拆分.md │ ├── 链表 │ │ ├── 148. 排序链表.md │ │ ├── 反转链表 │ │ │ ├── 总结-反转链表.md │ │ │ ├── 反转链表.md │ │ │ └── 92. 反转链表 II.md │ │ ├── 环形链表 │ │ │ ├── index.md │ │ │ └── 142. 环形链表 II.md │ │ ├── 删除排序链表中的重复元素.md │ │ ├── 两个链表的第一个公共节点.md │ │ ├── 倒数第K个节点.md │ │ ├── 找出链表的中间节点.md │ │ ├── 剑指 Offer 35. 复杂链表的复制.md │ │ ├── 合并两个有序链表.md │ │ └── 回文链表.md │ ├── 二分查找 │ │ ├── 875. 爱吃香蕉的珂珂.md │ │ ├── 1011. 在 D 天内送达包裹的能力.md │ │ ├── 0~n-1中缺失的数字.md │ │ ├── 鸡蛋掉落.md │ │ ├── 33. 搜索旋转排序数组.md │ │ └── 在排序数组中查找数字 I.md │ ├── 双指针 │ │ ├── 滑动窗口 │ │ │ ├── 567. 字符串的排列.md │ │ │ ├── 239. 滑动窗口最大值.md │ │ │ ├── index.md │ │ │ ├── 和为s的连续正数序列.md │ │ │ └── 76.最小覆盖子串.md │ │ └── 二分查找.md │ ├── 数据结构 │ │ ├── 数组转链表.md │ │ └── 数组转二叉树.md │ ├── 排序 │ │ ├── README.md │ │ ├── 插入排序.md │ │ ├── 选择排序.md │ │ ├── 计数排序.md │ │ └── 冒泡排序.md │ ├── 动态规划 │ │ ├── 经典 │ │ │ ├── index.md │ │ │ ├── 120.三角形的最小路径和.md │ │ │ ├── 322.零钱兑换.md │ │ │ ├── 64. 最小路径和.md │ │ │ ├── 152.乘积最大子数组.md │ │ │ ├── 1143.最长公共子序列.md │ │ │ └── 300.最长上升子序列.md │ │ ├── 不同的二叉搜索树.md │ │ ├── 接雨水.md │ │ ├── 最长数对链.md │ │ ├── 最大子序和.md │ │ ├── 最长公共前缀.md │ │ ├── 买卖股票的最佳时机I.md │ │ ├── 买卖股票的最佳时机III.md │ │ ├── 打家劫舍II.md │ │ ├── 无重叠区间.md │ │ └── 盛最多水的容器.md │ ├── 模板总结 │ │ └── index.md │ ├── BFS │ │ └── index.md │ └── 岛屿 │ │ └── 200. 岛屿数量.md ├── interview │ ├── Node │ │ ├── index.md │ │ └── koa的中间件.md │ ├── CSS │ │ ├── cssModules.md │ │ ├── postcss.md │ │ ├── 几个常见的单位.md │ │ ├── 清除浮动方法及原理.md │ │ ├── vertical-align.md │ │ ├── display:none和visibility:hidden的区别.md │ │ ├── flex.md │ │ ├── CSS选择器.md │ │ ├── 实现固定宽高比的div.md │ │ ├── 伪类和伪元素.md │ │ ├── css解析规则.md │ │ └── CSS基础知识点.md │ ├── JavaScript │ │ ├── 骨架屏.md │ │ ├── 上拉加载和下拉刷新.md │ │ ├── 线程和进程的区别.md │ │ ├── JSONP的实现原理.md │ │ ├── TS学习 │ │ │ ├── TS的内置类型.md │ │ │ └── TS中的关键词.md │ │ ├── 前端性能优化之常见指标上报.md │ │ ├── V8是如何执行一段代码的.md │ │ ├── 隐式类型转换.md │ │ ├── JS垃圾回收机制.md │ │ ├── 认识JS.md │ │ └── JS数字精度问题.md │ ├── Webpack │ │ ├── code split.md │ │ ├── index.md │ │ ├── vite.md │ │ ├── webpack的loader和plugin.md │ │ ├── 优化webpack代码体积.md │ │ └── webpack热更新原理.md │ ├── CICD.md │ ├── React │ │ ├── react hooks使用原则.md │ │ ├── react hooks.md │ │ ├── setState.md │ │ └── 自定义hook.md │ └── HTTP │ │ ├── websocket.md │ │ ├── HTTP请求方法.md │ │ ├── 浏览器缓存机制.md │ │ ├── 三次握手,四次挥手.md │ │ └── HTTP状态码.md ├── node │ ├── 基础.md │ └── rpc.md ├── .vuepress │ ├── public │ │ └── logo.jpeg │ └── menu.js ├── book │ ├── 《重学前端》.md │ ├── webpack原理与实践.md │ ├── 1.js │ └── 1.html ├── README.md └── 100day │ └── 文章 │ ├── 如何启动一个本地静态服务器.md │ └── 命令行参数.md ├── .vscode └── settings.json ├── package.json ├── .gitignore └── book.md /docs/menu/埋点监控.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/menu/沙箱.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/jsCode/三个input.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/jsCode/图片瀑布流.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/jsCode/实现Dialog.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/jsCode/实现一个单例模式.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/好文/JavaScript.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/其他/数组区间问题.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/回溯/494.目标和.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/仅仅反转字母.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/字符串的排列.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/数组/两数相加.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/数组/移动元素.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/数组/缺失的数字.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/栈和队列/单调栈.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/树/序列化二叉树.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/Node/index.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/jsCode/实现一个redux.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/139. 单词拆分.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/有重复字符串的排列组合.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/数学/二进制中1的个数.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/数学/如何求出最大公约数.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/链表/148. 排序链表.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/链表/反转链表/总结-反转链表.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/链表/环形链表/index.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/CSS/cssModules.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/CSS/postcss.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/CSS/几个常见的单位.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/CSS/清除浮动方法及原理.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/骨架屏.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/jsCode/实现一个inputNumber组件.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/jsCode/实现一个发布订阅模式和观察者模式.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/二分查找/875. 爱吃香蕉的珂珂.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/双指针/滑动窗口/567. 字符串的排列.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/数学/1~n 整数中 1 出现的次数.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/CSS/vertical-align.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/上拉加载和下拉刷新.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/线程和进程的区别.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/Webpack/code split.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/jsCode/手写webapck的plugin和loader.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/二分查找/1011. 在 D 天内送达包裹的能力.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/algorithm/双指针/滑动窗口/239. 滑动窗口最大值.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/JSONP的实现原理.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/TS学习/TS的内置类型.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/menu/容器.md: -------------------------------------------------------------------------------- 1 | - k8s 2 | - docker 3 | -------------------------------------------------------------------------------- /docs/algorithm/数据结构/数组转链表.md: -------------------------------------------------------------------------------- 1 | ```js 2 | ``` 3 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/前端性能优化之常见指标上报.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/jsCode/手写两列布局和三列布局.md: -------------------------------------------------------------------------------- 1 | ## 手写两列布局和三列布局(所有你知道的) -------------------------------------------------------------------------------- /docs/interview/Webpack/index.md: -------------------------------------------------------------------------------- 1 | ## webpack 关键点 2 | -------------------------------------------------------------------------------- /docs/好文/index.md: -------------------------------------------------------------------------------- 1 | 记录平时看到的一些比较好的文章,帮助自己构建完善的知识体系。 2 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/字符串相加.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | ## 解题方法 4 | 5 | -------------------------------------------------------------------------------- /docs/interview/Webpack/vite.md: -------------------------------------------------------------------------------- 1 | ## 一些问题 2 | 3 | 什么 vite 比 webpack 快 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "commentTranslate.multiLineMerge": true 3 | } 4 | -------------------------------------------------------------------------------- /docs/menu/index.md: -------------------------------------------------------------------------------- 1 | ## 记录平时要分享的内容的思路 2 | 3 | 也可以称之为大纲,来帮助更好的把知识都串联起来,形成知识体系。 4 | -------------------------------------------------------------------------------- /docs/node/基础.md: -------------------------------------------------------------------------------- 1 | - commonjs 2 | - 异步 3 | - IO 4 | - async await 5 | -------------------------------------------------------------------------------- /docs/algorithm/数学/index.md: -------------------------------------------------------------------------------- 1 | ## 有固定公式的题目 2 | 3 | 刷题是有套路的,如果有些是固定公式,那么直接学习一下,之后遇到类似的直接套用公式就好了。 4 | -------------------------------------------------------------------------------- /docs/algorithm/树/二叉搜索树/501.二叉搜索树中的众数.md: -------------------------------------------------------------------------------- 1 | ## 思路 2 | 3 | 二叉搜索树,中序遍历是升序的。重复的数字一定是连续的,可以转变为,求有序数组中的众数。 4 | -------------------------------------------------------------------------------- /docs/node/rpc.md: -------------------------------------------------------------------------------- 1 | ## node.js Buffer 编解码二进制数据包 2 | 3 | protobuf butter 4 | 5 | protocol-butters npm 包 6 | -------------------------------------------------------------------------------- /docs/.vuepress/public/logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funnycoderstar/blog/HEAD/docs/.vuepress/public/logo.jpeg -------------------------------------------------------------------------------- /docs/algorithm/排序/README.md: -------------------------------------------------------------------------------- 1 | ## 参考 2 | 3 | - [菜鸟教程之十大经典排序算法](https://www.runoob.com/w3cnote/ten-sorting-algorithm.html) 4 | -------------------------------------------------------------------------------- /docs/algorithm/数组/使用最小花费爬楼梯.md: -------------------------------------------------------------------------------- 1 | ## 2 | 3 | 要点 4 | 5 | - 理解题目含义:cost[i]是从楼梯第 i 个台阶向上爬需要支付的费用,而不是爬到 i 个台阶消耗的费用 6 | 状态转移方程: 7 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/V8是如何执行一段代码的.md: -------------------------------------------------------------------------------- 1 | js 是解释型语言 2 | 3 | - 生成 AST: 词法分析、语法分析 4 | - AST -> 字节码 5 | - 字节码 -> 机器码:解释器将字节码转换为机器码。 6 | -------------------------------------------------------------------------------- /docs/jsCode/other.md: -------------------------------------------------------------------------------- 1 | ## 他人面试遇到的题目 2 | 3 | 最长回文子串 4 | 接雨水 5 | 无重复最长子序列 6 | 7 | lRU 缓存 8 | 手写深拷贝 9 | 手写 promise 10 | 手写 asyncpool 11 | -------------------------------------------------------------------------------- /docs/menu/设计模式.md: -------------------------------------------------------------------------------- 1 | 提升自己的抽象总结能力。 2 | 学习设计模式,理解其中的设计思想。 3 | 4 | 结合自己平时在工作和生活中遇到的场景和问题进行思考。 5 | 6 | 之前看不懂,是因为自己对一些概念不了解,没有学习过计算机的一些基础知识,没有系统化学习过。问题只要发现了就有很多办法去解决。 7 | -------------------------------------------------------------------------------- /docs/book/《重学前端》.md: -------------------------------------------------------------------------------- 1 | ## 目标 2 | 3 | - 找到适合自己的前端学习方法 4 | - 建立起前端技术的知识架构 5 | - 理解前端技术背后的核心思想 6 | 7 | ## 建立知识架构 8 | 9 | 知识架构:可以理解为知识的”目录“或索引,可以帮助把零碎的知识组织起来,也能帮助发现一些知识上的盲区。 10 | -------------------------------------------------------------------------------- /docs/interview/CICD.md: -------------------------------------------------------------------------------- 1 | ## CICD 思路 2 | 3 | 分为上线前、上线后 4 | 5 | - 上线前: 6 | sonar 7 | - 上线后: 8 | 数据指标监控报警 9 | 10 | ## Sonar 11 | 12 | sonar 是一个代码质量管理平台。能检测一下问题 13 | 14 | 1. 缺乏单元测试。统计并展示 15 | 2. 重复代码。检测项目中存在的重复代码,并可以在线查看哪些代码重复。 16 | -------------------------------------------------------------------------------- /docs/algorithm/数组/用队列实现栈.md: -------------------------------------------------------------------------------- 1 | ## 题解 2 | 3 | 要用两个队列来实现栈,首先我们知道,队列是先进先出,栈是后进先出。 4 | 5 | 知道了以上要点,我们两个队列的用处也就一目了然了。 6 | 7 | 一个队列为主队列,一个为辅助队列,当入栈操作时,我们先将主队列内容导入辅助队列,然后将入栈元素放入主队列队头位置,再将辅助队列内容,依次添加进主队列即可。 8 | 9 | > 利用队列的特性:先进先出,而不要总是想着数组的 api, 数组既有队列相关的 api 也有栈相关的 api 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "vuepress dev docs", 4 | "build": "vuepress build docs" 5 | }, 6 | "devDependencies": { 7 | "vuepress": "^1.4.1", 8 | "vuepress-plugin-autobar": "boboidream/vuepress-bar", 9 | "vuepress-theme-lemon": "^1.0.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/algorithm/双指针/滑动窗口/index.md: -------------------------------------------------------------------------------- 1 | 滑动窗口框架 2 | 3 | ```js 4 | let left = 0, right = 0; 5 | 6 | while (right < s.size()) { 7 | // 增大窗口 8 | window.add(s[right]); 9 | right++; 10 | 11 | while (window needs shrink) { 12 | // 缩小窗口 13 | window.remove(s[left]); 14 | left++; 15 | } 16 | } 17 | 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/131. 分割回文串.md: -------------------------------------------------------------------------------- 1 | ## 131. 分割回文串 2 | 3 | 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。 4 | 5 | 回文串 是正着读和反着读都一样的字符串。 6 | 7 | 示例 1: 8 | 9 | 输入:s = "aab" 10 | 输出:[["a","a","b"],["aa","b"]] 11 | 示例 2: 12 | 13 | 输入:s = "a" 14 | 输出:[["a"]] 15 | 16 | 提示: 17 | 18 | 1 <= s.length <= 16 19 | s 仅由小写英文字母组成 20 | 21 | ## 代码 22 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/经典/index.md: -------------------------------------------------------------------------------- 1 | ## 子序列问题 2 | 3 | - [583. 两个字符串的删除操作](https://leetcode.cn/problems/delete-operation-for-two-strings/) 4 | - [712. 两个字符串的最小 ASCII 删除和](https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/) 5 | - [1143. 最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) 6 | - 最长上升子序列 7 | - 最大子序和 8 | 9 | 最长公共子序列问题是典型的二维动态规划问题。 10 | -------------------------------------------------------------------------------- /docs/interview/React/react hooks使用原则.md: -------------------------------------------------------------------------------- 1 | ## React hooks 使用原则 2 | 3 | 1. 只在 React 函数中调用 hook 4 | 2. 不要在循环、条件或嵌套函数中调用 Hook 5 | 6 | ## 原因 7 | 8 | 1. React hooks 本身就是 React 组件的钩子 9 | 2. 要确保 hooks 每次渲染时都保证同样的执行顺序。 10 | hooks 在底层依赖于顺序链表。 11 | - mountState(首次渲染): 构建链表并渲染 12 | - updateState: 依次遍历链表并渲染 13 | hooks 的渲染时通过“依次遍历”来定位每个 hooks 内容的,如果前后两次读到的链表在顺序上存在差异,然后渲染的结果自然是不可控的。 14 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/隐式类型转换.md: -------------------------------------------------------------------------------- 1 | - `String([])` 为 `''`; 2 | - `String({})`结果是什么什么? 答案是:`'[object object]'` 3 | - Boolean([]) 为 true 4 | - Boolean({})为 true 5 | - Number([]) 为 0 6 | - Number({}) 为 NAN 7 | 8 | ## 参考 9 | 10 | - [javascript 双等号的各种奇葩用例](https://blog.csdn.net/lantian_bupt/article/details/107566580) 11 | - [从一道面试题说起—js 隐式转换踩坑合集](https://juejin.cn/post/6844903694039777288) 12 | -------------------------------------------------------------------------------- /docs/interview/HTTP/websocket.md: -------------------------------------------------------------------------------- 1 | ## WebSocket 2 | HTTP协议通信只能由客户端发送,实现想消息通知的这种,我们只能使用“轮询”:每隔一段时间,就发出一个后端请求,询问有没有新的消息更新。 3 | 4 | WebSocket 和 HTTP 一样,同属于应用层协议。它最重要的用途是实现了客户端与服务端之间的全双工通信,当服务端数据变化时,可以第一时间通知到客户端。 5 | 6 | 除此之外,它与http协议不同的地方还有: 7 | - http只能由客户端发起,而webSocket是双向的。 8 | - webSocket传输的数据包相对于http而言很小,很适合移动端使用 9 | - 没有同源限制,可以跨域共享资源 10 | 11 | [WebSocket 教程](http://www.ruanyifeng.com/blog/2017/05/websocket.html) -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroText: 前端学习总结 4 | heroImage: ./logo.jpeg 5 | tagline: 6 | actionText: 快速上手 → 7 | actionLink: /algorithm/ 8 | features: 9 | - title: 算法系列 10 | details: JavaScript实现常见一些算法题,主要来自 leetcode和剑指offer 11 | - title: 编程题 12 | details: JS手写代码系列,比如手写节流,防抖,深拷贝等等 13 | - title: 前端知识 14 | details: 基础知识总结。 15 | footer: MIT Licensed | Copyright © 2022-present OneStar 16 | --- 17 | -------------------------------------------------------------------------------- /docs/algorithm/排序/插入排序.md: -------------------------------------------------------------------------------- 1 | ## 插入排序 2 | ```js 3 | // 插入排序 4 | this.insertionSort = function() { 5 | let len = array.length; 6 | let j; 7 | let temp; 8 | for(let i = 0; i < length; i++) { 9 | j = i; 10 | temp = array[i]; 11 | while( j > 0 && array[j - 1] > temp) { 12 | array[j] = array[j - 1]; 13 | j--; 14 | } 15 | array[j] = temp; 16 | } 17 | } 18 | ``` -------------------------------------------------------------------------------- /docs/jsCode/二进制转base64.md: -------------------------------------------------------------------------------- 1 | ```js 2 | let encodedData = window.btoa('Hello, world'); // 编码 3 | let decodedData = window.atob(encodedData); // 解码 4 | ``` 5 | 6 | - atob() :解码一个 Base64 字符串。 7 | - btoa() :从一个字符串或者二进制数据编码一个 Base64 字符串。 8 | 9 | - [MDN Base64 的编码与解码](https://developer.mozilla.org/zh-CN/docs/Glossary/Base64) 10 | - [手写二进制转 Base64 11 | ](https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/299) 12 | -------------------------------------------------------------------------------- /docs/algorithm/其他/位运算.md: -------------------------------------------------------------------------------- 1 | 从现代计算机中所有的数据二进制的形式存储在设备中。即 0、1 两种状态,计算机对二进制数据进行的运算(+、-、\*、/)都是叫位运算,即将符号位共同参与运算的运算。 2 | 3 | &(与):两位同时为 1,结果才为 1,否则结果为 0。 4 | |(或):只要有一个为 1 就为 1,两个位都为 0 时,结果才为 0。 5 | ~(取反):对一个二进制数按位取反,即将 0 变 1,1 变 0。 6 | ^(异或):相同为 0,不同为 1 7 | 8 | - [位运算(&、|、^、~、>>、<<)](https://www.runoob.com/w3cnote/bit-operation.html) 9 | - [表达式与运算符](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Expressions_and_Operators) 10 | -------------------------------------------------------------------------------- /docs/algorithm/回溯/机器人的运动范围.md: -------------------------------------------------------------------------------- 1 | [剑指 Offer 13. 机器人的运动范围](https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/) 2 | 地上有一个 m 行 n 列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于 k 的格子。例如,当 k 为 18 时,机器人能够进入方格 [35, 37] ,因为 3+5+3+7=18。但它不能进入方格 [35, 38],因为 3+5+3+8=19。请问该机器人能够到达多少个格子? 3 | 4 | 示例 1: 5 | 6 | 输入:m = 2, n = 3, k = 1 7 | 输出:3 8 | 示例 2: 9 | 10 | 输入:m = 3, n = 1, k = 0 11 | 输出:1 12 | -------------------------------------------------------------------------------- /docs/interview/CSS/display:none和visibility:hidden的区别.md: -------------------------------------------------------------------------------- 1 | ## display: none和 visibility:hidden的区别 2 | 3 | 1. 是否占据空间 4 | - display: none 不占据空间 5 | - visibility:hidden 占据空间 6 | 7 | 2. 是否渲染 8 | - display:none,会触发reflow(回流),进行渲染。 9 | - visibility:hidden,只会触发repaint(重绘),因为没有发现位置变化,不进行渲染。 10 | 11 | 3. 是否是继承属性(株连性) 12 | - display:none,display不是继承属性,元素及其子元素都会消失。 13 | - visibility:hidden,visibility是继承属性,若子元素使用了visibility:visible,则不继承,这个子孙元素又会显现出来。 -------------------------------------------------------------------------------- /docs/algorithm/其他/二维数组的遍历.md: -------------------------------------------------------------------------------- 1 | 48. 旋转图像 2 | 3 | 要点 4 | 5 | 1. 左上角和右下角镜像对称 6 | 2. 对 1 得到的产物,再做每行的翻转。 7 | 8 | == 9 | 10 | 54. 螺旋矩阵 11 | 要点 12 | 13 | 1. 4 个变量,来控制上下左右 4 个边界,随着每轮循环,边界会跟着变化 14 | 1. direction 来表示方向,判断 1 中的边界是否要修改方向,修改方向的时候同时修改边界 15 | 1. `m * n` 循环,`result.push(matix[i][j])` 16 | 17 | == 18 | 19 | 59. 螺旋矩阵 II 20 | 要点:跟 54 一样,不同的是,是给 `result[i][j] = k` 21 | 22 | ## 参考 23 | 24 | - [二维数组的花式遍历技巧](https://labuladong.github.io/algo/2/18/24/) 25 | -------------------------------------------------------------------------------- /docs/menu/linux.md: -------------------------------------------------------------------------------- 1 | 学习一个新的知识之前,要先积攒足够的疑惑。 2 | 3 | ## 学习阶段 4 | 5 | 1. 熟练使用 linux 命令行 6 | 2. 使用 linux 进行程序设计 7 | 3. 了解 linux 内核机制 8 | 4. 阅读 linux 内核代码 9 | 5. 实验定制 linux 组件 10 | 6. 最后落到生产实践 11 | 12 | ## linux 13 | 14 | - 文件系统 15 | - 设置环境变量 16 | - linux 远程登录 17 | 18 | ## Shell 19 | 20 | - bash 21 | - ~/.bashrc 22 | - ~/.zshrc 23 | - vim 24 | 25 | ## 工具 26 | 27 | - Homebrew 28 | 29 | 1. 安装 homebrew 30 | 2. 命令行找不到 homebrew,需要配置环境变量 31 | 32 | - man 33 | - wept 34 | -------------------------------------------------------------------------------- /docs/jsCode/setTimeout实现setInterval.md: -------------------------------------------------------------------------------- 1 | ### 第一种 2 | ```js 3 | setTimeout(function () { 4 | console.log('我被调用了'); 5 | setTimeout(arguments.callee, 100); 6 | }, 100); 7 | 8 | ``` 9 | 10 | callee 是 arguments 对象的一个属性。它可以用于引用该函数的函数体内当前正在执行的函数。在严格模式下,第5版 ECMAScript (ES5) 禁止使用arguments.callee()。当一个函数必须调用自身的时候, 避免使用 arguments.callee(), 通过要么给函数表达式一个名字,要么使用一个函数声明. 11 | 12 | ### 第二种 13 | ```js 14 | setTimeout(function fn(){ 15 | console.log('我被调用了'); 16 | setTimeout(fn, 100); 17 | },100); 18 | ``` -------------------------------------------------------------------------------- /docs/jsCode/数组乱序.md: -------------------------------------------------------------------------------- 1 | 2 | ## 数组乱序 - 洗牌算法: 3 | 4 | 从最后一个元素开始,从数组中随机选出一个位置,交换,直到第一个元素。 5 | 6 | ```js 7 | function shuffle(arr) { 8 | let current = array.length - 1; 9 | while(current > -1) { 10 | // 生成一个范围在当前下标到数组末尾元素下标之间的随机整数 11 | const random = Math.floor(array.length * Math.random()); 12 | // 将当前元素和随机选出的下标所指的元素互相交换 13 | [array[current], array[random]] = [array[random], array[current]]; 14 | current--; 15 | } 16 | return array; 17 | }; 18 | ``` -------------------------------------------------------------------------------- /docs/algorithm/树/二叉搜索树/二叉搜索树中第K小的元素.md: -------------------------------------------------------------------------------- 1 | 给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。 2 | 3 | 说明: 4 | 你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。 5 | 6 | 示例 1: 7 | ```js 8 | 输入: root = [3,1,4,null,2], k = 1 9 | 3 10 | / \ 11 | 1 4 12 | \ 13 |   2 14 | 输出: 1 15 | ``` 16 | 示例 2: 17 | ```js 18 | 输入: root = [5,3,6,2,4,null,null,1], k = 3 19 | 5 20 | / \ 21 | 3 6 22 | / \ 23 | 2 4 24 | / 25 | 1 26 | 输出: 3 27 | ``` 28 | 进阶: 29 | 如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化 kthSmallest 函数? 30 | -------------------------------------------------------------------------------- /docs/interview/React/react hooks.md: -------------------------------------------------------------------------------- 1 | ## useCallback 和 useMemo 的区别 2 | 3 | - 返回值: 4 | useCallback: 一个缓存的回调函数 5 | useMemo: 一个缓存的值 6 | 7 | - 参数 8 | - useCallback:需要缓存的函数,依赖项 9 | - useMemo:需要缓存的值(也可以是个计算然后再返回值的函数)依赖项 10 | - 使用场景: 11 | - useCallback:父组件更新时,通过 props 传递给子组件的函数也会重新创建,这个时候使用 useCallback 就可以缓存函数不使它重新创建 12 | - useMemo: 组件更新时,一些计算量很大的值也有可能被重新计算,这个时候就可以使用 useMemo 直接使用上一次缓存的值 13 | 14 | ### 参考 15 | 16 | - [useCallback 和 useMemo 的区别](https://www.jianshu.com/p/b8d27018ed22) 17 | -------------------------------------------------------------------------------- /docs/algorithm/排序/选择排序.md: -------------------------------------------------------------------------------- 1 | ## 选择排序 2 | 找到最小的值放第一位, 找到第二小的值放第二位; 3 | 复杂度为O(n^2) 4 | ```js 5 | // 选择排序 6 | let selectionSort = function() { 7 | let len = array.length; 8 | let indexMin; 9 | for(let i = 0; i < len -1; i++) { 10 | let indexMin = i; 11 | for(let j = i; j < len; j++) { 12 | if(array[indexMin] > array[j]) { 13 | indexMin = j; 14 | } 15 | } 16 | if( i !== indexMin) { 17 | swap(i, indexMin); 18 | } 19 | } 20 | } 21 | ``` -------------------------------------------------------------------------------- /docs/algorithm/字符串/反转字符串中的单词III.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。 3 | 4 | 示例 1: 5 | ```js 6 | 输入: "Let's take LeetCode contest" 7 | 输出: "s'teL ekat edoCteeL tsetnoc" 8 | ``` 9 | 注意:在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。 10 | 11 | ## 解题方法 12 | 13 | ```js 14 | /** 15 | * @param {string} s 16 | * @return {string} 17 | */ 18 | var reverseWords = function(s) { 19 | return s 20 | .split(' ') 21 | .map(item => item.split('').reverse().join('')) 22 | .join(' '); 23 | }; 24 | ``` -------------------------------------------------------------------------------- /docs/interview/JavaScript/JS垃圾回收机制.md: -------------------------------------------------------------------------------- 1 | ## 1.引用计数 2 | 此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。 3 | 4 | 限制:无法处理循环引用。在下面的例子中,两个对象被创建,并互相引用,形成了一个循环。它们被调用之后不会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。 5 | 6 | ## 2. 标记清除 7 | 当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。 8 | 9 | 垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。 -------------------------------------------------------------------------------- /docs/algorithm/其他/链表和数组.md: -------------------------------------------------------------------------------- 1 | 234. 回文链表 2 | 3 | 有两种常用的列表实现,分别为数组列表和链表 4 | 5 | - 数组列表底层是使用数组存储值,我们可以通过索引在 O(1) 的时间访问列表任何位置的值,这是由基于内存寻址的方式。 6 | - 链表存储的是称为节点的对象,每个节点保存一个值和指向下一个节点的指针。访问某个特定索引的节点需要 O(n) 的时间,因为要通过指针获取到下一个位置的节点。 7 | 8 | 注意我们比较的是节点值的大小,而不是节点本身。正确的比较方式是:node_1.val == node_2.val,而 node_1 == node_2 是错误的。 9 | 10 | 作者:LeetCode-Solution 11 | 链接:https://leetcode.cn/problems/palindrome-linked-list/solution/hui-wen-lian-biao-by-leetcode-solution/ 12 | 来源:力扣(LeetCode) 13 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 14 | 15 | 主要注意的点: 16 | 链表存储的是节点的对象,每个节点保存一个值和指向下一个节点的指针。在比较的时候,要注意是比较节点值,还是节点本身 17 | -------------------------------------------------------------------------------- /docs/interview/React/setState.md: -------------------------------------------------------------------------------- 1 | ### react setState 2 | - react的setstate过程, 收到新state怎么更新,发生了什么事情 3 | - 说一下几种情况的setstate的输出,为什么 4 | 5 | 6 | 在React中,如果是由React引发的事件处理(比如通过 onClick引发的事件处理),调用 setState 不会同步更新this.state,除此之外的setState调用会同步执行this.state。所谓"除此之外”,指的是绕过React通过 addEventListener 直接添加的事件处理函数,还有通过 setTimeout/setInterval产生的异步调用。 7 | 8 | 在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。 9 | -------------------------------------------------------------------------------- /docs/jsCode/实现normalize.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /** 3 | * 字符串仅由小写字母和 [] 组成,且字符串不会包含多余的空格。 4 | 示例一: 'abc' --> {value: 'abc'} 5 | 示例二:'[abc[bcd[def]]]' --> {value: 'abc', children: {value: 'bcd', children: {value: 'def'}}} 6 | */ 7 | 8 | function normalize(str) { 9 | const arr = str.match(/\w+/g); 10 | let result; 11 | while (arr.length) { 12 | const cur = arr.pop(); 13 | let temp = { value: cur }; 14 | if (result) { 15 | temp.children = result; 16 | } 17 | result = temp; 18 | } 19 | return result; 20 | } 21 | console.log(normalize('[abc[bcd[def]]]')); 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/interview/CSS/flex.md: -------------------------------------------------------------------------------- 1 | ## flex: 1 完整写法 2 | Flex 布局概念: 3 | 采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目" 4 | 5 | ### flex: 1 完整写法 6 | `flex`属性是`flex-grow`, `flex-shrink` 和 `flex-basis`, 默认值为`0 1 auto`。后两个属性可选。 7 | 8 | `flex-grow`属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。 9 | 10 | `flex-shrink`属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。 11 | 12 | `flex-basis`属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。 13 | 14 | > [Flex 布局教程:语法篇](http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html) 、[Flex 布局教程:实例篇](http://www.ruanyifeng.com/blog/2015/07/flex-examples.html) -------------------------------------------------------------------------------- /docs/algorithm/数学/只出现一次的数字.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 3 | 4 | 说明: 5 | 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 6 | 7 | 示例 1: 8 | ```js 9 | 输入: [2,2,1] 10 | 输出: 1 11 | ``` 12 | 13 | 示例 2: 14 | ```js 15 | 输入: [4,1,2,1,2] 16 | 输出: 4 17 | ``` 18 | 19 | ## 解题思路 20 | 使用异或的方法: 异或要化为二进制,来对比相同位置的数 相同置为 0 不同置为1 再化为十进制数。 21 | 零和任何数异或都等于任何数, 一个数异或两次就等于0; 22 | 又因为本题中除一个之外每个元素都出现两次,所以用循环异或所有数就等于 只出现一次的那个数 23 | 24 | ## 解题方法 25 | ```js 26 | /** 27 | * @param {number[]} nums 28 | * @return {number} 29 | */ 30 | var singleNumber = function(nums) { 31 | return nums.reduce((a, b) => a ^ b, 0); 32 | }; 33 | ``` -------------------------------------------------------------------------------- /docs/algorithm/排序/计数排序.md: -------------------------------------------------------------------------------- 1 | ## 计数排序 2 | 3 | ## 题目 4 | 5 | - [75. 颜色分类](https://leetcode-cn.com/problems/sort-colors/) 6 | 7 | ```js 8 | /** 9 | * @param {number[]} nums 10 | * @return {void} Do not return anything, modify nums in-place instead. 11 | */ 12 | var sortColors = function(nums) { 13 | let colors = [0, 0, 0]; 14 | for (let i = 0; i < nums.length; i++) { 15 | colors[nums[i]]++; 16 | } 17 | nums.length = 0; 18 | for (let i = 0; i < colors.length; i++) { 19 | for (let j = 0; j < colors[i]; j++) { 20 | nums.push(i); 21 | } 22 | } 23 | return nums; 24 | }; 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/interview/CSS/CSS选择器.md: -------------------------------------------------------------------------------- 1 | ## CSS选择器 2 | - 通用选择器(*) 3 | - 标签选择器(div) 4 | - class选择器(.wrap) 5 | - id选择器(#wrap) 6 | - 属性选择器(E[att], E[att=val], E[att~=val]) 7 | - E[att]: 匹配所有具有att属性的E元素,不考虑它的值 8 | - E[att=val]:匹配所有att属性等于"val"的E元素 9 | - E[att~=val]:匹配所有att属性具有多个空格分隔的值、其中一个值等于"val"的E元素 10 | - 相邻选择器(h1 + p) 11 | - 子选择器(ul > li) 12 | - 后代选择器(li a) 13 | - 伪类选择器 14 | - E:first-child:匹配父元素的第一个子元素 15 | - E:link 匹配所有未被点击的链接 16 | - E:focus 匹配获得当前焦点的E元素 17 | - E:not(s) 反选伪类,匹配不符合当前选择器的任何元素 18 | 19 | > 详细查看[CSS选择器笔记](http://www.ruanyifeng.com/blog/2009/03/css_selectors.html) 20 | 21 | 选择器的优先级(就近原则):!important > [ id > class > tag ] -------------------------------------------------------------------------------- /docs/algorithm/模板总结/index.md: -------------------------------------------------------------------------------- 1 | ## 题型的模板总结 2 | 3 | 总结一些经典题型的模板代码,需要理解并背诵,遇到题目的时候直接套模板。 4 | 5 | > 无论学习什么,都要把体系列出来,然后再分解(庖丁解牛),重复练习单独的部分。遇到单个的知识点,一定要再插入到自己的知识体系中。这个才是符合人脑的记忆结构,比如和已有知识产生链接,才能被更好的记忆和存储。链接得越多,记忆提取就会越准备,越快。 6 | 7 | - 二分 8 | - 回溯 9 | - 排列组合 10 | - 全排列 11 | - N 皇后 12 | - 优先队列(最大堆,最小堆) 13 | - 树 14 | - 二叉搜索树 15 | - 树的遍历 16 | - 链表 17 | - 反转链表系列 18 | - 删除某个节点 19 | - 回文链表 20 | - 动态规划 21 | - 排序算法 22 | - 快排 23 | - 归并 24 | - 堆 25 | - 分治 26 | - 区间题目 27 | - 并查集 28 | - 双指针 29 | - 左右 30 | - 快慢 31 | - 滑动窗口 32 | - 单调栈 33 | - 字典树 34 | - set & map 35 | - 36 | -------------------------------------------------------------------------------- /docs/jsCode/实现一个new.md: -------------------------------------------------------------------------------- 1 | ## 实现一个 new 2 | 3 | 要从 new 干了什么入手: 4 | 5 | 1. 创建一个新的空对象 obj 6 | 2. 将新对象的的原型指向当前函数的原型 7 | 3. 新创建的对象绑定到当前this上 8 | 4. 如果没有返回其他对象,就返回 obj,否则返回其他对象 9 | 10 | ```js 11 | function _new(constructor, ...arg) { 12 | // ① 创建一个新的空对象 obj 13 | const obj = {}; 14 | // ② 将新对象的的原型指向当前函数的原型 15 | obj.__proto__ = constructor.prototype; 16 | // ③ 新创建的对象绑定到当前this上 17 | const result = constructor.apply(obj, arg); 18 | // ④ 如果没有返回其他对象,就返回 obj,否则返回其他对象 19 | return typeof result === 'object' ? result : obj; 20 | } 21 | function Foo(name) { 22 | this.name = name; 23 | } 24 | var luckyStar = _new(Foo, 'luckyStar'); 25 | luckyStar.name; //luckyStar 26 | ``` -------------------------------------------------------------------------------- /docs/interview/CSS/实现固定宽高比的div.md: -------------------------------------------------------------------------------- 1 | ## 实现固定宽高比(width: height = 4: 3 )的div,怎么设置 2 | 利用css中 `padding`百分比的计算方法: `padding`设置为百分比,是以元素的宽度乘以`100%`从而得到的`padding`值的。 3 | 4 | 在`div`的`width`为固定的情况下,设置`height`为`0`,使内容自然溢出,再通过设置`padding-bottom`使元素有一定高度。 5 | ```css 6 | .element { 7 | /* 16:9宽高比,则设padding-bottom:56.25% */ 8 | /* height: 0px, 防止矩形被里面的内容撑出多余的高度*/ 9 | width: 100vw; 10 | height: 0px; 11 | padding-bottom: 56.25%; 12 | background: pink; 13 | } 14 | ``` 15 | 利用将`padding-top`或`padding-bottom`设置成百分比,来实现高度满足宽度的某个比例。因为,当`margin/padding`取形式为百分比的值时,无论是`left/right`,还是`top/bottom`,都是以父元素的`width`为参照物的! 16 | 17 | > [css实现宽高比](https://blog.csdn.net/Honeymao/article/details/77884744) -------------------------------------------------------------------------------- /docs/algorithm/数组/区域和检索 - 数组不可变.md: -------------------------------------------------------------------------------- 1 | 303. 区域和检索 - 数组不可变 2 | 3 | 使用前缀和,提高检索速度,时间复杂度为 O(1). 4 | 5 | ```js 6 | /** 7 | * @param {number[]} nums 8 | */ 9 | var NumArray = function(nums) { 10 | let n = nums.length; 11 | this.sums = new Array(n + 1).fill(0); 12 | for (let i = 0; i < n; i++) { 13 | // sums[i]表示 nums 从下标 00 到下标 i-1i−1 的前缀和。 14 | this.sums[i + 1] = nums[i] + this.sums[i]; 15 | } 16 | }; 17 | 18 | /** 19 | * @param {number} left 20 | * @param {number} right 21 | * @return {number} 22 | */ 23 | NumArray.prototype.sumRange = function(left, right) { 24 | return this.sums[right + 1] - this.sums[left]; 25 | }; 26 | ``` 27 | 28 | 前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。 29 | -------------------------------------------------------------------------------- /docs/interview/HTTP/HTTP请求方法.md: -------------------------------------------------------------------------------- 1 | ## HTTP 的请求方法有哪些?GET和POST区别? 2 | ### HTTP 的请求方法 3 | - GET:获取资源 4 | - POST:传输实体主体 5 | - PUT:传输文件 6 | - HEAD:获得报文首部 7 | - DELETE:删除文件 8 | - OPTIONS:询问支持的方法 9 | - TRACE: 追踪路径TRACE方法是让Web服务器端将之前的请求通信环回给客户端的方法。 10 | - 发送请求时,在Max-Forwards首部字段中填入数值,每经过一个服务器端就将该数字减1,当数值刚好减到0时,就停止继续传输,最后接收到请求的服务器端则返回状态码200OK的响应。 11 | - CONNECT: 要求用隧道协议链接代理 12 | 13 | ### GET和POST区别 14 | - 缓存: GET 请求会被浏览器主动缓存下来,留下历史记录,而 POST 默认不会 15 | - 编码: GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。 16 | - 参数: GET 一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输敏感信息。 17 | - 幂等: GET是幂等的,而POST不是。(幂等表示执行相同的操作,结果也是相同的) 18 | - TCP: GET请求会把浏览器会把http header和data一次性发出去,而POST会分成两个TCP数据包,首先发Header部分,如果服务器响应100(continue), 然后发 body 部分。(火狐浏览器除外,它的 POST 请求只发一个 TCP 包) 19 | -------------------------------------------------------------------------------- /docs/algorithm/链表/删除排序链表中的重复元素.md: -------------------------------------------------------------------------------- 1 | ```js 2 | var deleteDuplicates = function(head) { 3 | if (!head) { 4 | return head; 5 | } 6 | let cur = head; 7 | while (cur.next) { 8 | // 当前 cur 与 cur.next 对应的元素相同 9 | if (cur.val === cur.next.val) { 10 | // 将cur.next 从链表中移除 11 | cur.next = cur.next.next; 12 | } else { 13 | cur = cur.next; 14 | } 15 | } 16 | // 返回头部节点 17 | return head; 18 | }; 19 | ``` 20 | 21 | ## 问题 22 | 23 | 1. 为什么要新开一个 cur 来代替 head,直接用 head 实现不了 24 | 25 | - 因为指针移动了,不开一个,无法找到链表头返回。即要返回 head,使用 cur 来作为指针进行移动。 26 | - head 是链表的起始地址,如果不新开一个,最后找不到链表的起始位置 27 | 28 | 可以延伸为是直接使用 head,还是新创建一个变量来遍历的问题。 29 | 30 | 你这个问题问出了没上过数据结构的水平. 31 | -------------------------------------------------------------------------------- /docs/algorithm/链表/两个链表的第一个公共节点.md: -------------------------------------------------------------------------------- 1 | ## 两个链表的第一个公共节点 2 | 我们使用两个指针 node1,node2 分别指向两个链表 headA,headB 的头结点,然后同时分别逐结点遍历,当 node1 到达链表 headA 的末尾时,重新定位到链表 headB 的头结点;当 node2 到达链表 headB 的末尾时,重新定位到链表 headA 的头结点。 3 | 4 | 这样,当它们相遇时,所指向的结点就是第一个公共结点。 5 | 6 | 7 | ![getIntersectionNode](https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1585498246061.gif) 8 | 9 | ```js 10 | /** 11 | * @param {ListNode} headA 12 | * @param {ListNode} headB 13 | * @return {ListNode} 14 | */ 15 | var getIntersectionNode = function(headA, headB) { 16 | let node1 = headA; 17 | let node2 = headB; 18 | while(node1 !== node2) { 19 | node1 = node1 ? node1.next : headB; 20 | node2 = node2 ? node2.next : headA; 21 | } 22 | return node1; 23 | }; 24 | ``` -------------------------------------------------------------------------------- /docs/algorithm/数学/求众数.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 3 | 你可以假设数组是非空的,并且给定的数组总是存在众数。 4 | 5 | 示例 1: 6 | ···js 7 | 输入: [3,2,3] 8 | 输出: 3 9 | ``` 10 | 示例 2: 11 | ```js 12 | 输入: [2,2,1,1,1,2,2] 13 | 输出: 2 14 | ``` 15 | ## 解题思路 16 | 摩尔投票算法:每次从序列中选择两个不同的数字删除掉(活称为"抵消"),最后剩下的一个数字或几个相同的数字,就是出现次数大于总数一半的那个; 17 | 18 | 参考:[如何理解摩尔投票算法](https://www.zhihu.com/question/49973163) 19 | 20 | ## 解题方法 21 | ```js 22 | /** 23 | * @param {number[]} nums 24 | * @return {number} 25 | */ 26 | var majorityElement = function(nums) { 27 | let count = 0; 28 | let major = 0; 29 | for(let num of nums) { 30 | if(count == 0) { 31 | major = num; 32 | } 33 | count+= (major === num) ? 1 : -1; 34 | } 35 | return major; 36 | }; 37 | ``` -------------------------------------------------------------------------------- /docs/jsCode/实现一个async函数.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```js 4 | function spawn(genF) { 5 | return new Promise(function(resolve, reject) { 6 | const gen = genF(); 7 | function step(nextF) { 8 | let next; 9 | try { 10 | next = nextF(); 11 | } catch(e) { 12 | return reject(e) 13 | } 14 | if(next.done) { 15 | return resolve(next.value); 16 | } 17 | Promise.resolve(next.value).then(function(v) { 18 | step(function() { return gen.next(v)}) 19 | }, function (error) { 20 | step(function() { return gen.throw(e)}) 21 | }) 22 | } 23 | step(function() { return gen.next(undefined)}) 24 | }) 25 | } 26 | ``` 27 | 答案来源 [async 函数](https://es6.ruanyifeng.com/#docs/async) -------------------------------------------------------------------------------- /docs/algorithm/链表/倒数第K个节点.md: -------------------------------------------------------------------------------- 1 | ## 倒数第K个节点 2 | 3 | 1. 初始化: 使用双指针,i, j 4 | 2. 构建双指针距离: 先将i向后移动k次,此时 i,j的距离为k 5 | 3. 双指针共同移动: 同时移动i,j,直到i指向 null,此时j位置的val就是答案 6 | 3. 返回值 7 | 8 | ![img](https://pic.leetcode-cn.com/c11759b47df01442d2bacdc3a693531e1c5e905c741307f4bf61efffb08ce15d-aa.png) 9 | ```js 10 | /** 11 | * Definition for singly-linked list. 12 | * function ListNode(val) { 13 | * this.val = val; 14 | * this.next = null; 15 | * } 16 | */ 17 | /** 18 | * @param {ListNode} head 19 | * @param {number} k 20 | * @return {number} 21 | */ 22 | var kthToLast = function(head, k) { 23 | let p = head; 24 | for(let i = 0; i < k; i++) { 25 | p = p.next; 26 | } 27 | while(p) { 28 | p = p.next; 29 | head = head.next; 30 | } 31 | return head.val; 32 | }; 33 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directoriesa 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | _book/ 39 | .DS_Store 40 | ./docs/.vuepress/dist 41 | /dist/ 42 | /temp/ -------------------------------------------------------------------------------- /docs/algorithm/字符串/反转字符串.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 3 | 4 | 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 5 | 6 | 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 7 | 8 | 示例 1: 9 | ```js 10 | 输入:["h","e","l","l","o"] 11 | 输出:["o","l","l","e","h"] 12 | ``` 13 | 示例 2: 14 | ```js 15 | 输入:["H","a","n","n","a","h"] 16 | 输出:["h","a","n","n","a","H"] 17 | ``` 18 | 19 | ## 解题方法 20 | 21 | 这道题主要是理解题目要求:你必须原地修改输入数组。 22 | 可以循环长度 s.length / 2,第一个和最后一个互换位置,第二个和倒数第二个互换位置,以此类推,最后得到的s就是最后结果 23 | ```js 24 | /** 25 | * @param {string} s 26 | * @return {string} 27 | */ 28 | var reverseString = function(s) { 29 | const len = s.length; 30 | for(let i = 0; i < len / 2; i++) { 31 | const temp = s[i]; 32 | s[i] = s[len - i - 1]; 33 | s[len - i - 1] = temp; 34 | } 35 | return s; 36 | }; 37 | ``` -------------------------------------------------------------------------------- /docs/interview/CSS/伪类和伪元素.md: -------------------------------------------------------------------------------- 1 | ## 伪类和伪元素 2 | 3 | ### 为什么引入? 4 | `css`引入伪类和伪元素概念是为了格式化文档树以外的信息。伪类和伪元素是用来修饰不在文档树中的部分。 5 | ### 伪类 6 | **伪类** 用于当元素处于某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。比如说,用户悬停在指定的元素时,我们可以通:hover来描述这个元素的状态。虽然它和普通的css类类似,可以为已有的元素添加样式,但是它只有处于dom树无法描述的状态下才能为元素添加样式,所以将其称为伪类。 7 | 8 | ![图片来源网络](http://www.alloyteam.com/wp-content/uploads/2016/05/%E4%BC%AA%E7%B1%BB.png) 9 | ### 伪元素 10 | **伪元素** 用于创建不在文档树中的元素,并为其添加样式,比如说,我们可以通过:before来在一个元素前添加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。 11 | 12 | ![图片来源网络](http://www.alloyteam.com/wp-content/uploads/2016/05/%E4%BC%AA%E5%85%83%E7%B4%A0.png) 13 | 14 | CSS3 规范中的要求使用双冒号 (::) 表示伪元素,以此来区分伪元素和伪类,比如::before 和::after 等伪元素使用双冒号 (::),:hover 和:active 等伪类使用单冒号 (:)。虽然 CSS3 标准要求伪元素使用双冒号的写法,但也依然支持单冒号的写法。 15 | 16 | > [总结伪类和伪元素](http://www.alloyteam.com/2016/05/summary-of-pseudo-classes-and-pseudo-elements/#prettyPhoto) 17 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/TS学习/TS中的关键词.md: -------------------------------------------------------------------------------- 1 | ## 常用的关键词 2 | - typeof 3 | - keyof 4 | - type 5 | - interface 6 | 7 | ## typeof 可以获取值的类型 8 | ```js 9 | const obj = { a: '1' }; 10 | type Foo = typeof obj; // { a: string } 11 | ``` 12 | ## keyof 可以获取对象类型的所有 key 13 | ```js 14 | type Obj = { a: string; b: string } 15 | type Foo = keyof obj; // 'a' | 'b' 16 | ``` 17 | ## in 可以根据key 创建对象类型 18 | ```js 19 | type Obj = { [T in 'a' | 'b' | 'c']: string; } 20 | ``` 21 | ```js 22 | type Obj = { a: string; b: string }; 23 | type Foo = obj['a'];// string 24 | ``` 25 | 26 | ## 多关键词的组合用法 27 | ```js 28 | const obj = { a: '1', b: '1' }; 29 | type Foo = keyof typeof obj; // 'a' | 'b' 30 | const arr = [ 'a', 'b' ] as const; 31 | type Foo = (typeof arr)[number]; // 'a' | 'b' 32 | type Obj = { a: string; b: string }; 33 | type Foo = { [T in keyof Obj]: Obj[T] } // { a: string; b: string }; 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/algorithm/树/654.最大二叉树.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /** 3 | * @param {number[]} nums 4 | * @return {TreeNode} 5 | */ 6 | var constructMaximumBinaryTree = function(nums) { 7 | function buildTree(nums, start, end) { 8 | if (start > end) { 9 | return null; 10 | } 11 | // 找出数组中的最大值及索引 12 | let index = -1; 13 | let maxVal = Number.MIN_SAFE_INTEGER; 14 | for (let i = start; i <= end; i++) { 15 | if (nums[i] > maxVal) { 16 | index = i; 17 | maxVal = nums[i]; 18 | } 19 | } 20 | 21 | // 先构造根节点 22 | let root = new TreeNode(maxVal); 23 | root.left = buildTree(nums, start, index - 1); 24 | root.right = buildTree(nums, index + 1, end); 25 | return root; 26 | } 27 | return buildTree(nums, 0, nums.length - 1); 28 | }; 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/jsCode/千分位.md: -------------------------------------------------------------------------------- 1 | ## 155566.7 -> 155,566.7 2 | 3 | 1. 正则 4 | 5 | ```js 6 | function formatPrice(price) { 7 | return String(price).replace(/\B(?=(\d{3})+(?!\d))/g, ','); 8 | } 9 | ``` 10 | 11 | 2. 遍历 12 | 13 | ```js 14 | function formatPrice(price) { 15 | return String(price) 16 | .split('') 17 | .reverse() 18 | .reduce((prev, next, index) => { 19 | return (index % 3 ? next : next + ',') + prev; 20 | }); 21 | } 22 | ``` 23 | 24 | 3. toLocaleString 25 | 26 | ```js 27 | (999999999).toLocaleString(); // 999,999,999 28 | 29 | // 还可以加参数,进行更优雅的做法 30 | const options = { 31 | style: 'currency', 32 | currency: 'CNY', 33 | }; 34 | (999999).toLocaleString('zh-CN', options); // ¥999,999.00 35 | ``` 36 | 37 | ## 参考 38 | 39 | - [JS 格式化金钱(千分位加逗号、保留两位小数)](https://blog.csdn.net/weixin_42881768/article/details/115318314) 40 | -------------------------------------------------------------------------------- /docs/algorithm/链表/找出链表的中间节点.md: -------------------------------------------------------------------------------- 1 | ## 找出链表的中间节点 2 | 3 | 快慢指针做法: 4 | 1. 使用慢指针 slow 和快指针fast 两个指针同时遍历链表。快指针一次前进两个结点,速度是慢指针的两倍 5 | 2. 这样,当快指针到达链表尾部时,慢指针正好到达链表的中部。 6 | 7 | ![img](https://pic.leetcode-cn.com/404d110d9578be8c86697c991fa35a86412224911eb5d49a0ad001af59d5339e.gif) 8 | 9 | ```js 10 | /** 11 | * Definition for singly-linked list. 12 | * function ListNode(val) { 13 | * this.val = val; 14 | * this.next = null; 15 | * } 16 | */ 17 | /** 18 | * @param {ListNode} head 19 | * @return {ListNode} 20 | */ 21 | var middleNode = function(head) { 22 | let fast = head; 23 | let slow = head; 24 | // 循环的条件是 fast != null && fast.next != null,防止出现空指针异常。 25 | while(fast && fast.next) { 26 | slow = slow.next; 27 | fast = fast.next.next; 28 | } 29 | // 链表元素为奇数个时,slow 指向链表的中点 30 | // 链表元素为偶数个时,slow 指向链表两个中点的右边一个 31 | return slow; 32 | }; 33 | 34 | ``` -------------------------------------------------------------------------------- /docs/algorithm/链表/环形链表/142. 环形链表 II.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /** 3 | * Definition for singly-linked list. 4 | * function ListNode(val) { 5 | * this.val = val; 6 | * this.next = null; 7 | * } 8 | */ 9 | 10 | /** 11 | * @param {ListNode} head 12 | * @return {ListNode} 13 | */ 14 | var detectCycle = function(head) { 15 | let slow = head; 16 | let fast = head; 17 | while (fast && fast.next) { 18 | slow = slow.next; 19 | fast = fast.next.next; 20 | if (slow == fast) { 21 | break; 22 | } 23 | } 24 | 25 | // 如果快指针已经遍历完了,说明没有遇到环 26 | if (fast === null || fast.next === null) { 27 | return null; 28 | } 29 | // 计算环的起点 30 | // 两个指针中其中一个指向头节点,然后他们以相同速度前进,再次相遇时就是环开始的位置 31 | slow = head; 32 | while (slow !== fast) { 33 | slow = slow.next; 34 | fast = fast.next; 35 | } 36 | 37 | return slow; 38 | }; 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/algorithm/数组/按奇偶排序数组 II.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数。 4 | 5 | 对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数;当 A[i] 为偶数时, i 也是偶数。 6 | 7 | 你可以返回任何满足上述条件的数组作为答案。 8 | 9 |   10 | 11 | 示例: 12 | ```js 13 | 输入:[4,2,5,7] 14 | 输出:[4,5,2,7] 15 | 解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。 16 |   17 | ``` 18 | 提示: 19 | ```js 20 | 2 <= A.length <= 20000 21 | A.length % 2 == 0 22 | 0 <= A[i] <= 1000 23 | ``` 24 | 25 | ## 思路 26 | 找出奇数位上和偶数和偶数位上的奇数进行交换 27 | 28 | ## 代码实现 29 | ```js 30 | /** 31 | * @param {number[]} A 32 | * @return {number[]} 33 | */ 34 | var sortArrayByParityII = function(A) { 35 | let j = 1; 36 | for(let i = 0; i < A.length;) { 37 | if(i % 2 == A[i] % 2 ) { 38 | i++; 39 | if(j === i) { 40 | j++; 41 | } 42 | continue; 43 | } 44 | [A[i], A[j]] = [A[j], A[i]]; 45 | j++; 46 | } 47 | return A; 48 | }; 49 | ``` -------------------------------------------------------------------------------- /docs/interview/CSS/css解析规则.md: -------------------------------------------------------------------------------- 1 | ## css解析规则 2 | 3 | CSS选择器是 **从右向左解析** 。 4 | 5 | 若从左向右的匹配,发现不符合规则,需要进行回溯,会损失很多性能。 6 | 7 | 若从右向左匹配,先找到所有的最右节点,对于每一个节点,向上寻找父节点直到找到根元素或者满足条件的匹配规则,则结束这个分支的遍历。 8 | 9 | 两种匹配规则的性能差别很大,是因为从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点),而从左向右的匹配规则的性能都浪费在了失败的查找上面。 10 | 11 | 举例说明: 12 | ```js 13 | .mod-nav h3 span { font-size: 16px; } 14 | ``` 15 | 为什么从右向左的规则要比从左向右的高效? 16 | ![图片来源网络](https://images0.cnblogs.com/blog/551140/201309/26164119-951c7e9264f046e3aca49b8c466086fd.png) 17 | 18 | 若从左向右的匹配,过程是:从`.mod-nav`开始,遍历子节点`header`和子节点`div`,然后各自向子节点遍历。在右侧`div`的分支中,最后遍历到叶子节点`a`,发现不符合规则,需要回溯到`ul`节点,再遍历下一个`li-a`,假如有1000个`li`,则这`1000`次的遍历与回溯会损失很多性能。 19 | 20 | 21 | 再看看从右至左的匹配:先找到所有的最右节点span,对于每一个`span`,向上寻找节点`h3`,由`h3`再向上寻找`class=mod-nav`的节点,最后找到根元素html则结束这个分支的遍历。 22 | 23 | 很明显,两种匹配规则的性能差别很大。之所以会差别很大,是因为从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点);而从左向右的匹配规则的性能都浪费在了失败的查找上面。 24 | 25 | > 答案来源于 [CSS选择器从右向左的匹配规则](http://www.cnblogs.com/zhaodongyu/p/3341080.html) -------------------------------------------------------------------------------- /docs/algorithm/数学/丑数.md: -------------------------------------------------------------------------------- 1 | 丑数 就是只包含质因数 2、3 和 5 的正整数。 2 | 3 | 给你一个整数 n ,请你判断 n 是否为 丑数 。如果是,返回 true ;否则,返回 false 。 4 | 5 | ```js 6 | 示例 1: 7 | 8 | 输入:n = 6 9 | 输出:true 10 | 解释:6 = 2 × 3 11 | ``` 12 | 13 | 示例 2: 14 | 15 | ```js 16 | 输入:n = 1 17 | 输出:true 18 | 解释:1 没有质因数,因此它的全部质因数是 {2, 3, 5} 的空集。习惯上将其视作第一个丑数。 19 | 示例 3: 20 | 21 | 输入:n = 14 22 | 输出:false 23 | 解释:14 不是丑数,因为它包含了另外一个质因数 7 。 24 | ``` 25 | 26 | ## 思路 27 | 28 | 1. 边界条件,题目为 丑数 就是只包含质因数 2、3 和 5 的**正整数**。 所以 0 和负数不是丑数 29 | 2. 可以对 nn 反复除以 2,3,5,直到 n 不再包含质因数 2,3,5。若剩下的数等于 1,则说明 n 不包含其他质因数,是丑数;否则,说明 n 包含其他质因数,不是丑数。 30 | 31 | ```js 32 | /** 33 | * @param {number} n 34 | * @return {boolean} 35 | */ 36 | var isUgly = function(n) { 37 | if (n <= 0) { 38 | return false; 39 | } 40 | let factors = [2, 3, 5]; 41 | for (let factor of factors) { 42 | while (n % factor === 0) { 43 | n /= factor; 44 | } 45 | } 46 | return n === 1; 47 | }; 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/interview/Webpack/webpack的loader和plugin.md: -------------------------------------------------------------------------------- 1 | ## webpack 的 loader 和 plugin 2 | 3 | ### loader 4 | 5 | 对模块的源代码进行转换, webpack 只能处理 javaScript, 如果要处理其他类型的文件, 就需要使用 loader 进行转换; 6 | 7 | 1. loader 可以使你在 import 或"加载"模块时预处理文件 8 | 2. loader 允许你在 javascript 模块中 import css 文件 9 | 3. 将文件从其他语言转为 javascript, 比如 ts-loader 10 | 11 | 常用的 loader 12 | 13 | - style-loader: 创建 style 标签将 css 文件嵌入到 html 中 14 | - css-loader: 处理其中的@import 和 url() 15 | - less-loader: 将 less 转为 css 16 | - url-loader: 将图片大小小于 limit 参数的转成 base64 的图片(data url), 大于 limit 参数的, url-loader 会调用 file-loader 进行处理 17 | - file-loader: 解析项目中的 url 引入(不仅限于 css), 根据我们的配置, 将图片拷贝到响应的路径, 再根据我们的配置, 修改打包后文件使用路径, 使之指向正确的文件 18 | 19 | ### plugin 20 | 21 | plugin: 插件, 用来扩展 webpack 功能 22 | 23 | 常用的 plugin 24 | 25 | - htmlWebpackPlugin: 根据模板自动生成 html 文件, 并自动引用 css 和 js 文件 26 | - extract-text-webpack-plugin: 将 Js 中引用的样式文件单独抽离成 css 文件 27 | - DefinePlugin: 编译时配置全局变量 28 | - happypack:通过多进程模型,来加速代码构建 29 | -------------------------------------------------------------------------------- /docs/jsCode/实现一个iterator.md: -------------------------------------------------------------------------------- 1 | 2 | ## Iterator 的遍历过程是这样的。 3 | 4 | (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。 5 | 6 | (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。 7 | 8 | (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。 9 | 10 | (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。 11 | 12 | ```js 13 | function makeIterator(array) { 14 | var nextIndex = 0; 15 | return { 16 | next: function() { 17 | return nextIndex < array.length ? 18 | { 19 | value: array[nextIndex++], 20 | done: false 21 | } 22 | : 23 | { 24 | value: undefined, 25 | done: true 26 | }; 27 | } 28 | }; 29 | } 30 | const it = makeIterator(['a', 'b']); 31 | 32 | it.next() 33 | // { value: "a", done: false } 34 | it.next() 35 | // { value: "b", done: false } 36 | it.next() 37 | // { value: undefined, done: true } 38 | 39 | ``` 40 | 41 | 答案来源 [Iterator 和 for...of 循环](https://es6.ruanyifeng.com/#docs/iterator) -------------------------------------------------------------------------------- /docs/algorithm/链表/剑指 Offer 35. 复杂链表的复制.md: -------------------------------------------------------------------------------- 1 | https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/ 2 | 3 | ```js 4 | /** 5 | * // Definition for a Node. 6 | * function Node(val, next, random) { 7 | * this.val = val; 8 | * this.next = next; 9 | * this.random = random; 10 | * }; 11 | */ 12 | 13 | /** 14 | * @param {Node} head 15 | * @return {Node} 16 | */ 17 | var copyRandomList = function(head) { 18 | if (!head) { 19 | return null; 20 | } 21 | // 将新节点复制到map中 22 | let map = new Map(); 23 | let node = head; 24 | while (node) { 25 | map.set(node, new Node(node.val)); 26 | node = node.next; 27 | } 28 | // 对map中存储的节点做遍历操作 29 | node = head; 30 | while (node) { 31 | map.get(node).next = map.get(node.next) == undefined ? null : map.get(node.next); 32 | map.get(node).random = map.get(node.random); 33 | node = node.next; 34 | } 35 | return map.get(head); 36 | }; 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/algorithm/数组/全排列.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定一个 没有重复 数字的序列,返回其所有可能的全排列。 3 | 4 | 示例: 5 | ```js 6 | 输入: [1,2,3] 7 | 输出: 8 | [ 9 | [1,2,3], 10 | [1,3,2], 11 | [2,1,3], 12 | [2,3,1], 13 | [3,1,2], 14 | [3,2,1] 15 | ] 16 | ``` 17 | 18 | ## 思路 19 | 20 | 21 | ```js 22 | /** 23 | * @param {number[]} nums 24 | * @return {number[][]} 25 | */ 26 | var permute = function(nums) { 27 | let result = []; 28 | if(nums.length) { 29 | dfs(result, nums, 0) 30 | } 31 | return result; 32 | function swap(array, index1, index2) { 33 | [nums[index1], nums[index2]] = [nums[index2], nums[index1]] 34 | } 35 | function dfs(result, nums, start) { 36 | if(start === nums.length) { 37 | result.push([...nums]) 38 | } 39 | for(let i = start; i < nums.length; i++ ) { 40 | swap(nums, start, i); 41 | dfs(result, nums, start + 1); 42 | swap(nums, start, i); 43 | } 44 | } 45 | }; 46 | ``` -------------------------------------------------------------------------------- /docs/jsCode/模版渲染.md: -------------------------------------------------------------------------------- 1 | 实现一个 `render(template, context)` 方法,将 template 中的占位符用 context 填充。 2 | 3 | 示例: 4 | 5 | ```js 6 | var template = "{{name}}很厉害,才{{age}}岁" 7 | var context = {name:"bottle",age:"15"} 8 | 输入:template context 9 | 输出:bottle很厉害,才15岁 10 | ``` 11 | 12 | 要求: 13 | 14 | - 级联的变量也可以展开 15 | - 分隔符与变量之间允许有空白字符 16 | 17 | ### 代码 18 | 19 | ```js 20 | var template = '{{name}}很厉害,才{{age}}岁'; 21 | var context = { name: 'bottle', age: '15' }; 22 | function render(template, context) { 23 | return template.replace(/{{(.*?)}}/g, (match, key) => context[key.trim()]); 24 | } 25 | render(template, context); 26 | // "bottle很厉害,才15岁" 27 | ``` 28 | 29 | 循环与重复 30 | 31 | - `0 | 1`: `?` 32 | - `>= 0` 元字符`*`用来表示匹配 0 个字符或无数个字符。通常用来过滤某些可有可无的字符串。 33 | - `>=1`: `+` 34 | 35 | 分组 36 | 以`(`和`)`元字符所包含的正则表达式被分为一组,每一个分组都是一个子表达式 37 | 38 | ## 相关文档 39 | 40 | - [正则表达式不要背](https://juejin.cn/post/6844903845227659271) 41 | - [实现一个模板引擎](https://github.com/mqyqingfeng/Blog/issues/63) 42 | 43 | ### 拓展 44 | -------------------------------------------------------------------------------- /docs/algorithm/排序/冒泡排序.md: -------------------------------------------------------------------------------- 1 | ## 冒泡排序 2 | 3 | - 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 4 | - 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。 5 | - 针对所有的元素重复以上的步骤,除了最后一个。 6 | - 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 7 | 8 | > 比较任何两个相邻的值, 如果第一个比第一个大,则交换他们; 9 | 10 | ```js 11 | function bubbleSort() { 12 | let len = array.length; 13 | for (let i = 0; i < len; i++) { 14 | for (let j = 0; j < len - 1; j++) { 15 | if (array[j] > array[j + 1]) { 16 | [array[j], array[j + 1]] = [array[j + 1], array[j]]; 17 | } 18 | } 19 | } 20 | } 21 | // 升级后的冒泡排序 22 | function modifiedBubbleSort() { 23 | let len = array.length; 24 | for (let i = 0; i < len; i++) { 25 | for (let j = 0; j < len - 1 - i; j++) { 26 | if (array[j] > array[j + 1]) { 27 | [array[j], array[j + 1]] = [array[j + 1], array[j]]; 28 | } 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | ### 复杂度为 35 | 36 | - 时间复杂度为 O(n2) 37 | -------------------------------------------------------------------------------- /docs/algorithm/树/二叉树的最大深度.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定一个二叉树,找出其最大深度。 3 | 4 | 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 5 | 6 | 说明: 叶子节点是指没有子节点的节点。 7 | 8 | 示例: 9 | 给定二叉树 [3,9,20,null,null,15,7], 10 | ```js 11 | 3 12 | / \ 13 | 9 20 14 | / \ 15 | 15 7 16 | 17 | ``` 18 | 返回它的最大深度 3 。 19 | 20 | 21 | ## 解题思路 22 | 直观的方法是通过递归来解决问题。下面使用 DFS(深度优先搜索)来实现。 23 | 24 | 25 | ```js 26 | /** 27 | * Definition for a binary tree node. 28 | * function TreeNode(val) { 29 | * this.val = val; 30 | * this.left = this.right = null; 31 | * } 32 | */ 33 | /** 34 | * @param {TreeNode} root 35 | * @return {number} 36 | */ 37 | var maxDepth = function(root) { 38 | if(root === null) { 39 | return 0; 40 | } else { 41 | return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); 42 | } 43 | }; 44 | ``` 45 | ## 复杂度分析 46 | 47 | 48 | - 时间复杂度:我们每个结点只访问一次,因此时间复杂度为 O(N), 49 | - 空间复杂度:在最糟糕的情况下,树是完全不平衡的,例如每个结点只剩下左子结点,递归将会被调用 N 次(树的高度),因此保持调用栈的存储将是 O(N)。但在最好的情况下(树是完全平衡的),树的高度将是 log(N)。因此,在这种情况下的空间复杂度将是 O(log(N))。 50 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/反转字符串II.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。 3 | 4 | 如果剩余字符少于 k 个,则将剩余字符全部反转。 5 | 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。 6 |   7 | 8 | 示例: 9 | ```js 10 | 输入: s = "abcdefg", k = 2 11 | 输出: "bacdfeg" 12 | ``` 13 | 14 | 提示: 15 | 16 | 该字符串只包含小写英文字母。 17 | 给定字符串的长度和 k 在 [1, 10000] 范围内。 18 | 19 | 20 | ## 解题方法 21 | 22 | ```js 23 | /** 24 | * @param {string} s 25 | * @param {number} k 26 | * @return {string} 27 | */ 28 | var reverseStr = function(s, k) { 29 | let str = ''; 30 | let limit = 2*k; 31 | for(let i = 0; i < s.length; i+=limit) { 32 | let temp = s.slice(i, i + limit); 33 | if(i == 0) { 34 | temp = s.slice(0, limit); 35 | } 36 | if(temp.length < k) { 37 | temp = temp.split('').reverse().join(''); 38 | } else if(temp.length <= limit) { 39 | pre = temp.slice(0, k).split('').reverse().join(''); 40 | temp = pre + temp.slice(k); 41 | } 42 | str +=temp; 43 | } 44 | return str; 45 | }; 46 | ``` -------------------------------------------------------------------------------- /book.md: -------------------------------------------------------------------------------- 1 | - [深入理解计算机系统(原书第三版)](https://book.douban.com/subject/26912767/) 2 | - [现代操作系统(第3版)](https://book.douban.com/subject/3852290/) 3 | - [编译原理](https://book.douban.com/subject/3296317/) 4 | - [TCP/IP详解 卷1:协议 ](https://book.douban.com/subject/1088054/) 5 | - [图解TCP/IP : 第5版](https://book.douban.com/subject/24737674/) 6 | - [图解HTTP](https://book.douban.com/subject/25863515/) 7 | 8 | 极客时间:深入浅出计算机组成原理 9 | 10 | 计算机科学与技术专业 4 大基础:《数据结构》、《操作系统》、《计算机网络》、《计算机组成原理》。 11 | 12 | 网络 13 | 14 | 《TCP/IP详解卷1:协议》 15 | 16 | 17 | 18 | 网络是怎样连接的 19 | 程序是怎样跑起来的 20 | 计算机是怎么跑起来的 21 | 22 | 23 | ## 资源整理 24 | - [计算机类常用电子书整理](https://github.com/iamshuaidi/CS-Book) 25 | - [计算机电子书pdf整理](https://github.com/fuhmmin/it-ebooks-cn) 26 | - [计算机学习资源,电子书](https://github.com/SummerJoan3/books) 27 | 28 | 29 | ## 开源电子书 30 | - [《Flutter实战》](https://book.flutterchina.club/) 31 | - [《深入浅出Webpack》](https://github.com/gwuhaolin/dive-into-webpack) 32 | 33 | 34 | ## 好用的工具 35 | - [支持 Markdown 和富文本的在线简历排版工具](https://github.com/mdnice/markdown-resume) 36 | - [一键发布本地文章到主流博客平台的托盘助手](https://github.com/ystcode/BlogHelper) -------------------------------------------------------------------------------- /docs/algorithm/二分查找/0~n-1中缺失的数字.md: -------------------------------------------------------------------------------- 1 | ### 剑指 Offer 53 - II. 0 ~ n-1 中缺失的数字 2 | 3 | 一个长度为 n-1 的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围 0 ~ n-1 之内。在范围 0 ~ n-1 内的 n 个数字中有且只有一个数字不在该数组中,请找出这个数字。 4 | 5 | 示例 1: 6 | 7 | 输入: [0,1,3] 8 | 输出: 2 9 | 示例  2: 10 | 11 | 输入: [0,1,2,3,4,5,6,7,9] 12 | 输出: 8 13 | 14 | 来源:力扣(LeetCode) 15 | 链接:https://leetcode.cn/problems/que-shi-de-shu-zi-lcof 16 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 17 | 18 | ### 代码 19 | 20 | ```js 21 | /** 22 | * @param {number[]} nums 23 | * @return {number} 24 | */ 25 | var missingNumber = function(nums) { 26 | /** 27 | * 使用二分搜索 28 | * 左数组:nums[i] == i 29 | * 右数组:nums[i] !== i 30 | * 结果:找到第一个 nums[i] !== i的 值,即查找左边界 31 | */ 32 | let left = 0; 33 | let right = nums.length - 1; 34 | while (left <= right) { 35 | let mid = Math.floor(left + (right - left) / 2); 36 | if (nums[mid] === mid) { 37 | // [mid + 1, right]区间查找 38 | left = mid + 1; 39 | } else { 40 | right = mid - 1; 41 | } 42 | } 43 | return left; 44 | }; 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/认识JS.md: -------------------------------------------------------------------------------- 1 | ## JavaScript 是什么类型的语言 2 | 3 | JavaScript 是一种弱类型的、动态的语言 4 | 5 | - 弱类型,意味着你不需要告诉 JavaScript 引擎这个或那个变量是什么数据类型,JavaScript 引擎在运行代码的时候自己会计算出来。 6 | - 动态,意味着你可以使用同一个变量保存不同类型的数据。 7 | 8 | ## JavaScript是解释型语言 9 | 10 | ### 编译器和解释器 11 | 之所以存在编译器和解释器,是因为机器不能直接理解我们所写的代码,所以在执行程序之前,需要将我们所写的代码“翻译”成机器能读懂的机器语言。按语言的执行流程,可以把语言划分为编译型语言和解释型语言。 12 | 13 | **编译型语言在程序执行之前,需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件,这样每次运行程序时,都可以直接运行该二进制文件**,而不需要再次重新编译了。比如 C/C++、GO 等都是编译型语言 14 | 15 | 而由解释型语言编写的程序,在每次运行时都需要通过解释器对程序进行动态解释和执行。比如 Python、JavaScript 等都属于解释型语言。 16 | 17 | ![img](https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1589295645982.png) 18 | 19 | 从图中你可以看出这二者的执行流程,大致可阐述为如下: 20 | 1. 在编译型语言的编译过程中,编译器首先会依次对源代码进行词法分析、语法分析,生成抽象语法树(AST),然后是优化代码,最后再生成处理器能够理解的机器码。如果编译成功,将会生成一个可执行的文件。但如果编译过程发生了语法或者其他的错误,那么编译器就会抛出异常,最后的二进制文件也不会生成成功。 21 | 2. 在解释型语言的解释过程中,同样解释器也会对源代码进行词法分析、语法分析,并生成抽象语法树(AST),不过它会再基于抽象语法树生成字节码,最后再根据字节码来执行程序、输出结果。 22 | 23 | ## V8 是如何执行一段 JavaScript 代码的 24 | 25 | 1. 生成抽象语法树(AST)和执行上下文 26 | 生成AST需要经过两个阶段 27 | - 词法分析 28 | - 语法分析 29 | 2. 生成字节码 30 | 31 | 3. 执行代码 -------------------------------------------------------------------------------- /docs/jsCode/实现一个虚拟列表.md: -------------------------------------------------------------------------------- 1 | ## 虚拟列表 2 | 3 | 场景: 4 | 有大量数据需要展示的时候。 5 | 如何实现: 6 | 在处理用户滚动时,改变列表在可视区域的渲染部分。 7 | 8 | ## 关键点 9 | 10 | ![虚拟列表](https://user-images.githubusercontent.com/7871813/46521241-891dd380-c8b1-11e8-8ba4-774a062c7735.png) 11 | 几个概念 12 | 13 | - 可滚动区域: 14 | - 可视区域:视口的大小,即浏览器屏幕或手机屏幕的大小 15 | - 可见区域起始数据的 startIndex 16 | - 元素高度固定:`startIndex = scrollTop/itemHeight`(滚动条的高度/列表项的高度), 17 | - 可见区域结束数据的 endIndex 18 | - 元素高度固定:`endIndex = startIndex + (clientheight/itemHeigth)` 19 | 20 | 根据 startIndex 和 endIndex 取相应范围的数据,渲染到可视化区域 21 | 22 | - startOffset 23 | - endOffset 24 | startOffset 和 endOffset 会撑开容器的内容高度,可以起到缓冲的作用,使其保持滚动的平滑性 25 | 26 | ### 如果元素节点的高度不是固定的,怎么处理 27 | 28 | 方法一:x 29 | 给节点绑定获取高度的方法。 30 | 在节点加载渲染后调用此方法来动态获取元素正式高度,并且将此位置 index 和 height 信息缓存起来 31 | 存在问题:不能准确的计算出显示的 count 数,需要给每个元素预估一个高度 32 | 33 | 方法二: 34 | 添加一个获取列表项高度的方法,给这个方法传入 item 和 index, 返回对应列表项的高度。每次计算也会将 index 和 height 信息缓存起来。 35 | 每次滑动都需要遍历所有节点,找到当前 scrollTop 对应的元素位置,并且重新计算 startIndex 和 endIndex。 36 | 37 | ## 参考 38 | 39 | - [浅说虚拟列表的实现原理 ](https://github.com/dwqs/blog/issues/70) 40 | -------------------------------------------------------------------------------- /docs/algorithm/链表/合并两个有序链表.md: -------------------------------------------------------------------------------- 1 | # 合并两个有序链表 2 | 3 | JavaScript实现LeetCode第21题:合并两个有序链表 4 | 5 | ## 题目描述 6 | 7 | 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 8 | 示例: 9 | ```js 10 | 输入:1->2->4, 1->3->4 11 | 输出:1->1->2->3->4->4 12 | ``` 13 | ## 思路分析 14 | 新建一个链表,然后比较两个链表中的元素值,把较小的那个链到新链表中,由于两个输入链表的长度可能不同,所以最终会有一个链表先完成插入所有元素,则直接另一个未完成的链表直接链入新链表的末尾。 15 | 16 | ## 解法 17 | ```js 18 | /** 19 | * Definition for singly-linked list. 20 | * function ListNode(val) { 21 | * this.val = val; 22 | * this.next = null; 23 | * } 24 | */ 25 | /** 26 | * @param {ListNode} l1 27 | * @param {ListNode} l2 28 | * @return {ListNode} 29 | */ 30 | var mergeTwoLists = function(l1, l2) { 31 | let res = new ListNode(-1); 32 | let cur = res; 33 | while(l1 !== null && l2 !== null) { 34 | if (l1.val < l2.val){ 35 | cur.next = l1; 36 | l1 = l1.next; 37 | } else { 38 | cur.next = l2; 39 | l2 = l2.next; 40 | } 41 | cur = cur.next; 42 | } 43 | cur.next = (l1 === null) ? l2 : l1; 44 | return res.next; 45 | }; 46 | ``` -------------------------------------------------------------------------------- /docs/algorithm/数学/计算质数.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | ## 解题思路 4 | 5 | 质数:指在大于 1 的自然数中,除了 1 和它本身不再有其他因数的自然数。质数又称素数。 6 | 7 | ### 厄拉多塞筛法 8 | 9 | 先将 2-n 的个数放入表中,然后在 2 的上面画一个圆圈,然后划去 2 的其他倍数,第一个既未画圈又没有划去的数是 3,将它画圈,再划去 3 的其他倍数; 10 | 现在即未画圈又没有划去的数是 5,将它画圈并划去 5 的其他倍数......依次类推,一直到所有小于或等于 N 的各数都画了圈或者划去为止。这时,表中画了圈的以及未划去的那些数正好就是小于 N 的素数。 11 | 12 | ![img](https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1589090399702.gif) 13 | 14 | 每计算一个数,都要把它的倍数去掉。到了 n,数一下留下了几个数。 15 | 16 | ## 解题方法 17 | 18 | ```js 19 | /** 20 | * @param {number} n 21 | * @return {number} 22 | */ 23 | var countPrimes = function(n) { 24 | // isPrime[i] 表示数 i 是不是质数,如果是质数则为 1,否则为 0 25 | let isPrimes = new Array(n).fill(1); 26 | let ans = 0; 27 | for (let i = 2; i < n; i++) { 28 | if (isPrimes[i]) { 29 | ans += 1; 30 | // 从小到大遍历每个数,如果这个数为质数,则将其所有的倍数都标记为合数(除了该质数本身)即 00 31 | // 这样在运行结束的时候我们即能知道质数的个数。 32 | for (let j = i * i; j < n; j += i) { 33 | isPrimes[j] = 0; 34 | } 35 | } 36 | } 37 | return ans; 38 | }; 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/book/webpack原理与实践.md: -------------------------------------------------------------------------------- 1 | webpack 相关 2 | 3 | - 是什么,解决了什么问题,怎么工作的 4 | 一个 JS 应用程序的静态模块打包器。递归构建依赖关系树,包含应用程序需要的每个模块,然后将模块打包成一个或者多个 bundle。 5 | 实现前端模块化。 6 | 模块化的发展历程:1. 文件划分 -> 2. 命名空间 -> 3. IIFE -> 4. IIFE 依赖参数 。解决了模块代码的组织问题,但是存在问题:模块的加载 7 | 模块化规范: 8 | 9 | - CommonJS 规范,是 node.js 中的规范:一个文件就是一个模块,每个模块都有单独的作用域,通过 module.exports 导出成员,再通过 require 函数载入模块。浏览器使用的话会有一些问题:commonnjs 是同步方式加载模块,因为 Node.js 执行机制是在启动时加载模块,执行过程中只是使用模块。在浏览器端使用同步的加载模式,就会引起大量的同步模式请求,导致应用运行效率低下。 10 | - AMD:浏览器的规范,Require.js 库。在 AMD 规范中约定每个模块通过 define() 函数定义,这个函数默认可以接收两个参数,第一个参数是一个数组,用于声明此模块的依赖项;第二个参数是一个函数,参数与前面的依赖项一一对应,每一项分别对应依赖项模块的导出成员,这个函数的作用就是为当前模块提供一个私有空间。如果在当前模块中需要向外部导出成员,可以通过 return 的方式实现 11 | - CMD: 淘宝的 Sea.js。 12 | - es6 modules:主流的模块化 13 | 14 | - 模块化打包工具需要解决哪些问题 15 | 16 | - 它需要具备编译代码的能力 17 | - 能够将散落的模块再打包到一起 s 18 | - 它需要支持不同种类的前端模块类型 19 | 20 | - 用过哪些 loader 和 plugin,它们区别是什么 21 | 22 | - loader: 处理非 JS 的文件,webpack 自身只理解 JS。 23 | - plugin:解决 loader 无法解决的事。 24 | 25 | - tree shaking 26 | - code spliting 27 | - 缓存 28 | - 你用过或者你知道 webpack 相关的内容 29 | -------------------------------------------------------------------------------- /docs/menu/知识体系.md: -------------------------------------------------------------------------------- 1 | ## 知识体系 2 | 3 | 可以根据下面这些篇文章,补充和形成自己的知识体系。借助他人的力量,而不是单纯靠自己想。 4 | 5 | - [2021 年前端面试必读文章【超三百篇文章/赠复习导图】](https://juejin.cn/post/6844904116339261447), 6 | - [一篇文章构建你的 NodeJS 知识体系]() 7 | 8 | ### 计算机基础知识 9 | 10 | 《深入浅出计算机组成原理》 11 | 《趣谈网络协议》 12 | 《编译原理之美》 13 | 14 | 《透视 HTTP 协议》 15 | 《趣谈 Linux 操作系统》 16 | 17 | 因为没有很系统地学习过计算机的知识,所以每次看一个新的概念的时候,它里面提到的一些词,也看不懂啥意思,所以很难理解。所以需要补充一些基础的一些知识。 18 | 19 | ### 一些概念 20 | 21 | - Service Mesh 22 | [什么是 Service Mesh](https://zhuanlan.zhihu.com/p/61901608)。 23 | 微服务架构存在的问题: 服务间网络通信问题 24 | 如何管理和控制服务间的通信: 25 | - 服务注册、发现 26 | - 路由,流量转移 27 | - 弹性能力(熔断,超时,重试) 28 | - 安全(鉴权) 29 | - 是观察性(可视化) 30 | 31 | 32 | 微服务时代的 TCP/IP 协议, 管理和控制服务间的通信 33 | 34 | - API 网关 35 | - 鉴权,认证,授权 36 | - 负载均衡 37 | 38 | ### 稳定性相关: 39 | 40 | [超时,重试,熔断,限流](https://mp.weixin.qq.com/s/wIQIv4TAHRIqR_X9iSz3Hw?) 41 | [提升 Node.js 服务稳定性,需要关注哪些指标?](https://mp.weixin.qq.com/s/j-mxyccax2cCEhq6hv1mxQ) 42 | 43 | - 超时: A 调用 B 的时候,B 在响应的时候会特别慢,A 就要设置一个调用超时时间。 44 | - 重试:A调用B超时,可能是偶尔抖动,可以设置超时后重试一下。 45 | - 熔断:超时重试几次后,发现还是不行,就直接 46 | -------------------------------------------------------------------------------- /docs/interview/Webpack/优化webpack代码体积.md: -------------------------------------------------------------------------------- 1 | ## 优化 webpack 打包体积 2 | 3 | ### 一. 使用 webpack-bundle-analyzer 分析包的体积 4 | 5 | ```js 6 | ``` 7 | 8 | 构建完成后会在 8888 端口展示大小。 9 | 10 | 可以分析哪些问题? 11 | 12 | 1. 依赖的第三方模块文件大小 13 | 2. 业务里面的组件代码大小 14 | 15 | ### 二. 使用 webpack 进行图片压缩 16 | 17 | 分析页面加载资源,会发现图片会占用很大的体积。所以要对图片进行压缩,除了我们自己手动去使用其他平台压缩之外,还可以在 webpack 中配置压缩 18 | 19 | - 要求:基于 Node 库的 imagemin 或者 tinypng API 20 | - 使用:配置 image-webpack-loader 21 | 22 | Imagemin 的优点分析 23 | 24 | - 有很多定制选项 25 | - 可以引入更多第三方优化插件,例如 pngquant 26 | - 可以处理多种图片格式 27 | 28 | ### 三:tree shaking 29 | 30 | 1. 删除无用的 JS 31 | 32 | 使用: 33 | 34 | - webpack 默认支持,在 .babelrc 里设置 modules: false 即可 35 | - production mode 的情况下默认开启 36 | 要求:必须是 ES6 的语法,CJS 的方式不支持 37 | 38 | 2. 删除无用的 CSS 39 | 40 | - PurifyCSS: 遍历代码,识别已经用到的 CSS class 41 | - uncss: HTML 需要通过 jsdom 加载,所有的样式通过 PostCSS 解析,通过 document.querySelector 来识别在 html 文件里面不存在的选择器 42 | 43 | 3. 44 | 45 | ## 体积优化策略总结 46 | 47 | 1. 使用 webpack-bundle-analyzer 分析包的体积 48 | 2. 使用 webpack 进行图片压缩 49 | 3. 使用 TreeShaing 删除无用的 JS 和 css 50 | 4. 动态 Polyfill 51 | 5. 公共资源分离 52 | 6. Scope Hoisting 53 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/不同的二叉搜索树.md: -------------------------------------------------------------------------------- 1 | 2 | ## 题目描述 3 | 4 |
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
5 |
示例:
6 | 7 | ```javascript 8 | 输入: 3 9 | 输出: 5 10 | 解释: 11 | 给定 n = 3, 一共有 5 种不同结构的二叉搜索树: 12 | 13 | 1 3 3 2 1 14 | \ / / / \ \ 15 | 3 2 1 1 3 2 16 | / / \ \ 17 | 2 1 2 3 18 | ``` 19 | 20 | ## 解题方法 21 | 22 | - 每个树的构成都可以分为左子树和右子树,所以以 i 为根节点的二叉搜索树种类的个数可以分解为子问题:左子树的种类和右子树的种类 23 | - 左子树的节点范围:1...i-1 24 | - 右子树的节点范围:`i+1...n` 25 | - 那么就可以把同样的问题转接到左子树和右子树上。 26 | 27 | ```javascript 28 | /** 29 | * @param {number} n 30 | * @return {number} 31 | */ 32 | var numTrees = function (n) { 33 | // dp[i]表示以1...i为节点组成的二叉搜索树的种类 34 | let dp = new Array(n + 1).fill(0); 35 | dp[0] = 1; 36 | dp[1] = 1; 37 | // dp[1]已经确定,因此从2开始 38 | for (let i = 2; i <= n; i++) { 39 | // j表示分别从1为根节点至i为根节点 40 | for (let j = 1; j <= i ; j++) { 41 | dp[i] += dp[j - 1] * dp[i - j]; 42 | } 43 | } 44 | return dp[n]; 45 | }; 46 | ``` 47 | 48 | - 时间复杂度:O(n2) 49 | - 空间复杂度:O(n) 50 | -------------------------------------------------------------------------------- /docs/menu/元宇宙.md: -------------------------------------------------------------------------------- 1 | ## 元宇宙是什么 2 | 3 | 立体互联网和价值互联网。 4 | 5 | ### 立体互联网: 6 | 7 | VR(虚拟现实):走进的是一个全虚拟的世界, 8 | 9 | - Facebook 的虚拟现实头盔 10 | 11 | AR(增强现实):带着特殊眼镜走进一个实体的空间,但又能投射出虚拟物品 12 | 13 | - 苹果也已经将增强现实(AR)需要的深度相机嵌入到了每一台 iPhone 的前置照相系统。“深度相机系统”它能够投射几万个红外光点到我们的脸上,即时地建模和识别我们脸部的立体轮廓。” 14 | “这个功能除了解锁手机之外,还可以有很多用途。比如,它可以把我们的脸部表情、动作捕捉下来、贴到数字虚拟人的头像上,做出来一个表情、动作跟我们一模一样的数字虚拟人” 15 | “苹果已经给我们提供了两个主要的开发者工具。一个叫 ARKit,增强现实套件,用于调用手机上的传感器;另一个叫 RealityKit,现实套件,用于三维建模和显示” 16 | 17 | MR 混合现实,现实和虚拟结合在一起 18 | 19 | - 微软,推出头戴设备,一个特殊的眼镜: “你看到的是电脑创造的全息影像和现实叠加在一起的世界”。“我们即可以看到同事坐在对面的椅子上,又可以看到有的同事不在这个会议室,他们以全息影像的形式坐在会议室的椅子上。” 20 | 21 | 摘录来自: 方军. “说透元宇宙.epub。” Apple Books. 22 | 23 | ### 价值互联网: 24 | 25 | 经济生活数字化,财产价值也在慢慢往数字空间转换。以区块链为代表的技术支持,互联网会成为价值流程的网络。 26 | 27 | “可类比传统云服务平台的开发平台 Alchemy,最新融资估值超过百亿美元;NFT 交易市场 Opensea 的估值,在半年时间内从 15 亿美元上涨到了 133 亿美元,增长了 8 倍。” 28 | 29 | “在元宇宙时代,你可以穿行在真实与虚拟混合的城市,拥有多样化的娱乐与休闲方式。你可以拥有自己的数字财产,比如数字房产、数字艺术品等等。 30 | 你还可以和其他人在数字空间合伙创业。这个数字空间是三维立体美轮美奂的,但它同时又有着类似实体世界的经济运转逻辑。它是立体互联网和价值互联网的综合体。” 31 | 32 | 移动互联网 33 | 大数据 34 | 云计算 35 | 人工智能 36 | 37 | ## 三个阶段 38 | 39 | - 计算机图形时代 40 | - 计算机网络时代 41 | - 价值网络时代(区块链技术) 42 | -------------------------------------------------------------------------------- /docs/algorithm/树/二叉搜索树/二叉搜索树的后序遍历序列.md: -------------------------------------------------------------------------------- 1 | [剑指 Offer 33. 二叉搜索树的后序遍历序列](https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/) 2 | 3 | 解法一:递归分治 4 | 5 | ```js 6 | /** 7 | * @param {number[]} postorder 8 | * @return {boolean} 9 | */ 10 | var verifyPostorder = function(postorder) { 11 | function isPostorder(i, j) { 12 | // 说明子树节点数量 <= 1, 无需判断正确性,直接返回true 13 | if (i >= j) { 14 | return true; 15 | } 16 | let p = i; 17 | // 对于区间 [i,j] 找到第一个大于根节点(postorder[j])的节点, 索引记为 m, 18 | // 由此划分为左子树区间 [i, m - 1], [m, j - 1],根节点索引为 j 19 | while (postorder[p] < postorder[j]) { 20 | p++; 21 | } 22 | let m = p; 23 | // 找到第一个小于当前根节点的值,正常来说是遍历到结尾的。即:二叉搜素树右侧不应该出现比根节点小的值,如果出现了,则说明不符合,提前跳出循环 24 | while (postorder[p] > postorder[j]) { 25 | p++; 26 | } 27 | // 如果没遍历到末尾,提前跳出了循环,那么 p !== j,同时判断左子树和右子树是否都符合 28 | return p === j && isPostorder(i, m - 1) && isPostorder(m, j - 1); 29 | } 30 | return isPostorder(0, postorder.length - 1); 31 | }; 32 | ``` 33 | 34 | 解法二:单调栈 35 | 36 | ```js 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/jsCode/README.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | 3 | 通过题目来学习一些 javaScript 基础知识 4 | 5 | ### API 实现 6 | 7 | - [实现一个 new](./实现一个new.md) 8 | - [实现 instanceof](./实现instanceof.md) 9 | - [实现一个 iterator](./实现一个iterator.md) 10 | - [setTimeout 实现 setInterval](./setTimeout实现setInterval.md) 11 | - [写 call,apply,bind](./手写call,apply,bind.md) 12 | - [实现一个 EventEmitter](./发布-订阅模式的实现.md) 13 | 14 | ### 功能函数实现 15 | 16 | - [手写继承](./手写继承.md) 17 | - [实现一个防抖](./防抖.md) 18 | - [手写一个节流](./节流.md) 19 | - [浅比较和深比较](./浅比较和深比较.md) 20 | - [浅拷贝和深拷贝](./浅拷贝和深拷贝.md) 21 | - [数组乱序](./数组乱序.md) 22 | - [函数柯里化](./函数柯里化.md) 23 | 24 | ### 数据结构转换 25 | 26 | ### Promise 异步相关 27 | 28 | - [实现一个 Promise](./实现一个Promise.md) 29 | - [实现一个 async 函数](./实现一个async函数.md) 30 | - [限制并发请求](./限制并发请求.md) 31 | - [实现一个 LazyMan](./LazyMan.md) 32 | - [koa 洋葱模型 compose](./洋葱模型compose) 33 | - [实现一个 repeat 方法](./实现一个repeat方法.md) 34 | 35 | ### 功能实现 36 | 37 | - [简单实现一个 Vue 的双向绑定](./简单实现一个Vue的双向绑定.md) 38 | - [实现一个 vue 自定义指令-懒加载](./实现一个vue自定义指令-懒加载.md) 39 | - [实现一个轮播图](./实现一个轮播图.md) 40 | - [实现一个放大镜效果](./放大镜效果.md) 41 | 42 | (PS:一些代码实现,题目概念解释等参考了他人的文章,已注明来源,仅用于学习总结,如果有侵权,请联系删除) 43 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/接雨水.md: -------------------------------------------------------------------------------- 1 | ## [接雨水](https://leetcode-cn.com/problems/trapping-rain-water/) 2 | 3 | ## 解决方法 4 | 5 | 在一个位置能容下的雨水量等于它左右两边柱子最大高度的最小值减去它的高度 6 | 7 | ![img](https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1557758413176.png) 8 | 9 | 位置 `i`能容下雨水量:`min(3, 1) - 0 = 1` 10 | 所以问题就变成了: 如何找到所有位置的左右两边柱子的最大值 11 | 12 | **双指针法** 13 | 14 | ```js 15 | /** 16 | * @param {number[]} height 17 | * @return {number} 18 | */ 19 | var trap = function(height) { 20 | let result = 0; 21 | // l_max是height[0..left]中最高柱子的高度,r_max是height[right..end]的最高柱子的高度 22 | let leftMax = 0; 23 | let rightMax = 0; 24 | // 双指针,一边遍历,一边更新 leftMax, rightMax 25 | let left = 0; 26 | let right = height.length - 1; 27 | while (left < right) { 28 | leftMax = Math.max(leftMax, height[left]); 29 | rightMax = Math.max(rightMax, height[right]); 30 | if (leftMax < rightMax) { 31 | result += leftMax - height[left]; 32 | left++; 33 | } else { 34 | result += rightMax - height[right]; 35 | right--; 36 | } 37 | } 38 | return result; 39 | }; 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/最长数对链.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。 3 | 4 | 现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。 5 | 6 | 给定一个对数集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。 7 | 8 | 示例 : 9 | ```js 10 | 输入: [[1,2], [2,3], [3,4]] 11 | 输出: 2 12 | 解释: 最长的数对链是 [1,2] -> [3,4] 13 | ``` 14 | 注意: 15 | 16 | 给出数对的个数在 [1, 1000] 范围内。 17 | 18 | 来源:力扣(LeetCode) 19 | 链接:https://leetcode-cn.com/problems/maximum-length-of-pair-chain 20 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 21 | 22 | ## 解题方法 23 | 24 | ```js 25 | /** 26 | * @param {number[][]} pairs 27 | * @return {number} 28 | */ 29 | var findLongestChain = function(pairs) { 30 | pairs.sort((a, b) => a[0] - b[0]); 31 | let dp = new Array(pairs.length).fill(1); 32 | let max = 0; 33 | for(let i = 1; i < pairs.length; i++) { 34 | for(let j = 0; j < i; j++) { 35 | if(pairs[j][1] < pairs[i][0]) { 36 | dp[i] = Math.max(dp[i], dp[j] + 1); 37 | } 38 | } 39 | max = Math.max(max, dp[i]); 40 | } 41 | return max; 42 | }; 43 | ``` 44 | - 时间复杂度: O(n2),需要两重循环 45 | - 空间复杂度: O(n),dp数组占用的空间 -------------------------------------------------------------------------------- /docs/algorithm/动态规划/经典/120.三角形的最小路径和.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | 3 | 给定一个三角形 triangle ,找出自顶向下的最小路径和。 4 | 5 | 每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。 6 | 7 | 示例 1: 8 | 9 | ``` 10 | 输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]] 11 | 输出:11 12 | 解释:如下面简图所示: 13 | 2 14 | 3 4 15 | 6 5 7 16 | 4 1 8 3 17 | 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 18 | ``` 19 | 20 | 示例 2: 21 | 22 | ```js 23 | 输入:triangle = [[-10]] 24 | 输出:-10 25 | ``` 26 | 27 | 来源:力扣(LeetCode) 28 | 链接:https://leetcode.cn/problems/triangle 29 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 30 | 31 | ## 解题思路 32 | 33 | ```js 34 | // @lc code=start 35 | /** 36 | * @param {number[][]} triangle 37 | * @return {number} 38 | */ 39 | var minimumTotal = function(triangle) { 40 | // 从最后一个开始 41 | let mini = triangle[triangle.length - 1]; 42 | for (let i = triangle.length - 2; i >= 0; i--) { 43 | for (let j = 0; j < triangle[i].length; j++) { 44 | // 上一层走下来的最小值 + 值本身 45 | mini[j] = triangle[i][j] + Math.min(mini[j], mini[j + 1]); 46 | } 47 | } 48 | return mini[0]; 49 | }; 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/jsCode/实现instanceof.md: -------------------------------------------------------------------------------- 1 | ## instanceof 2 | 3 | ### `instanceof`的语法: 4 | ```js 5 | object instanceof constructor 6 | // 等同于 7 | constructor.prototype.isPrototypeOf(object) 8 | ``` 9 | - object: 要检测的对象 10 | - constructor:某个构造函数 11 | 12 | ### `instanceof`的代码实现: 13 | 14 | ```js 15 | function instanceof(L, R) { //L是表达式左边,R是表达式右边 16 | const O = R.prototype; 17 | L = L.__proto__; 18 | while(true) { 19 | if (L === null) 20 | return false; 21 | if (L === O) // 这里重点:当 L 严格等于 0 时,返回 true 22 | return true; 23 | L = L.__proto__; 24 | } 25 | } 26 | ``` 27 | 28 | `instanceof`原理: 检测 `constructor.prototype`是否存在于参数 object的 原型链上。`instanceof` 查找的过程中会遍历`object `的原型链,直到找到 `constructor` 的 `prototype` ,如果查找失败,则会返回`false`,告诉我们,`object` 并非是 `constructor` 的实例。 29 | 30 | ## Symbol.hasInstance 31 | 对象的`Symbol.hasInstance`属性,指向一个内部方法。当其他对象使用`instanceof`运算符,判断是否为该对象的实例时,会调用这个方法。比如,`foo instanceof Foo`在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)。 32 | 33 | ```js 34 | class MyClass { 35 | [Symbol.hasInstance](foo) { 36 | return foo instanceof Array; 37 | } 38 | } 39 | 40 | [1, 2, 3] instanceof new MyClass() // true 41 | ``` -------------------------------------------------------------------------------- /docs/book/1.js: -------------------------------------------------------------------------------- 1 | // function* genDemo() { 2 | // console.log('开始执行第一段'); 3 | // yield 'generator 2'; 4 | 5 | // console.log('开始执行第二段'); 6 | // yield 'generator 2'; 7 | 8 | // console.log('开始执行第三段'); 9 | // yield 'generator 2'; 10 | 11 | // console.log('执行结束'); 12 | // return 'generator 2'; 13 | // } 14 | 15 | // console.log('main 0'); 16 | // let gen = genDemo(); 17 | // console.log(gen.next().value); 18 | // console.log('main 1'); 19 | // console.log(gen.next().value); 20 | // console.log('main 2'); 21 | // console.log(gen.next().value); 22 | // console.log('main 3'); 23 | // console.log(gen.next().value); 24 | // console.log('main 4'); 25 | 26 | // const obj = { 27 | // name: 'objName', 28 | // say() { 29 | // console.log(this.name); 30 | // }, 31 | // read: () => { 32 | // console.log(this.name); 33 | // }, 34 | // }; 35 | // obj.say(); 36 | // obj.read(); 37 | 38 | function foo() { 39 | var a = { name: 'lucyStar' }; 40 | var b = a; 41 | a.name = 'litterStar'; 42 | console.log(a); 43 | console.log(b); 44 | } 45 | foo(); 46 | // { name: 'litterStar' }, { name: 'litterStar' } 47 | -------------------------------------------------------------------------------- /docs/.vuepress/menu.js: -------------------------------------------------------------------------------- 1 | function GeneratorMenu(pathPrefix, pathnameList) { 2 | let result = []; 3 | for(let i = 0; i < pathnameList.length; i++) { 4 | result[i] = [`${pathPrefix}${pathnameList[i]}`, pathnameList[i]]; 5 | } 6 | return result; 7 | } 8 | 9 | const linkList = GeneratorMenu('/algorithm/链表/', [ 10 | '合并两个有序链表', 11 | '反转链表', 12 | '回文链表', 13 | '倒数第K个节点', 14 | '找出链表的中间节点', 15 | '两个链表的第一个公共节点', 16 | 'LRU缓存机制', 17 | ]); 18 | const JSList = GeneratorMenu('/interview/JavaScript/', [ 19 | '从JS底层理解var,const,let', 20 | '赋值、浅拷贝、深拷贝区别', 21 | '函数柯里化', 22 | '一文理解this&call&apply&bind', 23 | 'typeof和instanceof原理', 24 | 'setTimeout和requestAnimationFrame', 25 | 'for...of原理解析', 26 | 'Generator函数', 27 | 'async原理解析', 28 | '详解ES6中的class', 29 | '装饰器', 30 | 'JavaScript的几种创建对象的方式', 31 | 'JavaScript的几种继承方式', 32 | '事件循环机制', 33 | ]); 34 | 35 | const VueList = GeneratorMenu('/interview/Vue/', [ 36 | '简单通俗理解vue3.0中的Proxy', 37 | 'keep-alive的实现原理及LRU缓存策略', 38 | 'nextTick的原理及运行机制', 39 | ]); 40 | module.exports = { 41 | linkList, 42 | JSList, 43 | VueList, 44 | } 45 | 46 | -------------------------------------------------------------------------------- /docs/algorithm/回溯/字符串的排列.md: -------------------------------------------------------------------------------- 1 | [剑指 Offer 38. 字符串的排列](https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/) 2 | 输入一个字符串,打印出该字符串中字符的所有排列。 3 | 4 | 你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。 5 | 6 | 示例: 7 | 8 | 输入:s = "abc" 9 | 输出:["abc","acb","bac","bca","cab","cba"] 10 | 11 | ## 题解 12 | 13 | 1. 解法一 14 | 15 | ```js 16 | /** 17 | * @param {string} s 18 | * @return {string[]} 19 | */ 20 | var permutation = function(s) { 21 | if (s.length === 0) { 22 | return ['']; 23 | } 24 | if (s.length === 1) { 25 | return [s]; 26 | } 27 | const res = []; 28 | const len = s.length; 29 | for (let i = 0; i < len; i++) { 30 | const char = s[i]; 31 | // newStr=去掉char后剩下的字符 32 | let newStr = s.slice(0, i) + slice(i + 1); 33 | // 递归产生newStr的全排列 34 | const next = permutation(newStr); 35 | next.forEach((item) => { 36 | res.push(char + item); 37 | }); 38 | } 39 | // 去重 40 | return [...new Set(res)]; 41 | }; 42 | ``` 43 | 44 | 2. 解法二:回溯 45 | 46 | ```js 47 | ``` 48 | 49 | [](https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/solution/tu-jie-hui-su-suan-fa-de-tong-yong-jie-t-kkri/) 50 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/最大子序和.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | JavaScript实现LeetCode第53题:最大子序和 4 | 5 | ## 题目描述 6 | 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 7 | 示例: 8 | ```js 9 | 10 | 输入: [-2,1,-3,4,-1,2,1,-5,4], 11 | 输出: 6 12 | 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 13 | ``` 14 | 15 | ## 解题思路 16 | 这是一道动态规划类的题目; 17 | 声明两个变量, `currentSum`: 之前连续几个值相加的和, `maxSum`: 当前最大的子序列和 18 | - 1.比较`nums[i]`,跟`currentSum`加上`nums[i]`后的值, 将较大的赋值给`currentSum`;此时计算的是用上`nums[i]`的最大的子序列和,但不一定是整个过程中的最大子序和,需要第二步骤的判断; 19 | 举例说明 : 20 | [1, 2, -1], 计算到第三个值是, `currentSum`为1+2-1=2, 但是在计算到第二个数的时候,它才是最大子序和,1 + 2 = 3, 所以需要声明一个变量`maxSum`, 来计算是否是当前最大的子序列和 21 | - 2.比较`currentSum`, `maxSum`, 将较大的值赋值给`maxSum` 22 | 23 | ## 代码实现 24 | ```js 25 | /** 26 | * @param {number[]} nums 27 | * @return {number} 28 | */ 29 | var maxSubArray = function(nums) { 30 | let maxSum = nums[0]; 31 | let currentSum = nums[0]; 32 | for(let i = 1; i < nums.length; i++) { 33 | // 比较当前的和跟加上nums[i]后的值, 将较大的赋值给currentSum 34 | currentSum = Math.max(nums[i], currentSum+=nums[i]); 35 | // 比较currentSum, maxSum, 将较大的值赋值给maxSum 36 | maxSum = Math.max(currentSum, maxSum); 37 | } 38 | return maxSum; 39 | }; 40 | 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/algorithm/其他/算法题分类.md: -------------------------------------------------------------------------------- 1 | ## 经典题型及对应的经典题目 2 | 3 | ### 回溯 4 | 5 | - 全排列 6 | 7 | ## 滑动窗口 8 | 9 | - 239. 滑动窗口最大值 10 | - 3. 无重复字符的最长子串 11 | - 567. 字符串的排列 12 | - 76. 最小覆盖子串 13 | - 438. 找到字符串中所有字母异位词 14 | 15 | ### 排序算法 16 | 17 | - 冒泡 18 | - 快排 19 | - 归并 20 | - 堆排序 21 | - 计数排序 22 | 23 | ### TopK 问题: 24 | 25 | 使用堆排序 26 | 27 | - 最小 K 个数 28 | - 前 K 个高频元素 29 | - 第 K 个最小元素 30 | 31 | ### 优先队列 32 | 33 | - 返回数据流中的第 K 大元素 34 | - 返回滑动窗口中的最大值 35 | 36 | ### LRU 缓存 37 | 38 | ### 合并区间问题 39 | 40 | ### 二分法: 41 | 42 | - 查找对应的元素 43 | - 查找左侧的元素 44 | - 查找右侧的元素 45 | 46 | ### 二叉搜索树相关 47 | 48 | ### 动态规划 49 | 50 | - 最长公共子序列 51 | - 最长递增子序列 52 | - 编辑距离 53 | 54 | 算法 - Algorithms 55 | 56 | - 排序算法:快速排序、归并排序、计数排序 57 | - 搜索算法:回溯、递归、剪枝技巧 58 | - 图论:最短路、最小生成树、网络流建模 59 | - 动态规划:背包问题、最长子序列、计数问题 60 | - 基础技巧:分治、倍增、二分、贪心 61 | 62 | 数据结构 - Data Structures 63 | 64 | - 数组与链表:单 / 双向链表、跳舞链 65 | - 栈与队列 66 | - 树与图:最近公共祖先、并查集 67 | - 哈希表 68 | - 堆:大 / 小根堆、可并堆 69 | - 字符串:字典树、后缀树 70 | 71 | 作者:力扣 (LeetCode) 72 | 链接:https://leetcode.cn/leetbook/read/top-interview-questions/xm8xw2/ 73 | 来源:力扣(LeetCode) 74 | 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 75 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/经典/322.零钱兑换.md: -------------------------------------------------------------------------------- 1 | ## 322. 零钱兑换 2 | 3 | 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 4 | 5 | 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回  -1 。 6 | 7 | 你可以认为每种硬币的数量是无限的。 8 | 9 | 示例  1: 10 | 11 | ```js 12 | 输入:coins = [1, 2, 5], amount = 11 13 | 输出:3 14 | 解释:11 = 5 + 5 + 1 15 | ``` 16 | 17 | 示例 2: 18 | 19 | ```js 20 | 输入:coins = [2], amount = 3 21 | 输出:-1 22 | ``` 23 | 24 | 示例 3: 25 | 26 | ```js 27 | 输入:coins = [1], amount = 0 28 | 输出:0 29 | ``` 30 | 31 | 来源:力扣(LeetCode) 32 | 链接:https://leetcode.cn/problems/coin-change 33 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 34 | 35 | ## 使用动态规划 36 | 37 | ```js 38 | /** 39 | * @param {number[]} coins 40 | * @param {number} amount 41 | * @return {number} 42 | */ 43 | var coinChange = function(coins, amount) { 44 | // dp[i]表示组成金额 i 所需最少的硬币数量 45 | let dp = new Array(amount + 1).fill(amount + 1); 46 | dp[0] = 0; 47 | for (let i = 1; i <= amount; i++) { 48 | for (let j = 0; j < coins.length; j++) { 49 | if (coins[j] <= i) { 50 | dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); 51 | } 52 | } 53 | } 54 | return dp[amount] > amount ? -1 : dp[amount]; 55 | }; 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/interview/JavaScript/JS数字精度问题.md: -------------------------------------------------------------------------------- 1 | ## JavaScript 中精度问题以及解决方案 2 | 3 | 1. 小数加法 4 | 5 | ```js 6 | 0.1 + 0.2 = 0.30000000000000004; 7 | ``` 8 | 9 | 2. 大数精度问题,比如 9999 9999 9999 9999 == 1000 0000 0000 0000 1 10 | 11 | 3. toFixed 四舍五入结果不准确,比如 1.335.toFixed(2) == 1.33 12 | 13 | 浮点数精度和 toFixed 其实属于同一类问题,都是由于浮点数无法精确表示引起的,如下: 14 | 15 | ## 原因 16 | 17 | 在计算机角度,计算机算的是二进制,而不是十进制。二进制后变成了无线不循环的数,而计算机可支持浮点数的小数部分可支持到 52 位,所有两者相加,在转换成十进制,得到的数就不准确了, 18 | 19 | ## 解法 20 | 21 | 1. 可以对结果进行指定精度的四舍五入 22 | 通过 toFixed(num) 方法来保留小数。因为这个方法是根据四舍五入来保留小数的,所以最后的计算结果不精确。 23 | 24 | ```js 25 | (1.35).toFixed(1); // 1.4 正确 26 | (1.335).toFixed(2); // 1.33 错误 27 | (1.3335).toFixed(3); // 1.333 错误 28 | (1.33335).toFixed(4); // 1.3334 正确 29 | (1.333335).toFixed(5); // 1.33333 错误 30 | (1.3333335).toFixed(6); // 1.333333 错误 31 | ``` 32 | 33 | toFixed()方法可把 Number 四舍五入为指定小数位数的数字。 34 | 35 | 2. 将浮点数转为整数运算,再对结果做除法。比如 0.1 + 0.2,可以转化为`(1*2)/10`。 36 | 3. 把浮点数转化为字符串,模拟实际运算的过程 37 | 38 | ### 三方库 39 | 40 | - [number-precision](https://github.com/nefe/number-precision) 41 | - [bignumber](https://github.com/MikeMcl/bignumber.js) 42 | - [decimal](https://github.com/MikeMcl/decimal.js) 43 | - [big](https://github.com/MikeMcl/big.js) 44 | -------------------------------------------------------------------------------- /docs/interview/Node/koa的中间件.md: -------------------------------------------------------------------------------- 1 | ## Koa-bodyparser 2 | 3 | bodyparser 是一类处理 request 的 body 的中间件函数,例如 Koa-bodyparser 就是和 Koa 框架搭配使用的中间件,帮助没有内置处理该功能的 Koa 框架提供解析 request.body 的方法,通过 app.use 加载 Koa-bodyparser 后,在 Koa 中就可以通过 ctx.request.body 访问到请求报文的报文实体啦! 4 | 5 | 要编写 body-parser 的代码,首先要了解两个方面的逻辑:请求相关事件和数据处理流程 6 | 7 | ### 请求相关事件 8 | 9 | - data 事件:当 request 接收到数据的时候触发,在数据传输结束前可能会触发多次,在事件回调里可以接收到 Buffer 类型的数据参数,我们可以将 Buffer 数据对象收集到数组里 10 | - end 事件:请求数据接收结束时候触发,不提供参数,我们可以在这里将之前收集的 Buffer 数组集中处理,最后输出将 request.body 输出。 11 | 12 | ### 数据处理流程 13 | 14 | - 在 request 的 data 事件触发时候,收集 Buffer 对象,将其放到一个命名为 chunks 的数组中 15 | - 在 request 的 end 事件触发时,通过 Buffer.concat(chunks)将 Buffer 数组整合成单一的大的 Buffer 对象 16 | - 解析请求首部的 Content-Encoding,根据类型,如 gzip,deflate 等调用相应的解压缩函数如 Zlib.gunzip,将 2 中得到的 Buffer 解压,返回的是解压后的 Buffer 对象 17 | - 解析请求的 charset 字符编码,根据其类型,如 gbk 或者 utf-8,调用 iconv 库提供的 decode(buffer, charset)方法,根据字符编码将 3 中的 Buffer 转换成字符串 18 | - 最后,根据 Content-Type,如 application/json 或'application/x-www-form-urlencoded'对 4 中得到的字符串做相应的解析处理,得到最后的对象,作为 request.body 返回 19 | 20 | - [bodyparser 实现原理解析](https://zhuanlan.zhihu.com/p/78482006) 21 | - [玩转 Koa -- koa-bodyparser 原理解析](https://juejin.cn/post/6844903761966530568) 22 | -------------------------------------------------------------------------------- /docs/algorithm/树/二叉搜索树/将有序数组转换为二叉搜索树.md: -------------------------------------------------------------------------------- 1 | # JavaScript实现LeetCode第108题:将有序数组转换为二叉搜索树 2 | ## 题目描述 3 | 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 4 | 5 | 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 6 | 7 | 示例: 8 | ```js 9 | 10 | 给定有序数组: [-10,-3,0,5,9], 11 | 12 | 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 13 | 14 | 0 15 | / \ 16 | -3 9 17 | / / 18 | -10 5 19 | ``` 20 | ## 思路 21 | 1.如果nums长度为0,返回null 22 | 2.如果nums长度为1,返回一个节点 23 | 3.如果nums长度大于1,首先以中点作为根节点,然后将两边的数组作为左右子树。 24 | ## 解决方案 25 | ```js 26 | /** 27 | * Definition for a binary tree node. 28 | * function TreeNode(val) { 29 | * this.val = val; 30 | * this.left = this.right = null; 31 | * } 32 | */ 33 | /** 34 | * @param {number[]} nums 35 | * @return {TreeNode} 36 | */ 37 | var sortedArrayToBST = function(nums) { 38 | if(nums.length === 0) { 39 | return null; 40 | } 41 | if(nums.length === 1) { 42 | return new TreeNode(nums[0]); 43 | } 44 | const mid = parseInt(nums.length / 2); 45 | let root = new TreeNode(nums[mid]); 46 | root.left = sortedArrayToBST(nums.slice(0, mid)); 47 | root.right = sortedArrayToBST(nums.slice(mid + 1)); 48 | return root; 49 | }; 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /docs/algorithm/链表/反转链表/反转链表.md: -------------------------------------------------------------------------------- 1 | JavaScript 实现 LeetCode 第 206 题: 反转链表 2 | 3 | ## 题目描述 4 | 5 | 反转一个单链表。 6 | 7 | 示例: 8 | 9 | ```js 10 | 输入: 1->2->3->4->5->NULL 11 | 输出: 5->4->3->2->1->NULL 12 | ``` 13 | 14 | ## 解题思路 15 | 16 | 使用迭代 17 | 18 | 在遍历列表时,将当前节点的 next 指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个元素。在更改引用之前,还需要另一个指针来存储下一个节点。不要忘记在最后返回新的头引用。 19 | 20 | ## 解题方法 21 | 22 | ```js 23 | /** 24 | * Definition for singly-linked list. 25 | * function ListNode(val) { 26 | * this.val = val; 27 | * this.next = null; 28 | * } 29 | */ 30 | /** 31 | * @param {ListNode} head 32 | * @return {ListNode} 33 | */ 34 | var reverseList = function(head) { 35 | let prevNode = null; // 前指针节点 36 | //每次循环,都将当前节点指向它前面的节点,然后当前节点和前节点后移 37 | while (head != null) { 38 | let tempNode = head.next; //临时节点,暂存当前节点的下一节点,用于后移 39 | head.next = prevNode; //将当前节点指向它前面的节点 40 | prevNode = head; //前指针后移 41 | head = tempNode; //当前指针后移 42 | } 43 | return prevNode; 44 | }; 45 | ``` 46 | 47 | ## 复杂度分析 48 | 49 | - 时间复杂度:O(n),假设 n 是列表的长度,时间复杂度是 O(n)。 50 | - 空间复杂度:O(1)。 51 | 52 | ## 参考 53 | 54 | [官方题解](https://leetcode-cn.com/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode/) 55 | -------------------------------------------------------------------------------- /docs/algorithm/双指针/二分查找.md: -------------------------------------------------------------------------------- 1 | 2 | ## 题目描述 3 | 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 4 | 5 | 6 | 示例 1: 7 | ```js 8 | 输入: nums = [-1,0,3,5,9,12], target = 9 9 | 输出: 4 10 | 解释: 9 出现在 nums 中并且下标为 4 11 | ``` 12 | 示例 2: 13 | ```js 14 | 输入: nums = [-1,0,3,5,9,12], target = 2 15 | 输出: -1 16 | 解释: 2 不存在 nums 中因此返回 -1 17 | ``` 18 | 19 | 提示: 20 | 21 | 1. 你可以假设 nums 中的所有元素是不重复的。 22 | 2. n 将在 [1, 10000]之间。 23 | 3. nums 的每个元素都将在 [-9999, 9999]之间 24 | 25 | ## 解题方法 26 | 1. 选择数组中的中间值 27 | 2. 如果选中值是待搜索值, 那么算法执行完毕 28 | 3. 如果待搜索值比选中值要小, 则返回步骤1并在选中值左边的子数组中寻找 29 | 4. 如果待搜索值比选中值要大, 则返回步骤1并在选中值右边的子数组中寻找 30 | 31 | ```js 32 | /** 33 | * @param {number[]} nums 34 | * @param {number} target 35 | * @return {number} 36 | */ 37 | var search = function(nums, target) { 38 | let low = 0; 39 | let high = nums.length - 1; 40 | while(low <= high) { 41 | const mid = Math.floor((low + high)/2); 42 | const element = nums[mid]; 43 | 44 | if(element < target) { 45 | low = mid + 1; 46 | } else if(element > target) { 47 | high = mid - 1; 48 | } else { 49 | return mid; 50 | } 51 | } 52 | return -1; 53 | }; 54 | ``` -------------------------------------------------------------------------------- /docs/algorithm/树/翻转二叉树.md: -------------------------------------------------------------------------------- 1 | ## 翻转二叉树 2 | ### 题目描述 3 | 翻转一棵二叉树。 4 | 5 | 示例: 6 | ```js 7 | 输入: 8 | 9 | 4 10 | / \ 11 | 2 7 12 | / \ / \ 13 | 1 3 6 9 14 | 输出: 15 | 16 | 4 17 | / \ 18 | 7 2 19 | / \ / \ 20 | 9 6 3 1 21 | ``` 22 | ### 解题思路 23 | 24 | 递归的交换左右子树。 25 | 1. 当节点为 null 的时候直接返回 26 | 2. 如果当前结点不为null,那么先将其左右子树进行翻转,然后交换左右子树。 27 | 3. 返回值为完成了翻转后的当前结点。 28 | ```js 29 | /** 30 | * Definition for a binary tree node. 31 | * function TreeNode(val) { 32 | * this.val = val; 33 | * this.left = this.right = null; 34 | * } 35 | */ 36 | /** 37 | * @param {TreeNode} root 38 | * @return {TreeNode} 39 | */ 40 | var invertTree = function(root) { 41 | // 当节点为 null 的时候直接返回 42 | if(root === null) { 43 | return root; 44 | } 45 | // 如果当前结点不为null,那么先将其左右子树进行翻转,然后交换左右子树 46 | let right = invertTree(root.right); 47 | let left = invertTree(root.left); 48 | root.left = right; 49 | root.right = left; 50 | 51 | // 返回值为完成了翻转后的当前结点 52 | return root; 53 | }; 54 | ``` 55 | - 时间复杂度:O(n),既然树中的每个节点都只被访问一次,那么时间复杂度就是 O(n),其中 n 是树中节点的个数。在反转之前,不论怎样我们至少都得访问每个节点至少一次,因此这个问题无法做地比 O(n) 更好了。 56 | - 本方法使用了递归,在最坏情况下栈内需要存放 O(h) 个方法调用,其中 h 是树的高度。由于 h $\in$ O(n),可得出空间复杂度为 O(n)。 -------------------------------------------------------------------------------- /docs/algorithm/数据结构/数组转二叉树.md: -------------------------------------------------------------------------------- 1 | ```js 2 | function arrayToTree(arr) { 3 | function TreeNode(val) { 4 | this.val = val; 5 | this.left = this.right = null; 6 | } 7 | if (arr.length === 0) { 8 | return null; 9 | } 10 | const root = new TreeNode(arr[0]); 11 | const list = [root]; 12 | let i = 1; 13 | while (list.length > 0) { 14 | const node = list.shift(); 15 | if (typeof arr[i] === 'number') { 16 | node.left = new TreeNode(arr[i]); 17 | list.push(node.left); 18 | } 19 | i++; 20 | if (typeof arr[i] === 'number') { 21 | node.right = new TreeNode(arr[i]); 22 | list.push(node.right); 23 | } 24 | i++; 25 | } 26 | return root; 27 | } 28 | 29 | function treeToArray(root) { 30 | if (!root) { 31 | return []; 32 | } 33 | const list = [root]; 34 | const result = []; 35 | while (list.length > 0 && list.some((l) => l)) { 36 | const node = list.shift(); 37 | result.push(node && node.val); 38 | if (node) { 39 | list.push(node.left || null); 40 | list.push(node.right || null); 41 | } 42 | } 43 | return result; 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/algorithm/BFS/index.md: -------------------------------------------------------------------------------- 1 | BFS 核心思想: 2 | 使用【队列】这种数据结构,每次将一个节点周围的所有节点加入队列 3 | 4 | BFS 和 DFS 的主要区别:BFS 找到的路径一定是最短的,但代价就是空间复杂度可能比 DFS 大很多。 5 | 6 | BFS: 本质上就是一幅「图」,让你从一个起点,走到终点,问最短路径 7 | BFS 的框架 8 | 9 | ```js 10 | // 计算从起点 start 到终点 target 的最近距离 11 | function BFS( start, target) { 12 | let queue = []; // 核心数据结构 13 | let visited = new Set(); // 避免走回头路 14 | 15 | queue.push(start); // 将起点加入队列 16 | visited.add(start); 17 | let step = 0; // 记录扩散的步数 18 | 19 | while (queue.length !== 0) { 20 | let sz = queue.length; 21 | /* 将当前队列中的所有节点向四周扩散 */ 22 | for (let i = 0; i < sz; i++) { 23 | let cur = queue.shift(); 24 | /* 划重点:这里判断是否到达终点 */ 25 | if (cur === target) 26 | return step; 27 | /* 将 cur 的相邻节点加入队列 */ 28 | for (let x in cur.adj()) 29 | if (x not in visited) { 30 | quque.push(x); 31 | visited.add(x); 32 | } 33 | } 34 | /* 划重点:更新步数在这里 */ 35 | step++; 36 | } 37 | } 38 | ``` 39 | 40 | - 队列 queue: BFS 的核心数据结构 41 | - cur.adj()泛指 cur 相邻的节点,比如说二维数组中,cur 上下左右四面的位置就是相邻节点; 42 | - visited 的主要作用是防止走回头路,大部分时候都是必须的,但是像一般的二叉树结构,没有子节点到父节点的指针,不会走回头路就不需要 visited。 43 | -------------------------------------------------------------------------------- /docs/interview/React/自定义hook.md: -------------------------------------------------------------------------------- 1 | ## 实现一个 usePrevious 2 | ```js 3 | import { useState, useEffect, useRef } from 'react'; 4 | 5 | // Usage 6 | function App() { 7 | // State value and setter for our example 8 | const [count, setCount] = useState(0); 9 | 10 | // Get the previous value (was passed into hook on last render) 11 | const prevCount = usePrevious(count); 12 | 13 | // Display both current and previous count value 14 | return ( 15 |
16 |

Now: {count}, before: {prevCount}

17 | 18 |
19 | ); 20 | } 21 | 22 | // Hook 23 | function usePrevious(value) { 24 | // The ref object is a generic container whose current property is mutable ... 25 | // ... and can hold any value, similar to an instance property on a class 26 | const ref = useRef(); 27 | 28 | // Store current value in ref 29 | useEffect(() => { 30 | ref.current = value; 31 | }, [value]); // Only re-run if value changes 32 | 33 | // Return previous value (happens before update in useEffect above) 34 | return ref.current; 35 | } 36 | ``` 37 | 38 | 39 | 40 | ## 强烈推荐阅读 41 | - [react-use](https://github.com/streamich/react-use): 常见的自定义hooks基本都有了,每个hooks代码都是单独的文件,很容易理解 -------------------------------------------------------------------------------- /docs/algorithm/数组/数组的交集.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定两个数组,编写一个函数来计算它们的交集。 3 | 4 | 示例 1: 5 | ```js 6 | 输入: nums1 = [1,2,2,1], nums2 = [2,2] 7 | 输出: [2] 8 | ``` 9 | 示例 2: 10 | ```js 11 | 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 12 | 输出: [9,4] 13 | ``` 14 | 说明: 15 | - 输出结果中的每个元素一定是唯一的。 16 | - 我们可以不考虑输出结果的顺序。 17 | 18 | ## 解题思路 19 | 幼稚的方法是根据第一个数组 nums1 迭代并检查每个值是否存在在 nums2 内。如果存在将值添加到输出。这样的方法会导致 O(nxm) 的时间复杂性,其中 n 和 m 是数组的长度。 20 | 21 | 22 | 使用set 来实现线性时间复杂度 23 | 24 | ## 解法方法 25 | 26 | ```js 27 | /** 28 | * @param {number[]} nums1 29 | * @param {number[]} nums2 30 | * @return {number[]} 31 | */ 32 | var intersection = function(nums1, nums2) { 33 | // 使用 set 来存储结果 34 | const result = new Set(); 35 | const set2 = new Set(nums2); 36 | for(let i = 0; i < nums1.length; i++) { 37 | // set查找的时间复杂度为 O(1) 38 | if(set2.has(nums1[i])) { 39 | result.add(nums1[i]); 40 | } 41 | } 42 | // 最后需要将set转成数组 43 | return Array.from(result); 44 | }; 45 | ``` 46 | 47 | ## 复杂度分析 48 | - 时间复杂度是: O(n),实际为( m + n),m为nums1的个数,n为 set2 (PS: 系数可以忽略) 49 | - 空间复杂度: O(n),我们忽略存储答案所使用的空间,因为它对算法本身并不重要。 50 | 51 | ## 参考 52 | [LeetCode第349题:两个数组的交集题解](https://leetcode-cn.com/problems/intersection-of-two-arrays/solution/liang-ge-shu-zu-de-jiao-ji-by-leetcode/) 53 | 54 | -------------------------------------------------------------------------------- /docs/algorithm/二分查找/鸡蛋掉落.md: -------------------------------------------------------------------------------- 1 | ## 887. 鸡蛋掉落 2 | 3 | 给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。 4 | 5 | 已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的楼层落下的鸡蛋都不会破。 6 | 7 | 每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。 8 | 9 | 请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少? 10 | 11 | 示例 1: 12 | 13 | 输入:k = 1, n = 2 14 | 输出:2 15 | 解释: 16 | 鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0 。 17 | 否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1 。 18 | 如果它没碎,那么肯定能得出 f = 2 。 19 | 因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。 20 | 示例 2: 21 | 22 | 输入:k = 2, n = 6 23 | 输出:3 24 | 25 | ## 代码 26 | 27 | 动态规划+二分查找 28 | 29 | ```js 30 | /** 31 | * @param {number} k 32 | * @param {number} n 33 | * @return {number} 34 | */ 35 | var superEggDrop = function(k, n) { 36 | // dp[i][j]表示一共j个鸡蛋,从i层扔下去 37 | let dp = new Array(k + 1).fill(0).map(() => new Array(n + 1).fill(0)); 38 | let j = 0; 39 | while (dp[k][j] < n) { 40 | j++; 41 | for (let i = 1; i <= k; i++) { 42 | // 状态:dp[i][j] 有i个鸡蛋,j次扔鸡蛋的测得的最多楼层 43 | // 转移方程:dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] + 1 44 | // dp[i - 1][j - 1] 表示碎了,dp[i - 1][j],表示没碎 45 | dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1] + 1; 46 | } 47 | } 48 | return j; 49 | }; 50 | ``` 51 | 52 | []() 53 | -------------------------------------------------------------------------------- /docs/menu/术语表.md: -------------------------------------------------------------------------------- 1 | ## 概念 2 | 3 | - 主机 4 | - 云主机 5 | - 虚拟主机 6 | 7 | ## 业务概念 8 | 9 | ### docker 10 | 11 | - 容器 12 | - 镜像 13 | - 仓库 14 | 15 | 如何使用别人的镜像 16 | 如何将自己的项目打包成 docker 镜像 17 | 18 | 虚拟机 19 | 操作系统 20 | 21 | ## 计算机 22 | 23 | - 操作系统 24 | 一个物理机有 CPU,内存,硬盘,网络组成,操作系统是一个大管家,负责资源分配。将硬件资源分配给不同的用户程序使用,在适当的时间把资源拿回来,再分配给其他用户进程。 25 | 26 | 进程管理,内存管理,文件系统,输入和输出设备管理;进程间通信,网络通信;虚拟化,容器化,linux 集群 27 | 28 | ## 计算机网络 29 | 30 | - 网关:转发其他服务器通信数据的服务器。从一个房间走到另一个房间,必然要经过一扇门。同样,从一个网络向另一个网络发送信息,也必须经过一道“关口”,这道关口就是网关。顾名思义,网关(Gateway)就是一个网络连接到另一个网络的“关口”。 31 | - 路由 32 | - 路由器 33 | - 子网掩码 34 | 35 | ### DNS 36 | 37 | DNS 记录值 38 | A 39 | CNAME 40 | 41 | CDN 42 | 43 | ## 容器 44 | 45 | - docker 46 | - k8s 47 | 48 | ## 登录相关 49 | 50 | - 鉴权 51 | - 认证 52 | - OAuth 2.0 53 | [理解 OAuth 2.0](https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html) 54 | - SSO 55 | - OAuth 2.0 和 SSOd 56 | - 数字签名 57 | 和平时手写签名的概念和作用是一样的,都是用于证明你的身份,且对所签署的文件和数据进行确认授权。 58 | - 凭证 59 | 60 | ## OAuth 2.0 61 | 62 | [OAuth 2.0 的一个简单解释](https://www.ruanyifeng.com/blog/2019/04/oauth_design.html) 63 | [OAuth 2.0 的四种方式](https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html) 64 | [GitHub OAuth 第三方登录示例教程](https://www.ruanyifeng.com/blog/2019/04/github-oauth.html) 65 | 66 | ### 授权机制 67 | 68 | ### 颁发令牌 69 | -------------------------------------------------------------------------------- /docs/jsCode/浅比较和深比较.md: -------------------------------------------------------------------------------- 1 | ## 浅比较 2 | 3 | 浅比较也称引用相等。在 javascript 中 `===` 是做浅比较,只检查左右两边的对象是否指向同一个引用。 4 | 5 | ```js 6 | const a = { x: 1 }; 7 | const b = { x: 1 }; 8 | console.log(a === b); 9 | // false 10 | ``` 11 | 12 | 此时 a 和 b 指向的是不同的对象引用,所以打印值为 false 13 | 14 | ```js 15 | const a = { x: 1 }; 16 | const b = a; 17 | console.log(a === b); 18 | // true 19 | ``` 20 | 21 | 此时 a 和 b 指向的是同一个对象的引用,所以打印值为 true 22 | 23 | ## 深比较 24 | 25 | 深比较会检查两个对象的所有属性是否都相等,需要用递归的方式遍历两个对象的所有属性,不管对象是不是同一对象的引用 26 | 27 | ## 比较两个对象是否相等 28 | 29 | ```js 30 | var deepEqual = function(x, y) { 31 | // 指向同一内存 32 | if (x === y) { 33 | return true; 34 | } 35 | // 判断是否是对象 36 | if (typeof x === 'object' && x != null && typeof y === 'object' && y != null) { 37 | // 判断对象属性的个数是否相等 38 | if (Object.keys(x).length !== Object.keys(y).length) { 39 | return false; 40 | } 41 | for (let prop in x) { 42 | if (y.hasOwnProperty(prop)) { 43 | if (!deepEqual(x[prop], y[prop])) { 44 | return false; 45 | } 46 | } else { 47 | return false; 48 | } 49 | } 50 | return true; 51 | } else { 52 | // 不是对象且不满足 === 则直接返回false 53 | return false; 54 | } 55 | }; 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/algorithm/栈和队列/225. 用队列实现栈.md: -------------------------------------------------------------------------------- 1 | [225. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/) 2 | 3 | ```js 4 | var MyStack = function() { 5 | // 存储结果 6 | this.queue = []; 7 | // 用来辅助 8 | this.queue1 = []; 9 | }; 10 | 11 | /** 12 | * @param {number} x 13 | * @return {void} 14 | */ 15 | MyStack.prototype.push = function(x) { 16 | if (this.queue.length === 0) { 17 | this.queue.push(x); 18 | } else { 19 | this.queue1.push(x); 20 | while (this.queue.length) { 21 | this.queue1.push(this.queue.shift()); 22 | } 23 | this.queue = this.queue1.slice(); 24 | this.queue1 = []; 25 | } 26 | }; 27 | 28 | /** 29 | * @return {number} 30 | */ 31 | MyStack.prototype.pop = function() { 32 | return this.queue.shift(); 33 | }; 34 | 35 | /** 36 | * @return {number} 37 | */ 38 | MyStack.prototype.top = function() { 39 | return this.queue.length && this.queue[0]; 40 | }; 41 | 42 | /** 43 | * @return {boolean} 44 | */ 45 | MyStack.prototype.empty = function() { 46 | return this.queue.length === 0; 47 | }; 48 | 49 | /** 50 | * Your MyStack object will be instantiated and called as such: 51 | * var obj = new MyStack() 52 | * obj.push(x) 53 | * var param_2 = obj.pop() 54 | * var param_3 = obj.top() 55 | * var param_4 = obj.empty() 56 | */ 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/algorithm/树/对称二叉树.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给定一个二叉树,检查它是否是镜像对称的。 4 | ```js 5 | 6 | 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 7 | 8 | 1 9 | / \ 10 | 2 2 11 | / \ / \ 12 | 3 4 4 3 13 |   14 | 15 | 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 16 | 17 | 1 18 | / \ 19 | 2 2 20 | \ \ 21 | 3 3 22 |   23 | ``` 24 | 进阶: 25 | 26 | 你可以运用递归和迭代两种方法解决这个问题吗? 27 | 28 | ## 思路分析 29 | 30 | 这个问题可以转化为 两个树在什么情况下互为镜像? 31 | 32 | 如果同时满足下面的条件,两个树互为镜像: 33 | 1. 它们的两个根结点具有相同的值。 34 | 2. 每个树的右子树都与另一个树的左子树镜像对称。 35 | 36 | ```js 37 | /** 38 | * Definition for a binary tree node. 39 | * function TreeNode(val) { 40 | * this.val = val; 41 | * this.left = this.right = null; 42 | * } 43 | */ 44 | /** 45 | * @param {TreeNode} root 46 | * @return {boolean} 47 | */ 48 | var isSymmetric = function(root) { 49 | function isMirror(p, q) { 50 | if(p == null && q == null) { 51 | return true; 52 | } 53 | if(p == null || q == null) { 54 | return false; 55 | } 56 | if(p.val !== q.val) { 57 | return false; 58 | } 59 | return isMirror(p.left, q.right) && isMirror(p.right, q.left); 60 | } 61 | return isMirror(root, root); 62 | }; 63 | ``` 64 | 65 | ## 复杂度分析 66 | - 时间复杂度:O(n),因为我们遍历整个输入树一次,所以总的运行时间为 O(n),其中 n 是树中结点的总数。 67 | - 空间复杂度:递归调用的次数受树的高度限制。在最糟糕情况下,树是线性的,其高度为 O(n)。因此,在最糟糕的情况下,由栈上的递归调用造成的空间复杂度为 O(n)。 -------------------------------------------------------------------------------- /docs/interview/HTTP/浏览器缓存机制.md: -------------------------------------------------------------------------------- 1 | ### 1,Expires策略 2 | - Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。 3 | ### 2,Cache-Control策略 4 | - 控制浏览器是否直接从浏览器缓读取数据还是重新发请求到服务器取数据,优先级高于Expires。 5 | ### 3,http协议几个参数的作用 6 | 1,Last-Modified/If-Modified-Since 配合Cache-Control 7 | - Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间(这个参数是和Cache-Control一起过来的)。 8 | - If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since ,则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。 9 | 10 | 2,ETag/If-None-Match 配合Cache-Control 11 | - Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。 12 | - If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match(Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304。 13 | 14 | 3,ETag和Last-Modified 15 | - HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题: 16 | 17 | Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间 18 | 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存 19 | 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形 20 | Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。 -------------------------------------------------------------------------------- /docs/jsCode/防抖.md: -------------------------------------------------------------------------------- 1 | ## 函数防抖(debounce) 2 | 防抖:不管事件触发频率多高,一定**在事件触发 n 秒后才执行**,如果在一个事件执行的 n秒内又触发了这个事件,就以新的事件的时间为准,n秒后才执行,总之,触发完事件 n 秒内不再触发事件,n秒后再执行。 3 | 4 | 思路: 5 | 1. 返回一个函数; 6 | 2. 每次触发事件时都取消之前的定时器 7 | 8 | 需要注意问题: 9 | 1. this指向 10 | 2. 参数的传递 11 | 3. 是否要立即调用一次 12 | 13 | ```js 14 | function debounce(fn, wait, immediate) { 15 | let timer = null; 16 | // 返回一个函数 17 | return function(...args) { 18 | // 每次触发事件时都取消之前的定时器 19 | clearTimeout(timer); 20 | // 判断是否要立即执行一次 21 | if(immediate && !timer) { 22 | fn.apply(this, args); 23 | } 24 | // setTimeout中使用箭头函数,就是让 this指向 返回的该闭包函数,而不是 debounce函数的调用者 25 | timer = setTimeout(() => { 26 | fn.apply(this, args) 27 | }, wait) 28 | } 29 | } 30 | ``` 31 | 通过闭包保存一个标记(timer)来保存setTimeout返回的值, 每当要触发函数的时候, 需要先把上一个setTimeout清除掉, 然后再创建一个新的setTimeout, 这样就能保证执行函数后的 wait 间隔内如果还要触发函数, 就不会执行fn 32 | 33 | ### 使用场景 34 | 1. 监听resize或scroll,执行一些业务处理逻辑 35 | ```js 36 | window.addEventListener('resize', debounce(handleResize, 200)); 37 | window.addEventListener('scroll', debounce(handleScroll, 200)); 38 | ``` 39 | window 的 resize、scroll, mousedown、mousemove, keyup、keydown等高频触发的事件 40 | 41 | 2. 搜索输入框,在输入后200毫秒搜索 42 | ```js 43 | debounce(fetchSearchData, 200); 44 | ``` 45 | 可以这样去理解记忆:函数防抖是 **在事件触发 n 秒后才执行**,在监听 scroll事件和 resize 事件时,只要 n 秒后才执行一次就可以了,不需要每次只要一触发 `scroll`或 `resize`的时候就执行,n秒内的执行是没有意义的(用户可能都感受不到,而且很容易造成卡顿)。 -------------------------------------------------------------------------------- /docs/algorithm/树/222.完全二叉树的节点个数.md: -------------------------------------------------------------------------------- 1 | 1、普通二叉树的节点个数 2 | 3 | ```js 4 | /** 5 | * @param {TreeNode} root 6 | * @return {number} 7 | */ 8 | var countNodes = function(root) { 9 | if (root == null) { 10 | return 0; 11 | } 12 | return 1 + countNodes(root.left) + countNodes(root.right); 13 | }; 14 | ``` 15 | 16 | - 时间复杂度 O(N) 17 | 18 | 2、满二叉树的节点总数 19 | 20 | 那如果是一棵满二叉树,节点总数就和树的高度呈指数关系: 21 | 22 | ```js 23 | /** 24 | * @param {TreeNode} root 25 | * @return {number} 26 | */ 27 | var countNodes = function(root) { 28 | let h = 0; 29 | while (root !== null) { 30 | root = root.left; 31 | h++; 32 | } 33 | return Math.pow(2, n) - 1; 34 | }; 35 | ``` 36 | 37 | 3、完全二叉树的节点总数 38 | 39 | 完全二叉树比普通二叉树特殊,但又没有满二叉树那么特殊,计算它的节点总数,可以说是普通二叉树和完全二叉树的结合版 40 | 41 | ```js 42 | var countNodes = function(root) { 43 | let l = root; 44 | let r = root; 45 | // 记录左、右子树的高度 46 | let heightLeft = 0; 47 | let heightRight = 0; 48 | while (l !== null) { 49 | l = l.left; 50 | heightLeft++; 51 | } 52 | while (r !== null) { 53 | r = r.right; 54 | heightRight++; 55 | } 56 | // 如果左右子树高度相同,则是一颗满二叉树 57 | if (heightLeft === heightRight) { 58 | return Math.pow(2, heightLeft) - 1; 59 | } 60 | // 如果左右高度不相同,则按照普通二叉树的逻辑计算 61 | return 1 + countNodes(root.left) + countNodes(root.right); 62 | }; 63 | ``` 64 | 65 | 结合计算普通二叉树和满二叉树的节点总数的方法来计算完全二叉树的节点总数,可以降低时间复杂度。 66 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/最长公共前缀.md: -------------------------------------------------------------------------------- 1 | JavaScript实现Leetcode [14. 最长公共前缀](https://leetcode-cn.com/problems/longest-common-prefix/) 2 | 3 | ## 题目描述 4 | 5 | 编写一个函数来查找字符串数组中的最长公共前缀。 6 | 7 | 如果不存在公共前缀,返回空字符串 ""。 8 | 9 | 示例 1: 10 | ```js 11 | 输入: ["flower","flow","flight"] 12 | 输出: "fl" 13 | ``` 14 | 示例 2: 15 | ```js 16 | 输入: ["dog","racecar","car"] 17 | 输出: "" 18 | ``` 19 | 解释: 输入不存在公共前缀。 20 | 说明: 21 | 22 | 所有输入只包含小写字母 `a-z`  23 | 24 | ## 解析思路 25 | 1. 字符串数组长度为`0`时,公共前缀为空,直接返回 26 | 2. 初始化公共前缀 `tempCommon` 为 第一个字符串 27 | 3. 遍历后面的字符串依次和 `tempCommon` 进行比较,两两找出公共前缀,最终结果即为 最长公共前缀 28 | 29 | ## 解题方法 30 | ```js 31 | 32 | /** 33 | * @param {string[]} strs 34 | * @return {string} 35 | * 1. tempCommon为 flower 36 | * 2. 比较 flower和flow,相同的值为 fl 37 | * 3. 比较 fl 和 flight,形同的值为 fl 38 | * 如何比较 flower和flow: c使用for循环,一个字符一个字符的比较 39 | */ 40 | var longesttempCommon = function(strs) { 41 | if(!strs.length) { 42 | return ''; 43 | } 44 | let common = ''; 45 | // 第一个字符串为初始值 46 | let tempCommon = strs[0]; 47 | for(let i = 0 ; i < tempCommon.length; i++) { 48 | // 遍历strs中剩下的 的值 49 | for(let j = 1; j < strs.length; j++) { 50 | // 每一个都和 tempCommon 比较,找到公共的部分,否则返回 common 51 | if(tempCommon[i] !== strs[j][i]) { 52 | return common; 53 | } 54 | } 55 | common += tempCommon[i]; 56 | } 57 | return common; 58 | }; 59 | ``` 60 | 61 | - 时间复杂度: O(n),n为所有字符串长度之和 62 | - 空间复杂度: O(1),使用常量来存储变量 -------------------------------------------------------------------------------- /docs/algorithm/数组/合并区间.md: -------------------------------------------------------------------------------- 1 | ## 合并区间 2 | 3 | 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 4 | 5 | 示例 1: 6 | 7 | ```js 8 | 输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 9 | 输出:[[1,6],[8,10],[15,18]] 10 | 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. 11 | ``` 12 | 13 | 示例 2: 14 | 15 | ```js 16 | 17 | 输入:intervals = [[1,4],[4,5]] 18 | 输出:[[1,5]] 19 | 解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。 20 | ``` 21 | 22 | ## 题解 23 | 24 | 我们解决区间问题的一般思路是先排序,然后观察规律。 25 | 显然,对于几个相交区间合并后的结果区间 x,x.start 一定是这些相交区间中 start 最小的,x.end 一定是这些相交区间中 end 最大的。 26 | 由于已经排了序,x.start 很好确定,求 x.end 也很容易,可以类比在数组中找最大值的过程: 27 | 28 | 1. 我们选择按 start 排序。 29 | 2. 判断是否有重合:后一个值的第一个值,如果小于前一个值的第二个值,就说明有重合 30 | 31 | ```js 32 | /** 33 | * @param {number[][]} intervals 34 | * @return {number[][]} 35 | */ 36 | var merge = function(intervals) { 37 | intervals.sort((a, b) => a[0] - b[0]); 38 | let res = []; 39 | res.push(intervals[0]); 40 | // res的最后一个 41 | let prev = intervals[0]; 42 | for (let i = 1; i < intervals.length; i++) { 43 | let current = intervals[i]; 44 | // 当前的第1个值和上一个的第二个值比较,判断是否有合并的区域 45 | if (current[0] <= prev[1]) { 46 | prev[1] = Math.max(current[1], prev[1]); 47 | } else { 48 | res.push(current); 49 | prev = current; 50 | } 51 | } 52 | return res; 53 | }; 54 | ``` 55 | 56 | ## 参考 57 | 58 | - [一文秒杀所有区间相关问题](https://mp.weixin.qq.com/s/Eb6ewVajH56cUlY9LetRJw) 59 | -------------------------------------------------------------------------------- /docs/algorithm/树/二叉树的所有路径.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定一个二叉树,返回所有从根节点到叶子节点的路径。 3 | 4 | 说明: 叶子节点是指没有子节点的节点。 5 | 6 | 示例: 7 | 8 | 输入: 9 | ```js 10 | 1 11 | / \ 12 | 2 3 13 | \ 14 | 5 15 | 16 | 输出: ["1->2->5", "1->3"] 17 | 18 | 解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3 19 | ``` 20 | 21 | ## 解题方法 22 | 23 | 使用递归 24 | 25 | 1. 当前的节点不是叶子节点,则在当前的路径末尾添加该节点,并递归遍历该节点的每一个孩子节点 26 | 2. 当前节点如果是叶子节点,则在当前的路径添加该节点后,就得到了一条从根节点到叶子节点的路径,就可以把该路径加到结果中 27 | 28 | ```js 29 | /** 30 | * Definition for a binary tree node. 31 | * function TreeNode(val) { 32 | * this.val = val; 33 | * this.left = this.right = null; 34 | * } 35 | */ 36 | /** 37 | * @param {TreeNode} root 38 | * @return {string[]} 39 | */ 40 | var binaryTreePaths = function(root) { 41 | let result = []; 42 | 43 | function getPath(root, path, result) { 44 | if(root !== null) { 45 | path += String(root.val) 46 | // 当前节点是叶子节点 47 | if(root.left === null && root.right === null) { 48 | // 将当前遍历的路径放到结果中 49 | result.push(path); 50 | } else { 51 | // 当前节点不是叶子节点,继续递归遍历 52 | path += '->'; 53 | getPath(root.left, path, result); 54 | getPath(root.right, path, result) 55 | 56 | } 57 | } 58 | 59 | } 60 | getPath(root, '', result); 61 | return result; 62 | }; 63 | ``` 64 | - 时间复杂度: O(N)。每个节点只会被访问一次,因此时间复杂度为 O(N),其中 N 表示节点数目。 65 | - 空间复杂度:O(N)。 66 | 67 | -------------------------------------------------------------------------------- /docs/algorithm/双指针/滑动窗口/和为s的连续正数序列.md: -------------------------------------------------------------------------------- 1 | [剑指 Offer 57 - II. 和为 s 的连续正数序列](https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) 2 | 3 | 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。 4 | 5 | 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。 6 | 7 | 示例 1: 8 | 9 | 输入:target = 9 10 | 输出:[[2,3,4],[4,5]] 11 | 示例 2: 12 | 13 | 输入:target = 15 14 | 输出:[[1,2,3,4,5],[4,5,6],[7,8]] 15 | 16 | 来源:力扣(LeetCode) 17 | 链接:https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof 18 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 19 | 20 | ## 解法 21 | 22 | 双指针(滑动窗口) 23 | 24 | ```js 25 | /** 26 | * @param {number} target 27 | * @return {number[][]} 28 | */ 29 | var findContinuousSequence = function(target) { 30 | let left = 1; 31 | let right = 2; 32 | let sum = 3; 33 | let result = []; 34 | while (left < right) { 35 | if (sum === target) { 36 | // 将 此时 left -> right的连续子串存储起来,放到结果中 37 | let ans = []; 38 | for (let k = left; k <= right; k++) { 39 | ans[k - left] = k; 40 | } 41 | result.push(ans); 42 | // 等于的情况,我们可以继续窗口往右搜索,同时缩小左边的 43 | sum = sum - left; 44 | left++; 45 | } else if (sum > target) { 46 | // 大于时:缩小窗口(缩短左边界) 47 | sum = sum - left; 48 | left++; 49 | } else { 50 | // 小于时:继续扩大窗口 51 | right++; 52 | sum = sum + right; 53 | } 54 | } 55 | return result; 56 | }; 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/经典/64. 最小路径和.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | 3 | 给定一个包含非负整数的 m x n  网格  grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 4 | 5 | 说明:每次只能向下或者向右移动一步。 6 | 7 | 示例 1: 8 | 9 | 输入:grid = [[1,3,1],[1,5,1],[4,2,1]] 10 | 输出:7 11 | 解释:因为路径 1→3→1→1→1 的总和最小。 12 | 示例 2: 13 | 14 | 输入:grid = [[1,2,3],[4,5,6]] 15 | 输出:12 16 | 17 | 来源:力扣(LeetCode) 18 | 链接:https://leetcode.cn/problems/minimum-path-sum 19 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 20 | 21 | ## 解法 22 | 23 | 动态规划: 24 | 确定状态转移方程:`dp[i][j] = Math.min(dp[i - 1][j], dp[i][ j - 1]) + grid[i][j]` 25 | 26 | ```js 27 | /** 28 | * @param {number[][]} grid 29 | * @return {number} 30 | */ 31 | var minPathSum = function(grid) { 32 | if (grid === null || grid.length === 0 || grid[0].length === 0) { 33 | return 0; 34 | } 35 | let rows = grid.length; 36 | let colums = grid[0].length; 37 | // 初始化dp数组 38 | let dp = new Array(rows).fill(0).map(() => new Array(colums).fill(0)); 39 | dp[0][0] = grid[0][0]; 40 | for (let i = 1; i < rows; i++) { 41 | dp[i][0] = dp[i - 1][0] + grid[i][0]; 42 | } 43 | for (let j = 1; j < colums; j++) { 44 | dp[0][j] = dp[0][j - 1] + grid[0][j]; 45 | } 46 | for (let i = 1; i < rows; i++) { 47 | for (let j = 1; j < colums; j++) { 48 | // 状态转移方程:dp[i][j] = Math.min(dp[i - 1][j], dp[i][ j - 1]) + grid[i][j]; 49 | dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; 50 | } 51 | } 52 | return dp[rows - 1][colums - 1]; 53 | }; 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/algorithm/栈和队列/946. 验证栈序列.md: -------------------------------------------------------------------------------- 1 | 给定  pushed  和  popped  两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。 2 | 3 | 示例 1: 4 | 5 | 输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1] 6 | 输出:true 7 | 解释:我们可以按以下顺序执行: 8 | push(1), push(2), push(3), push(4), pop() -> 4, 9 | push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1 10 | 示例 2: 11 | 12 | 输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2] 13 | 输出:false 14 | 解释:1 不能在 2 之前弹出。 15 | 16 | 来源:力扣(LeetCode) 17 | 链接:https://leetcode.cn/problems/validate-stack-sequences 18 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 19 | 20 | ## 题解 21 | 22 | 1. 按照入栈序列的顺序压栈 23 | 2. 每次压栈之后,循环判断 “栈顶元素 = 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出 24 | 3. 最后如果栈为空则满足条件,否则不满足 25 | 26 | ```js 27 | /** 28 | * @param {number[]} pushed 29 | * @param {number[]} popped 30 | * @return {boolean} 31 | */ 32 | var validateStackSequences = function(pushed, popped) { 33 | let i = 0; 34 | // 辅助栈 35 | let stack = []; 36 | for (let val of pushed) { 37 | // 按照入栈序列的顺序执行 38 | stack.push(val); 39 | // 判断 模拟栈的栈顶元素 是否跟 popped 数组此时要弹出的元素相等,相等的话模拟栈就弹出,popped 数组对应 i 指针向后移动一位 40 | while (stack.length && stack[stack.length - 1] === popped[i]) { 41 | stack.pop(); 42 | i += 1; 43 | } 44 | } 45 | // 如果在 pushed 数组全部元素都压入模拟栈后,模拟栈元素没随着 popped 清空,那么弹出序列对不上,返回 false 46 | return !stack.length; 47 | }; 48 | ``` 49 | 50 | 题目同 [剑指 Offer 31. 栈的压入、弹出序列](https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof) 51 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/买卖股票的最佳时机I.md: -------------------------------------------------------------------------------- 1 | 2 | 这是leetcode 121题: https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/ 3 | 4 | ### 题目描述 5 | 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 6 | 7 | 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。 8 | 9 | 注意你不能在买入股票前卖出股票。 10 | 11 | 示例 1: 12 | ```js 13 | 输入: [7,1,5,3,6,4] 14 | 输出: 5 15 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 16 | 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 17 | ``` 18 | 示例 2: 19 | ```js 20 | 输入: [7,6,4,3,1] 21 | 输出: 0 22 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 23 | ``` 24 | 25 | ### 解答思路 26 | #### 本题可以理解 27 | 28 | 假设我在第N天买入, 求计算以后几天的价格和第N天价格相差最多的一天。 29 | 也就是说在第N天的时候,要做两个判断, 30 | - 1. `price[n]`是否为最低价,需要重新计算最低价 31 | - 2. 计算当前的最大利润 32 | 33 | #### 解题思路 34 | 35 | - 设置两个变量 36 | - `maxProfit`: 最大利润值, 初始值为0 37 | - `minPrice`: 当前最低价格,初始值为Number.MAX_SAFE_INTEGER(任何一个值肯定都比这个初始值小) 38 | - 遍历`prices`里的每一个值 39 | - 求出`minPrice和prices[i]`两个中的最小值,赋值给minPrice; 40 | - 求出`maxProfit`,和`(prices[i] - minPrice`)的最大值,赋值给`maxProfit`。 41 | ### 解答方案 42 | ```js 43 | /** 44 | * @param {number[]} prices 45 | * @return {number} 46 | */ 47 | var maxProfit = function(prices) { 48 | // minPrice表示最小价格, 需要在最小价格的时候买入 49 | let minPrice = Number.MAX_SAFE_INTEGER; 50 | // 当前最大利润, 在最大利润的时候卖出 51 | let maxProfit = 0; 52 | for(let i = 0; i < prices.length; i++) { 53 | minPrice = Math.min(minPrice, prices[i]) 54 | maxProfit = Math.max(maxProfit, prices[i] - minPrice); 55 | } 56 | return maxProfit; 57 | }; 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /docs/algorithm/栈和队列/155. 最小栈.md: -------------------------------------------------------------------------------- 1 | [155.最小栈](https://leetcode.cn/problems/min-stack/) 2 | 3 | ```js 4 | /** 5 | * initialize your data structure here. 6 | */ 7 | var MinStack = function() { 8 | /** 9 | * 思路:有两个栈, 10 | - stack 存放所有的数据 11 | - minStack存放一个有序的栈结构,从上往下递递增 12 | min、push 及 pop 时间复杂度为O(1) 13 | 因为栈的插入、删除与读取操作都是 O(1),我们定义的每个操作最多调用栈操作两次 14 | */ 15 | 16 | this.minStack = []; 17 | this.stack = []; 18 | }; 19 | 20 | /** 21 | * @param {number} x 22 | * @return {void} 23 | */ 24 | MinStack.prototype.push = function(x) { 25 | const min = this.min(); 26 | this.stack.push(x); 27 | if (min === undefined || min >= x) { 28 | this.minStack.push(x); 29 | } 30 | }; 31 | 32 | /** 33 | * @return {void} 34 | */ 35 | MinStack.prototype.pop = function() { 36 | const min = this.min(); 37 | const val = this.stack.pop(); 38 | if (min === val) { 39 | this.minStack.pop(); 40 | } 41 | }; 42 | 43 | /** 44 | * @return {number} 45 | */ 46 | MinStack.prototype.top = function() { 47 | return this.stack[this.stack.length - 1]; 48 | }; 49 | 50 | /** 51 | * @return {number} 52 | */ 53 | MinStack.prototype.min = function() { 54 | return this.minStack[this.minStack.length - 1]; 55 | }; 56 | 57 | /** 58 | * Your MinStack object will be instantiated and called as such: 59 | * var obj = new MinStack() 60 | * obj.push(x) 61 | * obj.pop() 62 | * var param_3 = obj.top() 63 | * var param_4 = obj.min() 64 | */ 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/interview/Webpack/webpack热更新原理.md: -------------------------------------------------------------------------------- 1 | ## webpack 模块热替换(HMR - hot module Repacement) 2 | 3 | 在应用程序运行过程中,替换,添加或删除模块,而无需重新加载整个页面。 4 | 5 | - 保留在完全重新加载页面期间丢失的应用程序状态 6 | - 只更新变更内容,以节省宝贵时间 7 | - 在源代码中对 CSS/JS,会立刻在浏览器中进行更新。这几乎相当于浏览器 devtools 直接更改样式。 8 | 9 | webpack-dev-server 在编译之后不会写到任何输入文件,而是将 bundle 文件保存在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server 根路径上的真实文件一样。 10 | webpack-dev-server 中调用了 webpack-dev-middleware 和 webpack-hot-middleware 11 | webpack-dev-middleware:是一个封装器(wrapper),它可以把 webpack 处理过得文件发送到一个 server。webpack-dev-server 在内部使用了它,它也可以作为独立的 package 来使用 12 | webpack-hot-middleware: 在运行时更新所有类型的模块,而无需完全刷新。webpack-dev-server 在内部使用了它,只需要配置一下就可以,它也可以作为独立的 package 来使用。 13 | 14 | 增量编译:只编译修改的部分,没修改的部分不重新编译 15 | 热更新:处理的是编译完成后的页面刷新部分,只刷新修改的内容,而无需重新刷新 16 | 17 | ### 为什么需要 HMR 18 | 19 | 当你对代码进行修改保存后,webpack 将对代码重新打包,并将新的模块发送到浏览器,浏览器通过新的模块替换老的模块,这样在不刷新浏览器的前提下能对应用进行更新。 20 | 21 | **要区分两个概念, 浏览器刷新(location.reload)和 HMR 是两个概念** 22 | 已经有很多 live reload 的工具或库, 这些库监控文件的变化,然后通知浏览器端刷新页面。 23 | 24 | - live reload 工具并不能够保存应用的状态(states),当页面刷新后,应用之前的状态消息,还是上文中的例子,点击按钮出现弹窗,当浏览器刷新后,弹窗也随即消失,要恢复到之前状态,还需要再次点击按钮。而 Webpack HMR 则不会刷新浏览器,而是运行时对模块进行热替换,保证了应用状态不会丢失,提升了开发效率。 25 | 26 | ### HMR 原理 27 | 28 | 在应用中置换模块 29 | 30 | ## vue-loader 的 热重载(hot reload) 31 | 32 | 热重载 33 | 不只是当你修改文件的时候简单重新加载页面 34 | 35 | ## [react-hot-loader](https://github.com/gaearon/react-hot-loader) 36 | 37 | ## 参考 38 | 39 | - [Webpack Hot Module Replacement 的原理解析](https://github.com/Jocs/jocs.github.io/issues/15) 40 | 41 | 通过以下方式显著提升开发速度 1.保留页面的状态 2.只更新变更内容 42 | -------------------------------------------------------------------------------- /docs/algorithm/树/平衡二叉树.md: -------------------------------------------------------------------------------- 1 | 2 | JavaScript实现leetcode110. 平衡二叉树 3 | 4 | ## 题目描述 5 | 给定一个二叉树,判断它是否是高度平衡的二叉树。 6 | 7 | 本题中,一棵高度平衡二叉树定义为: 8 | 9 | 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 10 | 11 | 示例 1: 12 | 13 | 给定二叉树 [3,9,20,null,null,15,7] 14 | ```js 15 | 3 16 | / \ 17 | 9 20 18 | / \ 19 | 15 7 20 | ``` 21 | 返回 true 。 22 | 23 | 示例 2: 24 | 25 | 给定二叉树 [1,2,2,3,3,null,null,4,4] 26 | ```js 27 | 1 28 | / \ 29 | 2 2 30 | / \ 31 | 3 3 32 | / \ 33 | 4 4 34 | ``` 35 | 返回 false 。 36 | 37 | ## 思路分析 38 | 39 | 1. 定义一个获取当前节点高度的方法 40 | 2. 左右两个子树的高度差的绝对值超过1,则为false 41 | 3. 如果当前节点的左右子树满足高度差的绝对值不超过1,则需要继续判断其左右子树分别是否是平衡二叉树。 42 | 43 | ```js 44 | /** 45 | * Definition for a binary tree node. 46 | * function TreeNode(val) { 47 | * this.val = val; 48 | * this.left = this.right = null; 49 | * } 50 | */ 51 | /** 52 | * @param {TreeNode} root 53 | * @return {boolean} 54 | * // 没有思路 55 | */ 56 | var isBalanced = function(root) { 57 | if(root === null) { 58 | return true; 59 | } 60 | // 平衡二叉树的定义:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1 61 | if(Math.abs(maxHeight(root.left) - maxHeight(root.right)) > 1) { 62 | return false; 63 | } 64 | return isBalanced(root.left) && isBalanced(root.right); 65 | // 求树的最大高度 66 | function maxHeight(root) { 67 | if(root === null) { 68 | return 0 69 | } else { 70 | return 1 + Math.max(maxHeight(root.left), maxHeight(root.right)); 71 | } 72 | } 73 | }; 74 | ``` 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/algorithm/数学/圆圈中最后剩下的数字.md: -------------------------------------------------------------------------------- 1 | ## [剑指 Offer 62. 圆圈中最后剩下的数字](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) 2 | 3 | 0,1,···,n-1 这 n 个数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 m 个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。 4 | 5 | 例如,0、1、2、3、4 这 5 个数字组成一个圆圈,从数字 0 开始每次删除第 3 个数字,则删除的前 4 个数字依次是 2、0、4、1,因此最后剩下的数字是 3。 6 | 7 | 示例 1: 8 | 9 | 输入: n = 5, m = 3 10 | 输出: 3 11 | 示例 2: 12 | 13 | 输入: n = 10, m = 17 14 | 输出: 2 15 | 16 | ## 题解 17 | 18 | 1. 经典的约瑟夫环问题:递归公式 f(n) = (f(n - 1) + m) % n 19 | 2. 根据规律可以发现,最后剩下的数字(假设为 X)的位置向左移动了 m 位,也就是减少了 m,所以逆推 X 的位置时就要加上 m 就是公式中的 f(n) = (f(n - 1) + m) % n 20 | 3. 注意:n 是会随着个数的变化而改变,当 + m 后超过当前的总个数 n 时,需要回到队头重新计数,即需要进行取模运算 21 | 22 | 以题目给的代码为例 0、1、2、3、4 ,n=5,m=3 23 | 24 | - 第一轮:0 1 2 3 4 0 1 2 3 4,删除 2,此时 3 的下标为(0 + 3) % 5 = 3 25 | - 第二轮:3 4 0 1 3 4 0 1,删除 0,此时 3 的下标为(1 + 3) % 4 = 0 26 | - 第三轮:1 3 4 1 3 4,删除 4,此时 3 的下标为(1 + 3) % 3 = 1 27 | - 第四轮:1 3 1 3,删除 1,此时 3 的下标为(0 + 3) % 2 = 1 28 | - 第五轮:3,此时 3 的下标为 0 29 | 30 | 倒着就可以推出规律为 pos=(pos+m)%n 31 | 32 | ```js 33 | /** 34 | * @param {number} n 35 | * @param {number} m 36 | * @return {number} 37 | */ 38 | var lastRemaining = function(n, m) { 39 | let pos = 0; 40 | for (let i = 2; i <= n; i++) { 41 | pos = (pos + m) % i; 42 | } 43 | return pos; 44 | }; 45 | ``` 46 | 47 | - 时间复杂度:O(n),需要求解的函数值有 n 个。 48 | - 空间复杂度:O(1),只使用常数个变量。 49 | 50 | ## 参考 51 | 52 | - [换个角度举例解决约瑟夫环](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/huan-ge-jiao-du-ju-li-jie-jue-yue-se-fu-huan-by-as/) 53 | -------------------------------------------------------------------------------- /docs/algorithm/数组/扑克牌中的顺子.md: -------------------------------------------------------------------------------- 1 | # 扑克牌中的顺子 2 | 3 | JavaScript实现LeetCode第61题:扑克牌中的顺子 4 | 5 | ## 题目描述 6 | 7 | 从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。 8 | 9 | ```js 10 | 示例 1: 11 | 输入: [1,2,3,4,5] 12 | 输出: True 13 |   14 | 15 | 示例 2: 16 | 输入: [0,0,1,2,5] 17 | 输出: True 18 | ``` 19 | 限制: 20 | 1.数组长度为 5  21 | 2.数组的数取值为 [0, 13] . 22 | 23 | ## 思路分析 24 | - 1. 先判断数组长度是否为5,如果不是,则直接返回false 25 | - 2. 对数组进行排序,声明两个变量,一个为 `wangNums`,用来存储王的个数,一个为 `grapNums`,用来存储 排序后元素的差值 26 | - 3. 遍历数组,`nums[i]`如果值为0,则 `wangNums++`,如果`nums[i] == nums[i+1]`则直接返回false, 其他情况,计算差值,累加到 `grapNums` 27 | - 4. 最后判断如果 差值 (`grapNums`)小于王(`wangNums`)的个数,说明可以用王来构成顺子,返回true,否则返回false 28 | 29 | ## 解法 30 | ```js 31 | /** 32 | * @param {number[]} nums 33 | * @return {boolean} 34 | */ 35 | var isStraight = function(nums) { 36 | if(nums.length != 5) { 37 | return false; 38 | } 39 | 40 | nums.sort((a, b) => a - b); 41 | let wangNums = 0; // 王的个数 42 | let grapNums = 0; // 排序后元素的差值 43 | for(let i = 0; i < nums.length - 1; i++) { 44 | if(nums[i] == 0) { 45 | wangNums++; 46 | } else if(nums[i] == nums[i+1]) { 47 | //不是王,并且还是对子,那肯定不是顺子了 48 | return false; 49 | } else { 50 | //不是王,计算一下两两的差值,最后与王的个数做比较 51 | grapNums += (nums[i+1] - nums[i] - 1); 52 | } 53 | } 54 | //差值小于王的个数,说明可以用王来构成顺子 55 | if(grapNums <= wangNums) { 56 | return true; 57 | } 58 | return false; 59 | }; 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/100day/文章/如何启动一个本地静态服务器.md: -------------------------------------------------------------------------------- 1 | ## 如何启动一个本地静态服务器 2 | 3 | 背景:学习前端开发,想要调试静态页面以及js,发现直接打开本地会有跨域异常,因此需要启动一个静态服务器,只负责当前目录的文件路由, 4 | 5 | 目前尝试两种方案: 6 | 1. nginx 7 | 2. http-server 8 | 9 | ## nginx 10 | 11 | ### 安装 12 | ```js 13 | brew install nginx 14 | ``` 15 | ### 配置 16 | Mac通过brew安装后的配置文件位于:/usr/local/etc/nginx. 17 | 18 | 修改端口和文件目录: 19 | ```js 20 | server { 21 | listen 8090; 22 | server_name server.com; 23 | charset utf-8; 24 | location / { 25 | alias /Users/wangyaxing/test/; 26 | index index.html; 27 | } 28 | .... 29 | } 30 | ``` 31 | ### 启动和关闭 32 | ```js 33 | # 启动 34 | nginx 35 | # 关闭 36 | nginx -s stop 37 | ``` 38 | 启动后,打开浏览器,输入: localhost:8090/xxx.html即可。 39 | 40 | ## http-server 41 | [http-server](https://github.com/indexzero/http-server)是基于node.js的HTTP 服务器,它最大的好处就是: 42 | 可以使用任意一个目录成为服务器的目录,完全抛开后端的沉重工程,直接运行想要的js代码 43 | 44 | ### 安装 45 | ```js 46 | npm install -g http-server 47 | ``` 48 | 49 | ### 启动 50 | http-server就可以以 `该目录`为跟启动一个服务器 51 | ```js 52 | http-server [path] [options] 53 | 54 | ``` 55 | - path是目录的路径名称 56 | - 如果存在public目录,默认为./public; 57 | - 如果没有 public 目录,那么就是 根目录 ./ 58 | - options常用选项 59 | - -p 或者 --port 使用的端口号,默认为8080 60 | - -a 使用的IP地址,默认0.0.0.0 61 | 62 | > options更多选项,可以查看[http-server](https://github.com/indexzero/http-server#available-options) 63 | 64 | ### 使用 65 | 66 | ```js 67 | cd test/ 68 | http-server -p 8900 69 | ``` 70 | 你可以把 http-server -p 8900 写入到 package.json 文件中的 scripts 节点 71 | ```js 72 | "scripts": { 73 | "dev": "http-server -p 8900" 74 | }, 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/interview/HTTP/三次握手,四次挥手.md: -------------------------------------------------------------------------------- 1 | ## 三次握手,四次挥手,为什么是三次和四次 2 | 3 | ### 三次握手 4 | 1. 发送端首先发送一个带SYN标志的数据包给对方 5 | 2. 接收端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息 6 | 3. 最后,发送端再回传一个带ACK标志的数据包,代表“握手”结束。 7 | 8 | 若在握手过程中某个阶段莫名中断,TCP协议会再次以相同的顺序发送相同的数据包。 9 | 10 | ### 握手为什么是三次? 11 | 三次握手其实就是建立一个TCP连接时,需要客户端和服务器总共发出3个包,进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常。指定自己的初始化序列号为后面的可靠性传送做准备。 12 | 13 | 第一次握手:客户端发送网络包,服务端收到了。 14 | 这样服务端就能得出结论:客户端的发送能力。服务端的接收能力是正常的。 15 | 16 | 第二次握手:服务端发包,客户端收到了。 17 | 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收。发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。 18 | 19 | 第三次握手:客户端发包,服务端收到了。 20 | 这样服务端就能得出结论:客户端的接收。发送能力正常,服务器自己的发送、接收能力也正常。 21 | 22 | 试想如果是用两次握手,则会出现下面这种情况: 23 | > 如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。 24 | 25 | 26 | ### 四次挥手 27 | 1. 第一次挥手:主动关闭方发送一个`FIN`,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的`ack`确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。 28 | 2. 第二次挥手:被动关闭方收到`FIN`包后,发送一个`ACK`给对方,确认序号为收到序号+1(与`SYN`相同,一个`FIN`占用一个序号)。 29 | 3. 第三次挥手:被动关闭方发送一个`FIN`,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。 30 | 4. 第四次挥手:主动关闭方收到 `FIN`后,发送一个`ACK`给被动关闭方,确认序号为收到序号`+1`,至此,完成四次挥手。 31 | 32 | ### 挥手为什么是四次? 33 | 34 | 因为当服务端收到客户端的SYN连接请求报文后,可以直接发送 SYN + ACK 报文。其中 ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。 35 | 36 | > [面试官,不要再问我三次握手和四次挥手](https://zhuanlan.zhihu.com/p/86426969) -------------------------------------------------------------------------------- /docs/algorithm/树/另一个树的子树.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | JavaScript实现leetcode572. 另一个树的子树 3 | 4 | ## 题目描述 5 | 给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。 6 | 7 | 示例 1: 8 | 给定的树 s: 9 | ```js 10 | 3 11 | / \ 12 | 4 5 13 | / \ 14 | 1 2 15 | ``` 16 | 给定的树 t: 17 | 18 | ```js 19 | 4 20 | / \ 21 | 1 2 22 | ``` 23 | 返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。 24 | 25 | 示例 2: 26 | 给定的树 s: 27 | 28 | ```js 29 | 3 30 | / \ 31 | 4 5 32 | / \ 33 | 1 2 34 | / 35 | 0 36 | ``` 37 | 给定的树 t: 38 | 39 | ```js 40 | 4 41 | / \ 42 | 1 2 43 | ``` 44 | 返回 false。 45 | 46 | ## 思路分析 47 | 48 | 使用递归来解决,这道题需要判断树 t是不是 s的子树,那么以下三个条件当中只要满足一个就可以 49 | 1. 判断t和树s是不是结构相等 50 | 2. 判断树t是不是树s左子树的子结构 51 | 3. 判断树t是不是树s右子树的子结构 52 | 53 | ## 解题方法 54 | 55 | ```js 56 | /** 57 | * Definition for a binary tree node. 58 | * function TreeNode(val) { 59 | * this.val = val; 60 | * this.left = this.right = null; 61 | * } 62 | */ 63 | /** 64 | * @param {TreeNode} s 65 | * @param {TreeNode} t 66 | * @return {boolean} 67 | */ 68 | var isSubtree = function(s, t) { 69 | return s !== null && (isSameTree(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t)); 70 | 71 | function isSameTree(p, q) { 72 | if(p === null && q === null) { 73 | return true; 74 | } 75 | if(p === null || q === null) { 76 | return false; 77 | } 78 | return p.val === q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right); 79 | } 80 | }; 81 | ``` 82 | ## 复杂度分析 83 | 84 | - 时间复杂度:O(n),其中 n 为二叉树的节点个数。 85 | - 空间复杂度:O(n) -------------------------------------------------------------------------------- /docs/menu/区块链.md: -------------------------------------------------------------------------------- 1 | ## 密码学 2 | 3 | 哈希算法 4 | 交易签名 5 | 6 | ### 哈希算法 7 | 8 | 评判一个哈希算法是否优秀,有 4 个标准 9 | 10 | - 正向快速: 给出的明文信息可以快速计算得到 对应的 hash 值 11 | - 逆向困难:给定哈希结果,在有限的时间内基本不可能逆推出原始信息 12 | - 输入敏感:原始信息发生了一点变化,重新计算的哈希值应该与之前有很大差异 13 | - 避免碰撞:避免不同明文信息产生的相同的哈希值 14 | 15 | 区块链中的哈希:成就了区块链单点的不可篡改特性 16 | 用到的地方: 17 | 18 | - “交易哈希和块哈希,主要功能就是校验交易及区块的完整性,以及充当区块链网络中交易及区块的唯一标识” 19 | - “区块属性中包含了前一个区块的哈希,从而构建成一条由区块哈希关联的数据链条,” 20 | 21 | ### 对称加密算法和非对称加密算法 22 | 23 | 实际中都是混合使用这两种算法。 24 | 25 | - 明文数据使用对称加密算法进行加密 26 | - 使用非对称加密算法加密 对称加密的秘钥 和明文数据哈希。 27 | 28 | 对方收到信息后,用匹配成对的另一个秘钥解密数据并核对明文数据哈希。 29 | 30 | 非对称加密算法和哈希算法一样,是一类算法的统称。比较多的是 RSA 和 ECC 椭圆曲线加密算法。 31 | 32 | - RSA 多用于互联网信息传输中,比如 HTTPS 通信 33 | - 区块链中一般使用的是 ECC 类的变种 34 | 35 | 区块链中的非对称加密: 36 | “区块链中对非对称加密算法的使用并不是直接被用作数据加密,而是利用其具备身份确权的能力,也就是数字签名” 37 | 38 | ## 单个区块链是怎么存储的 39 | 40 | “区块链存储是如何设计的” 41 | 42 | “利用哈希算法对输入的敏感性,作恶者如想篡改区块链数据,必须从被修改处开始依次修改后续的全部区块,哈希算法的加入增加了作恶者篡改的成本,可以说是哈希算法成就了区块链单节点的不可篡改特性” 43 | 44 | ### 概念 45 | 46 | ### 交易 47 | 48 | 行为角度:交易等同于操作。操作内容与区块链协议有关。在以太坊中,一个操作可能是执行了智能合约里的一个方法 49 | 计算机技术角度:“交易是区块链网络中数据的最小的组成部分” 50 | 51 | 交易的属性:8 个属性 52 | 53 | 1. From 交易发起方 54 | 2. To, 交易接收方 55 | 3. 智能合约相关:智能合约名称,方法,参数列表 56 | 4. 时间戳:交易在客户端构建的时间,客户端独立添加。接收方会校验时间 57 | 5. 签名:From 字段的账户签发,向网络证明这笔交易确实是这个账户创建的。“使用账户拥有者手中的私钥对交易进行签名,而私钥只有账户拥有者持有。就像我们平常生活中使用的印章” 58 | 59 | ??“区块链中所有的交易基本上都是从区块链网络外发起的,区块链网络只接收交易而不生产交易,且不对交易做任何改动。也就是说,交易在客户端构建出来以后就固化了。” 60 | 61 | ### 区块 62 | 63 | 区块是用来存储交易数据的容器。交易是货物,区块是集装箱。 64 | “区块链是时间段的数据前后关联依次串联整合成的信息链条,而每一时间段的数据我们就称之为区块(Block)” 65 | “区块是指将节点一段时间内收到的所有(有效)交易打包而形成的一种数据结构,” 66 | 67 | ### 状态 68 | 69 | x-Ray 术语创建生词卡,轻松掌握重要概念 70 | 导出笔记和标注, 71 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/经典/152.乘积最大子数组.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | 3 | 给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 4 | 5 | 测试用例的答案是一个  32-位 整数。 6 | 7 | 子数组 是数组的连续子序列。 8 | 9 | 示例 1: 10 | 11 | ```js 12 | 输入: nums = [2,3,-2,4] 13 | 输出: 6 14 | 解释:  子数组 [2,3] 有最大乘积 6。 15 | ``` 16 | 17 | 示例 2: 18 | 19 | ```js 20 | 输入: nums = [-2,0,-1] 21 | 输出: 0 22 | 解释:  结果不能为 2, 因为 [-2,-1] 不是子数组。 23 | ``` 24 | 25 | 来源:力扣(LeetCode) 26 | 链接:https://leetcode.cn/problems/maximum-product-subarray 27 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 28 | 29 | ## 解题思路 30 | 31 | 第一感觉是使用跟 [53. 最大子序和]这道题的思路一样,推断出这道题的状态转移方程 32 | 33 | ```js 34 | cur = Math.max(cur * nums[i], nums[i]); 35 | result = Math.max(result, cur); 36 | ``` 37 | 38 | 但是结果并不是,因为 `[-2,3,-4]`, 根据上面得到的 3,但是实际是 24,`-2 * 3 * -4` 39 | 当前位置的最优解未必是由前一个位置的最优解转移得到的。 40 | 41 | **我们可以根据正负性进行分类讨论:** 42 | 43 | - 当前位置是负数,希望它前一个位置结尾的某个段的积也是个负数 44 | - 如果当前位置是一个正数的话,我们更希望以它前一个位置结尾的某个段的积也是个正数 45 | 46 | fmin(i),它表示以第 i 个元素结尾的乘积最小子数组的乘积。 47 | 48 | 使用滚动数组来代替 DP 数据,减小空间复杂度。 49 | 50 | ```js 51 | /** 52 | * @param {number[]} nums 53 | * @return {number} 54 | */ 55 | var maxProduct = function(nums) { 56 | if (nums.length <= 1) { 57 | return nums[0]; 58 | } 59 | let result = nums[0]; 60 | let max = nums[0]; 61 | let min = nums[0]; 62 | for (let i = 1; i < nums.length; i++) { 63 | let curMax = max; 64 | let curMin = min; 65 | max = Math.max(curMax * nums[i], nums[i], curMin * nums[i]); 66 | min = Math.min(curMax * nums[i], nums[i], curMin * nums[i]); 67 | result = Math.max(result, max); 68 | } 69 | return result; 70 | }; 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/algorithm/栈和队列/232. 用栈实现队列.md: -------------------------------------------------------------------------------- 1 | [232. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/) 2 | 3 | ```js 4 | var MyQueue = function() { 5 | // 输入栈 6 | this.inputStack = []; 7 | // 输出栈,主要是出的操作都从输出栈出去 8 | this.ouputStack = []; 9 | }; 10 | 11 | /** 12 | * @param {number} x 13 | * @return {void} 14 | */ 15 | MyQueue.prototype.push = function(x) { 16 | // 把元素放进输入栈 17 | this.inputStack.push(x); 18 | }; 19 | 20 | /** 21 | * @return {number} 22 | */ 23 | MyQueue.prototype.pop = function() { 24 | // // 如果输出栈为空,则把输入栈的数据放入进入,如果输出栈有值,则先使用输出栈的值 25 | if (this.ouputStack.length === 0) { 26 | while (this.inputStack.length) { 27 | this.ouputStack.push(this.inputStack.pop()); 28 | } 29 | } 30 | 31 | return this.ouputStack.pop(); 32 | }; 33 | 34 | /** 35 | * @return {number} 36 | */ 37 | MyQueue.prototype.peek = function() { 38 | // 如果输出栈为空,则把输入栈的数据放入进入,如果输出栈有值,则先使用输出栈的值 39 | if (this.ouputStack.length === 0) { 40 | while (this.inputStack.length) { 41 | this.ouputStack.push(this.inputStack.pop()); 42 | } 43 | } 44 | return this.ouputStack.length && this.ouputStack[this.ouputStack.length - 1]; 45 | }; 46 | 47 | /** 48 | * @return {boolean} 49 | */ 50 | MyQueue.prototype.empty = function() { 51 | return this.inputStack.length === 0 && this.ouputStack.length === 0; 52 | }; 53 | 54 | /** 55 | * Your MyQueue object will be instantiated and called as such: 56 | * var obj = new MyQueue() 57 | * obj.push(x) 58 | * var param_2 = obj.pop() 59 | * var param_3 = obj.peek() 60 | * var param_4 = obj.empty() 61 | */ 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/interview/HTTP/HTTP状态码.md: -------------------------------------------------------------------------------- 1 | ## HTTP状态码 2 | 3 | 1. 1xx (信息性状态码) 接受的请求正在处理 4 | 2. 2xx 成功 请求正常处理完毕 5 | - 200 OK 客户端发来的请求在服务器端被正常处理了 6 | - 204 No Content 服务器接收的请求已成功处理,但是返回的响应报文中不含实体的主体部分,另外,也不允许返回任何实体的主体 7 | 一般在只需要从客户端往服务器发送信息,而对客户端不需要发送新信息内容的情况下使用。 8 | - 206 Partial Content 客户端进行了范围请求,而服务器成功执行了这部分的Get请求,响应报文中包含Content-Range指定范围的实体内容 9 | 10 | 3. 3xx 重定向 需要进行附加操作已完成请求 11 | - 301 Moved Permanently 永久性重定向 12 | - 302 Found 临时性重定向 13 | - 303 See Other 表示由于请求对应的资源错在着另一个URI,应用GET方法定性获取请求的资源 14 | - 304 Not Modified 表示客户端发送附带条件的请求时,服务器允许请求访问资源,但未满足条件的情况下 15 | - 附带条件的请求是指采用GET方法的请求报文中包含If-Match,If-Modified-Since, If-None-Match,If-Range,If-Unmodified-Since中任一的首部 16 | 17 | 4. 4xx 客户端错误 服务器无法处理请求 18 | - 400 Bad Request 请求报文中存在语法错误 19 | - 401 Unauthorized 表示发送的请求需要有通过HTTP认证 20 | - 403 Forbidden 对请求资源的访问被服务器拒绝了 21 | - 404 Not Found 服务器无法找到请求的资源 22 | 23 | 5. 5xx 服务器错误 服务器处理请求错误 24 | - 500 Internal Server Error 服务器在执行时发生了错误 25 | - 503 Service Unavailable 表明服务器暂时处于超负载或正在进行停机维护 26 | 27 | ## 301和302的区别 28 | - 301是永久性重定向,搜索引擎在抓取新的内容的同时也将旧的网址替换为重定向之后的网址;常用的例如域名跳转:http:**** => https:**** 29 | - 302只是暂时的重定向,搜索引擎会抓取新的内容而保留旧的地址,因为服务器返回302,所以搜索引擎认为新的网址是暂时的。 30 | 31 | ### 从SEO的角度分析: 32 | 33 | #### 301重定向可促进搜索引擎优化效果: 34 | 从搜索引擎优化角度出发,301重定向是网址重定向最为可行的一种办法。当网站的域名发生变更后,搜索引擎只对新网址进行索引,同时又会把旧地址下原有的外部链接如数转移到新地址下,从而不会让网站的排名因为网址变更而收到丝毫影响。同样,在使用301永久性重定向命令让多个域名指向网站主域时,亦不会对网站的排名产生任何负面影响。 35 | 36 | #### 302重定向可影响搜索引擎优化效果: 37 | 38 | 迄今为止,能够对302重定向具备优异处理能力的只有Google。也就是说,在网站使用302重定向命令将其它域名指向主域时,只有Google会把其它域名的链接成绩计入主域,而其它搜索引擎只会把链接成绩向多个域名分摊,从而削弱主站的链接总量。既然作为网站排名关键因素之一的外链数量受到了影响,网站排名降低也是很自然的事情了。 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/买卖股票的最佳时机III.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 3 | 4 | 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 5 | 6 | 注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 7 | 8 | 示例 1: 9 | ```js 10 | 输入: [3,3,5,0,0,3,1,4] 11 | 输出: 6 12 | 解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。 13 |   随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。 14 | ``` 15 | 示例 2: 16 | ```js 17 | 输入: [1,2,3,4,5] 18 | 输出: 4 19 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。   20 |   注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。   21 |   因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 22 | ``` 23 | 示例 3: 24 | ```js 25 | 输入: [7,6,4,3,1] 26 | 输出: 0 27 | 解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。 28 | ``` 29 | ## 解题方法 30 | ```js 31 | /** 32 | * @param {number[]} prices 33 | * @return {number} 34 | */ 35 | var maxProfit = function(prices) { 36 | // dp[i][k][0 or 1], 第 i 天,最多进行 k 次交易, 0 代表手上没有股票,1代表手上有股票 37 | // dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) 38 | // dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) 39 | //第一次 买入, 卖出的利润 40 | let oneBuy = - prices[0]; 41 | let oneSell = 0; 42 | // 继第一次之后,第二次买入卖出的利润 43 | let twoBuy = - prices[0]; 44 | let twoSell = 0; 45 | for (let i = 0; i < prices.length; i++) { 46 | // 第一次买入后,利润为 -prices[i] 47 | oneBuy = Math.max(oneBuy, -prices[i]); 48 | oneSell = Math.max(oneSell, prices[i] + oneBuy); 49 | // 第二次买入后的利润, 第一次卖出的利润 - prices[i] 50 | twoBuy = Math.max(twoBuy, oneSell - prices[i]) 51 | twoSell = Math.max(twoSell, prices[i] + twoBuy); 52 | } 53 | return twoSell; 54 | 55 | }; 56 | ``` -------------------------------------------------------------------------------- /docs/jsCode/compose和pipe.md: -------------------------------------------------------------------------------- 1 | ## compose 函数 2 | 3 | 将嵌套执行的函数平铺,嵌套执行就是一个函数的返回值作为另一个函数的返回值。 4 | 函数式编程:将复杂的几个步骤拆成几个简单的可复用的简单步骤。 5 | 6 | ```js 7 | // 参数从右往左执行,所以multiply在前,add在后 8 | let res = compose(multiply, add)(10); 9 | ``` 10 | 11 | - Array.prototype.reduce(), 接受两个参数,一个是累加器函数,一个是初始值,累加器函数有四个参数,第一个是上一次计算的结果,第二个是当前值,第三个是当前 index,第四个是当前迭代的数组。是从左向右迭代。 12 | - Array.prototype.reduceRight:从右往左迭代 13 | 14 | 利用 reduceRight 函数实现 15 | 16 | ```js 17 | const compose = function() { 18 | const args = [].slice.apply(arguments); 19 | return function(x) { 20 | return args.reduceRight((res, cb) => cb(res), x); 21 | }; 22 | }; 23 | const add = (x) => x + 10; 24 | const multiply = (x) => x * 10; 25 | const calculate = compose(multiply, add); 26 | 27 | const res = calculate(10); 28 | console.log(res); 29 | ``` 30 | 31 | 使用 ES6 语法 32 | 33 | ```js 34 | const compose = (...args) => (x) => args.reduceRight((res, cb) => cb(res), x); 35 | ``` 36 | 37 | redux 的中间件就是用 compose 函数实现的,webpack 中 loader 的顺序也是从右向左的,这是因为它也是 compose 函数实现的。 38 | 39 | ## pipe 函数 40 | 41 | 跟 compose 函数的作用是一样的,也是将参数平铺,只不过它的顺序是从左往右。只要把 compose 实现使用的 reduceRight 改为 reduce 即可 42 | 43 | ```js 44 | const pipe = function() { 45 | const args = [].slice.apply(arguments); 46 | return function(x) { 47 | return args.reduce((res, cb) => cb(res), x); 48 | }; 49 | }; 50 | 51 | // 参数顺序改为从左往右 52 | let calculate = pipe(add, multiply); 53 | let res = calculate(10); 54 | console.log(res); // 结果还是200 55 | ``` 56 | 57 | ```js 58 | const pipe = (...args) => (x) => args.reduce((res, cb) => cb(res), x); 59 | ``` 60 | 61 | ## 参考 62 | 63 | - [compose 函数和 pipe 函数](http://dennisgo.cn/Articles/JavaScript/ComposePipe.html) 64 | -------------------------------------------------------------------------------- /docs/algorithm/岛屿/200. 岛屿数量.md: -------------------------------------------------------------------------------- 1 | 岛屿系列题目的核心考点就是用 DFS/BFS 算法遍历二维数组。 2 | 3 | ```js 4 | // 二叉树遍历框架 5 | void traverse(TreeNode root) { 6 | traverse(root.left); 7 | traverse(root.right); 8 | } 9 | 10 | // 二维矩阵遍历框架 11 | void dfs(int[][] grid, int i, int j, boolean[][] visited) { 12 | int m = grid.length, n = grid[0].length; 13 | if (i < 0 || j < 0 || i >= m || j >= n) { 14 | // 超出索引边界 15 | return; 16 | } 17 | if (visited[i][j]) { 18 | // 已遍历过 (i, j) 19 | return; 20 | } 21 | // 进入节点 (i, j) 22 | visited[i][j] = true; 23 | dfs(grid, i - 1, j, visited); // 上 24 | dfs(grid, i + 1, j, visited); // 下 25 | dfs(grid, i, j - 1, visited); // 左 26 | dfs(grid, i, j + 1, visited); // 右 27 | } 28 | 29 | ``` 30 | 31 | ## 代码 32 | 33 | ```js 34 | /** 35 | * @param {character[][]} grid 36 | * @return {number} 37 | */ 38 | var numIslands = function(grid) { 39 | let m = grid.length; 40 | let n = grid[0].length; 41 | let res = 0; 42 | for (let i = 0; i < m; i++) { 43 | for (let j = 0; j < n; j++) { 44 | if (grid[i][j] === '1') { 45 | res++; 46 | dfs(grid, i, j); 47 | } 48 | } 49 | } 50 | // 把坐标为i,j的上下左右元素染色 51 | function dfs(grid, i, j) { 52 | // 超出边界直接返回 53 | if (i < 0 || j < 0 || i >= m || j >= n) { 54 | return; 55 | } 56 | if (grid[i][j] === '0') { 57 | return; 58 | } 59 | grid[i][j] = '0'; 60 | dfs(grid, i - 1, j); 61 | dfs(grid, i + 1, j); 62 | dfs(grid, i, j - 1); 63 | dfs(grid, i, j + 1); 64 | } 65 | return res; 66 | }; 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/algorithm/树/相同的树.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给定两个二叉树,编写一个函数来检验它们是否相同。 4 | 5 | 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 6 | 7 | 示例 1: 8 | ```js 9 | 输入: 1 1 10 | / \ / \ 11 | 2 3 2 3 12 | 13 | [1,2,3], [1,2,3] 14 | 15 | 输出: true 16 | 17 | ``` 18 | 示例 2: 19 | ```js 20 | 输入: 1 1 21 | / \ 22 | 2 2 23 | 24 | [1,2], [1,null,2] 25 | 26 | 输出: false 27 | 28 | ``` 29 | 示例 3: 30 | ```js 31 | 输入: 1 1 32 | / \ / \ 33 | 2 1 1 2 34 | 35 | [1,2,1], [1,1,2] 36 | 37 | 输出: false 38 | 39 | ``` 40 | ## 思路分析 41 | 使用深度优先遍历; 42 | 43 | 1. 两棵树的当前节点都为null时,返回true 44 | 2. 当其中一个是null,另外一个不是 null时,返回 false 45 | 3. 当两个都不为null,但是值不相等时,返回 false 46 | 4. 上面情况都不满足,则不断递归两颗树的左右子树 47 | 48 | ```js 49 | /** 50 | * Definition for a binary tree node. 51 | * function TreeNode(val) { 52 | * this.val = val; 53 | * this.left = this.right = null; 54 | * } 55 | */ 56 | /** 57 | * @param {TreeNode} p 58 | * @param {TreeNode} q 59 | * @return {boolean} 60 | */ 61 | var isSameTree = function(p, q) { 62 | // 两棵树的当前节点都为null时,返回true 63 | if(p == null && q == null) { 64 | return true; 65 | } 66 | // 当其中一个是null,另外一个不是 null时,返回 false 67 | if(p == null || q == null) { 68 | return false 69 | } 70 | // 当两个都不为null,但是值不相等时,返回 false 71 | if(p.val !== q.val) { 72 | return false; 73 | } 74 | // 不断递归两颗树的左右子树 75 | return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); 76 | }; 77 | 78 | ``` 79 | ## 复杂度分析 80 | 81 | - 时间复杂度:O(n),n 为树的节点个数,因为每个节点否访问一次 82 | - 空间复杂度:最优情况(完全平衡二叉树)时为 O(log(N)),最坏情况下(完全不平衡二叉树)时为 O(N),用于维护递归栈。 -------------------------------------------------------------------------------- /docs/100day/文章/命令行参数.md: -------------------------------------------------------------------------------- 1 | ## node中可以通过process.argv来获取参数 2 | 1. 新建一个shell.js 3 | ```js 4 | console.log(process.argv); 5 | ``` 6 | 在终端输入 node shell.js 7 | ``` 8 | [ '/Users/wangyaxing/.nvm/versions/node/v10.7.0/bin/node', 9 | '/Users/wangyaxing/temp/vue-project/shell.js' ] 10 | ``` 11 | 在终端输入 node shell.js abc 12 | ```js 13 | [ '/Users/wangyaxing/.nvm/versions/node/v10.7.0/bin/node', 14 | '/Users/wangyaxing/temp/vue-project/shell.js', 15 | 'abc' ] 16 | ``` 17 | process.argv的用法是第一个是node文件, 第二个是脚本文件, 第三个是参数 18 | ## npm scripts(npm脚本)发送命令行参数 19 | ### 什么是npm scripts 20 | npm 允许在package.json文件里面,使用scripts字段定义脚本命令。 21 | ```js 22 | { 23 | // ... 24 | "scripts": { 25 | "build": "node build.js" 26 | } 27 | } 28 | ``` 29 | 上面代码是package.json文件的一个片段,里面的scripts字段是一个对象。它的每一个属性,对应一段脚本。比如,build命令对应的脚本是node build.js。 30 | ## 原理 31 | 执行npm run ,会自动创建一个shell, 在这个shell里面执行指定的脚本命令。 32 | 比较特别的是, 这个shell 会自动将当前目录下的node_modules/.bin子目录加入`PATH`,执行结束, 再将`PATH`变量恢复原样 33 | ## 通配符 34 | 由于 npm 脚本就是 Shell 脚本,因为可以使用 Shell 通配符。 35 | ```js 36 | "lint": "jshint *.js" 37 | "lint": "jshint **/*.js" 38 | ``` 39 | 上面代码中,*表示任意文件名,**表示任意一层子目录。 40 | 如果要将通配符传入原始命令,防止被 Shell 转义,要将星号转义。 41 | ```js 42 | "test": "tap test/\*.js" 43 | ``` 44 | ## 传参 45 | 将命令行参数发送到npm脚本: 46 | ```js 47 | npm run [command] [-- ] 48 | ``` 49 | 注意必要的`--`,需要将参数传递到npm命令本身,并将其传递给脚本。 50 | ## 使用webpack.DefinePlugin在打包时对文件中的变量进行替换 51 | ```js 52 | plugins: [ 53 | new webpack.DefinePlugin({ 54 | 'domain': process.argv[2] 55 | }), 56 | } 57 | ``` 58 | ## 参考 59 | [npm scripts 使用指南](http://www.ruanyifeng.com/blog/2016/10/npm_scripts.html) 60 | [如何向npm脚本发送命令行参数?](https://cloud.tencent.com/developer/ask/50047) 61 | [编译环境中的几种传参方法](https://crossjae.github.io/2018/01/14/npmpackage/) -------------------------------------------------------------------------------- /docs/algorithm/树/297. 二叉树的序列化与反序列化.md: -------------------------------------------------------------------------------- 1 | ## 297. 二叉树的序列化与反序列化 2 | 3 | 序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。 4 | 5 | 请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。 6 | 7 | 提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅  LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。 8 | 9 | 来源:力扣(LeetCode) 10 | 链接:https://leetcode.cn/problems/serialize-and-deserialize-binary-tree 11 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 12 | 13 | ## 题解 14 | 15 | 其实就是考察二叉树的遍历方式。 16 | 二叉树的遍历方式: 17 | 18 | - 递归:前序遍历、中序遍历、后序遍历 19 | - 迭代:层序遍历 20 | 21 | 1. 前序遍历 22 | 23 | ```js 24 | var serialize = function(root) { 25 | if (root === null) { 26 | return '#'; 27 | } 28 | const left = serialize(root.left); 29 | const right = serialize(root.right); 30 | return root.val + ',' + left + ',' + right; 31 | }; 32 | 33 | /** 34 | * Decodes your encoded data to tree. 35 | * 36 | * @param {string} data 37 | * @return {TreeNode} 38 | */ 39 | var deserialize = function(data) { 40 | if (data.length === 0) { 41 | return null; 42 | } 43 | const arr = data.split(','); 44 | return buildTreee(arr); 45 | function buildTreee(nodes) { 46 | if (nodes.length === 0) { 47 | return null; 48 | } 49 | 50 | const firstNode = nodes.shift(); 51 | if (firstNode === '#') { 52 | return null; 53 | } 54 | const root = new TreeNode(firstNode); 55 | root.left = buildTreee(nodes); 56 | root.right = buildTreee(nodes); 57 | return root; 58 | } 59 | }; 60 | 61 | /** 62 | * Your functions will be called as such: 63 | * deserialize(serialize(root)); 64 | */ 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/algorithm/链表/回文链表.md: -------------------------------------------------------------------------------- 1 | 2 | JavaScript实现leetcode234. 回文链表 3 | 4 | ## 题目描述 5 | 请判断一个链表是否为回文链表。 6 | 7 | 示例 1: 8 | ```js 9 | 输入: 1->2 10 | 输出: false 11 | 示例 2: 12 | 13 | 输入: 1->2->2->1 14 | 输出: true 15 | ``` 16 | 进阶: 17 | 你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题? 18 | 19 | ## 思路分析 20 | 避免使用 O(n)额外空间的方法就是改变输入。 21 | 我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。 22 | 23 | 方法步骤: 24 | 1. 找到前半部分链表的尾节点。同时反转前半部分链表。 25 | - 快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针到链表的中间。通过慢指针将链表分为两部分。 26 | - 若链表有奇数个节点,则中间的节点应该看作是前半部分。 27 | 2. 判断是否为回文。 28 | - 比较两个部分的值,当后半部分到达末尾则比较完成 29 | 30 | ## 解题方法 31 | 32 | ```js 33 | /** 34 | * Definition for singly-linked list. 35 | * function ListNode(val) { 36 | * this.val = val; 37 | * this.next = null; 38 | * } 39 | */ 40 | /** 41 | * @param {ListNode} head 42 | * @return {boolean} 43 | */ 44 | var isPalindrome = function(head) { 45 | if(head === null || head.next === null) { 46 | return true; 47 | } 48 | let mid = head; 49 | let pre = null; 50 | let reversed = null; 51 | // 快慢指针找出中间节点 52 | while(head !== null && head.next !== null) { 53 | pre = mid; 54 | mid = mid.next; 55 | head = head.next.next; 56 | // 反转前半段 57 | pre.next = reversed; 58 | reversed = pre; 59 | } 60 | // 如果是奇数个 61 | if(head) { 62 | mid = mid.next; 63 | } 64 | // 将反转完之后的前半段和后半段做对比 65 | while(mid) { 66 | if(reversed.val !== mid.val) { 67 | return false; 68 | } 69 | reversed = reversed.next; 70 | mid = mid.next; 71 | } 72 | return true; 73 | }; 74 | ``` 75 | ## 复杂度分析 76 | 77 | - 时间复杂度:O(n),其中 nn 指的是链表的大小。 78 | - 空间复杂度:O(1),我们是一个接着一个的改变指针,我们在堆栈上的堆栈帧不超过 O(1)。 -------------------------------------------------------------------------------- /docs/algorithm/回溯/698. 划分为k个相等的子集.md: -------------------------------------------------------------------------------- 1 | ## 经典回溯算法:集合划分问题 2 | 3 | 使用回溯算法 4 | 5 | ```js 6 | // @lc code=start 7 | /** 8 | * @param {number[]} nums 9 | * @param {number} k 10 | * @return {boolean} 11 | */ 12 | var canPartitionKSubsets = function(nums, k) { 13 | if (k > nums.length) { 14 | return false; 15 | } 16 | let sum = 0; 17 | for (let i = 0; i < nums.length; i++) { 18 | sum += nums[i]; 19 | } 20 | if (sum % k !== 0) { 21 | return false; 22 | } 23 | let bucket = new Array(k).fill(0); 24 | // 理论上每个集合的数字和 25 | let target = sum / k; 26 | // 提前对 nums 数组排序,把大的数字排在前面,那么大的数字会先被分配到 bucket 中,对于之后的数字,bucket[i] + nums[index] 会更大,更容易触发剪枝的 if 条件。 27 | nums.sort((a, b) => b - a); 28 | function backtrack(bucket, index) { 29 | if (index === nums.length) { 30 | // 检查所有桶的数字之和是否都是 target 31 | for (let i = 0; i < bucket.length; i++) { 32 | if (bucket[i] !== target) { 33 | return false; 34 | } 35 | } 36 | // nums 成功平分成 k 个子集 37 | return true; 38 | } 39 | for (let i = 0; i < bucket.length; i++) { 40 | // 剪枝,桶装装满了 41 | if (bucket[i] + nums[index] > target) { 42 | continue; 43 | } 44 | // 将 nums[index] 装入 bucket[i] 45 | bucket[i] += nums[index]; 46 | // 递归穷举下一个数字的选择 47 | if (backtrack(bucket, index + 1)) { 48 | return true; 49 | } 50 | // 撤销选择 51 | bucket[i] -= nums[index]; 52 | } 53 | return false; 54 | } 55 | return backtrack(bucket, 0); 56 | }; 57 | // @lc code=end 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/interview/CSS/CSS基础知识点.md: -------------------------------------------------------------------------------- 1 | ## CSS盒模型 2 | 每个盒子由四个部分(或称区域)组成: 3 | - 内容边界 Content edge、 4 | - 内边距边界 Padding Edge、 5 | - 边框边界 Border Edge、 6 | - 外边框边界 Margin Edge 7 | 8 | ![img](https://www.runoob.com/images/box-model.gif) 9 | 10 | box-sizing 属性定义了 user agent 应该如何计算一个元素的总宽度和总高度。 11 | - content-box 是默认值(标准盒子模型) 12 | - width = 内容的宽度 13 | - height = 内容的高度 14 | - .box {width: 350px; border: 10px solid black;} 在浏览器中的渲染的实际宽度将是 370px。 15 | - border-box 告诉浏览器:你想要设置的边框和内边距的值是包含在width内的, 16 | - width = border + padding + 内容的宽度 17 | - height = border + padding + 内容的高度 18 | - .box {width: 350px; border: 10px solid black;} 导致在浏览器中呈现的宽度为350px的盒子。 19 | 20 | ## 行内元素和块级元素的区别: 21 | 行内元素(display:block): 22 | - 只占据它对应标签的边框所包含的空间。 23 | - 行内元素不能包含块级元素,只能包含文本或者其它行内元素。 24 | - 盒模型的属性方面: 25 | - 行内元素设置width无效,height无效(可以设置line-height),margin上下无效,padding上下无效 26 | 27 | 块级元素(display:inline; ): 28 | - 独占一行,垂直方向排列,块级元素占据其父元素(容器)的整个空间, 29 | - 块级元素可以包含行内元素和块级元素 30 | 31 | 行内块元素(dispaly: inline-block): 32 | - 如input、img)既具有 block 元素可以设置宽高的特性,同时又具有 inline 元素默认不换行的特性。 33 | - inline-block 元素也可以设置 vertical-align(因为这个垂直对齐属性只对设置了inline-block的元素有效) 属性。 34 | 35 | ## position有哪些值,分别是什么含义 36 | 37 | CSS position属性用于指定一个元素在文档中的定位方式。 38 | - static 39 | - 默认,即元素在文档常规流中当前的布局位置 40 | - relative 41 | - 未脱离文档流 42 | 43 | - absolute 44 | - 相对定位的元素并未脱离文档流,而绝对定位的元素则脱离了文档流。在布置文档流中其它元素时,绝对定位元素不占据空间。绝对定位元素相对于最近的非 static 祖先元素定位。 45 | 46 | - fixed 47 | - 脱离文档流 48 | - 过指定元素相对于屏幕视口(viewport)的位置来指定元素位置 49 | - fixed 属性会创建新的层叠上下文。当元素祖先的 transform, perspective 或 filter 属性非 none 时,容器由视口改为该祖先。 50 | - sticky 51 | - 该值总是创建一个新的层叠上下文(stacking context), 一个sticky元素会“固定”在离它最近的一个拥有“滚动机制”的祖先上(当该祖先的overflow 是 hidden, scroll, auto, 或 overlay时),即便这个祖先不是真的滚动祖先。 -------------------------------------------------------------------------------- /docs/algorithm/回溯/51.N皇后.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /* 3 | * @lc app=leetcode.cn id=51 lang=javascript 4 | * 5 | * [51] N 皇后 6 | */ 7 | 8 | // @lc code=start 9 | /** 10 | * @param {number} n 11 | * @return {string[][]} 12 | */ 13 | var solveNQueens = function(n) { 14 | // '.' 表示空,'Q' 表示皇后,初始化空棋盘。 15 | const board = new Array(n).fill(0).map(() => new Array(n).fill('.')); 16 | const res = []; 17 | backtrack(0); 18 | return res; 19 | 20 | function isValid(row, col) { 21 | // 检查列是否有皇后互相冲突 22 | for (let i = 0; i < n; i++) { 23 | if (board[i][col] === 'Q') return false; 24 | } 25 | // 检查右上方是否有皇后互相冲突 26 | for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { 27 | if (board[i][j] === 'Q') return false; 28 | } 29 | // 检查左上方是否有皇后互相冲突 30 | for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { 31 | if (board[i][j] === 'Q') return false; 32 | } 33 | return true; 34 | } 35 | 36 | // 路径:board 中小于 row 的那些行都已经成功放置了皇后 37 | // 选择列表:第 row 行的所有列都是放置皇后的选择 38 | // 结束条件:row 超过 board 的最后一行 39 | function backtrack(row) { 40 | // 触发结束条件; 41 | if (row === board.length) { 42 | res.push(board.map((row) => row.join(''))); 43 | return; 44 | } 45 | 46 | for (let col = 0; col < n; col++) { 47 | // 排除不合法选择; 48 | if (!isValid(row, col)) { 49 | continue; 50 | } 51 | // 做选择; 52 | board[row][col] = 'Q'; 53 | // 进入下一行决策; 54 | backtrack(row + 1); 55 | // 撤销选择; 56 | board[row][col] = '.'; 57 | } 58 | } 59 | }; 60 | // @lc code=end 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/algorithm/数组/有效的山脉数组.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定一个整数数组 A,如果它是有效的山脉数组就返回 true,否则返回 false。 3 | 让我们回顾一下,如果 A 满足下述条件,那么它是一个山脉数组: 4 | ```js 5 | A.length >= 3 6 | 在 0 < i < A.length - 1 条件下,存在 i 使得: 7 | A[0] < A[1] < ... A[i-1] < A[i] 8 | A[i] > A[i+1] > ... > A[B.length - 1] 9 | ``` 10 | 示例 1: 11 | ```js 12 | 输入:[2,1] 13 | 输出:false 14 | ``` 15 | 示例 2: 16 | ```js 17 | 输入:[3,5,5] 18 | 输出:false 19 | ``` 20 | 示例 3: 21 | ```js 22 | 输入:[0,3,2,1] 23 | 输出:true 24 | ``` 25 | 26 | > 提示: 27 | 0 <= A.length <= 10000 28 | 0 <= A[i] <= 10000 29 | 30 | ## 解题思路 31 | 首先解读题目中山脉数组的定义:长度大于3,且先递增后递减的数组。 32 | 33 | 具体解决思路 34 | 1. 找到数组中最大值所在位置的索引和对应的值 35 | 2. 判断最大值索引是否大于0且小于数组长度-1(处理无法递增或者递减的情况) 36 | 3. 判断数组是否先递增到最大值索引,然后从最大值索引一直递减 37 | 38 | ## 代码实现 39 | ```js 40 | /** 41 | * @param {number[]} A 42 | * @return {boolean} 43 | */ 44 | var validMountainArray = function(A) { 45 | if(A.length < 3) { 46 | return false; 47 | } 48 | let max = A[0]; 49 | let maxIndex = 0; 50 | for(let i = 1; i < A.length; i++) { 51 | let a = A[i]; 52 | if(a > max) { 53 | max = a; 54 | maxIndex = i; 55 | } 56 | } 57 | if(maxIndex > 0 && maxIndex < A.length - 1) { 58 | let isIncrease = true; 59 | let isDecrease = true; 60 | for(let i = 0; i < maxIndex; i++) { 61 | if(A[i] > A[i+1]) { 62 | isIncrease = false; 63 | break; 64 | } 65 | } 66 | for(let i = maxIndex; i < A.length - 1; i++) { 67 | if(A[i] <= A[i+1]) { 68 | isDecrease = false; 69 | break; 70 | } 71 | } 72 | return isIncrease && isDecrease; 73 | } else { 74 | return false; 75 | } 76 | }; 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/字符串相乘.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。 3 | 4 | 示例 1: 5 | ```js 6 | 输入: num1 = "2", num2 = "3" 7 | 输出: "6" 8 | ``` 9 | 示例 2: 10 | ```js 11 | 输入: num1 = "123", num2 = "456" 12 | 输出: "56088" 13 | ``` 14 | 说明: 15 | 16 | 1. num1 和 num2 的长度小于110。 17 | 2. num1 和 num2 只包含数字 0-9。 18 | 3. num1 和 num2 均不以零开头,除非是数字 0 本身。 19 | 4. 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。 20 | 21 | ## 解题方法 22 | 23 | 最开始的思路是把字符串分别转成数字,然后相乘,会出现数字溢出的情况。同时题目要求不能使用现成的api来处理。所以放弃这个一开始就能想到的思路。 24 | 25 | 分析竖式相乘的步骤,转成代码的思维来解决。 26 | 27 | num1的第 i 位(高位从0开始 和 num2 的第 j 位相乘的结果在乘积中的位置是 [i + j, i+ j + 1]; 28 | 29 | 例如 123 45, 123的第1位 2和 45的第0位4乘积 08存放在结果的第 [1, 2]位中 30 | ```js 31 | index: 0 1 2 3 4 32 | 33 | 1 2 3 34 | * 4 5 35 | --------- 36 | 1 5 37 | 1 0 38 | 0 5 39 | --------- 40 | 0 6 1 5 41 | 1 2 42 | 0 8 43 | 0 4 44 | ---------- 45 | 0 5 5 3 5 46 | ``` 47 | 这样我们就可以单独对每一位进行相乘计算把结果存入相应的index中 48 | ```js 49 | /** 50 | * @param {string} num1 51 | * @param {string} num2 52 | * @return {string} 53 | 54 | * 55 | */ 56 | var multiply = function(num1, num2) { 57 | if(num1 === '0' || num2 === '0') { 58 | return '0'; 59 | } 60 | let m = num1.length; 61 | let n = num2.length; 62 | let res = new Array(m + n).fill(0); 63 | for(let i = m - 1; i >= 0; i--) { 64 | for(let j = n - 1; j >= 0; j--) { 65 | let sum = res[ i + j + 1] + +num1[i] * +num2[j] 66 | res[ i + j + 1] = sum % 10; 67 | res[ i + j ] += (sum / 10) | 0; 68 | 69 | } 70 | } 71 | // 如果最开始有0,则需要移除 72 | if(res[0] === 0) { 73 | res.shift(); 74 | } 75 | return res.join(''); 76 | }; 77 | ``` -------------------------------------------------------------------------------- /docs/algorithm/树/二叉搜索树/二叉搜索树迭代器.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。 4 | 5 | 调用 next() 将返回二叉搜索树中的下一个最小的数。 6 | 7 | 示例: 8 | 9 | ```js 10 | 11 | BSTIterator iterator = new BSTIterator(root); 12 | iterator.next(); // 返回 3 13 | iterator.next(); // 返回 7 14 | iterator.hasNext(); // 返回 true 15 | iterator.next(); // 返回 9 16 | iterator.hasNext(); // 返回 true 17 | iterator.next(); // 返回 15 18 | iterator.hasNext(); // 返回 true 19 | iterator.next(); // 返回 20 20 | iterator.hasNext(); // 返回 false 21 |   22 | ``` 23 | 提示: 24 | 25 | next() 和 hasNext() 操作的时间复杂度是 O(1),并使用 O(h) 内存,其中 h 是树的高度。 26 | 你可以假设 next() 调用总是有效的,也就是说,当调用 next() 时,BST 中至少存在一个下一个最小的数。 27 | 28 | 29 | ## 解题方法 30 | 31 | 使用中序遍历: 32 | 二叉搜索树的一个重要的特性是是二叉搜索树的中序序列是升序序列 33 | 34 | 1. 初始化一个空数组用来存放二叉搜索树的中序序列。 35 | 2. 我们按中序遍历二叉搜索树,按照左中右的顺序处理节点。 36 | 37 | ```js 38 | /** 39 | * Definition for a binary tree node. 40 | * function TreeNode(val) { 41 | * this.val = val; 42 | * this.left = this.right = null; 43 | * } 44 | */ 45 | /** 46 | * @param {TreeNode} root 47 | */ 48 | var BSTIterator = function(root) { 49 | // 初始化一个空数组用来存放二叉搜索树的中序序列。 50 | this.values = []; 51 | // 我们按中序遍历二叉搜索树,按照左中右的顺序处理节点。 52 | const preOrder = (root) => { 53 | if(root) { 54 | preOrder(root.left); 55 | this.values.push(root.val); 56 | preOrder(root.right); 57 | } 58 | } 59 | preOrder(root); 60 | }; 61 | 62 | /** 63 | * @return the next smallest number 64 | * @return {number} 65 | */ 66 | BSTIterator.prototype.next = function() { 67 | return this.values.shift(); 68 | }; 69 | 70 | /** 71 | * @return whether we have a next smallest number 72 | * @return {boolean} 73 | */ 74 | BSTIterator.prototype.hasNext = function() { 75 | return this.values.length > 0; 76 | }; 77 | 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/algorithm/数学/中位数.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。 3 | 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。 4 | 你可以假设 nums1 和 nums2 不会同时为空。 5 | 6 | 示例 1: 7 | ```js 8 | nums1 = [1, 3] 9 | nums2 = [2] 10 | 11 | 则中位数是 2.0 12 | ``` 13 | 14 | 示例 2: 15 | 16 | ```js 17 | nums1 = [1, 2] 18 | nums2 = [3, 4] 19 | 20 | 则中位数是 (2 + 3)/2 = 2.5 21 | ``` 22 | ## 解题方法 23 | 24 | ## 1. O(nlogn) 25 | 26 | 将两个数组合并成一个, 然后排序(JavaScript数组内置的sort函数), 复杂度O(nlogn), 最后根据数组的长度选择中位数 27 | ```js 28 | /** 29 | * @param {number[]} nums1 30 | * @param {number[]} nums2 31 | * @return {number} 32 | */ 33 | var findMedianSortedArrays = function(nums1, nums2) { 34 | const nums = nums1.concat(nums2); 35 | nums.sort(function(a, b) { 36 | return a - b; 37 | }); 38 | let len = nums.length; 39 | if (len % 2 === 0) { 40 | return (nums[len / 2] + nums[len / 2 - 1]) / 2; 41 | } else { 42 | return nums[(len - 1) / 2]; 43 | } 44 | }; 45 | ``` 46 | ### 2. O(n) 47 | 将两个有序的数组合并成一个有序的数组, 每个数组都已经是单独排过序的, 使用归并排序封装一个merge函数, 剩下思路上上述一样 48 | ```js 49 | /** 50 | * @param {number[]} nums1 51 | * @param {number[]} nums2 52 | * @return {number} 53 | */ 54 | var findMedianSortedArrays = function (nums1, nums2) { 55 | const merge = function (left, right) { 56 | var temp = []; 57 | while (left.length && right.length) { 58 | if (left[0] < right[0]) { 59 | temp.push(left.shift()); 60 | } else { 61 | temp.push(right.shift()); 62 | } 63 | } 64 | return temp.concat(left, right); 65 | }; 66 | let nums = merge(nums1, nums2); 67 | let len = nums.length; 68 | if (len % 2 === 0) { 69 | return (nums[len / 2] + nums[len / 2 - 1]) / 2; 70 | } else { 71 | return nums[(len - 1) / 2]; 72 | } 73 | }; 74 | ``` -------------------------------------------------------------------------------- /docs/jsCode/节流.md: -------------------------------------------------------------------------------- 1 | ## 函数节流(throttle) 2 | 函数节流:不管事件触发频率有多高,只在单位时间内执行一次。 3 | 4 | 有两种思路实现: 使用时间戳和定时器 5 | 6 | ### 使用时间戳 7 | 8 | ```js 9 | function throttle(fn, wait) { 10 | // 记录上一次执行的时间戳 11 | let previous = 0; 12 | return function(...args) { 13 | // 当前的时间戳,然后减去之前的时间戳,大于设置的时间间隔,就执行函数,否则不执行 14 | if(Date.now() - previous > wait) { 15 | // 更新上一次的时间戳为当前时间戳 16 | previous = Date.now(); 17 | fn.apply(this, args); 18 | } 19 | } 20 | } 21 | ``` 22 | 第一次事件肯定触发,最后一次不会触发(比如说监听 onmousemove,则鼠标停止移动时,立即停止触发事件) 23 | 24 | ### 使用定时器 25 | ```js 26 | function throttle(fn, wait) { 27 | // 设置一个定时器 28 | let timer = null; 29 | return function(...args) { 30 | // 判断如果定时器不存在就执行,存在则不执行 31 | if(!timer) { 32 | // 设置下一个定时器 33 | timer = setTimeout(() => { 34 | // 然后执行函数,清空定时器 35 | timer = null; 36 | fn.apply(this, args) 37 | }, wait) 38 | } 39 | } 40 | } 41 | ``` 42 | 第一次事件不会触发(fn是放在 setTimeout中执行的,所以第一次触发事件至少等待 wait 毫秒之后才执行),最后一次一定触发 43 | 44 | ### 定时器和时间戳结合 45 | 46 | 两者结合可以实现,第一次事件会触发,最后一次事件也会触发 47 | ```js 48 | function throttle(fn, wait) { 49 | // 记录上一次执行的时间戳 50 | let previous = 0; 51 | // 设置一个定时器 52 | let timer = null; 53 | return function(...args) { 54 | // 当前的时间戳,然后减去之前的时间戳,大于设置的时间间隔 55 | if(Date.now() - previous > wait) { 56 | clearTimeout(timer); 57 | timer = null 58 | // 更新上一次的时间戳为当前时间戳 59 | previous = Date.now(); 60 | fn.apply(this, args); 61 | } else if(!timer) { 62 | // 设置下一个定时器 63 | timer = setTimeout(() => { 64 | timer = null; 65 | fn.apply(this, args) 66 | }, wait) 67 | } 68 | } 69 | } 70 | ``` -------------------------------------------------------------------------------- /docs/book/1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | webComponent测试 7 | 8 | 9 | 24 | 38 |
39 | 测试自定义模板 40 | 41 |
42 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /docs/algorithm/二分查找/33. 搜索旋转排序数组.md: -------------------------------------------------------------------------------- 1 | ### 33. 搜索旋转排序数组 2 | 3 | 整数数组 nums 按升序排列,数组中的值 互不相同 。 4 | 5 | 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。 6 | 7 | 给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。 8 | 9 | 示例 1: 10 | 11 | 输入:nums = [4,5,6,7,0,1,2], target = 0 12 | 输出:4 13 | 示例 2: 14 | 15 | 输入:nums = [4,5,6,7,0,1,2], target = 3 16 | 输出:-1 17 | 示例 3: 18 | 19 | 输入:nums = [1], target = 0 20 | 输出:-1 21 | 22 | ### 代码 23 | 24 | 关键词:排序数组,可以使用二分 25 | 26 | ```js 27 | /** 28 | * @param {number[]} nums 29 | * @param {number} target 30 | * @return {number} 31 | */ 32 | var search = function(nums, target) { 33 | let i = 0; 34 | let j = nums.length - 1; 35 | while (i <= j) { 36 | let mid = Math.floor(i + (j - i) / 2); 37 | if (nums[mid] === target) { 38 | return mid; 39 | } 40 | if (nums[i] === target) { 41 | return i; 42 | } 43 | if (nums[j] === target) { 44 | return j; 45 | } 46 | 47 | if (nums[i] < nums[mid]) { 48 | // 说明 i 到mid是有序的 49 | if (nums[i] < target && target < nums[mid]) { 50 | j = mid - 1; 51 | } else { 52 | i = mid + 1; 53 | } 54 | } else { 55 | // mid 到 j有序的 56 | if (nums[mid] < target && target < nums[j]) { 57 | i = mid + 1; 58 | } else { 59 | j = mid - 1; 60 | } 61 | } 62 | } 63 | return -1; 64 | }; 65 | ``` 66 | 67 | 相似题目: 68 | 69 | - [剑指 Offer 11. 旋转数组的最小数字](https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/) 70 | - 「153. 寻找旋转排序数组中的最小值」 71 | - 「154. 寻找旋转排序数组中的最小值 II」 72 | -------------------------------------------------------------------------------- /docs/algorithm/数组/两句话中的不常见单词.md: -------------------------------------------------------------------------------- 1 | # JavaScript实现LeetCode第884题:两句话中的不常见单词 2 | ## 题目描述 3 | 给定两个句子 A 和 B 。(句子是一串由空格分隔的单词。每个单词仅由小写字母组成。) 4 | 5 | 如果一个单词在其中一个句子中只出现一次,在另一个句子中却没有出现,那么这个单词就是不常见的。 6 | 7 | 返回所有不常用单词的列表。 8 | 9 | 您可以按任何顺序返回列表。 10 | 11 | 示例 1: 12 | ```js 13 | 输入:A = "this apple is sweet", B = "this apple is sour" 14 | 输出:["sweet","sour"] 15 | ``` 16 | 示例 2: 17 | ```js 18 | 输入:A = "apple apple", B = "banana" 19 | 输出:["banana"] 20 | ``` 21 | ## 思路分析 22 | 仔细分析一下其实就是, 先把这两个参数转换成数组, 把这两个数组合并, 找出只出现一次的元素. 23 | 24 | ### 思路一: 25 | 遍历转换后的数组A和数组,找出只出现一次的元素 26 | ### 思路二: 27 | 将两个数组合并,新建一个Map, 里面的key是当前元素, value为出现的次数, 最后统计次数为1的元素 28 | 29 | ## 解决方法 30 | 方法一 31 | ```js 32 | /** 33 | * @param {string} A 34 | * @param {string} B 35 | * @return {string[]} 36 | */ 37 | function checkout(paramA, arrB, A) { 38 | let arrA = paramA.concat(); 39 | let result = []; 40 | for(let i = 0; i < arrA.length; i++) { 41 | let a = arrA[i]; 42 | arrA.splice(i, 1); 43 | if(!arrA.includes(a) && !arrB.includes(a) ) { 44 | result.push(a); 45 | } 46 | arrA = paramA.concat(); 47 | } 48 | return result; 49 | } 50 | var uncommonFromSentences = function(A, B) { 51 | let arrA = A.split(' '); 52 | let arrB = B.split(' '); 53 | return checkout(arrA, arrB, A).concat(checkout(arrB, arrA, B)); 54 | }; 55 | ``` 56 | 方法二 57 | ```js 58 | var uncommonFromSentences = function(A, B) { 59 | let map = new Map(); 60 | A.split(' ').concat(B.split(' ')).forEach(item => { 61 | if(map.has(item)) { 62 | map.set(item, map.get(item) + 1); 63 | } else { 64 | map.set(item, 1); 65 | } 66 | }); 67 | let result = []; 68 | map.forEach((value, key, map) => { 69 | if(value == 1) { 70 | result.push(key); 71 | } 72 | }) 73 | return result; 74 | }; 75 | ``` 76 | 77 | -------------------------------------------------------------------------------- /docs/jsCode/实现flat.md: -------------------------------------------------------------------------------- 1 | ## 实现 flat 2 | 3 | 具体可以参考 MDN 上的 [Array.prototype.flat()的替代方案](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/flat#%E6%9B%BF%E4%BB%A3%E6%96%B9%E6%A1%88) 4 | 5 | 1. 直接使用递归实现 6 | 7 | ```js 8 | const a = [1, [2, [3, [4, 5]]]]; 9 | const flat = (arr) => { 10 | let result = []; 11 | for (let i = 0; i < arr.length; i++) { 12 | if (Array.isArray(arr[i])) { 13 | result = result.concat(flat(arr[i])); 14 | } else { 15 | result.push(arr[i]); 16 | } 17 | } 18 | return result; 19 | }; 20 | console.log(flat(a)); 21 | ``` 22 | 23 | 2. 使用 reduce 24 | 25 | ```js 26 | const flat = (arr) => { 27 | return arr.reduce((pre, cur) => { 28 | return pre.concat(Array.isArray(cur) ? flat(cur) : cur); 29 | }, []); 30 | }; 31 | ``` 32 | 33 | 通过传入整数参数控制“拉平”层数 34 | 35 | ```js 36 | // 使用 reduce、concat 和递归展开无限多层嵌套的数组 37 | var arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]]; 38 | function flatDeep(arr, d = 1) { 39 | return d > 0 40 | ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), []) 41 | : arr.slice(); 42 | } 43 | flatDeep(arr1, Infinity); 44 | // [1, 2, 3, 1, 2, 3, 4, 2, 3, 4] 45 | ``` 46 | 47 | 3. 拓展运算符 48 | 49 | ```js 50 | const flatten = (arr) => { 51 | while (arr.some((item) => Array.isArray(item))) { 52 | arr = [].concat(...arr); 53 | } 54 | return arr; 55 | }; 56 | console.log(flatten(a)); 57 | ``` 58 | 59 | 4. split 和 toString 共同处理 60 | 61 | ```js 62 | const flat = (arr) => { 63 | return arr.toString().split(','); 64 | }; 65 | console.log(flat(a)); 66 | ``` 67 | 68 | ## 参考 69 | 70 | - [数组拍平(扁平化) flat 方法实现](https://segmentfault.com/a/1190000021366004) 71 | - [MDN-Array.prototype.flat()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) 72 | -------------------------------------------------------------------------------- /docs/algorithm/数组/分割数组为连续子序列.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 思路分析: 这道题得采用贪心策略,就是使序列尽可能的长。蛋式这种策略好像给人一种错误的感觉, 5 | 6 | 比如[1,2,3,3,4,5],如果采用此策略,将会是[1,2,3,4,5]和剩余的[3]。其实这个策略并不是这么简单的,比如它扫描到’1’的时候,由于它的前一个元素’0’不存在以’0’结尾的连续子序列,所以它这是向后寻找两个元素,凑成子序列[1,2,3](这时1,2,3各被消耗了一个)。接着我们就应该访问到’3’,我们又去查询以’2’结尾的有没有合法的连续序列,但是没有,所以它此时只能向后寻找两个元素,凑出连续子序列[3,4,5](3,4,5个被消耗了一次),结束访问。 7 | 8 | 9 | ```js 10 | 如果输入[1,2,3,3,4,4,5,5],刚开始访问'1',建立[1,2,3], 11 | 接着访问'3',建立[3,4,5] 12 | 接着访问'4',由于第一步建立了[1,2,3]以4 - 1结尾的连续子序列,所以它放入,得到[1,2,3,4] 13 | 接着访问'5',由于第一步建立了[1,2,3,4]以5 - 1结尾的连续子序列,所以它放入,得到[1,2,3,4,5] 14 | ``` 15 | 16 | ```js 17 | /** 18 | * @param {number[]} nums 19 | * @return {boolean} 20 | */ 21 | //具体思路看题解区 22 | var isPossible = function(nums) { 23 | let counter = new Array(10000).fill(0); 24 | let end = new Array(10000).fill(0); 25 | 26 | //以上实现两个hash(网上实现hash太费劲了,取了个巧,浪费了点空间) 27 | 28 | //先把每种牌的总数记下 29 | nums.forEach((item) => { 30 | counter[item]++; 31 | }); 32 | 33 | //以下采取贪心策略: 34 | for (let i = 0; i < nums.length; i++) { 35 | let item = nums[i]; 36 | if (counter[item] === 0) { 37 | //这种牌没得了,直接下一个 38 | continue; 39 | } 40 | counter[item]--; 41 | 42 | if (end[item - 1] > 0) { 43 | //可以续上先续上 44 | end[item - 1]--; 45 | end[item]++; 46 | } 47 | 48 | //因为是升序排列,所以如果有[1,2,3,4,4,5,6]时 49 | //先是4接上[1,2,3],然后另外一个4连上后头的,组成[4,5,6] 50 | //关键在数组已经按升序排序 51 | else if (counter[item + 1] > 0 && counter[item + 2] > 0) { 52 | counter[item + 1]--; 53 | counter[item + 2]--; 54 | end[item + 2]++; 55 | } else { 56 | return false; 57 | } 58 | } 59 | return true; 60 | }; 61 | ``` 62 | 63 | 64 | ## 65 | 66 | - [LeetCode 分割数组为连续子序列(贪心策略、hash表)](https://blog.csdn.net/qq_41855420/article/details/89378748) -------------------------------------------------------------------------------- /docs/algorithm/数组/缺失的第一个正数.md: -------------------------------------------------------------------------------- 1 | [41. 缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) 2 | 3 | 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。 4 | 5 | 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 6 | 7 | 示例 1: 8 | 9 | 输入:nums = [1,2,0] 10 | 输出:3 11 | 示例 2: 12 | 13 | 输入:nums = [3,4,-1,1] 14 | 输出:2 15 | 示例 3: 16 | 17 | 输入:nums = [7,8,9,11,12] 18 | 输出:1 19 | 20 | ## 题解 21 | 22 | 1. 使用 Set,时间复杂度和空间复杂度都为 O(n), 题目要求空间复杂度为 O(1) 23 | 24 | ```js 25 | /** 26 | * @param {number[]} nums 27 | * @return {number} 28 | */ 29 | var firstMissingPositive = function(nums) { 30 | let set = new Set(); 31 | for (let i = 0; i < nums.length; i++) { 32 | set.add(nums[i]); 33 | } 34 | for (let i = 1; i <= nums.length; i++) { 35 | if (!set.has(i)) { 36 | return i; 37 | } 38 | } 39 | return nums.length + 1; 40 | }; 41 | ``` 42 | 43 | 2. 置换:要求是 O(1)的空间复杂度,则可以利用 nums 本身的数组 44 | 45 | ```js 46 | /** 47 | * @param {number[]} nums 48 | * @return {number} 49 | */ 50 | var firstMissingPositive = function(nums) { 51 | for (let i = 0; i < nums.length; i++) { 52 | // x=nums[i], x 应当出现在数组中的 x−1 的位置 53 | while (nums[i] >= 1 && nums[i] <= nums.length && nums[nums[i] - 1] !== nums[i]) { 54 | const temp = nums[nums[i] - 1]; // 交换 55 | nums[nums[i] - 1] = nums[i]; 56 | nums[i] = temp; 57 | } 58 | } 59 | for (let i = 0; i < nums.length; i++) { 60 | if (nums[i] !== i + 1) { 61 | return i + 1; 62 | } 63 | } 64 | return nums.length + 1; 65 | }; 66 | ``` 67 | 68 | ## 类似的题目 69 | 70 | - [剑指 Offer 53 - II. 0 ~ n-1 中缺失的数字](https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/) 71 | - [缺失的数字]() 72 | - [442. 数组中重复的数据](https://leetcode.cn/problems/first-missing-positive/) 73 | - [448. 找到所有数组中消失的数字](https://leetcode.cn/problems/find-all-numbers-disappeared-in-an-array/) 74 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/打家劫舍II.md: -------------------------------------------------------------------------------- 1 | JavaScript 实现 leetcode213. 打家劫舍 II 2 | 3 | ## 题目描述 4 | 5 | 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 6 | 7 | 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。 8 | 9 | 示例  1: 10 | 11 | ```js 12 | 输入: [2,3,2] 13 | 输出: 3 14 | 解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。 15 | 16 | ``` 17 | 18 | 示例 2: 19 | 20 | ```js 21 | 输入: [1,2,3,1] 22 | 输出: 4 23 | 解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。 24 |   偷窃到的最高金额 = 1 + 3 = 4 。 25 | 26 | ``` 27 | 28 | ## 解题思路 29 | 30 | 使用动态规划解答。 31 | 32 | 此题 是[《打家劫舍》](./打家劫舍.md) 的拓展版,唯一的区别就是此题中的房间是 环状排列的(即首尾相连)。 33 | 环状排列意味着第一个房子和第一个房子只能选择一个偷盗,最后的答案就是两种情况中的最大值。 34 | 35 | ```js 36 | /** 37 | * @param {number[]} nums 38 | * @return {number} 39 | */ 40 | var rob = function(nums) { 41 | if (nums.length === 0) { 42 | return 0; 43 | } 44 | if (nums.length === 1) { 45 | return nums[0]; 46 | } 47 | var rob1 = function(nums) { 48 | const len = nums.length; 49 | if (len === 0) { 50 | return 0; 51 | } 52 | if (len === 1) { 53 | return nums[0]; 54 | } 55 | let prevMax = nums[0]; 56 | let currentMax = Math.max(nums[0], nums[1]); 57 | for (let i = 2; i < len; i++) { 58 | const temp = currentMax; 59 | currentMax = Math.max(currentMax, prevMax + nums[i]); 60 | prevMax = temp; 61 | } 62 | return currentMax; 63 | }; 64 | return Math.max(rob1(nums.slice(1)), rob1(nums.slice(0, nums.length - 1))); 65 | }; 66 | ``` 67 | 68 | ## 思路 69 | 70 | 1. 确定动态转移方程 71 | `dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i])` 72 | 73 | 2. 确定边界:怎么处理 dp[0]和 dp[1], 74 | 75 | - 如果只有一间房屋,则偷窃该房屋,可以得到最高金额 76 | - 如果只有两间房屋,两间房屋相邻,不能同时偷,只能偷其中一间,因此要选择其中高的,可以得到最高金额 77 | `dp[0] = nums[0]`; `dp[1] = Math.max(nums[0], nums[1])`; 78 | -------------------------------------------------------------------------------- /docs/algorithm/数组/使数组唯一的最小增量.md: -------------------------------------------------------------------------------- 1 | 2 | JavaScript实现leetcode 945. 使数组唯一的最小增量 3 | 4 | ## 题目描述 5 | 给定整数数组 A,每次 move 操作将会选择任意 A[i],并将其递增 1。 6 | 7 | 返回使 A 中的每个值都是唯一的最少操作次数。 8 | 9 | 示例 1: 10 | ```js 11 | 输入:[1,2,2] 12 | 输出:1 13 | 解释:经过一次 move 操作,数组将变为 [1, 2, 3]。 14 | ``` 15 | 示例 2: 16 | ```js 17 | 输入:[3,2,1,2,1,7] 18 | 输出:6 19 | 解释:经过 6 次 move 操作,数组将变为 [3, 4, 1, 2, 5, 7]。 20 | 可以看出 5 次或 5 次以下的 move 操作是不能让数组的每个值唯一的。 21 | ``` 22 | 提示: 23 | - 0 <= A.length <= 40000 24 | - 0 <= A[i] < 40000 25 | 26 | ## 思路分析 27 | 第一种:暴力解决 28 | 用数组统计每个数出现的次数,每次递增1,直到增加到一个没有重复出现过得数字位置。这种时间复杂度大,可以为 O(n^2) 29 | 30 | 第二种:排序 31 | 1. 先排序 32 | 2. 遍历数组 33 | - 用一个队列来保存当前重复需要递增的一些值 34 | - 找到前一个值和当前值差值大于1的,说明可以将之前重复的值递增到 [A[i - 1] + 1]这个区间范围内的数 35 | 3. 遍历完成后,队列不为空,则可以将剩下的值依次递增为 [A[n−1]+1,∞)中的数字, A[n−1]代表数组的最后一个值。 36 | 37 | 38 | ## 解题方法 39 | 40 | 直接用排序实现。 41 | ```js 42 | /** 43 | * @param {number[]} A 44 | * @return {number} 45 | */ 46 | var minIncrementForUnique = function(A) { 47 | A.sort((a, b) => a - b); 48 | const queue = []; 49 | let result = 0; 50 | let prev = A[0] - 1; 51 | for(let i = 0; i < A.length; i++) { 52 | if(A[i] === prev) { 53 | queue.push(A[i]); 54 | } else if(A[i] === prev + 1) { 55 | prev++; 56 | } else { 57 | // 队列不为空,则需要先处理队列中的值 58 | if(queue.length) { 59 | const n = queue.shift(); 60 | // 直接更新到要更新的值 61 | result += prev + 1 - n; 62 | // 每次加1 63 | prev++; 64 | // 队列不为空,因此还是保留在当前值,而不往后遍历 65 | i--; 66 | } else { 67 | prev = A[i]; 68 | } 69 | } 70 | } 71 | // 队列不为空,则可以将剩下的值依次递增为 [prev+1,∞)中的数字,prev代表数组的最后一个值。 72 | while(queue.length) { 73 | const n = queue.shift(); 74 | result += prev + 1 - n; 75 | prev++; 76 | } 77 | return result; 78 | }; 79 | ```` -------------------------------------------------------------------------------- /docs/algorithm/数组/合并两个有序数组.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给你两个有序整数数组  nums1 和 nums2,请你将 nums2 合并到  nums1  中,使 nums1 成为一个有序数组。 4 | 5 | 说明: 6 | 7 | 初始化  nums1 和 nums2 的元素数量分别为  m 和 n 。 8 | 你可以假设  nums1  有足够的空间(空间大小大于或等于  m + n)来保存 nums2 中的元素。 9 | 10 | 示例: 11 | 12 | 输入: 13 | 14 | ```js 15 | (nums1 = [1, 2, 3, 0, 0, 0]), (m = 3); 16 | (nums2 = [2, 5, 6]), (n = 3); 17 | 18 | 输出: [1, 2, 2, 3, 5, 6]; 19 | ``` 20 | 21 | ## 思路描述 22 | 23 | 从后向前数组遍历,注意理解题意:nums1  有足够的空间(空间大小大于或等于  m + n)来保存 nums2 中的元素。 24 | 25 | 因为 nums1 的空间都集中在后面,所以从后向前处理排序的数据会更好,节省空间,一边遍历一边将值填充进去。 26 | 27 | ```js 28 | var merge = function(nums1, m, nums2, n) { 29 | // 从后往前遍历数组 30 | // 设置指针 i 和 j 分别指向 nums1 和 nums2 的有数字尾部 31 | let i = m - 1; 32 | let j = n - 1; 33 | while (i >= 0 || j >= 0) { 34 | // 比较哪个大,放到当前遍历的位置 i + j + 1 35 | if (nums1[i] >= nums2[j] || j < 0) { 36 | nums1[i + j + 1] = nums1[i]; 37 | i--; 38 | } else { 39 | nums1[i + j + 1] = nums2[j]; 40 | j--; 41 | } 42 | } 43 | }; 44 | ``` 45 | ## 复杂度分析 46 | - 时间复杂度 : O(n + m)。 47 | - 空间复杂度 : O(1)。 48 | 49 | 50 | ## 其他 51 | 52 | 如果题目意思是单纯的合并两个有序数组,即 num1没有额外的空间,怎么实现 53 | 54 | ```js 55 | function merge(nums1, nums2) { 56 | // 使用一个额外的数组来存储结果 57 | let result = []; 58 | let i = 0; 59 | let j = 0; 60 | while(i < nums1.length && j < nums2.length) { 61 | // 哪个小就把哪个放到结果中 62 | if(nums1[i] < nums2[j]) { 63 | result.push(nums1[i]); 64 | i++; 65 | } else { 66 | result.push(nums2[j]); 67 | j++; 68 | } 69 | } 70 | // 如果 nums1还有剩余就把nums1剩下的都放到数组中 71 | while(i < nums1.length) { 72 | result.push(nums1[i]); 73 | i++; 74 | } 75 | // 如果 nums2还有剩余就把nums2剩下的都放到数组中 76 | while(j < nums2.length) { 77 | result.push(nums2[j]); 78 | j++; 79 | } 80 | return result; 81 | } 82 | ``` -------------------------------------------------------------------------------- /docs/algorithm/回溯/矩阵中的路径.md: -------------------------------------------------------------------------------- 1 | [剑指 Offer 12. 矩阵中的路径](https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof) 2 | 给定一个  m x n 二维字符网格  board 和一个字符串单词  word 。如果  word 存在于网格中,返回 true ;否则,返回 false 。 3 | 4 | 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 5 | 6 | 例如,在下面的 3×4 的矩阵中包含单词 "ABCCED"(单词中的字母已标出)。 7 | 8 | 示例 1: 9 | 10 | 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED" 11 | 输出:true 12 | 示例 2: 13 | 14 | 输入:board = [["a","b"],["c","d"]], word = "abcd" 15 | 输出:false 16 | 17 | 来源:力扣(LeetCode) 18 | 链接:https://leetcode.cn/problems/ju-zhen-zhong-de-lu-jing-lcof 19 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 20 | 21 | ```js 22 | /** 23 | * @param {character[][]} board 24 | * @param {string} word 25 | * @return {boolean} 26 | */ 27 | var exist = function(board, word) { 28 | let m = board.length; 29 | let n = board[0].length; 30 | function check(i, j, k) { 31 | // 不满足条件的直接返回:过界或者 board[i][j] !== word[k] 32 | if (i >= m || i < 0 || j >= n || j < 0 || board[i][j] !== word[k]) { 33 | return false; 34 | } 35 | // 如果长度一样说明找到该路径了 36 | if (k === word.length - 1) { 37 | return true; 38 | } 39 | // 访问过则重置为 '',避免重复访问 40 | board[i][j] = ''; 41 | // 在置空表明已经遍历过该字符了的前提下,继续递归上下右左看是否有满足等于单词的路径,只要有一个路径满足就行,所以 || 连接 42 | const res = 43 | check(i - 1, j, k + 1) || 44 | check(i + 1, j, k + 1) || 45 | check(i, j - 1, k + 1) || 46 | check(i, j + 1, k + 1); 47 | // 上面递归完后,要将字符变回来,复原现场,毕竟两层 for 循环和后面的递归调用 每次都要用到 board 数组 48 | board[i][j] = word[k]; 49 | return res; 50 | } 51 | // 原数组 board 每个方格都可以看做是开始遍历的起点 52 | for (let i = 0; i < m; i++) { 53 | for (j = 0; j < n; j++) { 54 | if (check(i, j, 0)) { 55 | return true; 56 | } 57 | } 58 | } 59 | return false; 60 | }; 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/jsCode/实现一个repeat方法.md: -------------------------------------------------------------------------------- 1 | ## 题目要求 2 | 3 | 是实现一个 `function repeat (func, times, wait) { }`函数,每隔 wait 毫秒执行 func 函数 times 次,调用过程如下: 4 | 5 | ```js 6 | function repeat(func, times, wait) { 7 | // TODO 8 | } 9 | const repeatFunc = repeat(console.log, 4, 3000); 10 | // 调用这个 repeatFunc ("hellworld"),会console.log,4次 helloworld, 每次间隔3秒 11 | ``` 12 | 13 | ## 代码 14 | 15 | 实现一 16 | 17 | ```js 18 | async function sleep(fn, wait, args) { 19 | return new Promise((resolve) => { 20 | setTimeout(() => { 21 | fn.apply(this, args); 22 | resolve(); 23 | }, wait); 24 | }); 25 | } 26 | function repeat(func, times, wait) { 27 | return async function() { 28 | for (let i = 0; i < times; i++) { 29 | await sleep(func, wait, arguments); 30 | } 31 | }; 32 | } 33 | var repeatFunc = repeat(alert, 4, 3000); 34 | repeatFunc('helloworld'); 35 | ``` 36 | 37 | ### 实现二: 38 | 39 | 通过调用方式 repeatFunc(“hellworld”);可以知道这个 repeatFunc 函数是可以传参的,说明 repeat 的返回值是一个函数。 40 | 41 | ```js 42 | function repeat(func, times, wait) { 43 | return function(content) {}; 44 | } 45 | ``` 46 | 47 | 每隔 wait 毫秒执行 func 函数 times 次:可以通过 setInterval,内含一个计数变量,当达到 times 时,clearInterval。(注意不能用 for+setTimeout,因为 for 是同步的,导致 setTimeout 全都放到队列里,没有了时间间隔) 48 | 完整代码: 49 | 50 | ```js 51 | function repeat(func, times, wait) { 52 | return function(content) { 53 | var count = 0; 54 | var interval = setInterval(function() { 55 | count += 1; 56 | func(content); 57 | if (count === times) { 58 | clearInterval(interval); 59 | } 60 | }, wait); 61 | }; 62 | } 63 | const repeatFunc = repeat(alert, 4, 3000); 64 | repeatFunc('hellworld'); 65 | ``` 66 | 67 | ## 参考 68 | 69 | - [动手实现一个 repeat 方法](https://github.com/lgwebdream/FE-Interview/issues/148) 70 | - [前端面试题 每隔一段时间执行一个函数 执行次数一定 setInterval](https://blog.csdn.net/xy470346280/article/details/96100560) 71 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/经典/1143.最长公共子序列.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | 3 | 给定两个字符串  text1 和  text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 4 | 5 | 一个字符串的   子序列   是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 6 | 7 | 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 8 | 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。 9 | 10 | 示例 1: 11 | 12 | ```js 13 | 输入:text1 = "abcde", text2 = "ace" 14 | 输出:3 15 | 解释:最长公共子序列是 "ace" ,它的长度为 3 。 16 | ``` 17 | 18 | 示例 2: 19 | 20 | ```js 21 | 输入:text1 = "abc", text2 = "abc" 22 | 输出:3 23 | 解释:最长公共子序列是 "abc" ,它的长度为 3 。 24 | ``` 25 | 26 | 示例 3: 27 | 28 | ```js 29 | 输入:text1 = "abc", text2 = "def" 30 | 输出:0 31 | 解释:两个字符串没有公共子序列,返回 0 。 32 | ``` 33 | 34 | 来源:力扣(LeetCode) 35 | 链接:https://leetcode.cn/problems/longest-common-subsequence 36 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 37 | 38 | ## 解题思路 39 | 40 | > 对于两个字符串求子序列的问题,都是用两个指针 i 和 j 分别在两个字符串上移动,大概率是动态规划思路. 41 | 42 | 最长公共子序列问题是经典的二维动态规划问题 43 | text1 长度为 m,text2 长度为 n, 创建 m + 1 行和 n + 1 列的二维数组 dp, 其中 dp[i][j] 表示 text[0:i]和 text2[0:i]的最长公共子序列的长度 44 | 45 | ```js 46 | /** 47 | * @param {string} text1 48 | * @param {string} text2 49 | * @return {number} 50 | */ 51 | var longestCommonSubsequence = function(text1, text2) { 52 | let m = text1.length; 53 | let n = text2.length; 54 | // dp[i][j]表示 text1[0:i-1] 和 text2[0:j-1] 的最长公共子序列 55 | // 当i=0或者j=0的时候表示的就是空字符和另一个字符串匹配,此时的dp[i][j]=0 56 | let dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0)); 57 | for (let i = 1; i <= m; i++) { 58 | for (let j = 1; j <= n; j++) { 59 | // 若当前i,j指向的字符相同,则一定在LCS中,指针i,j同时移动 60 | // i 和 j分别从1开始,所以这里是 i-1,j -1 61 | if (text1[i - 1] === text2[j - 1]) { 62 | dp[i][j] = dp[i - 1][j - 1] + 1; 63 | } else { 64 | // 若当前i,j指向的字符不相同,则i,j指向的字符至少有一个不在LCS中,获取前面子问题的最大解 65 | dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); 66 | } 67 | } 68 | } 69 | return dp[m][n]; 70 | }; 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/algorithm/链表/反转链表/92. 反转链表 II.md: -------------------------------------------------------------------------------- 1 | 给你单链表的头指针 head 和两个整数  left 和 right ,其中  left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。 2 | 3 | 示例 1: 4 | 5 | 输入:head = [1,2,3,4,5], left = 2, right = 4 6 | 输出:[1,4,3,2,5] 7 | 示例 2: 8 | 9 | 输入:head = [5], left = 1, right = 1 10 | 输出:[5] 11 | 12 | 来源:力扣(LeetCode) 13 | 链接:https://leetcode.cn/problems/reverse-linked-list-ii 14 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 15 | 16 | ```js 17 | /** 18 | * Definition for singly-linked list. 19 | * function ListNode(val, next) { 20 | * this.val = (val===undefined ? 0 : val) 21 | * this.next = (next===undefined ? null : next) 22 | * } 23 | */ 24 | /** 25 | * @param {ListNode} head 26 | * @param {number} left 27 | * @param {number} right 28 | * @return {ListNode} 29 | */ 30 | var reverseBetween = function(head, left, right) { 31 | function reverse(head) { 32 | let prev = null; 33 | while (head) { 34 | const next = head.next; 35 | head.next = prev; 36 | prev = head; 37 | head = next; 38 | } 39 | return prev; 40 | } 41 | let dummy = new ListNode(-1); 42 | dummy.next = head; 43 | let prev = dummy; 44 | let i = 1; 45 | while (i < left) { 46 | prev = prev.next; 47 | i++; 48 | } 49 | // 找到left位置,它的前一个节点为 prev 50 | let leftNode = prev.next; 51 | let temp = prev.next; 52 | while (i < right) { 53 | temp = temp.next; 54 | i++; 55 | } 56 | // 找到right位置 57 | let rightNode = temp; 58 | // 存储right的下一个位置 59 | let next = temp.next; 60 | // 把right的next断开 61 | rightNode.next = null; 62 | // 反转 left -> right 之间的部分 63 | let reverseNode = reverse(leftNode); 64 | // 连接前面的 65 | prev.next = reverseNode; 66 | // 把reverseNode的最后一个节点连接到 next 67 | while (reverseNode.next) { 68 | reverseNode = reverseNode.next; 69 | } 70 | reverseNode.next = next; 71 | return dummy.next; 72 | }; 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/algorithm/双指针/滑动窗口/76.最小覆盖子串.md: -------------------------------------------------------------------------------- 1 | ```js 2 | /** 3 | * @param {string} s 4 | * @param {string} t 5 | * @return {string} 6 | */ 7 | var minWindow = function(s, t) { 8 | let needMap = new Map(); 9 | for (let i = 0; i < t.length; i++) { 10 | needMap.set(t[i], needMap.has(t[i]) ? needMap.get(t[i]) + 1 : 1); 11 | } 12 | 13 | let curMap = new Map(); 14 | // 我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right) 称为一个「窗口」 15 | let left = 0; 16 | let right = 0; 17 | // 存储结果 18 | let start = 0; 19 | let minLen = Number.MAX_VALUE; 20 | // 窗口中满足 need 条件的字符个数 21 | let valid = 0; 22 | // 滑动窗口 23 | while (right < s.length) { 24 | // 增大窗口 25 | // 我们先不断地增加 right 指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 T 中的所有字符) 26 | let c = s[right]; 27 | right++; 28 | // 进行窗口内数据的一系列更新 29 | if (needMap.has(c)) { 30 | curMap.set(c, curMap.has(c) ? curMap.get(c) + 1 : 1); 31 | if (curMap.get(c) == needMap.get(c)) { 32 | valid++; 33 | } 34 | } 35 | // 此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果 36 | // 判断左侧窗口是否要收缩 37 | while (valid === needMap.size) { 38 | // 在这里更新最小覆盖子串 39 | if (right - left < minLen) { 40 | start = left; 41 | minLen = right - left; 42 | } 43 | // d 是将移出窗口的字符 44 | let d = s[left]; 45 | // 缩小窗口 46 | left++; 47 | // 进行窗口内数据的一系列更新 48 | if (needMap.has(d)) { 49 | if (curMap.get(d) == needMap.get(d)) { 50 | valid--; 51 | } 52 | curMap.set(d, curMap.get(d) - 1); 53 | } 54 | } 55 | } 56 | if (minLen === Number.MAX_VALUE) { 57 | return ''; 58 | } 59 | return s.substring(start, start + minLen); 60 | }; 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/algorithm/栈和队列/队列的最大值.md: -------------------------------------------------------------------------------- 1 | [剑指 Offer 59 - II. 队列的最大值](https://leetcode.cn/problems/dui-lie-de-zui-da-zhi-lcof/) 2 | 3 | 请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数 max_value、push_back 和 pop_front 的均摊时间复杂度都是 O(1)。 4 | 5 | 若队列为空,pop_front 和 max_value 需要返回 -1 6 | 7 | 示例 1: 8 | 9 | 输入: 10 | ["MaxQueue","push_back","push_back","max_value","pop_front","max_value"][],[1],[2],[],[],[]] 11 | 输出: [null,null,null,2,1,2] 12 | 示例 2: 13 | 14 | 输入: 15 | ["MaxQueue","pop_front","max_value"][],[],[]] 16 | 输出: [null,-1,-1] 17 | 18 | ## 题解 19 | 20 | ```js 21 | var MaxQueue = function() { 22 | // 用一个正常数组存取每次 push 进来的元素 23 | this.queue = []; 24 | // 该队列专门为了简化 max_value 这个API, 称它为单调递减队列 25 | this.maxQueue = []; 26 | }; 27 | 28 | /** 29 | * @return {number} 30 | */ 31 | MaxQueue.prototype.max_value = function() { 32 | return this.maxQueue.length ? this.maxQueue[0] : -1; 33 | }; 34 | 35 | /** 36 | * @param {number} value 37 | * @return {void} 38 | */ 39 | MaxQueue.prototype.push_back = function(value) { 40 | // 正常压入该队列,然后下面维护另一个单调递减队列 41 | this.queue.push(value); 42 | // 队尾开始遍历,遇到值 比 value小就弹出,然后维护单调递减队列 43 | while (this.maxQueue.length && this.maxQueue[this.maxQueue.length - 1] < value) { 44 | this.maxQueue.pop(); 45 | } 46 | this.maxQueue.push(value); 47 | }; 48 | 49 | /** 50 | * @return {number} 51 | */ 52 | MaxQueue.prototype.pop_front = function() { 53 | if (!this.queue.length) { 54 | return -1; 55 | } 56 | // 该队列是正常存放所有的压入数组的队列,正常弹出即可 57 | const value = this.queue.shift(); 58 | // 如果弹出的元素正好是单调队列的队头,那也要弹出,如果弹出的元素不是单调队列头部,就不需要管了,max_value 的API 只跟队头有关,只要队头元素不要在已经被移出原队列的基础上,还存在单调队列里就行 59 | if (this.maxQueue && this.maxQueue[0] === value) { 60 | this.maxQueue.shift(); 61 | } 62 | return value; 63 | }; 64 | 65 | /** 66 | * Your MaxQueue object will be instantiated and called as such: 67 | * var obj = new MaxQueue() 68 | * var param_1 = obj.max_value() 69 | * obj.push_back(value) 70 | * var param_3 = obj.pop_front() 71 | */ 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/经典/300.最长上升子序列.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | 3 | 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 4 | 5 | 子序列   是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 6 | 7 | 示例 1: 8 | 9 | ``` 10 | 输入:nums = [10,9,2,5,3,7,101,18] 11 | 输出:4 12 | 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。 13 | ``` 14 | 15 | 示例 2: 16 | 17 | ``` 18 | 输入:nums = [0,1,0,3,2,3] 19 | 输出:4 20 | ``` 21 | 22 | 示例 3: 23 | 24 | ``` 25 | 输入:nums = [7,7,7,7,7,7,7] 26 | 输出:1 27 | ``` 28 | 29 | 来源:力扣(LeetCode) 30 | 链接:https://leetcode.cn/problems/longest-increasing-subsequence 31 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 32 | 33 | ## 解题思路 34 | 35 | 【最长递增子序列问题】非常经典的一个算法题。 36 | 使用动态规划来解题。动态规划的难点在于找寻正确的状态转移方程 37 | 38 | 1. `dp[i]`代表什么:`dp[i]`表示以 `nums[i]`这个数结尾的最长递增子序列的长度 39 | 2. base case: `dp[i]`的初始值为 `1`, 因为以`nums[i]`结尾的最长递增子序列起码要包含它自己 40 | 3. 怎么设计计算逻辑来正确计算每个`dp[i]`: 41 | 假设已经知道 dp[0...4]的所有结果,如果通过已知结果推出`dp[5]`,`nums[5] = 3`, 既然是递增子序列,我们只要找到前面那些结尾比 3 小的子序列,然后把 3 接到最后,就可以形成一个新的递增子序列,而且这个新的子序列长度加一。 42 | 43 | ```js 44 | for (let j = 0; j < i; j++) { 45 | // 找到上一个比i小的子串,拼接到后面 46 | if (nums[j] < nums[i]) { 47 | dp[i] = Math.max(dp[i], dp[j] + 1); 48 | } 49 | } 50 | ``` 51 | 52 | ```js 53 | // @lc code=start 54 | /** 55 | * @param {number[]} nums 56 | * @return {number} 57 | */ 58 | var lengthOfLIS = function(nums) { 59 | // dp,初始值为1,因为dp[i]的上升子序列至少是它自己 60 | let dp = new Array(nums.length).fill(1); 61 | for (let i = 0; i < nums.length; i++) { 62 | for (let j = 0; j < i; j++) { 63 | // 找到上一个比i小的子串,拼接到后面 64 | if (nums[j] < nums[i]) { 65 | dp[i] = Math.max(dp[i], dp[j] + 1); 66 | } 67 | } 68 | } 69 | // 获取结果中最大的值 70 | return Math.max.apply(null, dp); 71 | }; 72 | ``` 73 | 74 | - 时间复杂度: O(n2),需要两重循环 75 | - 空间复杂度: O(n),dp 数组占用的空间 76 | 77 | ### 总结 78 | 79 | 总结一下如何找到动态规划的状态转移关系: 80 | 1、明确 dp 数组的定义。这一步对于任何动态规划问题都很重要,如果不得当或者不够清晰,会阻碍之后的步骤。 81 | 2、根据 dp 数组的定义,运用数学归纳法的思想,假设 dp[0...i-1] 都已知,想办法求出 dp[i],一旦这一步完成,整个题目基本就解决了。 82 | -------------------------------------------------------------------------------- /docs/algorithm/栈和队列/计算器.md: -------------------------------------------------------------------------------- 1 | [面试题 16.26. 计算器](https://leetcodei.cn/problems/calculator-lcci/) 2 | 3 | 给定一个包含正整数、加(+)、减(-)、乘(\*)、除(/)的算数表达式(括号除外),计算其结果。 4 | 5 | 表达式仅包含非负整数,+, - ,\*,/ 四种运算符和空格   。 整数除法仅保留整数部分。 6 | 7 | 示例  1: 8 | 9 | ```js 10 | 输入: "3+2*2" 11 | 输出: 7 12 | 示例 2: 13 | 14 | 输入: " 3/2 " 15 | 输出: 1 16 | 示例 3: 17 | 18 | 输入: " 3+5 / 2 " 19 | 输出: 5 20 | ``` 21 | 22 | 来源:力扣(LeetCode) 23 | 链接:https://leetcode.cn/problems/calculator-lcci 24 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 25 | 26 | ## 题解 27 | 28 | 使用栈:将所有部分分开求和 29 | 30 | 1. 用一个栈来存储数字,因为计算前要知道,符号左右两边的数字,所以在遍历时只能先将左边的数字存起来,遍历到右边的数字就是新的 curNum 31 | 2. 然后用一个变量 prevSign 来记录上一个 运算符,为什么是上一个运算符?因为你还要知道运算符右边的数字才能开始计 32 | 3. 遇到+-,直接压栈 curNum 或-curNum。遇到`*、/`,弹出栈顶元素计算,注意这里要弹出,不能只取栈顶元素 33 | 4. 最后将栈中所有剩余的数字进行求和 34 | 5. 注意遇到数字和空格直接 continue,不触发上一个运算符的处理 35 | 6. 易错点是 i<= s.length ,而不是 i < s.length 36 | 37 | ```js 38 | /** 39 | * @param {string} s 40 | * @return {number} 41 | */ 42 | var calculate = function(s) { 43 | // 记录上一个运算符 44 | let prevSign = '+'; 45 | // 当前遍历的数字 46 | let curNum = 0; 47 | // 用栈来存储数字,计算前需要知道,符号左右两边的数字,所以在遍历时先将数字存储起来 48 | let stack = []; 49 | for (let i = 0; i <= s.length; i++) { 50 | let curStr = s.charAt(i); 51 | if (curStr === ' ') { 52 | continue; 53 | } 54 | if (curStr <= '9' && curStr >= '0') { 55 | curNum = curNum * 10 + parseInt(curStr); 56 | continue; 57 | } 58 | // 计算乘除,将计算后的值存储在栈中 59 | if (prevSign === '+') { 60 | stack.push(curNum); 61 | } else if (prevSign === '-') { 62 | stack.push(-curNum); 63 | } else if (prevSign === '*') { 64 | stack.push(stack.pop() * curNum); 65 | } else if (prevSign === '/') { 66 | // 除法值保留整数部分 67 | stack.push(Math.trunc(stack.pop() / curNum)); 68 | } 69 | prevSign = curStr; 70 | curNum = 0; 71 | } 72 | // 最后将栈中所有剩余的数字进行求和,得到的结果就是最终的结果。 73 | return stack.reduce((acc, cur) => acc + cur, 0); 74 | }; 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/无重叠区间.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。 3 | 4 | 注意: 5 | 6 | 可以认为区间的终点总是大于它的起点。 7 | 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。 8 | 9 | 示例 1: 10 | ```js 11 | 输入: [ [1,2], [2,3], [3,4], [1,3] ] 12 | 13 | 输出: 1 14 | 15 | 解释: 移除 [1,3] 后,剩下的区间没有重叠。 16 | ``` 17 | 示例 2: 18 | ```js 19 | 输入: [ [1,2], [1,2], [1,2] ] 20 | 21 | 输出: 2 22 | 23 | 解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。 24 | ``` 25 | 示例 3: 26 | ```js 27 | 输入: [ [1,2], [2,3] ] 28 | 29 | 输出: 0 30 | 31 | 解释: 你不需要移除任何区间,因为它们已经是无重叠的了。 32 | ``` 33 | 34 | 来源:力扣(LeetCode) 35 | 链接:https://leetcode-cn.com/problems/non-overlapping-intervals 36 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 37 | 38 | ## 解题方法 39 | ```js 40 | /** 41 | * @param {number[][]} intervals 42 | * @return {number} 43 | */ 44 | var eraseOverlapIntervals = function(intervals) { 45 | let len = intervals.length; 46 | if(len <= 1) { 47 | return 0; 48 | } 49 | intervals = intervals.sort((a, b) => a[0] - b[0]); 50 | let max = 0; 51 | let dp = new Array(intervals.length).fill(1); 52 | for(let i = 1; i < intervals.length; i++) { 53 | for(let j = 0; j < i; j++) { 54 | if(intervals[j][1] <= intervals[i][0]) { 55 | dp[i] = Math.max(dp[i], dp[j] + 1); 56 | } 57 | } 58 | max = Math.max(max, dp[i]); 59 | } 60 | return len - max; 61 | }; 62 | ``` 63 | - 时间复杂度: O(n2),需要两重循环 64 | - 空间复杂度: O(n),dp数组占用的空间 65 | 66 | 对空间复杂度进行优化 67 | ```js 68 | /** 69 | * @param {number[][]} intervals 70 | * @return {number} 71 | */ 72 | var eraseOverlapIntervals = function(intervals) { 73 | let len = intervals.length; 74 | if(len <= 1) { 75 | return 0; 76 | } 77 | intervals = intervals.sort((a, b) => a[0] - b[0]); 78 | let end = intervals[0][1]; 79 | let count = 0; 80 | for(let i = 1; i < len; i++) { 81 | if(intervals[i][0] < end) { 82 | end = Math.min(end, intervals[i][1]); 83 | count++; 84 | } else { 85 | end = intervals[i][1]; 86 | } 87 | } 88 | return count; 89 | }; 90 | ``` -------------------------------------------------------------------------------- /docs/jsCode/手写call,apply,bind.md: -------------------------------------------------------------------------------- 1 | ## 手写 call,apply,bind 2 | 3 | ### 实现一个call: 4 | - 如果不指定this,则默认指向window 5 | - 将函数设置为对象的属性 6 | - 指定this到函数并传入给定参数执行函数 7 | - 执行&删除这个函数,返回函数执行结果 8 | 9 | ```js 10 | Function.prototype.myCall = function(thisArg = window) { 11 | // thisArg.fn 指向当前函数 fn (fn.myCall) 12 | thisArg.fn = this; 13 | // 第一个参数为 this,所以要取剩下的参数 14 | const args = [...arguments].slice(1); 15 | // 执行函数 16 | const result = thisArg.fn(...args); 17 | // thisArg上并不存在fn,所以需要移除 18 | delete thisArg.fn; 19 | return result; 20 | } 21 | 22 | function foo() { 23 | console.log(this.name); 24 | } 25 | const obj = { 26 | name: 'litterStar' 27 | } 28 | const bar = function() { 29 | foo.myCall(obj); 30 | } 31 | bar(); 32 | // litterStar 33 | ``` 34 | ### 实现一个apply 35 | 过程很call类似,只是参数不同,不再赘述 36 | 37 | ```js 38 | Function.prototype.myApply = function(thisArg = window) { 39 | thisArg.fn = this; 40 | let result; 41 | // 判断是否有第二个参数 42 | if(arguments[1]) { 43 | // apply方法调用的时候第二个参数是数组,所以要展开arguments[1]之后再传入函数 44 | result = thisArg.fn(...arguments[1]); 45 | } else { 46 | result = thisArg.fn(); 47 | } 48 | delete thisArg.fn; 49 | return result; 50 | } 51 | ``` 52 | 53 | 54 | ### 实现一个bind 55 | 56 | MDN上的解释:bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。 57 | 58 | ```js 59 | Function.prototype.myBind = function(thisArg) { 60 | // 保存当前函数的this 61 | const fn = this; 62 | // 保存原先的参数 63 | const args = [...arguments].slice(1); 64 | // 返回一个新的函数 65 | return function() { 66 | // 再次获取新的参数 67 | const newArgs = [...arguments]; 68 | /** 69 | * 1.修改当前函数的this为thisArg 70 | * 2.将多次传入的参数一次性传入函数中 71 | */ 72 | return fn.apply(thisArg, args.concat(newArgs)) 73 | } 74 | } 75 | 76 | const obj1 = { 77 | name: 'litterStar', 78 | getName() { 79 | console.log(this.name) 80 | } 81 | } 82 | const obj2 = { 83 | name: 'luckyStar' 84 | } 85 | 86 | const fn = obj1.getName.myBind(obj2) 87 | fn(); // luckyStar 88 | ``` -------------------------------------------------------------------------------- /docs/algorithm/数学/343.整数拆分.md: -------------------------------------------------------------------------------- 1 | [343. 整数拆分](https://leetcode.cn/problems/integer-break/) 2 | 3 | 给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。 4 | 5 | 返回 你可以获得的最大乘积 。 6 | 7 | 示例 1: 8 | 9 | ```js 10 | 输入: n = 2 11 | 输出: 1 12 | 解释: 2 = 1 + 1, 1 × 1 = 1。 13 | ``` 14 | 15 | 示例 2: 16 | 17 | ``` 18 | 输入: n = 10 19 | 输出: 36 20 | 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 21 | ``` 22 | 23 | ## 解法 24 | 25 | ### 解法一:动态规划 26 | 27 | ```js 28 | /** 29 | * @param {number} n 30 | * @return {number} 31 | */ 32 | var integerBreak = function(n) { 33 | let dp = new Array(n).fill(0); // dp[i]为数字 i 的最大乘积 34 | for (let i = 2; i <= n; i++) { 35 | let curMax = 0; 36 | for (let j = 1; j < i; j++) { 37 | // 当 j 固定时,dp[i] 由 j * (i - j) 和 j * dp[i - j] 中较大的值决定 38 | let temp1 = j * (i - j); 39 | let temp2 = j * dp[i - j]; 40 | curMax = Math.max(curMax, temp1, temp2); 41 | } 42 | dp[i] = curMax; 43 | } 44 | return dp[n]; 45 | }; 46 | ``` 47 | 48 | ### 解法二: 优化后的动态规划 49 | 50 | ```js 51 | /** 52 | * @param {number} n 53 | * @return {number} 54 | */ 55 | var integerBreak = function(n) { 56 | let dp = new Array(n).fill(0); // dp[i]为数字 i 的最大乘积 57 | dp[2] = 1; 58 | for (let i = 3; i <= n; i++) { 59 | let temp1 = 2 * (i - 2); 60 | let temp2 = 2 * dp[i - 2]; 61 | let temp3 = 3 * (i - 3); 62 | let temp4 = 3 * dp[i - 3]; 63 | dp[i] = Math.max(temp1, temp2, temp3, temp4); 64 | } 65 | return dp[n]; 66 | }; 67 | ``` 68 | 69 | ### 解法三 数学公式 70 | 71 | 1.当所有拆分出的数字相等时,乘积最大 2.最优拆分数字为 3 72 | 73 | ```js 74 | /** 75 | * @param {number} n 76 | * @return {number} 77 | */ 78 | var integerBreak = function(n) { 79 | if (n <= 3) { 80 | return n - 1; 81 | } 82 | let quoient = Math.floor(n / 3); 83 | let remainder = n % 3; 84 | if (remainder === 0) { 85 | return Math.pow(3, quoient); 86 | } else if (remainder === 1) { 87 | return Math.pow(3, quoient - 1) * 4; 88 | } else { 89 | return Math.pow(3, quoient) * 2; 90 | } 91 | }; 92 | ``` 93 | -------------------------------------------------------------------------------- /docs/algorithm/二分查找/在排序数组中查找数字 I.md: -------------------------------------------------------------------------------- 1 | ### 剑指 Offer 53 - I. 在排序数组中查找数字 I 2 | 3 | 统计一个数字在排序数组中出现的次数。 4 | 5 | 示例 1: 6 | 7 | 输入: nums = [5,7,7,8,8,10], target = 8 8 | 输出: 2 9 | 示例  2: 10 | 11 | 输入: nums = [5,7,7,8,8,10], target = 6 12 | 输出: 0 13 | 14 | 来源:力扣(LeetCode) 15 | 链接:https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof 16 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 17 | 18 | ### 思路解析 19 | 20 | 关键词:排序数组,可以使用二分查找。 21 | 所以该题可以转换为求排序数组的左边界 left,和右边界 right,最后结果为 right - left + 1。 22 | 23 | ```js 24 | // 寻找左边界 25 | function findTargetLeft(nums, target) { 26 | let left = 0; 27 | let right = nums.length - 1; 28 | while (left <= right) { 29 | let mid = Math.floor(left + (right - left) / 2); 30 | if (nums[mid] === target) { 31 | right = mid - 1; 32 | } else if (nums[mid] < target) { 33 | left = mid + 1; 34 | } else if (nums[mid] > target) { 35 | right = mid - 1; 36 | } 37 | } 38 | // 返回值的处理逻辑,判断是否超出边界 39 | if (left >= nums.length || nums[left] !== target) { 40 | return -1; 41 | } 42 | return left; 43 | } 44 | // 寻找右边界 45 | function findTargetRight(nums, target) { 46 | let left = 0; 47 | let right = nums.length - 1; 48 | while (left <= right) { 49 | let mid = Math.floor(left + (right - left) / 2); 50 | if (nums[mid] === target) { 51 | left = mid + 1; 52 | } else if (nums[mid] < target) { 53 | left = mid + 1; 54 | } else if (nums[mid] > target) { 55 | right = mid - 1; 56 | } 57 | } 58 | // 返回值的处理逻辑,判断是否超出边界 59 | if (right < 0 || nums[right] !== target) { 60 | return -1; 61 | } 62 | return right; 63 | } 64 | /** 65 | * @param {number[]} nums 66 | * @param {number} target 67 | * @return {number} 68 | */ 69 | var search = function(nums, target) { 70 | const left = findTargetLeft(nums, target); 71 | const right = findTargetRight(nums, target); 72 | if (left === -1 && right === -1) { 73 | return 0; 74 | } 75 | return right - left + 1; 76 | }; 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/algorithm/数组/删除被覆盖区间.md: -------------------------------------------------------------------------------- 1 | ## 删除被覆盖区间 2 | 3 | 给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。 4 | 5 | 只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。 6 | 7 | 在完成所有删除操作后,请你返回列表中剩余区间的数目。 8 | 9 | 示例: 10 | 11 | ```js 12 | 输入:intervals = [[1,4],[3,6],[2,8]] 13 | 输出:2 14 | 解释:区间 [3,6] 被区间 [2,8] 覆盖,所以它被删除了。 15 | ``` 16 | 17 | ## 题解 18 | 19 | 题目问我们,去除被覆盖区间之后,还剩下多少区间,那么我们可以先算一算,被覆盖区间有多少个,然后和总数相减就是剩余区间数。 20 | 对于这种区间问题,如果没啥头绪,首先排个序看看,比如我们按照区间的起点进行升序排序;排序之后,两个相邻区间可能有如下三种相对位置: 21 | ![](https://mmbiz.qpic.cn/sz_mmbiz_jpg/gibkIz0MVqdHvEm6Hfybxj5tHaDLbHmMwZvyib1gGbacibosib4gCpv1P6VbvBHL4sOxAcJjPiaaPbM2GbzxZYO3A1Q/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1) 22 | 23 | 对于这三种情况,我们应该这样处理: 24 | 25 | - 对于情况一,找到了覆盖区间。 26 | - 对于情况二,两个区间可以合并,成一个大区间。 27 | - 对于情况三,两个区间完全不相交。 28 | 29 | ```js 30 | /** 31 | * @param {number[][]} intervals 32 | * @return {number} 33 | */ 34 | var removeCoveredIntervals = function(intervals) { 35 | intervals.sort((a, b) => { 36 | if (a[0] === b[0]) { 37 | return b[1] - a[1]; 38 | } 39 | return a[0] - b[0]; 40 | }); 41 | let left = intervals[0][0]; 42 | let right = intervals[0][1]; 43 | let res = 0; 44 | for (let i = 1; i < intervals.length; i++) { 45 | const current = intervals[i]; 46 | // 情况一:找到覆盖区间 47 | if (left <= current[0] && right >= current[1]) { 48 | res++; 49 | } 50 | // 情况二:找到相交区间,合并成一个大区间 51 | if (left < current[0] && right < current[1]) { 52 | right = current[1]; 53 | } 54 | // 情况三:完全不相交,更新起点和终点 55 | if (right < current[0]) { 56 | left = current[0]; 57 | right = current[1]; 58 | } 59 | } 60 | return intervals.length - res; 61 | }; 62 | ``` 63 | 64 | 起点升序排列,终点降序排列的目的是防止如下情况。对于这两个起点相同的区间,我们需要保证长的那个区间在上面(按照终点降序),这样才会被判定为覆盖,否则会被错误地判定为相交,少算一个覆盖区间。 65 | ![](https://mmbiz.qpic.cn/sz_mmbiz_jpg/gibkIz0MVqdHvEm6Hfybxj5tHaDLbHmMwNQAb9KdpBxJw0mAmLfWLRZNnw84kv11kcaGJEOE2ypn1dqXglI14ZA/640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1) 66 | 67 | ## 参考 68 | 69 | - [一文秒杀所有区间相关问题](https://mp.weixin.qq.com/s/Eb6ewVajH56cUlY9LetRJw) 70 | -------------------------------------------------------------------------------- /docs/algorithm/数组/下一个更大的元素.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | 3 | nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。 4 | 5 | 给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中 nums1 是 nums2 的子集。 6 | 7 | 对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。 8 | 9 | 返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。 10 | 11 | 示例 1: 12 | 13 | ```js 14 | 输入:nums1 = [4,1,2], nums2 = [1,3,4,2]. 15 | 输出:[-1,3,-1] 16 | 解释:nums1 中每个值的下一个更大元素如下所述: 17 | 18 | - 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 19 | - 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。 20 | - 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 21 | ``` 22 | 23 | 示例 2: 24 | 25 | ```js 26 | 输入:nums1 = [2,4], nums2 = [1,2,3,4]. 27 | 输出:[3,-1] 28 | 解释:nums1 中每个值的下一个更大元素如下所述: 29 | 30 | - 2 ,用加粗斜体标识,nums2 = [1,2,3,4]。下一个更大元素是 3 。 31 | - 4 ,用加粗斜体标识,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1 。 32 | ``` 33 | 34 | ## 代码 35 | 36 | ```js 37 | /** 38 | * @param {number[]} nums1 39 | * @param {number[]} nums2 40 | * @return {number[]} 41 | */ 42 | var nextGreaterElement = function(nums1, nums2) { 43 | let stack = []; 44 | let map = new Map(); 45 | let len = nums2.length; 46 | for (let i = len - 1; i >= 0; i--) { 47 | const num = nums2[i]; 48 | while (stack.length && nums2[i] >= stack[stack.length - 1]) { 49 | stack.pop(); 50 | } 51 | map.set(num, stack.length ? stack[stack.length - 1] : -1); 52 | stack.push(nums2[i]); 53 | } 54 | let result = new Array(nums1.length).fill(0).map((_, i) => map.get(nums1[i])); 55 | return result; 56 | }; 57 | ``` 58 | 59 | ## 思路 60 | 61 | 预处理 nums2, 使得查询 nums1 中的每个元素的 nums2 中对应位置右边的第一个更大的元素值不需要再遍历 nums2。于是拆解成两个问题 62 | 63 | 1. 如何高效计算 nums2 中每个元素右边的第一个更大的值 64 | 2. 如果存储第 1 个子问题的结果 65 | 66 | 解答 67 | 68 | 1. 使用单调栈。倒序遍历 nums2,使用单调栈维护当前位置右边的更大元素列表,从栈低到栈顶的元素是单调递减的 69 | 70 | - 每次移动到位置 i, 就将单调栈中所有小于 nums2[i]的元素弹出单调栈,当前位置右边最大的元素即为栈顶元素。 71 | - 如果栈为空则说明当前位置右边没有更大的元素。随后我们将位置 i 的元素入栈。 72 | 73 | 2. 使用哈希表存储 1 中的结果。将元素值与其右边第一个更大的元素值的对应关系存入哈希表 74 | 75 | ## 问题 76 | 77 | 怎么去构建最小栈。使用 map 存储什么呢?单调栈+哈希表 78 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/盛最多水的容器.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点  (i, ai) 。在坐标内画 n 条垂直线,垂直线 i  的两个端点分别为  (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与  x  轴共同构成的容器可以容纳最多的水。 4 | 5 | 说明:你不能倾斜容器,且  n  的值至少为 2。 6 | 7 | ![leetcode](http://aliyun-lc-upload.oss-cn-hangzhou.aliyuncs.com/aliyun-lc-upload/uploads/2018/07/25/question_11.jpg) 8 | 9 | 图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为  49。 10 | 11 | 示例: 12 | 13 | ```js 14 | 输入:[1,8,6,2,5,4,8,3,7] 15 | 输出:49 16 | ``` 17 | 18 | ## 思路 19 | 20 | 使用双指针的方法,初始时,left 指向最左边,right 指向最右边,每次移动 对应数字比较小的指针, 容纳的水量为 `两个指针指向的数字中较小值∗指针之间的距离` 21 | 22 | ```js 23 | /** 24 | * @param {number[]} height 25 | * @return {number} 26 | */ 27 | var maxArea = function(height) { 28 | // 在初始时,左右指针分别指向数组的左右两端 29 | let l = 0; 30 | let r = height.length - 1; 31 | let maxArea = 0; 32 | 33 | while (l < r) { 34 | // 容纳的水量为 两个指针指向的数字中较小值∗指针之间的距离 35 | maxArea = Math.max(maxArea, (r - l) * Math.min(height[r], height[l])); 36 | // 移动对应数字较小的那个指针 37 | if (height[r] >= height[l]) { 38 | l++; 39 | } else { 40 | r--; 41 | } 42 | } 43 | return maxArea; 44 | }; 45 | ``` 46 | 47 | - 时间复杂度:O(N),双指针总计最多遍历整个数组一次。 48 | - 空间复杂度:O(1),只需 d 要额外的常数级别的空间。 49 | 50 | ### 总结 51 | 52 | 1. 下面这段 `if` 语句为什么要移动较低的一边: 53 | 54 | ```js 55 | // 移动对应数字较小的那个指针 56 | if (height[r] >= height[l]) { 57 | l++; 58 | } else { 59 | r--; 60 | } 61 | ``` 62 | 63 | 因为矩形的高度是由 `Math.min(height[left], height[right])`即较低的一边决定的 64 | 65 | 2. 双指针技巧 66 | 67 | 用 `left` 和 `right`两个指针从两端向中心收缩,一边收缩一边计算`[left, right]`之间的矩形面积,取最大的面积值即是答案。 68 | 我们不要想整体,而应该去想局部。 仅仅对于位置 i,能装下多少水呢?对于位置 i,能够装的水为: 69 | 70 | ```js 71 | water[i] = min( 72 | //左边最高的柱子 73 | max(height[0..i]), 74 | // 右边最高的柱子 75 | max(height[i..end]) 76 | ) - height[i] 77 | ``` 78 | 79 | ## 参考 80 | 81 | - [leetcode 官方题解](https://leetcode-cn.com/problems/container-with-most-water/solution/sheng-zui-duo-shui-de-rong-qi-by-leetcode-solution/) 82 | - [详解一道高频面试题:接雨水](https://mp.weixin.qq.com/s/8E2WHPdArs3KwSwaxFunHw) 83 | -------------------------------------------------------------------------------- /docs/algorithm/其他/数据结构和算法的框架思维.md: -------------------------------------------------------------------------------- 1 | ## 数据结构的存储方式 2 | 3 | 数据结构的存储方式只有两种:数组(顺序存储)和链表(链式存储)。 4 | 其他的数据结构都是以这个为基础实现的。数组和链表是【结构基础】,其他的都是【上层建筑】 5 | 6 | 数组和链表的优缺点: 7 | 8 | - 数组由于是紧凑连续存储,可以随机访问,通过索引快速找到对应元素,而且相对节约存储空间。但正因为连续存储,内存空间必须一次性分配够,所以说数组如果要扩容,需要重新分配一块更大的空间,再把数据全部复制过去,时间复杂度 O(N);而且你如果想在数组中间进行插入和删除,每次必须搬移后面的所有数据以保持连续,时间复杂度 O(N)。 9 | 10 | - 链表因为元素不连续,而是靠指针指向下一个元素的位置,所以不存在数组的扩容问题;如果知道某一元素的前驱和后驱,操作指针即可删除该元素或者插入新元素,时间复杂度 O(1)。但是正因为存储空间不连续,你无法根据一个索引算出对应元素的地址,所以不能随机访问;而且由于每个元素必须存储指向前后元素位置的指针,会消耗相对更多的储存空间。 11 | 12 | ## 数据结构的基本操作 13 | 14 | 遍历+访问,即增删改查。 15 | 数据结构的种类很多,但他们存在的目的都是在不同的应用场景中,尽可能高效的增删改查。 16 | 如何遍历+访问?就是就是两种形式:线性和非线性。 17 | 线性的就是 `for/while`,非线性就是递归。再具体一点,无非以下几种框架: 18 | 19 | 1. 数组遍历框架, 典型的线性迭代结构: 20 | 21 | ```js 22 | function traverse(arr) { 23 | for (let i = 0; i < arr.length; i++) { 24 | // 迭代访问 arr[i] 25 | } 26 | } 27 | ``` 28 | 29 | 2. 链表遍历框架,兼具迭代和递归结构: 30 | 31 | ```js 32 | /* 基本的单链表节点 */ 33 | class ListNode { 34 | var val; 35 | ListNode next; 36 | } 37 | 38 | function traverse(head) { 39 | for (let p = head; p != null; p = p.next) { 40 | // 迭代访问 p.val 41 | } 42 | } 43 | 44 | function traverse(head) { 45 | // 递归访问 head.val 46 | traverse(head.next); 47 | } 48 | ``` 49 | 50 | 3. 二叉树的遍历框架,典型的非线性递归遍历框架 51 | 52 | ```js 53 | /* 基本的二叉树节点 */ 54 | class TreeNode { 55 | int val; 56 | TreeNode left, right; 57 | } 58 | 59 | function traverse(root) { 60 | traverse(root.left); 61 | traverse(root.right); 62 | } 63 | 64 | ``` 65 | 66 | 二叉树的递归遍历方式和链表的递归遍历方式很相似,二叉树结构和单链表结构也很相似,如果再多几条叉,N 叉树的遍历也是类似 67 | 二叉树框架可以扩展为 N 叉树的遍历框架: 68 | 69 | ```js 70 | /* 基本的 N 叉树节点 */ 71 | class TreeNode { 72 | var val; 73 | TreeNode[] children; 74 | } 75 | 76 | function traverse( root) { 77 | for (let child of root.children) 78 | traverse(child); 79 | } 80 | 81 | ``` 82 | 83 | N 叉树的遍历又可以扩展为图的遍历,因为图就是好几 N 叉棵树的结合体。你说图是可能出现环的?这个很好办,用个布尔数组 visited 做标记就行了,这里就不写代码了。 84 | 85 | 所谓框架,就是套路。不管增删查改,这些代码都是永远无法脱离的结构,你可以把这个结构作为大纲,根据具体问题在框架上添加代码就行了,下面会具体举例。 86 | 87 | ## 算法刷题指南 88 | 89 | 数据结构是工具,算法是通过合适的工具解决特定问题的方法。在学算法之前,起码要了解那些常用的数据结构,了解他们的特性和缺陷。 90 | 91 | [学习算法和刷题的框架思维](https://labuladong.github.io/algo/1/2/) 92 | -------------------------------------------------------------------------------- /docs/algorithm/回溯/46.全排列.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给定一个 没有重复 数字的序列,返回其所有可能的全排列。 4 | 5 | 示例: 6 | 7 | ```js 8 | 输入: [1, 2, 3]; 9 | 输出: [ 10 | [1, 2, 3], 11 | [1, 3, 2], 12 | [2, 1, 3], 13 | [2, 3, 1], 14 | [3, 1, 2], 15 | [3, 2, 1], 16 | ]; 17 | ``` 18 | 19 | ## 思路 20 | 21 | ```js 22 | /** 23 | * @param {number[]} nums 24 | * @return {number[][]} 25 | */ 26 | var permute = function(nums) { 27 | //交换两个元素 28 | function swap(arr, i, j) { 29 | [arr[j], arr[i]] = [arr[i], arr[j]]; 30 | } 31 | // 从下标 p开始到 q之间的全排列 32 | function perm(nums, p, q) { 33 | // 当只有一个元素时,停止递归 34 | if (p == q) { 35 | return result.push([...nums]); 36 | } 37 | for (let i = p; i <= q; i++) { 38 | // 1.1把最开始的元素和i交换 39 | swap(nums, p, i); 40 | // 1.2,把除p之外的元素做全排列 41 | perm(nums, p + 1, q); 42 | // 2. 之后再交换回来,进行下一轮全排列 43 | swap(nums, p, i); 44 | } 45 | } 46 | let result = []; 47 | perm(nums, 0, nums.length - 1); 48 | return result; 49 | }; 50 | ``` 51 | 52 | > 推荐视频讲解 [全排列](https://www.bilibili.com/video/BV1dx411S7WR?spm_id_from=333.788.b_636f6d6d656e74.24) 53 | 54 | 学习回溯算法的经典算法。 55 | 56 | ```js 57 | // @lc code=start 58 | /** 59 | * @param {number[]} nums 60 | * @return {number[][]} 61 | */ 62 | 63 | var permute = function(nums) { 64 | let res = []; 65 | // 记录路径 66 | let track = []; 67 | // 记录路径中的元素是否有被使用 68 | let used = new Array(nums.length).fill(false); 69 | 70 | function backTrack(nums, track, used) { 71 | // 满足条件,加入结果 72 | if (track.length === nums.length) { 73 | res.push(track.slice()); 74 | return; 75 | } 76 | for (let i = 0; i < nums.length; i++) { 77 | if (used[i]) { 78 | continue; 79 | } 80 | // 做选择 81 | track.push(nums[i]); 82 | used[i] = true; 83 | // 进入下一层决策树 84 | backTrack(nums, track, used); 85 | // 撤销选择 86 | track.pop(); 87 | used[i] = false; 88 | } 89 | } 90 | backTrack(nums, track, used); 91 | return res; 92 | }; 93 | // @lc code=end 94 | ``` 95 | -------------------------------------------------------------------------------- /docs/algorithm/数组/三数之和.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 4 | 5 | 注意:答案中不可以包含重复的三元组。 6 | 7 | 示例: 8 | ```js 9 | 给定数组 nums = [-1, 0, 1, 2, -1, -4], 10 | 11 | 满足要求的三元组集合为: 12 | [ 13 | [-1, 0, 1], 14 | [-1, -1, 2] 15 | ] 16 | ``` 17 | 18 | ## 解题方法 19 | 使用`排序 + 双指针`方法解决; 20 | 21 | 1. 首先进行数组排序,时间复杂度 O(nlogn) 22 | 2. 对数组nums进行遍历,每遍历一个值利用其下标 i,形成一个固定值 `nums[i]` 23 | 3. 如果 `nums[i]` 大于0, 则三数之和必然无法等于0,直接结束循环 24 | 4. 如果 nums[i] == nums[i-1],则说明该数字重复,会导致结果重复,所以应该跳过 25 | 5. 再使用前指针指向 `start = i + 1`处,后指针指向`end = nums.length - 1`,也就是结尾处 26 | 6. 根据 `sum = nums[i] + nums[start] + nums[end]`结果,判断 sum 与 0 的大小关系,满足则添加进入结果,此时 `start++; end--` 如果 `sum < 0`,则`start++`, 如果 `sum > 0`, 则 `end--` 27 | 7. sum === 0 的时候还要考虑结果重复的情况 28 | - nums[start] == nums[start+1] 则会导致结果重复,应该跳过,start++ 29 | - nums[end] == nums[end-1] 则会导致结果重复,应该跳过,end-- 30 | 31 | ```js 32 | /** 33 | * @param {number[]} nums 34 | * @return {number[][]} 35 | */ 36 | var threeSum = function(nums) { 37 | let result = []; 38 | let len = nums.length; 39 | if(nums === null || len < 3) { 40 | return result; 41 | } 42 | nums.sort((a, b) => a - b); 43 | for(let i = 0; i < len; i++) { 44 | // 如果当前数字大于0,则三数之和一定大于0,所以结束循环 45 | if(nums[i] > 0) { 46 | break; 47 | } 48 | // 去重 49 | if( i > 0 && nums[i] === nums[ i - 1]) { 50 | continue; 51 | } 52 | let start = i + 1; 53 | let end = len - 1; 54 | while(start < end) { 55 | let sum = nums[i] + nums[start] + nums[end]; 56 | if(sum === 0) { 57 | result.push([nums[i], nums[start], nums[end]]); 58 | // 去重 59 | while(start < end && nums[start] === nums[start + 1]) { 60 | start++; 61 | } 62 | // 去重 63 | while(start < end && nums[end] === nums[end - 1]) { 64 | end--; 65 | } 66 | start++; 67 | end--; 68 | } else if(sum < 0) { 69 | start++; 70 | } else if(sum > 0) { 71 | end--; 72 | } 73 | } 74 | } 75 | return result; 76 | 77 | }; 78 | ``` --------------------------------------------------------------------------------