├── Flutter学习笔记 └── Flutter 入门指南.md ├── Git学习笔记 └── Git.md ├── JavaScript学习笔记 ├── ES6的模块化export和import.md ├── JQuery.md ├── JavaScript 函数式编程.md ├── javascript高级.md ├── js正则表达式.md ├── 数组、字符串常用函数掌握.md └── 红宝书读书笔记 │ └── 红宝书笔记.md ├── Node学习笔记 └── Node入门.md ├── README.md ├── React学习笔记 ├── Mobx的基本使用.md ├── React基础入门.md ├── React学习.md ├── Redux的基本使用.md ├── 深入react技术栈阅读笔记.md └── 源码深入 │ └── 原理 │ ├── Redux、React-Redux、中间件原理剖析.md │ └── setState是同步或异步.md ├── TS学习笔记 ├── TS体操类型.md └── TS学习笔记.md ├── Vue学习笔记 ├── Vue CLI.md ├── Vue router.md ├── Vuex.md ├── vue-cli3进行路径自定义.md └── 源码深入 │ └── Vue双向绑定原理及实现.md ├── Vue源码分析 └── Vue源码阅读笔记 │ ├── 01-Vue源码准备工作.md │ ├── 02-数据驱动.md │ └── new Vue发生了什么?.md ├── Webpack学习笔记 ├── SplitChunkPlugins拆包.md └── Webpack.md ├── 其他 └── mac mysql安装踩坑记录.md ├── 思考 └── 如何学习前端.md ├── 操作系统学习笔记(期末) ├── 操作系统笔记.md ├── 操作系统简答题.md └── 操作系统计算题.md ├── 数据结构与算法 ├── 二叉树篇 │ ├── 572. 另一个树的子树.md │ ├── 617. 合并二叉树.md │ ├── 【1】 二叉树前言.md │ ├── 【Leetcode 101】对称二叉树.md │ ├── 【Leetcode 104】二叉树的最大深度.md │ ├── 【Leetcode 111】二叉树的最小深度.md │ ├── 【Leetcode 144】二叉树的前序遍历.md │ ├── 【Leetcode 145】二叉树的后序遍历.md │ ├── 【Leetcode 230】二叉搜索树的第k小的元素.md │ ├── 【Leetcode 94】二叉树的中序遍历.md │ ├── 【剑指Offer 07】重建二叉树.md │ ├── 【剑指Offer 26】树的子结构.md │ ├── 【剑指Offer 27】二叉树的镜像.md │ ├── 【剑指Offer 33】二叉搜索树的后序遍历.md │ ├── 【剑指Offer 34】二叉树中和为某一值的路径.md │ ├── 【剑指Offer 36】二叉搜索树与双向链表.md │ ├── 【剑指Offer 37】序列化二叉树.md │ ├── 【剑指Offer 55】平衡二叉树.md │ ├── 【剑指Offer07 变型题】给前中遍历,求后序遍历.md │ ├── 【经典】二叉树的下一个节点.md │ ├── 二叉树中和为某一值的路径.md │ ├── 二叉树中的最大路径和.md │ ├── 二叉树之字形层序遍历.md │ ├── 二叉树根节点到叶子节点的所有路径和.md │ ├── 二叉树的右视图.md │ ├── 从尾到头打印链表.md │ ├── 判断t1树中是否有与t2树拓扑结构完全相同的子树.md │ ├── 判断该二叉树是否为搜索二叉树和完全二叉树.md │ ├── 剑指 Offer 54. 二叉搜索树的第k大节点.md │ ├── 求二叉搜索树最近公共祖先.md │ └── 求二叉树最近公共祖先.md ├── 动态规划 │ ├── 300. 最长递增子序列.md │ ├── NC19 子数组的最大累加和问题.md │ ├── leetcode 5. 最长回文子串.md │ ├── leetcode 62 不同路径.md │ ├── leetcode 64. 最小路径和.md │ ├── leetcode 72 编辑距离.md │ ├── 剑指 Offer 10- II. 青蛙跳台阶问题.md │ └── 动态规划.md ├── 哈希表 │ ├── 【leetcode 771】宝石与石头.md │ └── 哈希表前言.md ├── 回溯法 │ ├── leetcode 131. 分割回文串.md │ ├── leetcode 39 组合总和.md │ ├── leetcode 40 组合总和2.md │ ├── leetcode 47全排列2.md │ ├── leetcode 70 组合.md │ ├── leetcode 90 子集2.md │ ├── leetcode46 全排列.md │ ├── 剑指 Offer 38. 字符串的排列.md │ ├── 剑指 Offer II 105. 岛屿的最大面积.md │ ├── 回溯法.md │ ├── 岛屿数量.md │ └── 牛客网 数字字符串转成IP地址.md ├── 堆篇 │ ├── 【剑指Offer 40】最小的k个数.md │ ├── 【剑指Offer 41】数据流中的中位数.md │ ├── 堆的基本操作.md │ └── 堆篇前言.md ├── 字符串 │ ├── 933. 最近的请求次数.md │ ├── NC52 牛客 括号序列.md │ ├── NC97 字符串出现次数的TopK问题.md │ ├── 【剑指 Offer 20】表示数值的字符串.md │ ├── 【剑指Offer 05】替换空格.md │ ├── 【剑指Offer 19】正则表达式匹配.md │ └── 大数相加.md ├── 数组篇 │ ├── NC22 合并两个有序的数组.md │ ├── NC41 最长无重复子数组.md │ ├── NC7 买卖股票的最好时机.md │ ├── NC95 数组中的最长连续子序列.md │ ├── 【1】数组前言.md │ ├── 【Leetcode 15】三数之和.md │ ├── 【Leetcode 18】四数之和.md │ ├── 【Leetcode 1】两数之和.md │ ├── 【leetcode 42】接雨水.md │ ├── 【leetcode 611】有效三角形的个数.md │ ├── 【leetcode 面试题 10.01】. 合并排序的数组.md │ ├── 【剑指Offer 21】调整数组顺序使奇数位于偶数前面.md │ ├── 【剑指Offer 26】顺时针打印矩阵.md │ ├── 【剑指Offer 37】数组中出现次数超过数组长度一半的数字.md │ ├── 【剑指Offer 42】连续子数组的最大和.md │ ├── 【剑指Offer 50】第一个只出现一次的字符.md │ ├── 【剑指Offer 57 Ⅱ】和为S的人连续正整数序列.md │ ├── 【剑指Offer 57】和为S的两个数字.md │ ├── 【剑指Offer 61】扑克牌中的顺子.md │ ├── 【剑指Offer 66】构建乘积数组.md │ ├── 合并有序数组.md │ ├── 数组去重.md │ ├── 斐波那契数列.md │ ├── 比较版本号.md │ ├── 牛客合并区间.md │ └── 猿辅导笔试编程 逆时针输出.md ├── 栈和队列 │ ├── 【leetcode 239】滑动窗口最大值.md │ ├── 【剑指 Offer 30】包含min函数的栈.md │ ├── 【剑指 Offer 31】 栈的压入、弹出序列.md │ ├── 【剑指Offer 09】用两个栈实现队列.md │ ├── 栈和队列前言.md │ ├── 表达式求值.md │ └── 设计LRU缓存结构.md ├── 设计LRU缓存结构.md └── 链表篇 │ ├── K 个一组翻转链表.md │ ├── NC132 环形链表的约瑟夫问题.md │ ├── NC2 重排链表.md │ ├── NC24 删除有序链表中重复的元素-II.md │ ├── NC69 链表中倒数最后k个节点.md │ ├── NC70 单链表的排序.md │ ├── NC96 判断一个链表是否为回文结构.md │ ├── 【1】链表前言.md │ ├── 【Leetcode 142】环形链表Ⅱ(链表环的入口节点).md │ ├── 【Leetcode 160】相交链表.md │ ├── 【Leetcode 237】删除链表中的节点.md │ ├── 【Leetcode 430】扁平化多级双向链表.md │ ├── 【Leetcode 92】反转链表.md │ ├── 【剑指Offer 06】从尾到头打印链表.md │ ├── 【剑指Offer 141】 环形链表.md │ ├── 【剑指Offer 22】链表中倒数第k个节点.md │ ├── 【剑指Offer 35】复杂链表的复制.md │ ├── 【剑指Offer 52】两个链表的第一个公共节点.md │ ├── 【剑指Offer 62】圆圈中最后剩下的数字(约瑟夫环).md │ ├── 两个链表生成相加链表.md │ ├── 二叉树中的最大路径和.md │ ├── 二叉树根节点到叶子节点的所有路径和.md │ ├── 删除有序链表中重复的元素-I.md │ ├── 删除链表的倒数第 N 个结点.md │ ├── 判断t1树中是否有与t2树拓扑结构完全相同的子树.md │ ├── 判断该二叉树是否为搜索二叉树和完全二叉树。.md │ ├── 反转链表2 │ ├── 反转链表2.md │ ├── 合并K个升序链表.md │ ├── 合并两个有序链表.md │ └── 牛客NC132 环形链表的约瑟夫问题.md ├── 计算机网络学习笔记(期末) └── 计算机网络笔记.md ├── 读书笔记(其他) ├── 你的灯亮着吗 │ └── 你的灯亮着吗.md ├── 小强升职记 │ ├── 小强升职记.md │ └── 小强升职记.pdf ├── 自控力 │ └── 自控力.md └── 金字塔原理 │ └── 金字塔原理.md ├── 软件度量学习笔记(期末) └── 软件度量.md └── 项目总结 ├── React个人博客 ├── React个人博客数据库+接口文档.md └── 开发过程中遇到的问题及知识点.md ├── React仿网易云 ├── 01-React+TS项目环境搭建.md ├── 开发调研.md ├── 总结沉淀.md └── 接口文档.md ├── Vue电商管理系统项目总结 ├── 01电商后台管理系统概述.md ├── 02 配置API接口服务器并调试接口.md ├── 03 登录退出功能.md ├── 04 系统主页和用户列表功能.md ├── 05 用户权限管理.md ├── 06 商品分类.md ├── 07 参数管理.md ├── 08 商品列表功能的实现.md ├── 09 数据报表功能.md ├── 10-项目优化上线.md ├── Vue电商后台项目部署指南.md ├── webpack打包优化数据记录.md ├── 使用pm2管理应用报错:.md └── 项目总结.md └── 其他项目总结 ├── TodoList项目总结.md ├── 品优购项目总结.md └── 星巴克购物系统项目总结.md /Git学习笔记/Git.md: -------------------------------------------------------------------------------- 1 | # Git 2 | 3 | ### 创建仓库 4 | 5 | ```other 6 | //第一步,进入当前要进入文件目录 7 | $ mkdir XXX //创建子目录 如果子目录存在则不用这一步 8 | $ cd XXX //进入子目录 9 | $ pwd //显示路径 确认以进入目录 10 | 11 | //第二步,通过git init命令把这个目录变成Git可以管理的仓库: 12 | $ git init 13 | ``` 14 | 15 | 16 | 17 | ### 添加、提交文件 18 | 19 | ```other 20 | $ git add file //添加文件至暂存区 21 | $ git commit -m"explain" //提交 22 | //可添加多个 一次提交 23 | ``` 24 | 25 | 26 | 27 | ### 版本管理 28 | 29 | ```other 30 | $ git status //查看工作区与版本库的状态 31 | $ git log //查看提交记录 32 | $ git log --pretty=oneline 33 | $ git reflog //查看命令记录 34 | $ cat file //查看内容 35 | $ git reset --hard xxx//版本回退 xxx为版本号或 HEAD^ HEAD^^ HEAD~数字 。git用HEAD表示当前版本,,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100. 36 | 37 | ``` 38 | 39 | 40 | 41 | ### 修改管理 42 | 43 | ```other 44 | $ git diff HEAD -- file //查看工作区与版本库最新版本的区别 45 | $ git checkout -- file //撤销(丢弃)工作区的修改 46 | //①修改后未放暂存区, 撤销修改后与版本库一致。 47 | //②加入暂存区又做修改,撤销修改后回到添加到暂存 区的状态。 48 | $ git rm file //在版本库删除文件 49 | $ git checkout -- file //误删时,复制版本库的最新版本。 50 | ``` 51 | 52 | 53 | 54 | ### 关联远程仓库 55 | 56 | ```git 57 | $ git remote add origin git@github.com:ruoruochen/learngit.git//关联远程库 58 | $ git push -u origin master //本地库所有内容推送至远程库 59 | $ git push origin master //可推送最新修改 60 | ``` 61 | 62 | 63 | 64 | ### 克隆远程库 65 | 66 | ```git 67 | $ git clone git@github.com:ruoruochen/gitskills.git //克隆 68 | $ cd gitskills //进入子目录 69 | $ ls //查看目录内容 70 | ``` 71 | 72 | 73 | 74 | ## 分支管理 75 | 76 | ### 创建和合并分支 77 | 78 | ```git 79 | $ git checkout -b dev //创建dev分支并切换到dev分支 80 | $ git switch -c dev //创建并切换到dev分支 建议使用switch 81 | //相当于一下两条命令: 82 | //$ git branch dev //创建分支 83 | //$ git checkout dev 84 | 85 | $ git branch //查看当前分支 86 | $ git checkout master //切回master分支 87 | $ git switch master //切回master 88 | $ git merge dev //git merge用于合并指定分支到当前分支,即合并dev到当前分支master 89 | $ git branch -d dev //删除分支 90 | ``` 91 | 92 | 93 | 94 | ### 解决冲突 95 | 96 | ```git 97 | 当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。 98 | 99 | 解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。 100 | ``` 101 | 102 | 103 | 104 | ### 分支管理策略 105 | 106 | - 强制禁用`Fast forward`模式进行合并:$ git merge --no-ff -m "merge with no-ff" dev 107 | - 查看分支历史 $ git log --graph --pretty=oneline --abbrev-commit 108 | 109 | 110 | 111 | ### Bug分支 112 | 113 | - 把当前工作现场“储藏”起来:$ git stash 114 | 115 | - 查看工作现场:$ git stash list 116 | 117 | - 将stash内容回复的两种方法: 118 | 119 | 1. ​ git stash apply。恢复后,stash内容并不删除,你需要用`git stash drop`来删除。 120 | 2. git stash pop。恢复的同时把stash内容也删了 121 | 122 | - `cherry-pick`命令,让我们能复制一个特定的提交到当前分支 123 | 124 | ```git 125 | $ git cherry-pick 4c805e2 126 | ``` 127 | 128 | 129 | 130 | ### Feature分支 131 | 132 | - 添加一个新功能,新建一个feature分支,在上面开发,完成后,合并,最后删除该分支。 133 | 134 | 135 | 136 | ### 多人协作 137 | 138 | - 查看远程库信息:git remote 或git remote -v (更详细信息) 139 | 140 | - 推送分支: 141 | 142 | ```git 143 | $ git push origin master 144 | ``` 145 | 146 | - 推送失败,先用`git pull`抓取远程的新提交,若有冲突,处理冲突。 147 | - 在本地创建和远程分支对应的分支,使用`git checkout -b branch-name origin/branch-name`,本地和远程分支的名称最好一致 148 | - 建立本地分支和远程分支的关联,使用`git branch --set-upstream branch-name origin/branch-name`; 149 | 150 | 151 | 152 | ### Rebase 153 | 154 | - rebase操作可以把本地未push的分叉提交历史整理成直线; 155 | - rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。 156 | 157 | 158 | 159 | # 标签管理 160 | 161 | ### 创建标签 162 | 163 | - 创建标签: 164 | 165 | - 给最新提交的commit打上标签:$ git tag 166 | 167 | - 给指定commit打标签:$ git tag v0.9 f52c633 168 | 169 | - 还可以创建带有说明的标签,用`-a`指定标签名,`-m`指定说明文字: 170 | 171 | ``` 172 | $ git tag -a -m "balbalabala" 173 | ``` 174 | 175 | - 查看所有标签: $ git tag 176 | 177 | - 查看标签信息:git show 178 | 179 | 180 | 181 | ### 操作标签 182 | 183 | - 删除标签(未推送至远程):$ git tag -d 184 | - 删除远程标签: 185 | 1. 本地删除 :$ git tag -d 186 | 2. 远程删除 :$ git push origin :refs/tags/tagname 187 | 3. 登录Github检查是否删除成功。 188 | - 推送某个标签至远程:$ git push origin 189 | - 一次性推送全部未推送至远程的本地标签: $ git push origin --tags 190 | 191 | 192 | 193 | 194 | 195 | ### 使用Gitee 196 | 197 | - 查看远程库信息:`git remote -v` 198 | - 删除远程库:git remote rm origin 199 | - 可以同步多个远程库,git给远程库默认名origin,多个远程库需用不同名称标识。 -------------------------------------------------------------------------------- /Node学习笔记/Node入门.md: -------------------------------------------------------------------------------- 1 | # Node入门基础 2 | 3 | Node.js原生的工作方式是**事件驱动**的,因此很快。 4 | 5 | 1. 效率高。在 Web 应用程序中,主要响应时间成本通常是执行所有数据库查询所需的时间总和。使用 node,您可以一次执行所有查询,将响应时间减少到执行最慢查询所需的持续时间。 6 | 2. JS 原始速度快,V8快。 7 | 8 | ![image-20211113214709902](https://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/202111132147090.png) 9 | 10 | 参考连接:[Understanding node.js](http://debuggable.com/posts/understanding-node-js:4bd98440-45e4-4a9a-8ef7-0f7ecbdd56cb) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # front-end-note 2 | 这个仓库是笔者学习前端时的前端学习笔记,主要总结一些比较重要的知识点和前端面试问题,希望对大家有所帮助。 3 | -------------------------------------------------------------------------------- /Vue学习笔记/vue-cli3进行路径自定义.md: -------------------------------------------------------------------------------- 1 | # vue-cli3进行路径自定义总结(vue-cli3配置踩雷:These dependencies were not found:) 2 | 3 | 今天弄这个弄了好久,于是想记录下来,方便自己也方便他人。 4 | 5 | ### vue-cli2 6 | 7 | 首先vue-cli2要是想要修改自定义路径,需要到build文件夹下面的、base.conf.js文件里面 8 | 9 | ![image-20210116194732900](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116194732900.png) 10 | 11 | 修改如下部分代码,在alias中加入新的自定义键值对即可。 12 | 13 | ![image-20210116194747986](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116194747986.png) 14 | 15 | ### vue-cli3 16 | 17 | 在vuecli3中,推崇零配置,故不再存在build文件夹,所有的配置都需要在一个自己增加的文件vue.config.js中添加。 18 | 19 | 首先,在项目根目录下添加vue.config.js:**注意!必须是这个名字** 20 | 21 | ![image-20210116194956811](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116194956811.png) 22 | 23 | 这个文件应该导出一个包含了选项的对象: 24 | 25 | ```js 26 | // vue.config.js 27 | module.exports = { 28 | // 选项... 29 | } 30 | ``` 31 | 32 | 随后在vue.config.js中进行配置,导入path、resolve方法,最后在导出对象中配置别名。**set前面的参数要有@** 别问我为什么,问为什么我也不知道!搞了半天居然是这个的原因我气死了! 33 | 34 | ![image-20210116203515129](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116203515129.png) 35 | 36 | 如果不加**@**会报错:![img](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/0A61BC01.png) 37 | 38 | ``` 39 | ERROR Failed to compile with 2 errors 40 | These dependencies were not found: 41 | ``` 42 | 43 | 44 | 45 | ![image-20210116203819332](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116203819332.png) 46 | 47 | 48 | 49 | vue.config.js文件配置可以具体参考:https://cli.vuejs.org/zh/config/#vue-config-js 50 | 51 | 使用: 52 | 53 | ![image-20210116204219491](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116204219491.png) -------------------------------------------------------------------------------- /其他/mac mysql安装踩坑记录.md: -------------------------------------------------------------------------------- 1 | # mac mysql安装踩坑记录 2 | 3 | - 优先推荐使用brew安装 4 | 5 | 6 | 7 | ## 安装mysql 8 | 9 | ```bash 10 | brew install mysql 11 | ``` 12 | 13 | 14 | 15 | ## 设置mysql 16 | 17 | 全程勾选y,如果执行改命令行 18 | 19 | ```shell 20 | mysql_secure_installation 21 | ``` 22 | 23 | 24 | 25 | **意外报错处理:** 26 | 27 | - 如果执行`mysql_secure_installation`,报错`can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)` 28 | 29 | - 原因:mysql未执行 30 | - 解决方案:执行命令`mysql.sever start` 31 | 32 | - 如果执行`mysql.sever start`,报错`...Error The server quit without updating PID file` 33 | 34 | - 原因:`mysql`二次安装,之前未卸载干净 35 | 36 | - 解决方案:`mysql`完全删除 37 | 38 | - `Case1`:使用`homebrew`安装的,执行以下命令行 39 | 40 | - ```shell 41 | brew uninstall mysql 42 | brew cleanup 43 | ``` 44 | 45 | - `Case2`:使用安装包下载的,在系统偏好设置中卸载 46 | 47 | 统一检查,完全删除残余文件 48 | 49 | ```shell 50 | sudo rm /usr/local/mysql 51 | sudo rm -rf /usr/local/var/mysql; 52 | sudo rm -rf /usr/local/mysql* 53 | sudo rm -rf /Library/StartupItems/MySQLCOM 54 | sudo rm -rf /Library/PreferencePanes/My* 55 | rm -rf ~/Library/PreferencePanes/My* 56 | sudo rm -rf /Library/Receipts/mysql* 57 | sudo rm -rf /Library/Receipts/MySQL* 58 | sudo rm -rf /var/db/receipts/com.mysql.* 59 | ``` 60 | 61 | - **重点:**重启电脑,再重新安装mysql -------------------------------------------------------------------------------- /操作系统学习笔记(期末)/操作系统计算题.md: -------------------------------------------------------------------------------- 1 | ## 计算题 2 | 3 | #### 生产者消费者问题 4 | 5 | 信号量:互斥信号量 `mutex`(多个进程不能同时访问) ,(资源信号量)空缓存区数量`empty`,(资源信号量)使用缓存区数量`full`,分别初始化为1,n,0。 6 | 7 | 其他变量:in、out代表第一个资源和最后一个资源;buf[n] 代表缓冲区,类型为Item。 8 | 9 | **流程图** 10 | 11 | ![image-20210512210137576](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210512210137576.png) 12 | 13 | ```js 14 | int in = 0, out = 0; 15 | item buffer[n]; 16 | semaphore metux = 1,empty = n,full = 0; 17 | producer(){ 18 | while( true ) { 19 | wait(empty); // 等待空缓存区 20 | wait(metux); // 等待互斥锁 21 | 22 | buffer[in] = nextp // 将新资源放到buffer[in]位置 23 | in = ( in + 1 ) % 10; 24 | empty -- ; 25 | signal(mutex); // 释放互斥锁 26 | signal(full); // 增加使用缓存区 27 | } 28 | } 29 | 30 | consumer() { 31 | while( true ) { 32 | wait(full); // 等待使用缓存区 33 | wait(mutex); // 等待互斥锁 34 | 35 | // consumer 36 | nextc = buffer[out]// 将buf[out]位置的的资源取走 37 | out = ( out + 1 ) % 10; 38 | ful-- 39 | signal(mutex); // 释放互斥锁 40 | signal(empty); // 增加空缓存区 41 | } 42 | } 43 | 44 | void main(){ 45 | cobegin 46 | producer();producer(); 47 | coend 48 | } 49 | ``` 50 | 51 | #### 读者-写者问题 52 | 53 | #### 理发师问题 54 | 55 | #### 银行家算法(避免死锁) 56 | 57 | 1. 银行家算法的数据结构 58 | 59 | (1)资源向量Available。这是一个含有m个元素的数组,代表每一类可用资源的数目,随该类资源的分配和回收而动态地改变。如果Available[j]=K,则表示系统中现有j类资源K个。 60 | 61 | (2)最大需求矩阵Max。这是一个n×m的矩阵,它定义了系统中n个进程对m类资源的最大需求。如果Max[i,j]=K,则表示进程i需要j类资源的最大数目为K。 62 | 63 | (3)分配矩阵Allocation。这也是一个n×m的矩阵,它定义了系统中每一类资源当前已分配给每一进程的资源数。 Allocation[i,j]=K,表示进程i当前已有j类资源的数目为K。 64 | 65 | (4)需求矩阵Need。这也是一个n×m的矩阵,用以表示每一个进程尚需的各类资源数。如果Need[i,j]=K,表示进程 i 还需要 j类资源K个,才能完成其任务。 66 | 67 | 满足以下公式 68 | 69 | **`Need[i,j]= Max[i,j] - Allocation[i,j]`** 70 | 71 | 2. 银行家算法 72 | 73 | 设Request i是进程Pi的请求向量,如果Request i[j]=K,表示进程Pi需要K个j类资源。当Pi发出资源请求后,系统按下述步骤进行检查: 74 | 75 | (1) 如果Request i[j]≤Need[i,j],便转向步骤(2);否则认为出错,因为它所需要的资源数已超过它所宣布的最大值。 76 | 77 | (2) 如果Requesti[j]≤Available[j],便转向步骤(3);否则,表示尚无足够资源,Pi须等待。 78 | 79 | (3) 系统试探着把资源分配给进程Pi,并修改下面数据结构中的数值: `Available[j]:= Available[j]-Requesti[j];` 80 | 81 | `Allocation[i,j]:= Allocation[i,j]+Requesti[j];` 82 | 83 | `Need[i,j]:= Need[i,j]-Requesti[j];` 84 | 85 | (4)系统执行安全性算法,检查此次资源分配后系统是否处于安全状态。若安全,才正式将资源分配给进程Pi,以完成本次分配;否则,将本次的试探分配作废,恢复原来的资源分配状态,让进程Pi等待。 86 | 87 | **安全性算法** 88 | 89 | ​ 系统所执行的安全性算法可描述如下: 90 | 91 | ​ (1) 设置两个向量: 92 | 93 | ​ ① 工作向量Work,它表示系统可提供给进程继续运行所需的各类资源数目,它含有m个元素,在执行安全算法开始时,Work:=Available。 94 | 95 | ​ ② Finish,它表示系统是否有足够的资源分配给进程, 使之运行完成。开始时先做Finish[i]:=false;当有足够资源分配给进程时,再令Finish[i]:=true。 96 | 97 | ​ (2) 从进程集合中找到一个能满足下述条件的进程: 98 | 99 | ​ ① Finish[i]=false; 100 | 101 | ​ ② Need[i,j]≤Work[j]; 若找到,执行步骤(3),否则,执行步骤(4)。 102 | 103 | ​ (3) 当进程Pi获得资源后,可顺利执行,直至完成,并释放出分配给它的资源,故应执行: 104 | 105 | ​ `Work[j]:= Work[j]+Allocation[i,j];` 106 | 107 | ​ ` Finish[i]:=true;` 108 | 109 | go to step (2); 110 | 111 | ​ (4) 如果所有进程的Finish[i]=true都满足,则表示系统处于安全状态;否则,系统处于不安全状态。 -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/572. 另一个树的子树.md: -------------------------------------------------------------------------------- 1 | #### 572. 另一个树的子树 2 | 3 | ```js 4 | function isSametree(root1,root2){ 5 | if(!root1&&!root2)return true; 6 | if(!root1 || !root2) return false; 7 | 8 | return root1.val == root2.val &&isSametree(root1.left,root2.left) &&isSametree(root1.right,root2.right); 9 | } 10 | 11 | var isSubtree = function(root, subRoot) { 12 | if(!root&&!subRoot) return true; 13 | if(!root || !subRoot) return false; 14 | //判断以当前节点为根节点 是否为子树 15 | if(isSametree(root,subRoot)){ 16 | return true; 17 | } 18 | //否则以当前节点左右节点查找 19 | 20 | return isSubtree(root.left,subRoot) || isSubtree(root.right,subRoot); 21 | }; 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/617. 合并二叉树.md: -------------------------------------------------------------------------------- 1 | #### 617. 合并二叉树 2 | 3 | 前序遍历 拿到当前root进行合并,子树的合并交给递归去做。 4 | 5 | 将合并覆盖到root1树上。 6 | 7 | **递归** 8 | 9 | ```js 10 | var mergeTrees = function(root1, root2) { 11 | //本身是树结构符合递归条件 12 | if(!root1&&!root2) return root1; 13 | if(!root1 && root2) return root2; 14 | if(root1 && !root2) return root1; 15 | root1.val += root2.val; 16 | //左右子树递归 17 | root1.left = mergeTrees(root1.left,root2.left); 18 | root1.right = mergeTrees(root1.right,root2.right); 19 | return root1; 20 | }; 21 | ``` 22 | 23 | **迭代** 24 | 25 | ```js 26 | var mergeTrees = function(root1, root2) { 27 | if(!root1 && !root2) return null; 28 | if(!root1) return root2; 29 | if(!root2) return root1; 30 | //打平比较 31 | const quene = [root1,root2]; 32 | while(quene.length){ 33 | let cur1 = quene.shift(); 34 | let cur2 = quene.shift(); 35 | cur1.val += cur2.val; 36 | if(cur1.left && cur2.left){ 37 | quene.push(cur1.left); 38 | quene.push(cur2.left); 39 | } 40 | 41 | if(cur1.right && cur2.right){ 42 | quene.push(cur1.right); 43 | quene.push(cur2.right); 44 | } 45 | 46 | if(!cur1.left && cur2.left){ 47 | cur1.left = cur2.left; 48 | } 49 | 50 | if(!cur1.right && cur2.right){ 51 | cur1.right = cur2.right; 52 | } 53 | } 54 | return root1; 55 | }; 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【1】 二叉树前言.md: -------------------------------------------------------------------------------- 1 | # 二叉树前言 2 | 3 | ## 二叉树概念 4 | 5 | 树是用来模拟具有树状结构性质的数据集合。根据它的特性可以分为非常多的种类,对于我们来讲,掌握二叉树这种结构就足够了,它也是树最简单、应用最广泛的种类。 6 | 7 | > 二叉树是一种典型的树状结构。如它名字所描述的那样,二叉树是每个节点最多有两个子树的树结构,通常子树被称作“左子树”和“右子树”。 8 | 9 | ## 二叉树遍历★★★★★ 10 | 11 | **二叉树遍历是重点中的重点,需要掌握递归版本和非递归版本,同时掌握机写和手写。真正考察基本功的是非递归版本**。 12 | 13 | - [二叉树的中序遍历](http://www.conardli.top/docs/dataStructure/二叉树/二叉树的中序遍历.html) 14 | - [二叉树的前序遍历](http://www.conardli.top/docs/dataStructure/二叉树/二叉树的前序遍历.html) 15 | - [二叉树的后序遍历](http://www.conardli.top/docs/dataStructure/二叉树/二叉树的后序遍历.html) 16 | 17 | > 根据前序遍历和中序遍历的特点重建二叉树,逆向思维,很有意思的题目 18 | 19 | - [重建二叉树](http://www.conardli.top/docs/dataStructure/二叉树/重建二叉树.html) 20 | - [求二叉树的遍历](http://www.conardli.top/docs/dataStructure/二叉树/重建二叉树.html#题目2-求二叉树的遍历) 21 | 22 | ### 二叉树的对称性 23 | 24 | - [对称的二叉树](http://www.conardli.top/docs/dataStructure/二叉树/对称的二叉树.html) 25 | - [二叉树的镜像](http://www.conardli.top/docs/dataStructure/二叉树/二叉树的镜像.html) 26 | 27 | ### 二叉搜索树 28 | 29 | **设x是二叉搜索树中的一个结点。如果y是x左子树中的一个结点,那么y.key≤x.key。如果y是x右子树中的一个结点,那么y.key≥x.key。** 30 | 31 | ​ 在二叉搜索树中: 32 | 33 | - 若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值; 34 | - 若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值; 35 | - 任意结点的左、右子树也分别为二叉搜索树 36 | 37 | 得益于二叉搜索树的性质,当使用中序遍历来访问一棵二叉搜索树上的所有结点时,最后得到的访问序列恰好是所有结点关键字的升序序列。 38 | 39 | - [二叉搜索树的第k小的元素](http://www.conardli.top/docs/dataStructure/二叉树/二叉搜索树的第k个节点.html#题目) 40 | - [二叉搜索树的后序遍历](http://www.conardli.top/docs/dataStructure/二叉树/二叉搜索树的后序遍历.html) 41 | 42 | ### 二叉树的深度 43 | 44 | > 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 45 | 46 | > 平衡二叉树:左右子树深度之差大于1 47 | 48 | - [二叉树的最大深度](http://www.conardli.top/docs/dataStructure/二叉树/二叉树的最大深度.html) 49 | - [二叉树的最小深度](http://www.conardli.top/docs/dataStructure/二叉树/二叉树的最小深度.html#考察点) 50 | - [平衡二叉树](http://www.conardli.top/docs/dataStructure/二叉树/平衡二叉树.html) 51 | 52 | ### 二叉树的经典题型 53 | 54 | [二叉树中和为某一值的路径]() 55 | 56 | [二叉搜索树与双向链表]() 57 | 58 | [序列化二叉树]() 59 | 60 | [二叉树的下一个节点]() 61 | 62 | [树的子结构]() 63 | 64 | **完全二叉树的特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。** 65 | 66 | # 更多资料 67 | 68 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 69 | 70 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 71 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 72 | 73 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【Leetcode 101】对称二叉树.md: -------------------------------------------------------------------------------- 1 | # 对称二叉树 2 | 3 | ## 题目 4 | 5 | 请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。 6 | 7 | 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 8 | 9 | ``` 10 | 1 11 | / \ 12 | 2 2 13 | / \ / \ 14 | 3 4 4 3 15 | ``` 16 | 17 | **示例 1:** 18 | 19 | ``` 20 | 输入:root = [1,2,2,3,4,4,3] 21 | 输出:true 22 | ``` 23 | 24 | ## 思路 25 | 26 | 二叉树的右子树是二叉树左子树的镜像二叉树。 27 | 28 | 镜像二叉树:两颗二叉树根结点相同,但他们的左右两个子节点交换了位置。 29 | 30 | 对称二叉树满足以下条件: 31 | 32 | - 根节点相同 33 | - root1的左节点与右root2的右节点相同 34 | - root1的右节点与root2的左节点相同 35 | 36 | ## 代码 37 | 38 | `没想出来` 39 | 40 | ```js 41 | var isSymmetric = function(root) { 42 | return isSymmetricalTree(root, root); 43 | }; 44 | function isSymmetricalTree(node1, node2) { 45 | //特判 46 | //如果两棵树都为空树,对称 47 | if (!node1 && !node2) { 48 | return true; 49 | } 50 | //一棵为空,另一棵不为空,不对称 51 | if (!node1 || !node2) { 52 | return false; 53 | } 54 | //如果根节点不相同,不对称 55 | if (node1.val != node2.val) { 56 | return false; 57 | } 58 | //递归判断左子树的左节点与右子树的右节点、左子树的右节点与右子树的左节点 59 | return isSymmetricalTree(node1.left, node2.right) && isSymmetricalTree(node1.right, node2.left); 60 | } 61 | ``` 62 | 63 | ![image-20210118234535675](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210118234535675.png) 64 | 65 | # 更多资料 66 | 67 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 68 | 69 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 70 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【Leetcode 104】二叉树的最大深度.md: -------------------------------------------------------------------------------- 1 | # 二叉树的最大深度 2 | 3 | ## 题目 4 | 5 | 给定一个二叉树,找出其最大深度。 6 | 7 | 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 8 | 9 | **说明:** 叶子节点是指没有子节点的节点。 10 | 11 | **示例:** 12 | 13 | 给定二叉树 `[3,9,20,null,null,15,7]`, 14 | 15 | ```text 16 | 3 17 | / \ 18 | 9 20 19 | / \ 20 | 15 7 21 | ``` 22 | 23 | 返回它的最大深度 3 。 24 | 25 | ## 思路 26 | 27 | - 深度优先遍历 + 分治 28 | - 一棵二叉树的最大深度 = 左子树深度和右子树深度的最大值+1 29 | 30 | ## 代码 31 | 32 | **dfs 深度优先遍历** 33 | 34 | ```js 35 | var maxDepth = function (root) { 36 | if (!root) { 37 | return 0; 38 | } 39 | return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; 40 | }; 41 | ``` 42 | 43 | ![image-20210119144930487](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210119144930487.png) 44 | 45 | **bfs 广度优先遍历** 46 | 47 | ```js 48 | var maxDepth = function (root) { 49 | if (!root) return 0; 50 | let depth = 1; 51 | let quene = [root]; 52 | while (quene.length) { 53 | //当前层节点数 54 | const levelSize = quene.length; 55 | //节点出列,子节点入队列 56 | for (let i = 0; i < levelSize; i++) { 57 | const cur = quene.shift(); 58 | if (cur.left) quene.push(cur.left); 59 | if (cur.right) quene.push(cur.right); 60 | } 61 | if (quene.length) depth++; 62 | } 63 | return depth; 64 | }; 65 | ``` 66 | 67 | 68 | 69 | # 更多资料 70 | 71 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 72 | 73 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 74 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【Leetcode 111】二叉树的最小深度.md: -------------------------------------------------------------------------------- 1 | # 二叉树的最小深度 2 | 3 | #### 题目 4 | 5 | 给定一个二叉树,找出其最小深度。 6 | 7 | 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 8 | 9 | 说明: 叶子节点是指没有子节点的节点。 10 | 11 | 示例: 12 | 13 | 给定二叉树 `[3,9,20,null,null,15,7]`, 14 | 15 | ```js 16 | 3 17 | / \ 18 | 9 20 19 | / \ 20 | 15 7 21 | ``` 22 | 23 | 返回它的最小深度 2 24 | 25 | #### 思路 26 | 27 | 深度优先+分治 28 | 29 | - 左右子树不为空:左右子树深度和右子树深度最小值+1 30 | - 左子树为空:右子树深度最小值+1 31 | - 右子树为空:左子树深度最小值+1 32 | 33 | #### 代码 34 | 35 | ```js 36 | /* 思路 递归 37 | 1.功能:求最小深度 38 | 2.出口 root空 return 0 39 | 3.等价表达式 40 | 左子树空 return 右子树最小深度+1 41 | 右子树空 return 左子树最小深度+1 42 | 左右子树均不空 return 左子树、右子树最小深度的最小值+1 */ 43 | var minDepth = function (root) { 44 | if (!root) { 45 | return 0; 46 | } 47 | 48 | if (!root.left) { 49 | return minDepth(root.right) + 1; 50 | } 51 | 52 | if (!root.right) { 53 | return minDepth(root.left) + 1; 54 | } 55 | 56 | return Math.min(minDepth(root.left), minDepth(root.right)) + 1; 57 | }; 58 | ``` 59 | 60 | ![image-20210119150654319](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210119150654319.png) 61 | 62 | # 更多资料 63 | 64 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 65 | 66 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 67 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【Leetcode 144】二叉树的前序遍历.md: -------------------------------------------------------------------------------- 1 | # 二叉树前序遍历 2 | 3 | 所谓前序遍历,即按照中左右的顺序进行遍历。 4 | 5 | ## 题目 6 | 7 | 给定一个二叉树,返回它的**前序**遍历。 8 | 9 | 示例: 10 | 11 | ```js 12 | 输入: [1,null,2,3] 13 | 1 14 | \ 15 | 2 16 | / 17 | 3 18 | 输出: [1,2,3] 19 | ``` 20 | 21 | ## 代码 22 | 23 | #### 递归算法 24 | 25 | ```js 26 | var preorderTraversal = function(root , array = []){ 27 | if(root){ 28 | array.push(root.val); 29 | preorderTraversal(root.left,array); 30 | preorderTraversal(root.right,array); 31 | } 32 | return array; 33 | } 34 | ``` 35 | 36 | ![image-20210116174115082](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116174115082.png) 37 | 38 | #### 非递归实现 39 | 40 | 初始化一个栈和结果数组,当栈不为空或目标节点不为空时,重复下面的步骤: 41 | 42 | 1. 目标节点存入结果数组,节点入栈 → 直至左孩子为空 43 | 2. 栈顶元素出栈,以栈顶元素为根节点 44 | 3. 以右孩子为目标节点,执行1、 2、 3 45 | 46 | ```js 47 | var preorderTraversal = function(root){ 48 | const result = []; 49 | const stack = []; 50 | let current = root; 51 | while(stack.length > 0 || current){ 52 | while(current){ 53 | result.push(current.val); 54 | stack.push(current); 55 | current = current.left; 56 | } 57 | current = stack.pop(); 58 | current = current.right; 59 | } 60 | return result; 61 | } 62 | ``` 63 | 64 | ![image-20210116174628464](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116174628464.png) 65 | 66 | # 更多资料 67 | 68 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 69 | 70 | 📖数据结构博客专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 71 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【Leetcode 145】二叉树的后序遍历.md: -------------------------------------------------------------------------------- 1 | # 二叉树后序遍历 2 | 3 | 所谓后续遍历,即按照左右中的顺序进行遍历。 4 | 5 | ## 题目 6 | 7 | 给定一个二叉树,返回它的**后序**遍历。 8 | 9 | 示例: 10 | 11 | ```js 12 | 输入: [1,null,2,3] 13 | 1 14 | \ 15 | 2 16 | / 17 | 3 18 | 输出: [3,2,1] 19 | ``` 20 | 21 | ## 代码 22 | 23 | #### 递归算法 24 | 25 | ```js 26 | var postorderTraversal = function(root , array = []){ 27 | if(root){ 28 | postorderTraversal(root.left , array); 29 | postorderTraversal(root.right , array); 30 | array.push(root.val); 31 | } 32 | return array; 33 | } 34 | ``` 35 | 36 | ![image-20210117115414649](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210117115414649.png) 37 | 38 | #### 非递归算法 39 | 40 | 初始化一个栈、结果数组和记录上次访问节点的变量,当栈不为空或根节点不为空时,重复下面的步骤: 41 | 42 | 1. 将左孩子入栈 → 直至左孩子为空 43 | 2. 栈顶节点的右节点为空或被访问过 → 节点出栈,存入结果数组,标记为已访问,继续出栈查找。 44 | 3. 栈顶节点的右节点不为空且未被访问 ,以右孩子为目标节点,执行1 、2 、3 45 | 46 | ```js 47 | var postorderTraversal = function (root) { 48 | const result = []; 49 | const stack = []; 50 | var last = null; //标记上一个访问的节点 51 | let current = root; 52 | while (stack.length > 0 || current) { 53 | while (current) { 54 | stack.push(current); 55 | current = current.left; 56 | } 57 | current = stack[stack.length - 1]; 58 | if (!current.right || current.right == last) { 59 | current = stack.pop(); 60 | result.push(current.val); 61 | last = current; 62 | current = null; 63 | } else { 64 | current = current.right; 65 | } 66 | } 67 | return result; 68 | } 69 | ``` 70 | 71 | ![image-20210117120650062](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210117120650062.png) 72 | 73 | # 更多资料 74 | 75 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 76 | 77 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 78 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【Leetcode 230】二叉搜索树的第k小的元素.md: -------------------------------------------------------------------------------- 1 | # 二叉搜索树的第k小的元素 2 | 3 | ## 题目 4 | 5 | 给定一棵二叉搜索树,请找出其中的第k小的结点。 例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。 6 | 7 | **示例 1:** 8 | 9 | ``` 10 | 输入: root = [3,1,4,null,2], k = 1 11 | 3 12 | / \ 13 | 1 4 14 | \ 15 | 2 16 | 输出: 1 17 | ``` 18 | 19 | ## 思路 20 | 21 | 二叉搜索树的中序遍历即排序后的节点,本题实际考察二叉树的遍历。 22 | 23 | ## 代码 24 | 25 | #### 递归实现 26 | 27 | ```js 28 | function KthNode(root, k) { 29 | const arr = []; 30 | orderTraversal(root, arr) 31 | if (k > 0 && k <= arr.length) { 32 | return arr[k - 1]; 33 | } else { 34 | return null 35 | } 36 | 37 | } 38 | 39 | function orderTraversal(root, arr) { 40 | if (root) { 41 | orderTraversal(root.left, arr); 42 | arr.push(root); 43 | orderTraversal(root.right, arr); 44 | } 45 | } 46 | ``` 47 | 48 | ![image-20210119114752627](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210119114752627.png) 49 | 50 | #### 非递归实现 51 | 52 | ```js 53 | function KthNode(root, k) { 54 | const result = []; 55 | const stack = []; 56 | let current = root; 57 | while (current || stack.length > 0) { 58 | while (current) { 59 | stack.push(current); 60 | current = current.left; 61 | } 62 | current = stack.pop(); 63 | result.push(current); 64 | current = current.right; 65 | } 66 | if (k > 0 && k <= result.length) { 67 | return result[k - 1]; 68 | } else { 69 | return null; 70 | } 71 | } 72 | 73 | ``` 74 | 75 | ![image-20210119113401949](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210119113401949.png) 76 | 77 | #### 其实我们不需要获取所有的result存起来,只要获取第k个result就好了 78 | 79 | ```js 80 | function kNode(node, k) { 81 | //非递归.中序 82 | const stack = []; 83 | let current = node; 84 | let sum = 1; 85 | while (current || stack.length > 0) { 86 | while (current) { 87 | stack.push(current); 88 | current = current.left; 89 | } 90 | current = stack.pop(); 91 | if (sum === k) { 92 | return current; 93 | } else { 94 | sum++; 95 | } 96 | current = current.right; 97 | } 98 | } 99 | ``` 100 | 101 | ![image-20210119114324363](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210119114324363.png) 102 | 103 | 104 | 105 | | 写法 | 优点 | 缺点 | 106 | | -------- | ------------------------- | -------------------- | 107 | | 递归写法 | 简单明了 | 性能太差,容易栈溢出 | 108 | | 循环写法 | 性能强 ,理论上不会栈溢出 | 不直观 | 109 | 110 | # 更多资料 111 | 112 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 113 | 114 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 115 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【Leetcode 94】二叉树的中序遍历.md: -------------------------------------------------------------------------------- 1 | # 二叉树的中序遍历 2 | 3 | 二叉树的中序遍历,即以左中右的顺序依次遍历数据元素。 4 | 5 | ## 题目 6 | 7 | 给定一个二叉树,返回它的**中序**遍历。 8 | 9 | 示例: 10 | 11 | ```js 12 | 输入: [1,null,2,3] 13 | 1 14 | \ 15 | 2 16 | / 17 | 3 18 | 输出: [1,3,2] 19 | ``` 20 | 21 | ## 代码 22 | 23 | #### 解法1:递归实现 24 | 25 | 递归有两种写法,递归函数本身、在闭包中递归。 26 | 27 | ```js 28 | var inorderTraversal = function(root , array = []){ 29 | //如果根节点不为空 30 | if(root){ 31 | //对左节点遍历 32 | inorderTraversal(root.left,array); 33 | array.push(root.val); 34 | //对右节点遍历 35 | inorderTraversal(root.right,array); 36 | } 37 | return array; 38 | } 39 | ``` 40 | 41 | **复杂度分析:** 42 | 43 | 时间复杂度:O(n) 44 | 45 | ![image-20210116165332422](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116165332422.png) 46 | 47 | #### 解法2:非递归实现(迭代方法) 48 | 49 | 初始化一个栈和结果数组,当栈不为空或目标节点不为空时,重复下面的步骤: 50 | 51 | 1. 根节点和左节点入栈 →直至没有左孩子 52 | 2. 栈顶元素出栈,存入结果数组,将出栈元素作为根节点 53 | 3. 以右孩子为目标节点,执行1、2、3 54 | 55 | ```js 56 | var inorderTraversal = function(root){ 57 | var result= []; 58 | var stack = []; 59 | var current = root; 60 | while(stack.length >0 || current){ 61 | while(current){ 62 | stack.push(current); 63 | current = current.left; 64 | } 65 | current = stack.pop(); 66 | result.push(current.val); 67 | current = current.right; 68 | } 69 | return result; 70 | } 71 | ``` 72 | 73 | ![image-20210116170537739](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116170537739.png) 74 | 75 | # 更多资料 76 | 77 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 78 | 79 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 80 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【剑指Offer 07】重建二叉树.md: -------------------------------------------------------------------------------- 1 | # 重建二叉树 2 | 3 | ## 题目 4 | 5 | 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 6 | 7 | 例如输入前序遍历序列`{1,2,4,7,3,5,6,8}`和中序遍历序列`{4,7,2,1,5,3,8,6}`,则重建二叉树并返回。 8 | 例如,给出 9 | 10 | ``` 11 | 前序遍历 preorder = [3,9,20,15,7] 12 | 中序遍历 inorder = [9,3,15,20,7] 13 | ``` 14 | 15 | 16 | 返回如下的二叉树: 17 | 18 | 3 19 | / \ 20 | 9 20 21 | / \ 22 | 15 7 23 | ## 思路 24 | 25 | - 前序遍历:根节点 + 左孩子 + 右孩子 26 | - 中序遍历:左孩子 + 根节点 + 右孩子 27 | - 后序遍历:左孩子 + 右孩子 + 根节点 28 | 29 | 故我们可以得出以下规律: 30 | 31 | 1. 从前序遍历中找到根节点root 32 | 2. 在中序遍历中查找root的位置index,即可获取左右子树的长度 33 | 3. 截取左子树的前序遍历、右子树的前序遍历 34 | 4. 截取左子树的中序遍历、右子树的中序遍历 35 | 5. 递归重建二叉树 分别获得左右子树。 36 | 6. 利用根节点和左右子树即可重建二叉树。 37 | 38 | ![image-20210107140237965](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210107140237965.png) 39 | 40 | ```js 41 | function reConstructBinaryTree(pre, inorder) { 42 | if (pre.length === 0) { 43 | return null; 44 | } 45 | if (pre.length === 1) { 46 | return new TreeNode(pre[0]); 47 | } 48 | const root = pre[0]; 49 | const index = inorder.indexOf(root); 50 | // 分割前序遍历 51 | const preLeft = pre.slice(1, index+1); 52 | const preRight = pre.slice(index + 1); 53 | //分割中序遍历 54 | const inorderLeft = inorder.slice(0, index); 55 | const inorderRight = inorder.slice(index + 1); 56 | //分别求解左右子树并生成二叉树 57 | const node = new TreeNode(root); 58 | node.left = reConstructBinaryTree(preLeft, inorderLeft); 59 | node.right = reConstructBinaryTree(preRight, inorderRight); 60 | return node; 61 | } 62 | ``` 63 | 64 | ![image-20210117143245101](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210117143245101.png) 65 | 66 | **练习题** 67 | 68 | [leetcode 106 中后序构建二叉树](https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) 69 | 70 | # 更多资料 71 | 72 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 73 | 74 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 75 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【剑指Offer 26】树的子结构.md: -------------------------------------------------------------------------------- 1 | # 树的子结构 2 | 3 | ## 题目 4 | 5 | 输入两棵二叉树`A`,`B`,判断`B`是不是`A`的子结构。(ps:我们约定空树不是任意一个树的子结构) 6 | 7 | ## 思路 8 | 9 | 首先找到`A`树中和`B`树根节点相同的节点 10 | 11 | 从此节点开始,递归`AB`树比较是否有不同节点 12 | 13 | ## 代码 14 | 15 | ```js 16 | function HasSubtree(pRoot1, pRoot2) { 17 | let result = false; 18 | //均不空 19 | if (pRoot1 && pRoot2) { 20 | //根节点相同 21 | if (pRoot1.val === pRoot2.val) { 22 | //比较两者 23 | result = compare(pRoot1, pRoot2); 24 | } 25 | //没找到时进行查找 26 | if (!result) { 27 | //比较A的右子树与B树 28 | result = HasSubtree(pRoot1.right, pRoot2); 29 | } 30 | if (!result) { 31 | //比较A的左子树与B树 32 | result = HasSubtree(pRoot1.left, pRoot2); 33 | } 34 | } 35 | return result; 36 | } 37 | 38 | function compare(pRoot1, pRoot2) { 39 | if (pRoot2 === null) { 40 | return true; 41 | } 42 | if (pRoot1 === null) { 43 | return false; 44 | } 45 | //根节点不一样,返回 46 | if (pRoot1.val !== pRoot2.val) { 47 | return false; 48 | } 49 | //根节点一样,左子树与左子树比较,右子树与右子树比较 50 | return compare(pRoot1.right, pRoot2.right) && compare(pRoot1.left, pRoot2.left); 51 | } 52 | ``` 53 | 54 | # 更多资料 55 | 56 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 57 | 58 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 59 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【剑指Offer 27】二叉树的镜像.md: -------------------------------------------------------------------------------- 1 | # 二叉树的镜像 2 | 3 | ## 题目 4 | 5 | 操作给定的二叉树,将其变换为源二叉树的镜像。 6 | 7 | ``` 8 | 例如输入: 9 | 10 |      4 11 |    /   \ 12 |   2     7 13 |  / \   / \ 14 | 1   3 6   9 15 | 镜像输出: 16 | 17 |      4 18 |    /   \ 19 |   7     2 20 |  / \   / \ 21 | 9   6 3   1 22 | 23 | ``` 24 | 25 | **示例 1:** 26 | 27 | ``` 28 | 输入:root = [4,2,7,1,3,6,9] 29 | 输出:[4,7,2,9,6,3,1] 30 | ``` 31 | 32 | ## 思路 33 | 34 | 递归交换二叉树所有节点的左右节点位置 35 | 36 | ### 代码 37 | 38 | ```js 39 | /* 思路,符合递归的要求,每个子树都要进行节点交换 40 | 1.交换左右两个节点 41 | 2.对两个节点再进行镜像处理 42 | 43 | 递归的功能:交换节点(镜像处理) 44 | 递归的出口:节点为空 45 | 等价表达式:整棵树的镜像 = 交换左右节点 + 左子树镜像 + 右子树镜像*/ 46 | var mirrorTree = function (root) { 47 | if (root) { 48 | const tmp = root.left; 49 | root.left = root.right; 50 | root.right = tmp; 51 | mirrorTree(root.left); 52 | mirrorTree(root.right); 53 | } 54 | return root; 55 | }; 56 | ``` 57 | 58 | ![image-20210118235610794](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210118235610794.png) 59 | 60 | # 更多资料 61 | 62 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 63 | 64 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 65 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【剑指Offer 33】二叉搜索树的后序遍历.md: -------------------------------------------------------------------------------- 1 | # 二叉搜索树的后序遍历 2 | 3 | ## 题目 4 | 5 | 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 `true`,否则返回 `false`。假设输入的数组的任意两个数字都互不相同。 6 | 7 | 参考以下这颗二叉搜索树: 8 | 9 | 10 | 5 11 | / \ 12 | 2 6 13 | / \ 14 | 1 3 15 | **示例 1:** 16 | 17 | ``` 18 | 输入: [1,6,3,2,5] 19 | 输出: false 20 | ``` 21 | 22 | **示例 2:** 23 | 24 | ``` 25 | 输入: [1,3,2,6,5] 26 | 输出: true 27 | ``` 28 | 29 | ## 思路 30 | 31 | 1. 后序遍历分成三部分: 32 | - 最后一个节点为根节点 33 | - 左子树的值比根节点小 34 | - 右子树的值比根节点大 35 | 2. 先检验左子树,左侧比根节点小的值均判定为左子树 36 | 3. 除最后一个节点和左子树外的其他值为右子树,若右子树有一个比根节点小,则返回false。 37 | 4. 若存在左右子树,递归检测是否规范。 38 | 39 | **注意!!!** 40 | 41 | **1.在获取右子树序列时需要把根节点排除在外。** 42 | 43 | **2. i j < sequence.length - 1** 44 | 45 | **3.rightArr从i开始 postorder.length-1结束(不包含此元素)** 46 | 47 | #### 代码 48 | 49 | ```js 50 | var verifyPostorder = function (postorder) { 51 | if (postorder.length <= 0) { 52 | return true; 53 | } 54 | 55 | let node = postorder[postorder.length - 1]; 56 | for (var i = 0; i < postorder.length - 1; i++) { 57 | if (postorder[i] > node) { 58 | break; 59 | } 60 | } 61 | for (var j = i + 1; j < postorder.length - 1; j++) { 62 | if (postorder[j] < node) { 63 | return false; 64 | } 65 | } 66 | let leftArr = []; 67 | let rightArr = []; 68 | if (i > 0) { 69 | leftArr = postorder.slice(0, i); 70 | } 71 | 72 | if (i < postorder.length - 1) { 73 | rightArr = postorder.slice(i , postorder.length-1); 74 | } 75 | return verifyPostorder(leftArr) && verifyPostorder(rightArr); 76 | }; 77 | ``` 78 | 79 | ![image-20210119122244653](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210119122244653.png) 80 | 81 | # 更多资料 82 | 83 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 84 | 85 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 86 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【剑指Offer 34】二叉树中和为某一值的路径.md: -------------------------------------------------------------------------------- 1 | # 二叉树中和为某一值的路径 2 | 3 | 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。 4 | 5 | 示例: 6 | 给定如下二叉树,以及目标和 sum = 22, 7 | 8 | 5 9 | / \ 10 | 4 8 11 | / / \ 12 | 11 13 4 13 | / \ / \ 14 | 7 2 5 1 15 | 16 | 返回: 17 | 18 | ``` 19 | [ 20 | [5,4,11,2], 21 | [5,8,4,5] 22 | ] 23 | ``` 24 | 25 | ## 思路 26 | 27 | 套用回溯算法的思路 28 | 29 | 设定一个结果数组result来存储所有符合条件的路径 30 | 31 | 设定一个栈stack来存储当前路径中的节点 32 | 33 | 设定一个和sum来标识当前路径之和 34 | 35 | - 从根结点开始深度优先遍历,每经过一个节点,将节点入栈 36 | - 到达叶子节点,且当前路径之和等于给定目标值,则找到一个可行的解决方案,将其加入结果数组 37 | - 遍历到二叉树的某个节点时有2个可能的选项,选择前往左子树或右子树 38 | - 若存在左子树,继续向左子树递归 39 | - 若存在右子树,继续向右子树递归 40 | - 若上述条件均不满足,或已经遍历过,将当前节点出栈,向上回溯 41 | 42 | ```js 43 | // dfs 深度优先遍历 44 | // 递归减少规模 45 | // 1.函数功能:计算二叉树中某路径的和 === 给定值 46 | // 2.递归条件:到达叶子结点:if sum === 给定值,存入 47 | // 3.等价表达式,减小规模 在每个结点有两条路走,走左节点,走右节点。 48 | var pathSum = function (root, sum) { 49 | const result = []; 50 | if (root) { 51 | getSumPath(root, sum, [], 0, result); 52 | } 53 | return result; 54 | }; 55 | 56 | var getSumPath = function (root, resultsum, stack, mysum, result) { 57 | stack.push(root.val); 58 | mysum += root.val; 59 | 60 | if (!root.left && !root.right && resultsum === mysum) { 61 | result.push(stack.slice(0)); 62 | } 63 | 64 | if (root.left) { 65 | getSumPath(root.left, resultsum, stack, mysum, result); 66 | } 67 | 68 | if (root.right) { 69 | getSumPath(root.right, resultsum, stack, mysum, result) 70 | } 71 | 72 | //两个方向都走完了,无路可走,回溯。 73 | //此处pop是为了从stack中删除该节点,不要占位置。 74 | stack.pop(); 75 | } 76 | 77 | ``` 78 | 79 | ![image-20210131195947280](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210131195947280.png) 80 | 81 | ```js 82 | function pathSum( root , sum ) { 83 | //递归 84 | //状态变量:temp数组,node当前结点,currentSum当前和 85 | //递归出口:到达叶子结点且currentSum == sum 86 | //剪枝:currentSum>sum时不再往下求 87 | //递归列表,往左右子树走 88 | const res = []; 89 | var dfs = function(node,currentSum,temp){ 90 | //一进来就放 91 | temp.push(node.val); 92 | currentSum += node.val; 93 | 94 | if(!node.left && !node.right && currentSum == sum){ 95 | res.push(temp.slice(0)); 96 | } 97 | 98 | if(node.left){ 99 | dfs(node.left,currentSum,temp); 100 | } 101 | 102 | if(node.right){ 103 | dfs(node.right,currentSum,temp); 104 | } 105 | temp.pop(); 106 | } 107 | if(root){ 108 | dfs(root,0,[]); 109 | } 110 | 111 | return res; 112 | } 113 | ``` 114 | 115 | 116 | 117 | # 更多资料 118 | 119 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 120 | 121 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 122 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【剑指Offer 36】二叉搜索树与双向链表.md: -------------------------------------------------------------------------------- 1 | # 二叉搜索树与双向链表 2 | 3 | `自己想确实没思路` 4 | 5 | ## 题目 6 | 7 | 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。 8 | 9 | 10 | 11 | 为了让您更好地理解问题,以下面的二叉搜索树为例: 12 | 13 | ![img](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/bstdlloriginalbst.png) 14 | 15 | 我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。 16 | 17 | 下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。 18 | 19 | ![img](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/bstdllreturndll.png) 20 | 21 | 特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。 22 | 23 | ## 思路及代码 24 | 25 | #### 解法 1: 递归+中序遍历 26 | 27 | 结合中序遍历,递归处理二叉树。初始化一个代表上一个节点的 pre 变量。递归中要做的就是:pre 的 right 指针指向当前节点 node,node 的 left 指向 pre,并且将 pre 更新为 node。 28 | 29 | 要注意的是,当递归到最下面的左节点时,pre 为空,要保留节点作为循环链表的 head。并在中序遍历结束后,处理头节点和尾节点的指针关系。 30 | 31 | ```js 32 | var treeToDoublyList = function (root) { 33 | if (!root) { 34 | return; 35 | } 36 | var head = null; 37 | var pre = head; 38 | inorder(root); 39 | // 完成中序遍历后,pre指向了最后一个节点 40 | // 将其闭合成环状结构 41 | head.left = pre; 42 | pre.right = head; 43 | return head; 44 | 45 | function inorder(root) { 46 | if (!root) return; 47 | //遍历左子树 48 | inorder(root.left, pre); 49 | 50 | if (!pre) { 51 | //如果是左子树的最左边结点,那么记录下来,因为它将是head 52 | head = root; 53 | } else { 54 | //指向root 55 | pre.right = root; 56 | } 57 | root.left = pre; 58 | pre = root; 59 | 60 | // 遍历右子树 61 | inorder(root.right) 62 | } 63 | }; 64 | ``` 65 | 66 | ![image-20210202171431685](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210202171431685.png) 67 | 68 | #### 解法 2: 非递归+中序遍历 69 | 70 | 这里可以将递归转换为非递归的的中序遍历。转化思路是用栈来模拟递归调用的过程,其他的处理和解法 1 一样。 71 | 72 | ```js 73 | var treeToDoublyList = function (root) { 74 | if (!root) { 75 | return; 76 | } 77 | 78 | //非递归中序遍历 79 | const stack = []; 80 | var current = root; 81 | var pre = null; 82 | var head = null; 83 | while (stack.length > 0 || current) { 84 | while (current) { 85 | stack.push(current); 86 | current = current.left; 87 | } 88 | current = stack.pop(); 89 | //左子树的左后一个结点 90 | if (!pre) { 91 | head = current; 92 | } else { 93 | pre.right = current 94 | } 95 | current.left = pre; 96 | pre = current; 97 | current = current.right; 98 | } 99 | 100 | //循环链表 101 | head.left = pre; 102 | pre.right = head; 103 | return head; 104 | }; 105 | ``` 106 | 107 | ![image-20210202172505867](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210202172505867.png) 108 | 109 | # 更多资料 110 | 111 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 112 | 113 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 114 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 115 | 116 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【剑指Offer 37】序列化二叉树.md: -------------------------------------------------------------------------------- 1 | # 序列化二叉树 2 | 3 | ## 题目 4 | 5 | 请实现两个函数,分别用来序列化和反序列化二叉树 6 | 7 | ## 思路 8 | 9 | - 若一颗二叉树是不完全的,我们至少需要两个遍历才能将它重建(像题目重建二叉树一样) 10 | - 但是这种方式仍然有一定的局限性,比如二叉树中不能出现重复节点。 11 | - 如果二叉树是一颗完全二叉树,我们只需要知道前序遍历即可将它重建。 12 | - 因此在序列化时二叉树时,可以将空节点使用特殊符号存储起来,这样就可以模拟一棵完全二叉树的前序遍历 13 | - 在重建二叉树时,当遇到特殊符号当空节点进行处理 14 | 15 | ## 代码 16 | 17 | ```js 18 | //序列化 19 | function Serialize(pRoot, arr = []) { 20 | //空 21 | if (!pRoot) { 22 | arr.push('#'); 23 | } else { 24 | //中左右 25 | arr.push(pRoot.val); 26 | Serialize(pRoot.left, arr) 27 | Serialize(pRoot.right, arr) 28 | } 29 | //以逗号分隔 30 | return arr.join(','); 31 | } 32 | //反序列化 33 | function Deserialize(s) { 34 | //字符串为空 35 | if (!s) { 36 | return null; 37 | } 38 | //split() 方法用于把一个字符串分割成字符串数组。 39 | return deserialize(s.split(',')); 40 | } 41 | 42 | function deserialize(arr) { 43 | let node = null; 44 | //取出第一个元素 45 | const current = arr.shift(); 46 | //节点不为空 47 | if (current !== '#') { 48 | //根节点 49 | node = { val: current } 50 | //左右子树 51 | node.left = deserialize(arr); 52 | node.right = deserialize(arr); 53 | } 54 | return node; 55 | } 56 | ``` 57 | 58 | # 更多资料 59 | 60 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 61 | 62 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 63 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【剑指Offer 55】平衡二叉树.md: -------------------------------------------------------------------------------- 1 | # 平衡二叉树 2 | 3 | ## 题目 4 | 5 | 输入一棵二叉树,判断该二叉树是否是平衡二叉树。 6 | 7 | > 平衡二叉树:每个子树的深度之差不超过1 8 | 9 | **示例 1:** 10 | 11 | ![img](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/balance_1.jpg) 12 | 13 | ``` 14 | 输入:root = [3,9,20,null,null,15,7] 15 | 输出:true 16 | ``` 17 | 18 | ## 思路 19 | 20 | #### 解法1 21 | 22 | **我感觉这个方法不太好理解** 23 | 24 | - 后续遍历二叉树,在遍历二叉树每个节点前都会遍历其左右子树 25 | 26 | - 若左右子树存在一个不平衡或左右子树差值大于1,则整棵树不平衡 27 | - 若左右子树平衡,则返回当前树深:左右子树最大深度+1 28 | 29 | ```js 30 | // 1.函数功能:求二叉树是否平衡 31 | // 2.递归出口:树为空时,平衡;如果不平衡,返回-1 32 | // 3.等价表达式: 33 | // 某二叉树平衡 = 左子树平衡 + 右子树平衡 + 左右子树树深<1 34 | function IsBalanced_Solution(pRoot) { 35 | return balanced(pRoot) != -1; 36 | } 37 | function balanced(node) { 38 | //空树,平衡,树深0 39 | if (!node) { 40 | return 0; 41 | } 42 | //计算左右子树树深 43 | const left = balanced(node.left); 44 | const right = balanced(node.right); 45 | //判断是否平衡 46 | if (left === -1 || right === -1 || Math.abs(left - right) > 1) { 47 | return -1; 48 | } 49 | //如果平衡,返回树深 50 | return Math.max(left, right) + 1; 51 | } 52 | ``` 53 | 54 | ![image-20210119152531319](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210119152531319.png) 55 | 56 | #### 解法2 57 | 58 | ```js 59 | var getMaxDeep = (root)=>{ 60 | //最大深度 61 | if(!root) return 0; 62 | return Math.max(getMaxDeep(root.left),getMaxDeep(root.right))+1; 63 | } 64 | 65 | var isBalanced = function(root) { 66 | //平衡二叉树 = 当前树平衡 + 左子树平衡 + 右子树平衡 67 | //判断树平衡:左右深度相差<=1 68 | //递归判断 获取左子树高度 右子树高度 69 | if(!root) return true 70 | let left = getMaxDeep(root.left); 71 | let right = getMaxDeep(root.right); 72 | if(Math.abs(left-right)>1){ 73 | return false; 74 | } 75 | return isBalanced(root.left) && isBalanced(root.right); 76 | }; 77 | ``` 78 | 79 | 80 | 81 | # 更多资料 82 | 83 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 84 | 85 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 86 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【剑指Offer07 变型题】给前中遍历,求后序遍历.md: -------------------------------------------------------------------------------- 1 | # 给前中遍历,求后序遍历。 2 | 3 | ## 题目 4 | 5 | 给定一棵二叉树的前序遍历和中序遍历,求其后序遍历 6 | 7 | 输入描述: 8 | 9 | 两个字符串,其长度n均小于等于26。 第一行为前序遍历,第二行为中序遍历。 二叉树中的结点名称以大写字母表示:A,B,C....最多26个结点。 10 | 11 | 输出描述: 12 | 13 | 输入样例可能有多组,对于每组测试样例, 输出一行,为后序遍历的字符串。 14 | 15 | 样例: 16 | 17 | ```text 18 | 输入 19 | ABC 20 | BAC 21 | FDXEAG 22 | XDEFAG 23 | 24 | 输出 25 | BCA 26 | XEDGAF 27 | ``` 28 | 29 | ## 思路 30 | 31 | 本题一共有两种思路: 32 | 33 | 1. 重建二叉树后进行后序遍历。 34 | 2. 递归拼接二叉树的后序遍历。★★★★★推荐 35 | 36 | 第一种思路,常规思路,比较繁琐,代码如下 37 | 38 | ```js 39 | function getPostTraversal(pre, vin) { 40 | // 1、重构二叉树 41 | function reContructBinaryTree(pre, vin) { 42 | if (pre.length === 0) { 43 | return null; 44 | } 45 | if (pre.length === 1) { 46 | return new TreeNode(pre[0]); 47 | } 48 | const root = pre[0]; 49 | const index = vin.indexOf(root); 50 | const preLeft = pre.substring(1, index+1); 51 | const preRight = pre.substring(index + 1); 52 | const vinLeft = vin.substring(0, index); 53 | const vinRight = vin.substring(index + 1); 54 | const node = new TreeNode(root); 55 | node.left = reContructBinaryTree(preLeft, vinLeft); 56 | node.right = reContructBinaryTree(preRight, vinRight); 57 | return node; 58 | } 59 | // 2、后序遍历 60 | function postorderTraversal(root) { 61 | const result = ""; 62 | const stack = []; 63 | var last = null; 64 | let current = root; 65 | while (current || stack.length > 0) { 66 | while (current) { 67 | stack.push(current); 68 | current = current.left; 69 | } 70 | current = stack[stack.length - 1]; 71 | if (!current.right || current.right == last) { 72 | current = stack.pop(); 73 | result = result + current.val; 74 | last = current; 75 | current = null; 76 | } else { 77 | current = current.right; 78 | } 79 | } 80 | return result; 81 | } 82 | // 3、调用 83 | const mytree = reContructBinaryTree(pre, vin); 84 | const postResult = postorderTraversal(mytree); 85 | return postResult; 86 | } 87 | ``` 88 | 89 | 第二种思路,非常的简洁!!!代码如下: 90 | 91 | ```js 92 | function getHRD(pre, vin) { 93 | if (!pre) { 94 | return ''; 95 | } 96 | if (pre.length === 1) { 97 | return pre; 98 | } 99 | const head = pre[0]; 100 | const splitIndex = vin.indexOf(head); 101 | const vinLeft = vin.substring(0, splitIndex); 102 | const vinRight = vin.substring(splitIndex + 1); 103 | const preLeft = pre.substring(1, splitIndex + 1); 104 | const preRight = pre.substring(splitIndex + 1); 105 | return getHRD(preLeft, vinLeft) + getHRD(preRight, vinRight) + head; 106 | } 107 | ``` 108 | 109 | # 更多资料 110 | 111 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 112 | 113 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 114 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/【经典】二叉树的下一个节点.md: -------------------------------------------------------------------------------- 1 | # 二叉树的下一个节点 2 | 3 | ## 题目 4 | 5 | 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 6 | 7 | ## 思路 8 | 9 | 中序遍历的顺序 左 - 根 - 右 10 | 11 | 所以寻找下一个节点的优先级应该反过来 优先级 右 - 根 - 左 12 | 13 | - 右节点不为空 - 取右节点的最左侧节点 14 | - 右节点为空 - 如果节点是父亲节的左节点 取父节点 15 | - 右节点为空 - 如果节点是父亲节的右节点 父节点已经被遍历过,再往上层寻找... 16 | - 左节点一定在当前节点之前被遍历过 17 | 18 | 以下图的二叉树来分析: 19 | 20 | ![image-20210108132221902](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210108115437042.png) 21 | 22 | 中序遍历: CBDAEF 23 | 24 | - B - 右节点不为空,下一个节点为右节点D 25 | - C - 右节点为空,C是父节点的左节点,取父节点B 26 | - D - 右节点为空,D是父节点的右节点,再往上蹭分析,B是其父节点的左节点,取B的父节点A 27 | - F - 右节点为空,F是父节点的右节点,没有符合条件的节点,F为遍历的最后一个节点,返回null 28 | 29 | ## 代码 30 | 31 | ```js 32 | function GetNext(pNode) { 33 | //空 34 | if (!pNode) { 35 | return null; 36 | } 37 | //右节点不空 38 | if (pNode.right) { 39 | pNode = pNode.right; 40 | //取右节点的最左侧节点 41 | while (pNode.left) { 42 | pNode = pNode.left; 43 | } 44 | return pNode; 45 | } else { 46 | while (pNode) { 47 | //没有父节点 48 | if (!pNode.next) { 49 | return null; 50 | //该节点为父节点左节点 51 | } else if (pNode == pNode.next.left) { 52 | return pNode.next; 53 | } 54 | //该节点为父节点的右节点,则向上层寻找 55 | pNode = pNode.next; 56 | } 57 | return pNode; 58 | } 59 | } 60 | ``` 61 | 62 | # 更多资料 63 | 64 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 65 | 66 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 67 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/二叉树中和为某一值的路径.md: -------------------------------------------------------------------------------- 1 | ### 二叉树中和为某一值的路径 2 | 3 | 思路:递归 4 | 5 | **刻意学习递归三部曲:** 6 | 7 | 1、画出递归树,找到状态变量。 8 | 9 | 2、找出递归出口 10 | 11 | 3、递归列表 12 | 13 | 4、剪枝 14 | 15 | 16 | 17 | 1、状态变量:当前节点、总和 18 | 19 | 2、节点空,return 0;叶子结点 return sum*10 + root.val 20 | 21 | 3、递归列表:左子树递归 + 右子树递归加和 22 | 23 | ```js 24 | function sumNumbers( root ) { 25 | //递归 26 | var dfs = function(root,sum){ 27 | if(!root) return 0; 28 | if(!root.left && !root.right){ 29 | return sum*10 + root.val; 30 | } 31 | return dfs(root.left,sum*10+root.val)+dfs(root.right,sum*10+root.val); 32 | } 33 | return dfs(root,0); 34 | } 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/二叉树中的最大路径和.md: -------------------------------------------------------------------------------- 1 | ## 二叉树中的最大路径和 2 | 3 | 有点类似贪心算法,如果利益点<0则舍弃 4 | 5 | ```js 6 | var maxPathSum = function(root) { 7 | // maxSum 8 | //递归状态变量 root 9 | //递归出口 !root return; 10 | //当前路径和innermax = root.val + left + right innermax > maxSum maxSum替换 11 | //对外输出最大路径和 = root.val + Math.max(left,right),如果<0 则从0重新开始。 12 | let maxSum = -Infinity; 13 | let dfs = (root)=>{ 14 | if(!root) return 0 ; 15 | const left = dfs(root.left); 16 | const right = dfs(root.right); 17 | const innerMax = root.val + left + right; 18 | maxSum = Math.max(maxSum,innerMax) 19 | const outputMax = root.val + Math.max(left,right) 20 | return outputMax<0?0:outputMax; 21 | } 22 | dfs(root); 23 | return maxSum; 24 | }; 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/二叉树之字形层序遍历.md: -------------------------------------------------------------------------------- 1 | ```js 2 | if(!root){ 3 | return []; 4 | } 5 | //层序遍历反转 6 | const res = []; 7 | const stack = [root]; 8 | let count =0; 9 | while(stack.length){ 10 | count++; 11 | let size = stack.length; 12 | let temp = []; 13 | for(let i=0;i{ 9 | if(!root) return 0; 10 | if(!root.left && !root.right){ 11 | return sum*10 +root.val; 12 | } 13 | return dfs(root.left,sum*10+root.val) + dfs(root.right,sum*10+root.val); 14 | } 15 | return dfs(root,0); 16 | 17 | } 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/二叉树的右视图.md: -------------------------------------------------------------------------------- 1 | #### 二叉树的右视图 2 | 3 | ```js 4 | function solve( xianxu , zhongxu ) { 5 | let tree = reConstructBinaryTree(xianxu,zhongxu); 6 | //层序遍历 7 | if(!tree) return []; 8 | const res= []; 9 | const quene = [tree]; 10 | let level = quene.length; 11 | while(quene.length){ 12 | level--; 13 | let cur = quene.shift(); 14 | if(cur.left) quene.push(cur.left); 15 | if(cur.right) quene.push(cur.right); 16 | if(!level){ 17 | res.push(cur.val); 18 | level = quene.length; 19 | } 20 | } 21 | return res; 22 | } 23 | 24 | function reConstructBinaryTree(pre, inorder) { 25 | if (pre.length === 0) { 26 | return null; 27 | } 28 | if (pre.length === 1) { 29 | return new TreeNode(pre[0]); 30 | } 31 | const root = pre[0]; 32 | const index = inorder.indexOf(root); 33 | // 分割前序遍历 34 | const preLeft = pre.slice(1, index+1); 35 | const preRight = pre.slice(index + 1); 36 | //分割中序遍历 37 | const inorderLeft = inorder.slice(0, index); 38 | const inorderRight = inorder.slice(index + 1); 39 | //分别求解左右子树并生成二叉树 40 | const node = new TreeNode(root); 41 | node.left = reConstructBinaryTree(preLeft, inorderLeft); 42 | node.right = reConstructBinaryTree(preRight, inorderRight); 43 | return node; 44 | } 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/从尾到头打印链表.md: -------------------------------------------------------------------------------- 1 | ## 从尾到头打印链表 2 | 3 | Easy 4 | 5 | ```js 6 | var reversePrint = function(head) { 7 | const res =[]; 8 | let thead = head 9 | while(thead){ 10 | res.push(thead.val); 11 | thead = thead.next; 12 | } 13 | return res.reverse(); 14 | }; 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/判断t1树中是否有与t2树拓扑结构完全相同的子树.md: -------------------------------------------------------------------------------- 1 | #### **判断t1树中是否有与t2树拓扑结构完全相同的子树** 2 | 3 | t1包含t2的情况:t1 == t2 t2在t1的左子树 t2在t1的右子树 4 | 5 | 如何树判断是否相等 root.val相等 左子树=左子树 右子树=右子树 6 | 7 | 递归: 8 | 9 | 1、状态变量 两棵树 10 | 2、出口 !root1 return false 11 | 3、列表 return isContains(roo1.left,root2) || isContains(roo1.right,root2) || isSameTree(root1,root2); 12 | 13 | ```js 14 | function isContains( root1 , root2 ) { 15 | //t1包含t2的情况:t1 == t2 t2在t1的左子树 t2在t1的右子树 16 | //如何树判断是否相等 root.val相等 左子树=左子树 右子树=右子树 17 | //递归判断 状态变量 两棵树 18 | //出口 !root1 return false 19 | //列表 return isContains(roo1.left,root2) || isContains(roo1.right,root2) || isSameTree(root1,root2); 20 | if(!root1) return false; 21 | return isContains(root1.left,root2) || isContains(root1.right,root2) || isSameTree(root1,root2); 22 | } 23 | 24 | function isSameTree(root1,root2){ 25 | if(!root1&&!root2) return true; 26 | if(!root1 || !root2 || root1.val !== root2.val) return false; 27 | return isSameTree(root1.left,root2.left) && isSameTree(root1.right,root2.right); 28 | } 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/判断该二叉树是否为搜索二叉树和完全二叉树.md: -------------------------------------------------------------------------------- 1 | ### 判断该二叉树是否为搜索二叉树和完全二叉树。 2 | 3 | ```js 4 | function judgeIt( root ) { 5 | var IsSearchTree = function(root,min,max){ 6 | if(!root) return true; 7 | if(root.val max) return false; 8 | return IsSearchTree(root.left,min,root.val) && IsSearchTree(root.right,root.val,max); 9 | } 10 | var IsFullTree = function(root){ 11 | if(!root){ 12 | return true; 13 | } 14 | const quene=[root]; 15 | let [leaf,left,right]=[false,false,false]; 16 | while(quene.length){ 17 | const head=quene.shift(); 18 | left=head.left; 19 | right=head.right; 20 | if((leaf&&(left||right))||(!left&&right)){ 21 | return false; 22 | } 23 | if(left){ 24 | quene.push(left); 25 | } 26 | if(right){ 27 | quene.push(right); 28 | } 29 | else{ 30 | leaf=true; 31 | } 32 | } 33 | return true; 34 | } 35 | let res = []; 36 | var res1= IsSearchTree(root,-Infinity, Infinity); 37 | var res2 = IsFullTree(root); 38 | res.push(res1); 39 | res.push(res2); 40 | return res; 41 | } 42 | ``` 43 | 44 | **判断搜索二叉树:** 45 | 46 | 1. 设置min -Inifity maxInfity 47 | 2. 如果val max return false **注意:当前只判断当前结点的val与最大最小的比较,子结点由递归去处理** 48 | 3. 判断左右子树是否为搜索二叉树,左:min min ,max root.val ;右:min root.val max max。 49 | 50 | **判断完全二叉树:** 51 | > 完全二叉树的定义:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。 52 | > 换句话说:完全二叉树是不跳跃的从左至右层序。 53 | 54 | 1. 队列存储结点,默认不为叶子结点、无左子树、无右子树。 55 | 2. 非完全二叉树的情况 56 | - 如果当前节点为叶子结点且存在左子树或右子树,return false 57 | - 左子树不存在,存在右子树,return false。 58 | 3. 如果有左子树,左子树入列;如果有右子树,右子树入列;如果没有右子树,则当前节点为叶子结点leaf= true 59 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/剑指 Offer 54. 二叉搜索树的第k大节点.md: -------------------------------------------------------------------------------- 1 | #### 剑指 Offer 54. 二叉搜索树的第k大节点 2 | 3 | easy,反中序 为递减。一个计数器判断当前为第几大节点,到达k直接return 不继续计算。 4 | 5 | ```js 6 | var kthLargest = function(root, k) { 7 | //中序遍历就是递增 反中序 递减 右 中 左 8 | if(!root) return; 9 | const stack =[]; 10 | let current = root,count=0; 11 | while(stack.length || current){ 12 | while(current){ 13 | stack.push(current); 14 | current = current.right; 15 | } 16 | 17 | current = stack.pop(); 18 | count++; 19 | if(count == k){ 20 | return current.val; 21 | } 22 | current = current.left; 23 | } 24 | }; 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/求二叉搜索树最近公共祖先.md: -------------------------------------------------------------------------------- 1 | 利用二叉搜索树的特点 2 | 首先判断 p 和 q 是否相等,若相等,则直接返回 p 或 q 中的任意一个,程序结束 3 | 4 | 若不相等,则判断 p 和 q 在向左还是向右的问题上,是否达成了一致 5 | 如果 p 和 q 都小于root, 哥俩一致认为向左👈,则 root = root.left 6 | 如果 p 和 q 都大于root, 哥俩一致认为向右👉,则 root = root.right 7 | 如果 p 和 q 哥俩对下一步的路线出现了分歧,说明 p 和 q 在当前的节点上就要分道扬镳了,当前的 root 是哥俩临别前一起走的最后一站 8 | 返回当前 root 9 | 程序结束 10 | 11 | ```js 12 | var lowestCommonAncestor = function (root, p, q) { 13 | if (!root) { 14 | return null; 15 | } 16 | if (p.val === q.val) { 17 | return p; 18 | } 19 | 20 | while (root) { 21 | if (root.val > p.val && root.val > q.val) { 22 | root = root.left; 23 | } else if (root.val < p.val && root.val < q.val) { 24 | root = root.right; 25 | } else { 26 | return root; 27 | } 28 | } 29 | }; 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /数据结构与算法/二叉树篇/求二叉树最近公共祖先.md: -------------------------------------------------------------------------------- 1 | #### 求二叉树最近公共祖先 2 | 3 | 根据以上定义,若 root是 p, q 的 最近公共祖先 ,则只可能为以下情况之一: 4 | 5 | - p 和 q 在 root 的子树中,且分列 rootroot 的 异侧(即分别在左、右子树中); 6 | - p = root ,且 q 在 root 的左或右子树中; 7 | - q = root ,且 p 在 root的左或右子树中; 8 | 9 | 10 | 11 | **刻意练习:** 12 | 13 | 1、递归状态:根节点、查找节点1,查找节点2。 14 | 15 | 2、递归出口: 16 | 17 | 如果说root为空 或者 一个节点为当前root ,则最近的公共祖先为root,return root。 18 | 19 | 3、递归列表: 20 | 21 | 分别在根节点的左右子树查找节点1、2的最近公共祖先。 22 | 23 | 如果左边找不到最近公共祖先,return right 去右边找。 24 | 25 | 如果右边也找不到最近公共祖先,return root。 26 | 27 | ```js 28 | var lowestCommonAncestor = function(root, p, q) { 29 | if (!root || root === p || root === q) return root; 30 | const left = lowestCommonAncestor(root.left, p, q); 31 | const right = lowestCommonAncestor(root.right, p, q); 32 | if (!left) return right; // 左子树找不到,返回右子树 33 | if (!right) return left; // 右子树找不到,返回左子树 34 | return root; 35 | }; 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /数据结构与算法/动态规划/300. 最长递增子序列.md: -------------------------------------------------------------------------------- 1 | ### 300. 最长递增子序列(只需要求长度) 2 | [leetcode最长递增子序列(只需要求长度)](https://leetcode-cn.com/problems/longest-increasing-subsequence/) 3 | 4 | 动态规划思想刻意练习: 5 | 符合最长、最大、最小、数量标准,且数量大,采用动态规划。 6 | 7 | 1、定义:dp[i]为以i为最后一个递增子序列的元素,递增长度。题目所求为dp max 8 | 9 | 2、转移:当前元素与前面的递增长度作比较,如果nums[i]nums[j]) dp[i] = Math.max(dp[i],dp[j]+1); 21 | if(dp[i]>max){ 22 | max = dp[i]; 23 | } 24 | } 25 | } 26 | return max; 27 | }; 28 | ``` 29 | 30 | ### 最长递增子序列(需要求出序列数组) 31 | [最长递增子序列(需要求出序列数组)](https://www.nowcoder.com/practice/9cf027bf54714ad889d4f30ff0ae5481?tpId=188&&tqId=38586&rp=1&ru=/activity/oj&qru=/ta/job-code-high-week/question-ranking) 32 | 33 | 暂时没看懂,二分+动规,先埋坑吧 34 | 35 | ```js 36 | function LIS( arr ) { 37 | //动态规划 超时 一般在10^9以上就会超时 38 | // let len = arr.length; 39 | // if(len === 1) return arr; 40 | // let dp = new Array(len).fill([]); 41 | // let max = 1,res = []; 42 | // for(let i =1;iarr[j]){ 45 | // if(dp[j].length+1>dp[i].length){ 46 | // dp[i]=[...dp[j]]; 47 | // } 48 | // } 49 | // } 50 | // dp[i].push(arr[i]); 51 | // if(res.length递增序列最后一个,推入 64 | if(arr[i]>temp[temp.length-1]){ 65 | temp.push(arr[i]); 66 | dp[i] = temp.length; 67 | }else{ 68 | //此时temp有序,通过二分法查找arr[i]在temp中的位置,即可得到dp[i]的最长递增长度 69 | let left = 0,right = temp.length-1; 70 | while(left<=right){ 71 | let mid = Math.floor((left+right)/2); 72 | if(temp[mid]>arr[i]){ 73 | right = mid-1; 74 | }else if(temp[mid]0){ 93 | if(dp[index] === cur){ 94 | res.unshift(arr[index]); 95 | cur--; 96 | } 97 | index--; 98 | } 99 | return res; 100 | 101 | } 102 | ``` 103 | -------------------------------------------------------------------------------- /数据结构与算法/动态规划/NC19 子数组的最大累加和问题.md: -------------------------------------------------------------------------------- 1 | #### NC19 子数组的最大累加和问题 2 | 3 | ```js 4 | function maxsumofSubarray( arr ) { 5 | //贪心 遍历数组 相加,如果sum<0 没有价值,重新计算 6 | let max = -Infinity; 7 | let sum = 0; 8 | for(let i =0;i0){ 29 | dp[i] = dp[i-1]+arr[i]; 30 | }else{ 31 | dp[i] = arr[i]; 32 | } 33 | max = Math.max(dp[i],max); 34 | } 35 | return max; 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /数据结构与算法/动态规划/leetcode 5. 最长回文子串.md: -------------------------------------------------------------------------------- 1 | ## leetcode 5. 最长回文子串 2 | 3 | #### 题目 4 | 5 | 给你一个字符串 `s`,找到 `s` 中最长的回文子串。 6 | 7 | **示例 1:** 8 | 9 | ``` 10 | 输入:s = "babad" 11 | 输出:"bab" 12 | 解释:"aba" 同样是符合题意的答案。 13 | ``` 14 | 15 | - `1 <= s.length <= 1000` 16 | - `s` 仅由数字和英文字母(大写和/或小写)组成 17 | 18 | #### 方案一:暴力法 19 | 20 | 两个变量,遍历起始位置和子串长度,判断当前子串是否为回文串,如果是且子串长度>max,则记录子串,更改max值。 21 | 22 | 时间复杂度大概为O(n^3),有题目得到s.length <=1000 ,那么程序执行语句次数大概在10^9以内,不会超时。 23 | 24 | ``` 25 | 一般来说,程序执行语句次数一般控制在10^8 到10^9以下 不会超时 26 | ``` 27 | 28 | **代码如下:** 29 | 30 | ```js 31 | var longestPalindrome = function (s) { 32 | //暴力法 33 | var isPar = function (str) { 34 | let left = 0, right = str.length - 1; 35 | while (left < right) { 36 | if (str[left++] === str[right--]) continue; 37 | return false; 38 | } 39 | return true; 40 | } 41 | 42 | let len = s.length; 43 | let ans = ""; 44 | let max = 0; 45 | if (!s || len < 2) { 46 | return s; 47 | } 48 | for (let i = 0; i < len; i++) { 49 | for (let j = 1; j <= len - i; j++) { 50 | 51 | if (isPar(s.substr(i, j)) && j > max) { 52 | 53 | max = j; 54 | // console.log(s.substr(i, j) + max); 55 | ans = s.substr(i, j); 56 | } 57 | } 58 | } 59 | return ans; 60 | }; 61 | 62 | console.log(longestPalindrome("bb")); 63 | ``` 64 | 65 | ![image-20210330215109341](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210330215109341.png) 66 | 67 | #### 方案二:动态规划 68 | 69 | 首先对题目进行分析:我们要求最长回文串,一般都要设一个max来记录当前最大值,回文串满足最优子结构问题,一个索引从i 到 j 的子串是否形成回文串,取决去 s[i] 是否等于 s[j] 和s[i+1]到s[j-1]是否形成回文串。 70 | 71 | 如果i 到 j形成回文串,且长度>max,则记录子串,更新max,继续搜索。 72 | 73 | **按照刻意练习的思路去思考动规:** 74 | 75 | 1、定义数组元素的含义。 76 | 77 | dp[i] [j] 表示 索引从i 到 j 的子串是否形成回文串。true 为形成,false 为不形成。我们所求为在求解dp的过程中,找到最长的形成回文串的子串。 78 | 79 | 2、状态转移方程 80 | 81 | 形成回文串:s[i] === s[j] && (子串为长度为0 、1 或者 dp[i+1] [j-1] ) 。 82 | 83 | **需要先判断子串长度,因为如果子串长度长度为<=1时,dp[i+1] [j-1]是没有意义的,比如i=0,j=1;此时dp[1] [0]无意义** 84 | 85 | dp[i] [j] = s[i] === s[j] && (j - i < 2 || dp[i + 1] [j - 1]); (i从len-1遍历,j从i遍历) 86 | 87 | - 因为i+1 需要先获得大的dp,所有i需要从大到小求起 88 | - j-1 需要先获得小的dp,所以j 需要从小到大求起 89 | - 因为dp[i] [j] 的含义为 索引 i 到 索引 j 是否形成回文串,故j>i是没有意义的,j需要从i开始求起 90 | 91 | 3、初始值。 92 | 93 | 当只有一个字符时,形成回文串。 94 | 95 | ```js 96 | for (let i = 0; i < len; i++) { 97 | dp[i][i] = true;//1个字符 98 | } 99 | ``` 100 | 101 | **代码如下:** 102 | 103 | ```js 104 | var longestPalindrome = function (s) { 105 | let len = s.length; 106 | let ans = ''; 107 | let dp = new Array(len); 108 | for (let i = 0; i < len; i++) { 109 | dp[i] = new Array(len).fill(0); 110 | } 111 | 112 | //初始化 113 | for (let i = 0; i < len; i++) { 114 | dp[i][i] = true;//1个字符 115 | } 116 | for (let i = len - 1; i >= 0; i--) { 117 | //因为j>i没有意义,所有j从i开始 118 | for (let j = i; j < len; j++) { 119 | dp[i][j] = s[i] == s[j] && (j - i < 2 || dp[i + 1][j - 1]); 120 | if (dp[i][j] && j - i + 1 > ans.length) { 121 | ans = s.substring(i, j + 1); 122 | } 123 | } 124 | } 125 | return ans; 126 | } 127 | 128 | ``` 129 | <<<<<<< HEAD 130 | 131 | 132 | 133 | ![image-20210330223351262](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210330223351262.png) 134 | ======= 135 | ```js 136 | var longestPalindrome = function(s) { 137 | //max = 1 start = 0len = s.length; 138 | //动态规划 dp[i][j] 下标i到j 是否形成回文串 true /false; i>=j才有意义 139 | //状态转移 dp[i][j] = dp[i+1][j-1] && s[i] === s[j];(j>1 imax? max = xxx,start = i 142 | // 初始化 dp[len-1][len-i] = true; 143 | let max = 1,start =0,len = s.length; 144 | let dp =new Array(len); 145 | for(let i =0;i=0;i--){ 151 | for(let j =i;jmax){ 154 | max = j-i+1; 155 | start = i; 156 | end = j; 157 | } 158 | } 159 | } 160 | 161 | return s.substr(start,max); 162 | }; 163 | ``` 164 | 165 | 166 | ![image-20210330223351262](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210330223351262.png) 167 | >>>>>>> a9f8a625a0f9e0944befa320c5e63711529a707a 168 | -------------------------------------------------------------------------------- /数据结构与算法/动态规划/leetcode 62 不同路径.md: -------------------------------------------------------------------------------- 1 | ## leetcode 62 不同路径 2 | 3 | #### 题目 4 | 5 | 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 6 | 7 | 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 8 | 9 | 问总共有多少条不同的路径? 10 | 11 | **示例 1:** 12 | 13 | ![img](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/robot_maze.png) 14 | 15 | ``` 16 | 输入:m = 3, n = 7 17 | 输出:28 18 | ``` 19 | 20 | #### 代码 21 | 22 | ```js 23 | var uniquePaths = function (m, n) { 24 | if (m <= 0 || n <= 0) { 25 | return 1; 26 | } 27 | let dp = new Array(m); 28 | for (let i = 0; i < m; i++) { 29 | dp[i] = new Array(n); 30 | } 31 | for (let i = 0; i < m; i++) { 32 | dp[i][0] = 1; 33 | } 34 | for (let j = 0; j < n; j++) { 35 | dp[0][j] = 1; 36 | } 37 | 38 | //状态转移 39 | for (let i = 1; i < m; i++) { 40 | for (let j = 1; j < n; j++) { 41 | dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; 42 | } 43 | } 44 | return dp[m - 1][n - 1]; 45 | }; 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /数据结构与算法/动态规划/leetcode 64. 最小路径和.md: -------------------------------------------------------------------------------- 1 | ## leetcode 64. 最小路径和 2 | 3 | #### 题目 4 | 5 | 给定一个包含非负整数的 `*m* x *n*` 网格 `grid` ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 6 | 7 | **说明:**每次只能向下或者向右移动一步。 8 | 9 | **示例 1:** 10 | 11 | ![img](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/minpath.jpg) 12 | 13 | ``` 14 | 输入:grid = [[1,3,1],[1,5,1],[4,2,1]] 15 | 输出:7 16 | 解释:因为路径 1→3→1→1→1 的总和最小。 17 | ``` 18 | 19 | #### 代码 20 | 21 | ```js 22 | var minPathSum = function (grid) { 23 | let m = grid.length; 24 | let n = grid[0].length; 25 | // console.log(m); 26 | // console.log(n); 27 | 28 | let dp = new Array(m); 29 | for (let i = 0; i < m; i++) { 30 | dp[i] = new Array(n); 31 | } 32 | 33 | dp[0][0] = grid[0][0]; 34 | //状态初始化 35 | for (let i = 1; i < m; i++) { 36 | dp[i][0] = grid[i][0] + dp[i - 1][0]; 37 | } 38 | 39 | for (let j = 1; j < n; j++) { 40 | dp[0][j] = grid[0][j] + dp[0][j - 1]; 41 | } 42 | 43 | 44 | //状态转移 45 | for (let i = 1; i < m; i++) { 46 | for (let j = 1; j < n; j++) { 47 | dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; 48 | } 49 | } 50 | return dp[m - 1][n - 1]; 51 | }; 52 | 53 | console.log(minPathSum([[1, 3, 1], [1, 5, 1], [4, 2, 1]])); 54 | 55 | ``` 56 | 57 | -------------------------------------------------------------------------------- /数据结构与算法/动态规划/leetcode 72 编辑距离.md: -------------------------------------------------------------------------------- 1 | ## leetcode 72 编辑距离 2 | 3 | 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数。 4 | 5 | 你可以对一个单词进行如下三种操作: 6 | 7 | - 插入一个字符 8 | - 删除一个字符 9 | - 替换一个字符 10 | 11 | **示例 1:** 12 | 13 | ``` 14 | 输入:word1 = "horse", word2 = "ros" 15 | 输出:3 16 | 解释: 17 | horse -> rorse (将 'h' 替换为 'r') 18 | rorse -> rose (删除 'r') 19 | rose -> ros (删除 'e') 20 | ``` 21 | 22 | **示例 2:** 23 | 24 | ``` 25 | 输入:word1 = "intention", word2 = "execution" 26 | 输出:5 27 | 解释: 28 | intention -> inention (删除 't') 29 | inention -> enention (将 'i' 替换为 'e') 30 | enention -> exention (将 'n' 替换为 'x') 31 | exention -> exection (将 'n' 替换为 'c') 32 | exection -> execution (插入 'u') 33 | ``` 34 | 35 | **提示:** 36 | 37 | - 0 <= word1.length, word2.length <= 500 38 | - word1 和 word2 由小写英文字母组成 -------------------------------------------------------------------------------- /数据结构与算法/动态规划/剑指 Offer 10- II. 青蛙跳台阶问题.md: -------------------------------------------------------------------------------- 1 | ## 剑指 Offer 10- II. 青蛙跳台阶问题 2 | 3 | #### 题目 4 | 5 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 6 | 7 | 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 8 | 9 | **示例 1:** 10 | 11 | ``` 12 | 输入:n = 2 13 | 输出:2 14 | ``` 15 | 16 | **示例 3:** 17 | 18 | ``` 19 | 输入:n = 0 20 | 输出:1 21 | ``` 22 | 23 | #### 代码 24 | 25 | ```JS 26 | var numWays = function (n) { 27 | if (n <= 1) { 28 | return 1; 29 | } 30 | let dp = new Array(n + 1); 31 | dp[0] = 0; 32 | dp[1] = 1; 33 | dp[2] = 2; 34 | for (let i = 3; i <= n; i++) { 35 | dp[i] = (dp[i - 1] + dp[i - 2]) % 1e9 + 7; 36 | } 37 | return dp[n]; 38 | }; 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /数据结构与算法/哈希表/【leetcode 771】宝石与石头.md: -------------------------------------------------------------------------------- 1 | # 宝石与石头 2 | 3 | ## 题目 4 | 5 | 给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。 6 | 7 | J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。 8 | 9 | **示例 1:** 10 | 11 | ``` 12 | 输入: J = "aA", S = "aAAbbbb" 13 | 输出: 3 14 | ``` 15 | 16 | **示例 2:** 17 | 18 | ``` 19 | 输入: J = "z", S = "ZZ" 20 | 输出: 0 21 | ``` 22 | 23 | **注意:** 24 | 25 | - S 和 J 最多含有50个字母。 26 | - J 中的字符不重复。 27 | 28 | ## 代码 29 | 30 | #### 解法1:indexOf寻找字符下标 31 | 32 | ```js 33 | var numJewelsInStones = function (jewels, stones) { 34 | let count = 0; 35 | for (let i = 0; i < jewels.length; i++) { 36 | let char = jewels[i]; 37 | var temp = stones; 38 | let index = temp.indexOf(char); 39 | while (index != -1) { 40 | count++; 41 | index = temp.slice(index + 1).indexOf(char); 42 | } 43 | } 44 | return count; 45 | }; 46 | ``` 47 | 48 | ![image-20210202195023544](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210202195023544.png) 49 | 50 | **时间复杂度:O(m*n)** 51 | 52 | #### 解法二:哈希集合 53 | 54 | ![image-20210202200101995](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210202200101995.png) -------------------------------------------------------------------------------- /数据结构与算法/回溯法/leetcode 131. 分割回文串.md: -------------------------------------------------------------------------------- 1 | # leetcode 131. 分割回文串 2 | 3 | 给你一个字符串 `s`,请你将 `s` 分割成一些子串,使每个子串都是 **回文串** 。返回 `s` 所有可能的分割方案。 4 | 5 | **回文串** 是正着读和反着读都一样的字符串。 6 | 7 | **示例 1:** 8 | 9 | ``` 10 | 输入:s = "aab" 11 | 输出:[["a","a","b"],["aa","b"]] 12 | ``` 13 | 14 | **示例 2:** 15 | 16 | ``` 17 | 输入:s = "a" 18 | 输出:[["a"]] 19 | ``` 20 | 21 | ![image-20210320143025007](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210320143025007.png) 22 | 23 | **解题思路:** 24 | 25 | 我们对题目进行分析,我们要将字符串分割成若干个回文串,我们的求解思路是从第一个字符开始划分,当形成回文串后,将该回文串放入结果中,再对该下标后的字符进行判断是否形成回文串,直至搜索到最后一个字符,如果到最后一个字符还未形成回文串,则回溯。 26 | 27 | **使用刻意练习的思路思考:** 28 | 29 | 1、递归树和状态变量。状态变量为当前搜索下标,新数组。将当前搜索下标前的回文串放入新数组中。 30 | 31 | ![image-20210320143545190](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210320143545190.png) 32 | 33 | 2、递归出口:当搜索下标到达最后,index ===len 34 | 35 | 3、选择列表:for循环 从当前下标到最后,判断当前下标到j之间是否形成回文串,若形成则temp.push,dfs(j+1,temp) 36 | 37 | 4、剪枝:无 38 | 39 | 5、撤回:pop 40 | 41 | ```js 42 | var partition = function (s) { 43 | const res = []; 44 | const len = s.length; 45 | const f = new Array(len).fill(0).map(() => new Array(len).fill(true)); 46 | 47 | for (let i = len - 1; i >= 0; i--) { 48 | for (let j = i + 1; j < len; j++) { 49 | f[i][j] = (s[i] === s[j]) && f[i + 1][j - 1]; 50 | } 51 | } 52 | 53 | var dfs = function (index, temp) { 54 | if (index === len) { 55 | return res.push(temp.slice()); 56 | } 57 | 58 | for (let i = index; i < len; i++) { 59 | if (f[index][i]) { 60 | temp.push(s.slice(index, i + 1)); 61 | dfs(i + 1, temp); 62 | temp.pop(); 63 | } 64 | } 65 | } 66 | 67 | dfs(0, []); 68 | return res; 69 | }; 70 | 71 | console.log(partition("aab")); 72 | 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /数据结构与算法/回溯法/leetcode 39 组合总和.md: -------------------------------------------------------------------------------- 1 | # leetcode 39 组合总和 2 | 3 | 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 4 | 5 | candidates 中的数字可以无限制重复被选取。 6 | 7 | **说明:** 8 | 9 | 所有数字(包括 target)都是正整数。 10 | 解集不能包含重复的组合。 11 | **示例 1:** 12 | 13 | ``` 14 | 输入:candidates = [2,3,6,7], target = 7, 15 | 所求解集为: 16 | [ 17 | [7], 18 | [2,2,3] 19 | ] 20 | ``` 21 | 22 | **解题思路:** 23 | 24 | 我们来思考一下题目是需要使用什么数据结构和算法。首先要求总和,就是在所给数组中进行选择,可以形成一颗树形结构,为树形问题,故可以采取回溯法来解决。 25 | 26 | **按照刻意练习的思路思考:** 27 | 28 | 1、递归树和状态变量。状态变量为当前值下标、当前和、新数组。 29 | 30 | ![image-20210314161444720](C:%5CUsers%5CAsus%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20210314161444720.png) 31 | 32 | 2、递归出口:sum === target push 33 | 34 | 3、选择列表 for循环当前下标后的数组 push进temp中,递归下一层 35 | 36 | 4、剪枝:sum>target return 37 | 38 | 5、撤销 for中撤销 pop 39 | 40 | ```js 41 | var combinationSum = function (candidates, target) { 42 | let len = candidates.length; 43 | const res = []; 44 | 45 | var dfs = function (sum, index, temp) { 46 | if (sum === target) { 47 | res.push(temp.slice()); 48 | } 49 | if (sum > target) { 50 | return; 51 | } 52 | for (let i = index; i < len; i++) { 53 | temp.push(candidates[i]); 54 | dfs(sum + candidates[i], i, temp); 55 | temp.pop(); 56 | } 57 | } 58 | 59 | dfs(0, 0, []); 60 | return res; 61 | }; 62 | 63 | 64 | console.log(combinationSum([2, 3, 6, 7], 7)); 65 | 66 | ``` 67 | 68 | -------------------------------------------------------------------------------- /数据结构与算法/回溯法/leetcode 40 组合总和2.md: -------------------------------------------------------------------------------- 1 | # leetcode 40 组合总和2 2 | 3 | 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 4 | 5 | candidates 中的每个数字在每个组合中只能使用一次。 6 | 7 | **说明:** 8 | 9 | - 所有数字(包括目标数)都是正整数。 10 | - 解集不能包含重复的组合。 11 | 12 | **示例 1:** 13 | 14 | ``` 15 | 输入: candidates = [10,1,2,7,6,1,5], target = 8, 16 | 所求解集为: 17 | [ 18 | [1, 7], 19 | [1, 2, 5], 20 | [2, 6], 21 | [1, 1, 6] 22 | ] 23 | ``` 24 | 25 | **解题思路:** 26 | 27 | 分析本题所采用的数据结构与算法,可知属于树形问题,采用回溯法。 28 | 29 | - 对数组进行从小到大的排序。 30 | - 每个数字只有取和不取两种状态,分别搜索。 31 | 32 | **按照刻意练习的思路思考:** 33 | 34 | 1、递归树和状态变量。状态变量:当期值下标、总和、新数组 35 | 36 | ![image-20210314163518591](C:%5CUsers%5CAsus%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20210314163518591.png) 37 | 38 | 2、递归出口:sum === target push 39 | 40 | 3、选择列表: for循环当前下标+1后的元素 41 | 42 | 4、剪枝:sum>target return 43 | 44 | 5、撤销操作:for循环中pop 45 | 46 | ```js 47 | var combinationSum2 = function (candidates, target) { 48 | candidates.sort((a, b) => a - b); 49 | var len = candidates.length; 50 | const res = []; 51 | 52 | var dfs = function (sum, start, temp) { 53 | if (sum === target) { 54 | res.push(temp.slice()); 55 | } 56 | if (sum > target) { 57 | return; 58 | } 59 | for (let i = start; i < len; i++) { 60 | if (i > start && candidates[i] === candidates[i - 1]) continue; 61 | temp.push(candidates[i]); 62 | dfs(sum + candidates[i], i + 1, temp); 63 | temp.pop(); 64 | } 65 | } 66 | dfs(0, 0, []); 67 | return res; 68 | }; 69 | 70 | console.log(combinationSum2([10, 1, 2, 7, 6, 1, 5], 8)); 71 | 72 | ``` 73 | 74 | -------------------------------------------------------------------------------- /数据结构与算法/回溯法/leetcode 47全排列2.md: -------------------------------------------------------------------------------- 1 | # leetcode 47全排列2 2 | 3 | 给定一个可包含重复数字的序列 `nums` ,**按任意顺序** 返回所有不重复的全排列。 4 | 5 | **示例 1:** 6 | 7 | ``` 8 | 输入:nums = [1,1,2] 9 | 输出: 10 | [[1,1,2], 11 | [1,2,1], 12 | [2,1,1]] 13 | ``` 14 | 15 | **解题思路:** 16 | 17 | 这道题我们可以看成填写一个n列的空格,我们需要从左往右填写n个数,每个数只能填一次,我们自然而然地想到穷举,可以画出一个递归树,该题目为树形问题,故可以采取回溯法。 18 | 19 | - 由于每个数只能填一次,我们需要使用标记数组来标记数字是否填过。由于为可重复数组,我们需要为每一个元素都做一个标记,故使用数组设为false。 20 | - 由于需要去重,我们需要降序排序。 21 | 22 | **刻意练习的思路来思考:** 23 | 24 | 1、递归树和状态变量。状态变量,当前填写的空格位置Index,新数组temp。 25 | 26 | ![image-20210316070558120](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210316070558120.png) 27 | 28 | 2、递归出口:两种选其一:当新数组长度===len 或 index ==len 29 | 30 | 3、选择列表:for循环遍历选择数组, 31 | 32 | 4、剪枝: 33 | 34 | - ​ 如果 i >0 且当前元素与上一元素相同,且上一元素已被使用,则continue 35 | 36 | >i > 0 是防止 nums[i-1]索引<0 37 | 38 | - 如果used[i] ===false 即,当前元素已被选过,则 continue 39 | 40 | 5、撤销:在for中撤销 ,pop 和 use恢复false 41 | 42 | ```js 43 | var permuteUnique = function (nums) { 44 | const len = nums.length; 45 | nums.sort((a, b) => a - b) 46 | const res = []; 47 | const used = new Array(len).fill(false); 48 | 49 | var dfs = function (index, temp) { 50 | if (index == len) { 51 | return res.push(temp.slice()); 52 | } 53 | 54 | for (let i = 0; i < len; i++) { 55 | if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) continue; 56 | if (used[i]) continue; 57 | used[i] = true; 58 | temp.push(nums[i]); 59 | dfs(index + 1, temp); 60 | temp.pop(); 61 | used[i] = false; 62 | } 63 | } 64 | 65 | dfs(0, []); 66 | return res; 67 | }; 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /数据结构与算法/回溯法/leetcode 70 组合.md: -------------------------------------------------------------------------------- 1 | # leetcode 77 组合 2 | 3 | 给定两个整数 *n* 和 *k*,返回 1 ... *n* 中所有可能的 *k* 个数的组合。 4 | 5 | **示例:** 6 | 7 | ``` 8 | 输入: n = 4, k = 2 9 | 输出: 10 | [ 11 | [2,4], 12 | [3,4], 13 | [2,3], 14 | [1,2], 15 | [1,3], 16 | [1,4], 17 | ] 18 | ``` 19 | 20 | **解题思路** 21 | 22 | 看到题目首先思考使用什么算法、使用什么数据结构。n个数中选k个,每个数字有两种状态,被选或没被选,那么这个时候,我们会发现这个题目的是树形问题。 23 | 24 | 以n=4,k=2为例,当我们选择数字1,第二个数字的选择只能是2,3,4,形成了一个选择树形,故我们锁定,本题的解题思路采用回溯法。 25 | 26 | **使用刻意练习的思路思考:** 27 | 28 | 1、递归树,状态变量:当前所在数值、新数组。 29 | 30 | ![image-20210314155703006](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210314155703006.png) 31 | 32 | 2、递归出口:当新数组长度 === k 33 | 34 | 3、选择列表:从当前所在数值向后搜索,加入新数组 35 | 36 | 4、剪枝:暂无 37 | 38 | 5、撤销:当前值加入新数组后,回溯时需要撤销 39 | 40 | ```js 41 | var combine = function (n, k) { 42 | const res = []; 43 | var dfs = function (start, temp) { 44 | if (temp.length === k) { 45 | return res.push(temp.slice()); 46 | } 47 | for (let i = start; i <= n; i++) { 48 | temp.push(i); 49 | dfs(i + 1, temp); 50 | temp.pop(); 51 | } 52 | } 53 | dfs(1, []); 54 | return res; 55 | }; 56 | 57 | console.log(combine(4, 2)); 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /数据结构与算法/回溯法/leetcode 90 子集2.md: -------------------------------------------------------------------------------- 1 | # leetcode 90 子集2 2 | 3 | 给定一个可能包含重复元素的整数数组 ***nums***,返回该数组所有可能的子集(幂集)。 4 | 5 | **说明:**解集不能包含重复的子集。 6 | 7 | **解题思路:** 8 | 9 | 对于数组中的每一个数字,有两种状态,取或不取。这个问题为一个树形问题,故采用回溯法解决。难点在于如何去重,即剪枝。 10 | 11 | - 将数组从小到大排序,重复数字,只取第一个。 12 | 13 | - 一个数字有两种状态: 14 | 15 | 1、不取,直接存入结果数组 16 | 17 | 2、取,继续往后搜索。 18 | 19 | **刻意联系的思路:** 20 | 21 | 1、递归树,状态变量:当前下标和新数组 22 | 23 | ![image-20210314153106066](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210314153106066.png) 24 | 25 | 2、递归出口:当前下标到达末尾,即搜索完毕。 26 | 27 | 3、选择列表:从当前下标数字往后的数字选取,存入新数组中 28 | 29 | ​ **若存在重复数字,只取第一次出现的数字,接着continue** 30 | 31 | 4、剪枝,去重 32 | 33 | 5、撤销操作:新数组中pop 34 | 35 | ```js 36 | var subsetsWithDup = function (nums) { 37 | let len = nums.length 38 | nums.sort((a, b) => a - b); 39 | const res = []; 40 | helper(); 41 | return res; 42 | 43 | function helper(temp = [], start = 0) { 44 | //不选择当前数字,直接push 45 | res.push(temp); 46 | //当全选时,退出 47 | if (temp.length === len) return; 48 | //选择列表 49 | for (let i = start; i < len; i++) { 50 | //相同元素只取第一个 51 | if (i > start && nums[i] === nums[i - 1]) continue 52 | temp.push(nums[i]); 53 | helper(temp.slice(), i + 1); 54 | temp.pop(); 55 | } 56 | } 57 | 58 | }; 59 | 60 | 61 | console.log(subsetsWithDup([1, 2, 2])); 62 | 63 | ``` 64 | 65 | -------------------------------------------------------------------------------- /数据结构与算法/回溯法/leetcode46 全排列.md: -------------------------------------------------------------------------------- 1 | # leetcode46 全排列 2 | 3 | 给定一个 **没有重复** 数字的序列,返回其所有可能的全排列。 4 | 5 | **示例:** 6 | 7 | ``` 8 | 输入: [1,2,3] 9 | 输出: 10 | [ 11 | [1,2,3], 12 | [1,3,2], 13 | [2,1,3], 14 | [2,3,1], 15 | [3,1,2], 16 | [3,2,1] 17 | ] 18 | ``` 19 | 20 | **解题思路:** 21 | 22 | 分析题目可知为树形问题,可以使用回溯算法。且每一个数字只能选择一次,故我们需要使用一个变量记录数字是否被选择过。 23 | 24 | **使用刻意练习的思路思考:** 25 | 26 | 1、递归树和状态变量。状态变量为新数组。 27 | 28 | ![image-20210315145416093](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210315145416093.png) 29 | 30 | 2、递归出口 len === n 31 | 32 | 3、选择列表 for循环 如果数字被选过,则continue。选择该数字,标记 33 | 34 | 4、剪枝 :不能选重复的 35 | 36 | 5、撤销:for 循环中撤销,pop并取消标记。 37 | 38 | ```js 39 | var permute = function (nums) { 40 | let len = nums.length; 41 | const res = []; 42 | const used = []; 43 | 44 | var dfs = function (temp) { 45 | if (temp.length === len) { 46 | return res.push(temp.slice()); 47 | } 48 | 49 | for (let i = 0; i < len; i++) { 50 | if (used[nums[i]]) continue; 51 | used[nums[i]] = true; 52 | temp.push(nums[i]); 53 | dfs(temp); 54 | temp.pop(); 55 | used[nums[i]] = false; 56 | } 57 | } 58 | 59 | dfs([]); 60 | return res; 61 | }; 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- /数据结构与算法/回溯法/剑指 Offer 38. 字符串的排列.md: -------------------------------------------------------------------------------- 1 | # 剑指 Offer 38. 字符串的排列 2 | 3 | 输入一个字符串,打印出该字符串中字符的所有排列。 4 | 5 | 你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。 6 | 7 | **示例:** 8 | 9 | ``` 10 | 输入:s = "abc" 11 | 输出:["abc","acb","bac","bca","cab","cba"] 12 | 13 | 输入:s = "aab" 14 | 输出:["aba","aab","baa"] 15 | ``` 16 | 17 | **解题思路:** 18 | 19 | 分析题目,我们可以很容易地看出可以使用遍历的方法进行求解,即为树形问题,使用回溯法。在该题中,题目没有说明字符串中字符不重复,故我们需要对数组进行排列去重。 20 | 21 | 我们可以把题目想象为一个len个空格的待填格子,每个格子需要从字符串中选择一个字符填入,每个字符只能填写一次。这个时候,当前填写格子的下标就作为状态变量。 22 | 23 | **注意数组元素为字符时,升序排序直接使用sort** 24 | 25 | **按照刻意练习的步骤来思考:** 26 | 27 | 1、递归树和状态变量。状态变量为当前填写格子的下标和新数组。 28 | 29 | ![image-20210319231855138](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210319231855138.png) 30 | 31 | 2、递归出口:当前填写格子下标 === len 32 | 33 | 3、选择列表:for循环字符数组,进行数组去重,数组去重条件为: 34 | 35 | ```js 36 | if(i>0 && arr[i] === arr[i-1] && used[i-1]) continue 37 | ``` 38 | 39 | **特别注意:used[i-1] 需要重复元素的上一个元素被使用过才去重** 40 | 41 | 如果当前元素使用过也continue。 42 | 43 | 4、剪枝:如上 44 | 45 | 5、撤销操作:pop 和 used[i] = false; 46 | 47 | ```js 48 | var permutation = function (s) { 49 | const len = s.length; 50 | const arr = s.split(''); 51 | arr.sort() 52 | const res = []; 53 | const used = new Array(len).fill(false); 54 | 55 | var dfs = function (index, temp) { 56 | if (index === len) { 57 | return res.push(temp.join('')); 58 | } 59 | 60 | for (let i = 0; i < len; i++) { 61 | if (i > 0 && arr[i] === arr[i - 1] && used[i - 1]) continue; 62 | if (used[i]) continue; 63 | temp.push(arr[i]); 64 | used[i] = true; 65 | dfs(index + 1, temp); 66 | temp.pop(); 67 | used[i] = false; 68 | } 69 | } 70 | 71 | dfs(0, []); 72 | return res; 73 | } 74 | 75 | console.log(permutation("baa")); 76 | ``` 77 | 78 | ![image-20210319232306218](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210319232306218.png) -------------------------------------------------------------------------------- /数据结构与算法/回溯法/剑指 Offer II 105. 岛屿的最大面积.md: -------------------------------------------------------------------------------- 1 | #### 剑指 Offer II 105. 岛屿的最大面积 2 | 3 | ```js 4 | var maxAreaOfIsland = function(grid) { 5 | let row = grid.length; 6 | let col = grid[0].length; 7 | let count = 0 ; 8 | let ans = 0; 9 | let visited = [...new Array(row)].map(() => new Array(col).fill(false)) 10 | let dirs = [[-1,0],[1,0],[0,-1],[0,1]]; 11 | function dfs(i,j){ 12 | count++; 13 | visited[i][j] = true; 14 | for(const dir of dirs){ 15 | let nextRow = i +dir[0] , nextCol = j +dir[1]; 16 | if(check(nextRow, nextCol)&& !visited[nextRow][nextCol] &&grid[nextRow][nextCol] == 1){ 17 | dfs(nextRow , nextCol); 18 | } 19 | } 20 | } 21 | function check(row,col){ 22 | return row >= 0 && row < grid.length && col >=0 &&col =row || j<0 || j>=coll || grid[i][j] == 0 7 | //递归列表 上右下左 8 | let direction = [[-1,0],[0,1],[1,0],[0,-1]]; 9 | let row = grid.length,col = grid[0].length; 10 | let count =0; 11 | let dfs = (i,j)=>{ 12 | if(i<0||i>=row||j<0||j>=col||grid[i][j] == 0)return 13 | grid[i][j] = 0; 14 | //四个方向 15 | for(let dir of direction){ 16 | dfs(i+dir[0],j+dir[1]); 17 | } 18 | } 19 | for(let i=0;i4){ 23 | return 24 | } 25 | 26 | if(index === len && temp.length ===4){ 27 | return res.push(temp.join('.')); 28 | } 29 | 30 | for(let i =1;i<=3;i++){ 31 | if(index+i<=len){ 32 | var num =s.substr(index,i); 33 | if(Number(num) <= 255){ 34 | if((num.length>1&&num[0]!='0') || (num.length===1)){ 35 | temp.push(num); 36 | dfs(index+i,temp); 37 | temp.pop(); 38 | } 39 | 40 | } 41 | 42 | 43 | 44 | } 45 | } 46 | } 47 | 48 | dfs(0,[]); 49 | return res; 50 | } 51 | module.exports = { 52 | restoreIpAddresses : restoreIpAddresses 53 | }; 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /数据结构与算法/堆篇/【剑指Offer 40】最小的k个数.md: -------------------------------------------------------------------------------- 1 | # 最小的k个数 2 | 3 | ## 题目 4 | 5 | 输入整数数组 `arr` ,找出其中最小的 `k` 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。 6 | 7 | **示例 1:** 8 | 9 | ``` 10 | 输入:arr = [3,2,1], k = 2 11 | 输出:[1,2] 或者 [2,1] 12 | ``` 13 | 14 | #### 思路 15 | 16 | #### 解法1 17 | 18 | sort排序 19 | 20 | ```js 21 | var getLeastNumbers = function (arr, k) { 22 | const result = [] 23 | arr.sort((a, b) => a - b); 24 | for (let i = 0; i < k; i++) { 25 | result.push(arr[i]); 26 | } 27 | return result; 28 | }; 29 | ``` 30 | 31 | 32 | 33 | ![image-20210116114412121](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116114412121.png) 34 | 35 | #### 解法2 36 | 37 | 堆 38 | 39 | 堆是一种非常常用的数据结构。最大堆的性质是:节点值大于子节点的值,堆顶元素是最大元素。利用这个性质,整体的算法流程如下: 40 | 41 | 创建大小为 k 的最大堆 42 | 将数组的前 k 个元素放入堆中 43 | 从下标 k 继续开始依次遍历数组的剩余元素: 44 | 如果元素小于堆顶元素,那么取出堆顶元素,将当前元素入堆 45 | 如果元素大于/等于堆顶元素,不做操作 46 | 由于堆的大小是 K,空间复杂度是O(K)O(K),时间复杂度是O(NlogK)O(NlogK)。 47 | 48 | 由于 JavaScript 中没有堆,所以需要手动实现。代码如下: 49 | 50 | ```js 51 | function swap(arr, i, j) { 52 | [arr[i], arr[j]] = [arr[j], arr[i]]; 53 | } 54 | 55 | class MaxHeap { 56 | constructor(arr = []) { 57 | this.container = []; 58 | if (Array.isArray(arr)) { 59 | arr.forEach(this.insert.bind(this)); 60 | } 61 | } 62 | 63 | insert(data) { 64 | const { container } = this; 65 | 66 | container.push(data); 67 | let index = container.length - 1; 68 | while (index) { 69 | let parent = Math.floor((index - 1) / 2); 70 | if (container[index] <= container[parent]) { 71 | break; 72 | } 73 | swap(container, index, parent); 74 | index = parent; 75 | } 76 | } 77 | 78 | extract() { 79 | const { container } = this; 80 | if (!container.length) { 81 | return null; 82 | } 83 | 84 | swap(container, 0, container.length - 1); 85 | const res = container.pop(); 86 | const length = container.length; 87 | let index = 0, 88 | exchange = index * 2 + 1; 89 | 90 | while (exchange < length) { 91 | // 如果有右节点,并且右节点的值大于左节点的值 92 | let right = index * 2 + 2; 93 | if (right < length && container[right] > container[exchange]) { 94 | exchange = right; 95 | } 96 | if (container[exchange] <= container[index]) { 97 | break; 98 | } 99 | swap(container, exchange, index); 100 | index = exchange; 101 | exchange = index * 2 + 1; 102 | } 103 | 104 | return res; 105 | } 106 | 107 | top() { 108 | if (this.container.length) return this.container[0]; 109 | return null; 110 | } 111 | } 112 | 113 | /** 114 | * @param {number[]} arr 115 | * @param {number} k 116 | * @return {number[]} 117 | */ 118 | var getLeastNumbers = function(arr, k) { 119 | const length = arr.length; 120 | if (k >= length) { 121 | return arr; 122 | } 123 | 124 | const heap = new MaxHeap(arr.slice(0, k)); 125 | for (let i = k; i < length; ++i) { 126 | if (heap.top() > arr[i]) { 127 | heap.extract(); 128 | heap.insert(arr[i]); 129 | } 130 | } 131 | return heap.container; 132 | }; 133 | 134 | ``` 135 | 136 | ![image-20210116115315865](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210116115315865.png) -------------------------------------------------------------------------------- /数据结构与算法/堆篇/堆篇前言.md: -------------------------------------------------------------------------------- 1 | ## 堆篇前言 2 | 3 | 堆的底层实际上是一棵完全二叉树,可以用数组实现 4 | 5 | - 每个的节点元素值不小于其子节点 - 最大堆 6 | - 每个的节点元素值不大于其子节点 - 最小堆 7 | 8 | ![image-20210115112916117](../../img/image-20210115112916117.png) 9 | 10 | >堆在处理某些特殊场景时可以大大降低代码的时间复杂度,例如在庞大的数据中找到最大的几个数或者最小的几个数,可以借助堆来完成这个过程。 11 | 12 | [堆的基本操作]() 13 | 14 | [数据流中的中位数]() 15 | 16 | [最小的k个数]() -------------------------------------------------------------------------------- /数据结构与算法/字符串/933. 最近的请求次数.md: -------------------------------------------------------------------------------- 1 | #### 933. 最近的请求次数 2 | 3 | ```js 4 | var RecentCounter = function() { 5 | this.quene = []; 6 | }; 7 | 8 | /** 9 | * @param {number} t 10 | * @return {number} 11 | */ 12 | RecentCounter.prototype.ping = function(t) { 13 | //每次Ping 入队 14 | this.quene.push(t); 15 | let time = t - 3000; 16 | //遍历quene 17 | while(this.quene.length){ 18 | if(this.quene[0] b[1] - a[1]); 18 | return res.slice(0, k) 19 | } 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /数据结构与算法/字符串/【剑指 Offer 20】表示数值的字符串.md: -------------------------------------------------------------------------------- 1 | # 表示数值的字符串 2 | 3 | ## 题目 4 | 5 | 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、"5e2"、"-123"、"3.1416"、"-1E-16"、"0123"都表示数值,但"12e"、"1a3.14"、"1.2.3"、"+-5"及"12e+5.4"都不是。 6 | 7 | ## 思路 8 | 9 | #### 解法一 :使用trim方法 10 | 11 | 字符串去除空格。 12 | 13 | 如果不存在,返回false, 14 | 15 | 如果存在,转换成Number,判断是否为NaN。 16 | 17 | ```js 18 | var isNumber = function(s) { 19 | return s.trim()?!isNaN(Number(s)):false 20 | }; 21 | ``` 22 | 23 | 或 24 | 25 | ```js 26 | var isNumber = function(s) { 27 | if(!s.trim()) return false 28 | return Number(s).toString() !== 'NaN' 29 | }; 30 | 31 | ``` -------------------------------------------------------------------------------- /数据结构与算法/字符串/【剑指Offer 05】替换空格.md: -------------------------------------------------------------------------------- 1 | # 替换空格 2 | 3 | ## 题目 4 | 5 | 请实现一个函数,把字符串 `s` 中的每个空格替换成"%20"。 6 | 7 | **示例 1:** 8 | 9 | ``` 10 | 输入:s = "We are happy." 11 | 输出:"We%20are%20happy." 12 | ``` 13 | 14 | **限制:** 15 | 16 | ``` 17 | 0 <= s 的长度 <= 10000 18 | ``` 19 | 20 | ## 思路 21 | 22 | #### 方法1 空格将字符串切割成数组 再用`20%`进行连接。 23 | 24 | ```js 25 | var replaceSpace = function (s) { 26 | return s.split(' ').join("%20"); 27 | }; 28 | ``` 29 | 30 | ![image-20210216114634301](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210216114634301.png) 31 | 32 | #### 方法2 使用正则表达式 33 | 34 | 用正则表达式找到所有空格依次替换 35 | 36 | **注意,在replace使用正则表达式,不需要加引号** 37 | 38 | ```js 39 | var replaceSpace = function (s) { 40 | return s.replace(/\s/g, '%20'); 41 | }; 42 | ``` 43 | 44 | ![image-20210216114806214](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210216114806214.png) 45 | 46 | -------------------------------------------------------------------------------- /数据结构与算法/字符串/【剑指Offer 19】正则表达式匹配.md: -------------------------------------------------------------------------------- 1 | # 正则表达式匹配 2 | 3 | ## 题目 4 | 5 | 请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。 6 | 7 | **示例 1:** 8 | 9 | ``` 10 | 输入: 11 | s = "aa" 12 | p = "a" 13 | 输出: false 14 | 解释: "a" 无法匹配 "aa" 整个字符串。 15 | ``` 16 | 17 | **示例 2:** 18 | 19 | ``` 20 | 输入: 21 | s = "aa" 22 | p = "a*" 23 | 输出: true 24 | 解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。 25 | ``` 26 | 27 | - `s` 可能为空,且只包含从 `a-z` 的小写字母。 28 | - `p` 可能为空,且只包含从 `a-z` 的小写字母以及字符 `.` 和 `*`,无连续的 `'*'`。 29 | 30 | ### 思路 31 | 32 | #### 解法1 正则表达式匹配 33 | 34 | 直接创建正则表达式匹配。**面试千万别用** 35 | 36 | ```js 37 | var isMatch = function (s, p) { 38 | var reg = new RegExp(p); 39 | var result = s.match(reg); 40 | if (result === null) { 41 | return false; 42 | } 43 | return result[0] === s ? true : false; 44 | }; 45 | ``` 46 | 47 | ```js 48 | var isMatch = function (s, p) { 49 | var reg = new RegExp('^'+p+'$'); 50 | return reg.test(s); 51 | }; 52 | ``` 53 | 54 | 55 | 56 | ![image-20210218135533249](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210218135533249.png) 57 | 58 | #### 解法二 动态规划 暂时放弃 59 | 60 | ```js 61 | var isMatch = function (s, p) { 62 | let dp = Array(s.length + 1); 63 | for (let i = 0; i < dp.length; i++) { 64 | dp[i] = Array(p.length + 1).fill(false) 65 | } 66 | dp[0][0] = true; 67 | for (let i = 1; i < p.length; i++) { 68 | if (p.charAt(i) === "*") { 69 | dp[0][i + 1] = dp[0][i - 1] 70 | } 71 | } 72 | 73 | for (let i = 0; i < s.length; i++) { 74 | for (let j = 0; j < p.length; j++) { 75 | if (p.charAt(j) === '.') { 76 | dp[i + 1][j + 1] = dp[i][j] 77 | } 78 | 79 | if (p.charAt(j) === s.charAt(i)) { 80 | dp[i + 1][j + 1] = dp[i][j] 81 | } 82 | 83 | if (p.charAt(j) === '*') { 84 | if (p.charAt(j - 1) !== s.charAt(i) && p.charAt(j - 1) !== '.') { 85 | dp[i + 1][j + 1] = dp[i + 1][j - 1] 86 | } else { 87 | dp[i + 1][j + 1] = (dp[i + 1][j] || dp[i][j + 1] || dp[i + 1][j - 1]) 88 | } 89 | } 90 | } 91 | } 92 | return dp[s.length][p.length] 93 | }; 94 | ``` 95 | 96 | -------------------------------------------------------------------------------- /数据结构与算法/字符串/大数相加.md: -------------------------------------------------------------------------------- 1 | ```JS 2 | var addStrings = function (num1, num2) { 3 | var i = num1.length - 1; j = num2.length - 1; 4 | var add = 0; 5 | var result = []; 6 | while (i >= 0 || j >= 0 || add !== 0) { 7 | //补位 8 | var x = i >= 0 ? num1[i] - '0' : 0; 9 | var y = j >= 0 ? num2[j] - '0' : 0; 10 | var temp = x + y + add; 11 | result.push(temp % 10); 12 | add = Math.floor(temp / 10); 13 | i -= 1; 14 | j -= 1; 15 | } 16 | return result.reverse().join(''); 17 | } 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/NC22 合并两个有序的数组.md: -------------------------------------------------------------------------------- 1 | #### NC22 合并两个有序的数组 2 | 3 | ```js 4 | function merge( A, m, B, n ) { 5 | //把B合并到A 并返回A 先拼接后排序 6 | for(let i =0;ia-b); 12 | } 13 | ``` 14 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/NC41 最长无重复子数组.md: -------------------------------------------------------------------------------- 1 | #### NC41 最长无重复子数组 2 | 3 | ```js 4 | function maxLength( arr ) { 5 | let max = 1; 6 | let map = new Map(); 7 | //左窗口边界 8 | let temp = 0; 9 | //滑动窗口 10 | //i为right边界 temp 为left边界 11 | //map 判断重复与否 键 数字 值 下标 12 | //无重复数组 = 重复数字的下一个-重复数字 13 | for(let i =0;ia-b); 11 | arr = Array.from(new Set(arr)) 12 | console.log(arr) 13 | let max = 1; 14 | let len = 1; 15 | for(let i =1;i 上面链表中提到的一类题目,主要是利用两个或多个不同位置的指针,通过速度和方向的变换解决问题。注意这种技巧经常在排序数组中使用。 9 | 10 | 使用大小指针求解,不断逼近结果,最后取得最终值。 11 | 12 | 适用场景: 13 | 14 | - 选择 / 求和固定个数的元素 15 | - 数组有序 / 可以改变数组顺序使其有序 16 | 17 | 18 | 19 | - [调整数组顺序使奇数位于偶数前面](http://www.conardli.top/docs/dataStructure/数组/调整数组顺序使奇数位于偶数前面.html) 20 | - [和为S的两个数字](http://www.conardli.top/docs/dataStructure/数组/和为S的两个数字.html) 21 | - [和为S的连续正整数序列](http://www.conardli.top/docs/dataStructure/数组/和为S的连续正整数序列.html) 22 | 23 | ## 2 N数之和问题 24 | 25 | >非常常见的问题,基本上都是一个套路,主要考虑如何比暴力法降低时间复杂度,而且也会用到上面的双指针技巧 26 | 27 | - [两数之和](http://www.conardli.top/docs/dataStructure/数组/两数之和.html) 28 | - [三数之和](http://www.conardli.top/docs/dataStructure/数组/三数之和.html) 29 | - [四数之和 (附加一道五数之和作为练习题)](http://www.conardli.top/docs/dataStructure/数组/四数之和.html) 30 | 31 | ## 3 二维数组 32 | 33 | >建立一定的抽象建模能力,将实际中的很多问题进行抽象 34 | 35 | - [构建乘积数组](http://www.conardli.top/docs/dataStructure/数组/构建乘积数组.html) 36 | - [顺时针打印矩阵](http://www.conardli.top/docs/dataStructure/数组/顺时针打印矩阵.html) 37 | 38 | ## 4 数据统计 39 | 40 | >数组少不了的就是统计和计算,此类问题考察如何用更高效的方法对数组进行统计计算。 41 | 42 | - [数组中出现次数超过数组长度一半的数字](http://www.conardli.top/docs/dataStructure/数组/数组中出现次数超过数组长度一半的数字.html) 43 | - [连续子数组的最大和](http://www.conardli.top/docs/dataStructure/数组/连续子数组的最大和.html) 44 | - [扑克牌顺子](http://www.conardli.top/docs/dataStructure/数组/扑克牌顺子.html) 45 | - [第一个只出现一次的字符](http://www.conardli.top/docs/dataStructure/数组/第一个只出现一次的字符.html) 46 | 47 | # 更多资料 48 | 49 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 50 | 51 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 52 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 53 | 54 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【Leetcode 15】三数之和.md: -------------------------------------------------------------------------------- 1 | # 三数之和 2 | 3 | ## 题目 4 | 5 | 给定一个包含 `n` 个整数的数组`nums`,判断 `nums` 中是否存在三个元素`a,b,c` ,使得 `a + b + c = 0 ?`找出所有满足条件且不重复的三元组。 6 | 7 | 注意:答案中不可以包含重复的三元组。 8 | 9 | ```js 10 | 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 11 | 12 | 满足要求的三元组集合为: 13 | [ 14 | [-1, 0, 1], 15 | [-1, -1, 2] 16 | ] 17 | ``` 18 | 19 | ## 思路 20 | 21 | 题目中说明可能会出现多组结果,所以我们要考虑好去重 22 | 23 | - 1.为了方便去重,我们首先将数组排序 24 | - 2.对数组进行遍历,取当前遍历的数`nums[i]`为一个基准数,遍历数后面的数组为寻找数组 25 | - 3.在寻找数组中设定两个起点,最左侧的`left`(`i+1`)和最右侧的`right`(`length-1`) 26 | - 4.判断`nums[i] + nums[left] + nums[right]`是否等于0,如果等于0,加入结果,并分别将`left`和`right`移动一位 27 | - 5.如果结果大于0,将`right`向左移动一位,向结果逼近 28 | - 5.如果结果小于0,将`left`向右移动一位,向结果逼近 29 | 30 | 注意整个过程中要考虑去重 31 | 32 | ## 代码 33 | 34 | ```js 35 | var threeSum = function (nums) { 36 | const result = []; 37 | //从小到大排列 38 | nums.sort((a, b) => a - b); 39 | for (let i = 0; i < nums.length; i++) { 40 | // 跳过重复数字 41 | if (i && nums[i] === nums[i - 1]) { continue; } 42 | //左指针 43 | let left = i + 1; 44 | //右指针 45 | let right = nums.length - 1; 46 | while (left < right) { 47 | const sum = nums[i] + nums[left] + nums[right]; 48 | if (sum > 0) { 49 | right--; 50 | } else if (sum < 0) { 51 | left++; 52 | } else { 53 | //值=0 加入结果数组 54 | result.push([nums[i], nums[left++], nums[right--]]); 55 | //继续移动指针寻找 56 | // 跳过重复数字 57 | while (nums[left] === nums[left - 1]) { 58 | left++; 59 | } 60 | // 跳过重复数字 61 | while (nums[right] === nums[right + 1]) { 62 | right--; 63 | } 64 | } 65 | } 66 | } 67 | return result; 68 | } 69 | ``` 70 | 71 | <<<<<<< HEAD 72 | ======= 73 | ```js 74 | var threeSum = function(nums) { 75 | //定一(相同跨过) 移二 76 | nums.sort((a,b)=>a-b); 77 | const res = []; 78 | for(let i = 0;ii+1&&nums[p1]===nums[p1-1])) { 85 | p1++; 86 | continue 87 | } 88 | if((p2>>>>>> a9f8a625a0f9e0944befa320c5e63711529a707a 110 | ![image-20210202193325642](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210202193325642.png) 111 | 112 | # 更多资料 113 | 114 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 115 | 116 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 117 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 118 | 119 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【Leetcode 1】两数之和.md: -------------------------------------------------------------------------------- 1 | # 两数之和 2 | 3 | ## 题目 4 | 5 | 给定一个整数数组 `nums` 和一个目标值 `target`,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。 6 | 7 | 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 8 | 9 | 示例: 10 | 11 | ```js 12 | 给定 nums = [2, 7, 11, 15], target = 9 13 | 14 | 因为 nums[0] + nums[1] = 2 + 7 = 9 15 | 所以返回 [0, 1] 16 | ``` 17 | 18 | ## 思路 19 | 20 | 使用一个`map`将遍历过的数字存起来,值作为`key`,下标作为值。 21 | 22 | 对于每一次遍历: 23 | 24 | - 取`map`中查找是否有`key`为`target-nums[i]`的值 25 | - 如果取到了,则条件成立,返回。 26 | - 如果没有取到,将当前值作为`key`,下标作为值存入`map` 27 | 28 | 时间复杂度:`O(n)` 29 | 30 | 空间复杂度`O(n)` 31 | 32 | ## 代码 33 | 34 | ```js 35 | /* 思路: 36 | 使用一个map将遍历过的数字存起来,val为键,下标为值 37 | 对于每一次遍历,在map中查找是否有target-nums[i]的值 38 | 如果有,则条件成立,返回坐标。 39 | 如果没有,将该数字存入map中 */ 40 | 41 | var twoSum = function (nums, target) { 42 | const map = new Map(); 43 | if (Array.isArray(nums)) { 44 | for (let i = 0; i < nums.length; i++) { 45 | if (map.has(target - nums[i])) { 46 | return [map.get(target - nums[i]), i]; 47 | } else { 48 | // 存入该数 49 | map.set(nums[i], i); 50 | } 51 | } 52 | } 53 | return []; 54 | }; 55 | 56 | 57 | ``` 58 | 59 | ![image-20210202183428003](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210202183428003.png) 60 | 61 | # 更多资料 62 | 63 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 64 | 65 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 66 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 67 | 68 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【leetcode 42】接雨水.md: -------------------------------------------------------------------------------- 1 | # 【leetcode 42】接雨水 2 | 3 | ### 题目 4 | 5 | 给定 *n* 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 6 | 7 | **示例 1:** 8 | 9 | ![img](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/rainwatertrap.png) 10 | 11 | ``` 12 | 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 13 | 输出:6 14 | 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 15 | ``` 16 | 17 | ### 解决思路 18 | 19 | **概念:** 20 | 21 | ``` 22 | left_max: 左边的最大值,它是从左往右遍历找到的 23 | right_max: 右边的最大值,它是从右往左遍历找到的 24 | left: 从左往右处理的当前下标 25 | right: 从右往左处理的当前下标 26 | ``` 27 | 28 | 解题思路:根据短板效应,我们可以得出一个结论,在某个位置i处,它能存的水,取决于它左右两边的最大值中最小的一个。 29 | 30 | - 当我们从左往右处理left下标时,对于left来说,leftMax是真实有效的,但是他不能保证rightMax是真的最大值,因为有可能没有遍历到的中间存在更大的值。 31 | 32 | - 当我们从右往左处理right下标时,对于right来说,rightMax是真实有效的,但是他不能保证leftMax是真的最大值,同上面原因。 33 | 34 | 这时候我们可以得出一个结论,对于left下标而言,左边最大值一定为leftMax,右边最大值一定>=rightMax;同理,对于right下标而言,右边最大值一定是rightMax,左边最大值一定>=leftMax。 35 | 36 | **最终思路:** 37 | 38 | 我们进行一个循环,当left < right 39 | 40 | (积水都是从低位开始积的这个道理,大家都明白吧) 41 | 42 | 判断左右left right的高度,哪个低,先计算它积水的多少,然后移动指针,继续比较高度。 43 | 44 | ```js 45 | var trap = function (arr) { 46 | let left = 0; 47 | let right = arr.length - 1; 48 | let leftMax = 0; 49 | let rightMax = 0; 50 | let count = 0; 51 | 52 | while (left < right) { 53 | if (arr[left] < arr[right]) { 54 | leftMax = Math.max(leftMax, arr[left]); 55 | count += leftMax - arr[left]; 56 | left++; 57 | } else { 58 | rightMax = Math.max(rightMax, arr[right]); 59 | count += rightMax - arr[right]; 60 | right--; 61 | } 62 | } 63 | return count; 64 | } 65 | 66 | console.log(trap([4, 2, 0, 3, 2, 5])); 67 | 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【leetcode 611】有效三角形的个数.md: -------------------------------------------------------------------------------- 1 | ## 【leetcode 611】有效三角形的个数 2 | 3 | 给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。 4 | 5 | **示例 1:** 6 | 7 | ```js 8 | 输入: [2,2,3,4] 9 | 输出: 3 10 | 解释: 11 | 有效的组合是: 12 | 2,3,4 (使用第一个 2) 13 | 2,3,4 (使用第二个 2) 14 | 2,2,3 15 | ``` 16 | 17 | #### 解法一:枚举 18 | 19 | **形成三角形的条件:两边之和大于第三边** 20 | 21 | ```js 22 | var triangleNumber = function (arr) { 23 | let count = 0; 24 | let len = arr.length; 25 | 26 | for (let i = 0; i < len; i++) { 27 | for (let j = i + 1; j < len; j++) { 28 | for (let k = j + 1; k < len; k++) { 29 | if (arr[i] + arr[j] > arr[k] && arr[i] + arr[k] > arr[j] && arr[j] + arr[k] > arr[i]) { 30 | count++; 31 | } 32 | } 33 | } 34 | } 35 | return count; 36 | }; 37 | ``` 38 | 39 | - 时间复杂度:O(N^3),其中 N是数组的长度。 40 | - 空间复杂度:O(1)。 41 | 42 | #### 解法二:排序+双指针 43 | 44 | 如果三角形的三条边长从小到大为 a 、 b 、 c ,当且仅当 a + b > c 这三条边能组成三角形,即两条短边加起来 > 第三边即可 45 | 46 | **解题思路:** 47 | 48 | 先数组排序,排序完后,固定最长的边,利用双指针法判断其余边。 49 | 50 | - 最长边从后往前搜索,nums[k] ( k 从 nums.length - 1 往左搜索至 0) 51 | 52 | - 双指针 i , j。以 nums[i] (i 从 0 往右搜索)作为最短边,以 nums[j] 作为第二个数 ( j 从 nums.length - 2 往左搜索) 。 53 | 54 | 55 | - 判断 nums[i] + nums[j] 是否大于 nums[k] 56 | 57 | - 如果nums[i] + nums[j] > nums[k] ,则可构成三角形的三元组个数加 j-i ,并且 j 往前移动一位( j-- ),继续进入下一轮判断。 58 | 59 | ``` 60 | nums[i] + nums[j] > nums[k] 则: 61 | 62 | nums[i+1] + nums[j] > nums[k] 63 | nums[i+2] + nums[j] > nums[k] 64 | ... 65 | nums[j-1] + nums[j] > nums[k] 66 | 67 | 因为nums[i]后面的数都比nums[i]大。 68 | ``` 69 | 70 | - 如果nums[i] + nums[j] <= nums[k],则 i ++ 继续判断。 71 | 72 | 73 | ```js 74 | var triangleNumber = function (arr) { 75 | let count = 0; 76 | let len = arr.length; 77 | 78 | for (let k = len - 1; k > 1; k--) { 79 | let i = 0, j = len - 2; 80 | if (arr[i] + arr[j] > arr[k]) { 81 | count += j - i; 82 | j--; 83 | } else { 84 | i++; 85 | } 86 | } 87 | return count; 88 | }; 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【leetcode 面试题 10.01】. 合并排序的数组.md: -------------------------------------------------------------------------------- 1 | # 面试题 10.01. 合并排序的数组 2 | 3 | 给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。 4 | 5 | 初始化 A 和 B 的元素数量分别为 m 和 n。 6 | 7 | #### 1、B放进A里排序 8 | 9 | ```js 10 | var merge = function (A, m, B, n) { 11 | //B放进A里排序 12 | for (let i = m; i < m + n; i++) { 13 | A[i] = B[i - m]; 14 | } 15 | A.sort((a, b) => a - b); 16 | return A; 17 | }; 18 | ``` 19 | 20 | ![image-20210326171416758](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210326171416758.png) 21 | 22 | #### 2、双指针 23 | 24 | ```js 25 | var merge = function (A, m, B, n) { 26 | let i = 0, j = 0; 27 | const arr = []; 28 | while (i < m && j < n) { 29 | if (A[i] < B[j]) { 30 | arr.push(A[i]); 31 | i++; 32 | } else { 33 | arr.push(B[j]); 34 | j++; 35 | } 36 | } 37 | if (i < m) { 38 | for (let t = i; t < m; t++) { 39 | arr.push(A[t]); 40 | } 41 | } else if (j < n) { 42 | for (let t = j; t < n; t++) { 43 | arr.push(B[t]); 44 | } 45 | } 46 | for (let t = 0; t < m + n; t++) { 47 | A[t] = arr[t]; 48 | } 49 | return A; 50 | }; 51 | ``` 52 | 53 | ![image-20210326171955720](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210326171955720.png) 54 | 55 | #### 3、 -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【剑指Offer 21】调整数组顺序使奇数位于偶数前面.md: -------------------------------------------------------------------------------- 1 | # 调整数组顺序使奇数位于偶数前面 2 | 3 | ## 题目 4 | 5 | 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分 6 | 7 | ## 思路 8 | 9 | 两个指针start、end,一个从前开始,一个从后开始,比较指针所指元素。 10 | 11 | 1. 若arr[start]为偶数,arr[end]为奇数,交换两个元素,start++,end--。 12 | 2. 若arr[start]为偶数,arr[end]也为偶数,end--,直至end指针所指为奇数,交换。 13 | 3. 若arr[start]为奇数,start++,直至start指向偶数。 14 | 4. 当start>end交换完成。 15 | 16 | ## 代码 17 | 18 | ```js 19 | function reOrderArray(arr) { 20 | let start = 0; 21 | let end = arr.length - 1; 22 | while (start < end) { 23 | //奇数 24 | while (arr[start] % 2 === 1) { 25 | start++; 26 | } 27 | //此时start所指为偶数,对后面判断 28 | while (arr[end] % 2 === 0) { 29 | end--; 30 | } 31 | //此时end所指为奇数,若start 若需要保证相对顺序不变,则不能用上面的写法,需要让两个指针同时从左侧开始 45 | 46 | # 更多资料 47 | 48 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 49 | 50 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 51 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 52 | 53 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【剑指Offer 37】数组中出现次数超过数组长度一半的数字.md: -------------------------------------------------------------------------------- 1 | # 数组中出现次数超过数组长度一半的数字 2 | 3 | ## 题目 4 | 5 | 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。 6 | 7 | ## 思路 8 | 9 | ### 解法1 10 | 11 | map存储,开辟一个额外空间存储每个值出现的次数,时间复杂度最大为O(n),逻辑简单。 12 | 13 | ```js 14 | var majorityElement = function (nums) { 15 | if (nums && nums.length > 0) { 16 | var length = nums.length; 17 | var map = {}; 18 | for (let i = 0; i < length; i++) { 19 | if (map[nums[i]]) { 20 | map[nums[i]]++; 21 | } else { 22 | map[nums[i]] = 1; 23 | } 24 | if (map[nums[i]] > length / 2) { 25 | return nums[i]; 26 | } 27 | } 28 | } 29 | return 0; 30 | }; 31 | ``` 32 | 33 | ![image-20210114092832417](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210114092832417.png) 34 | 35 | **时间复杂度:O(n)** 36 | 37 | **空间复杂度:O(n)** 38 | 39 | ### 解法2 40 | 41 | 目标值的个数比其他所有值加起来的数多 42 | 43 | 记录两个变量 1.数组中的某个值 2.次数 44 | 45 | 1.当前遍历值和上一次遍历值相等?次数+1 : 次数-1。 46 | 47 | 2.次数变为0后保存新的值。 48 | 49 | 3.遍历结束后保存的值,判断其是否复合条件 50 | 51 | 事件复杂度O(n) 不需要开辟额外空间 , 逻辑稍微复杂。 52 | 53 | ```js 54 | var majorityElement = function (nums) { 55 | if (nums && nums.length > 0) { 56 | var target = nums[0]; 57 | var count = 1; 58 | for (let i = 0; i < nums.length; i++) { 59 | if (nums[i] === target) { 60 | count++; 61 | } else { 62 | count--; 63 | } 64 | 65 | if (count === 0) { 66 | target = nums[i]; 67 | count = 1; 68 | } 69 | } 70 | count = 0; 71 | for (let i = 0; i < nums.length; i++) { 72 | if (nums[i] === target) { 73 | count++; 74 | } 75 | } 76 | return count > nums.length / 2 ? target : 0; 77 | } 78 | return 0; 79 | }; 80 | ``` 81 | 82 | ![image-20210114093524689](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210114093524689.png) 83 | 84 | **时间复杂度O(n²)** 85 | 86 | **空间复杂度O(1)** 87 | 88 | # 更多资料 89 | 90 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 91 | 92 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 93 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 94 | 95 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【剑指Offer 42】连续子数组的最大和.md: -------------------------------------------------------------------------------- 1 | # 连续子数组的最大和 2 | 3 | 剑指Offer42:[连续子数组的最大和](https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/) 4 | 5 | ## 题目 6 | 7 | 输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值,要求时间复杂度为`O(n)` 8 | 9 | 例如:`{6,-3,-2,7,-15,1,2,2}`,连续子向量的最大和为8(从第0个开始,到第3个为止)。 10 | 11 | ## 思路 12 | 13 | 记录一个当前连续子数组最大值 `max` 默认值为数组第一项 14 | 15 | 记录一个当前连续子数组累加值 `sum` 默认值为数组第一项 16 | 17 | 1.从数组第二个数开始,若 `sum<0` 则当前的`sum`不再对后面的累加有贡献,`sum = 当前数` 18 | 19 | 2.若 `sum>0` 则`sum = sum + 当前数` 20 | 21 | 3.比较 `sum` 和 `max` ,`max = 两者最大值` 22 | 23 | ```js 24 | var maxSubArray = function (nums) { 25 | if (nums && nums.length > 0) { 26 | var sum = nums[0]; 27 | var max = nums[0]; 28 | for (let i = 1; i < nums.length; i++) { 29 | if (sum < 0) { 30 | sum = nums[i]; 31 | } else { 32 | sum = sum + nums[i]; 33 | } 34 | 35 | if (sum > max) { 36 | max = sum; 37 | } 38 | } 39 | return max; 40 | } 41 | return 0; 42 | }; 43 | ``` 44 | 45 | ![image-20210114094622296](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210114110307145.png) 46 | 47 | # 更多资料 48 | 49 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 50 | 51 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 52 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 53 | 54 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【剑指Offer 50】第一个只出现一次的字符.md: -------------------------------------------------------------------------------- 1 | # 第一个只出现一次的字符 2 | 3 | 剑指Offer:[第一个只出现一次的字符](https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/) 4 | 5 | ## 题目 6 | 7 | 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。 8 | 9 | **示例:** 10 | 11 | ``` 12 | s = "abaccdeff" 13 | 返回 "b" 14 | 15 | s = "" 16 | 返回 " " 17 | ``` 18 | 19 | ## 思路 20 | 21 | ### 解法1 22 | 23 | 用一个`map`存储每个字符出现的字数 24 | 25 | 第一次循环存储次数,第二次循环找到第一个出现一次的字符。 26 | 27 | 时间复杂度`O(n)`、空间复杂度`O(n)` 28 | 29 | ```js 30 | var firstUniqChar = function (s) { 31 | if (!s) { 32 | return " "; 33 | } 34 | const countMap = {}; 35 | const arr = s.split(""); 36 | for (let i = 0; i < s.length; i++) { 37 | let current = arr[i]; 38 | let count = countMap[current]; 39 | countMap[current] = count ? count + 1 : 1; 40 | 41 | } 42 | for (let i = 0; i < s.length; i++) { 43 | if (countMap[arr[i]] === 1) { 44 | return arr[i]; 45 | } 46 | } 47 | return " "; 48 | }; 49 | ``` 50 | 51 | ![image-20210114110307145](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210114110536085.png) 52 | 53 | ### 解法2 54 | 55 | 使用`js`的`array`提供的`indexOf`和`lastIndexOf`方法 56 | 57 | 遍历字符串,比较每个字符第一次和最后一次出现的位置是否相同。 58 | 59 | `indexOf`的时间复杂度为`O(n)`,所以整体的时间复杂度为O(n2),空间复杂度为`0`。 60 | 61 | ```js 62 | var firstUniqChar = function (s) { 63 | if (!s) { 64 | return " "; 65 | } 66 | for (let i = 0; i < s.length; i++) { 67 | if (s.indexOf(s[i]) === s.lastIndexOf(s[i])) { 68 | return s[i]; 69 | } 70 | } 71 | return " "; 72 | }; 73 | ``` 74 | 75 | ![image-20210114110536085](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210114103919035.png) -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【剑指Offer 57 Ⅱ】和为S的人连续正整数序列.md: -------------------------------------------------------------------------------- 1 | # 和为S的人连续正整数序列 2 | 3 | ## 题目 4 | 5 | 输入一个正数`S`,打印出所有和为S的连续正数序列。 6 | 7 | 例如:输入`15`,有序`1+2+3+4+5` = `4+5+6` = `7+8` = `15` 所以打印出3个连续序列`1-5`,`5-6`和`7-8`。 8 | 9 | ## 思路 10 | 11 | - 创建一个容器`child`,用于表示当前的子序列,初始元素为`1,2` 12 | - 记录子序列的开头元素`small`和末尾元素`big` 13 | - `big`向右移动子序列末尾增加一个数 `small`向右移动子序列开头减少一个数 14 | - 当子序列的和大于目标值,`small`向右移动,子序列的和小于目标值,`big`向右移动 15 | 16 | ## 代码 17 | 18 | ```js 19 | function FindContinuousSequence(sum) { 20 | const result = []; 21 | const child = [1, 2]; 22 | let big = 2; 23 | let small = 1; 24 | let currentSum = 3; 25 | while (big < sum) { 26 | //序列和小于目标值 且big小于目标值执行,直至超过目标值 27 | //存入数组,sum变化,big右移 28 | while (currentSum < sum && big < sum) { 29 | child.push(++big); 30 | currentSum += big; 31 | } 32 | //序列和大于目标值,且指针没越位,直至越位 33 | while (currentSum > sum && small < big) { 34 | //移除child的第一个元素 35 | child.shift(); 36 | //左指针自增 sum变化。 37 | currentSum -= small++; 38 | } 39 | //如果值符合,且序列个数>1,存入结果数组,继续寻找。 40 | if (currentSum === sum && child.length > 1) { 41 | result.push(child.slice()); 42 | child.push(++big); 43 | currentSum += big; 44 | } 45 | } 46 | return result; 47 | } 48 | ``` 49 | 50 | # 更多资料 51 | 52 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 53 | 54 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 55 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 56 | 57 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【剑指Offer 57】和为S的两个数字.md: -------------------------------------------------------------------------------- 1 | # 和为S的两个数字 2 | 3 | ## 题目 4 | 5 | 输入一个递增排序的数组和一个数字`S`,在数组中查找两个数,使得他们的和正好是`S`,如果有多对数字的和等于`S`,输出两个数的乘积最小的。 6 | 7 | ## 思路 8 | 9 | > 数组中可能有多对符合条件的结果,而且要求输出乘积最小的,说明要分布在两侧 比如 `3,8` `5,7` 要取`3,8`。 10 | 11 | 看了题目了,很像`leetcode`的第一题【两数之和】,但是题目中有一个明显不同的条件就是数组是有序的,可以使用使用大小指针求解,不断逼近结果,最后取得最终值。 12 | 13 | - 设定一个小索引`left`,从`0`开始 14 | - 设定一个大索引`right`,从`array.length`开始 15 | - 判断`array[left] + array[right]`的值`s`是否符合条件 16 | - 符合条件 - 返回 17 | - 大于`sum`,`right`向左移动 18 | - 小于`sum`,`left`向右移动 19 | - 若`left=right`,没有符合条件的结果 20 | 21 | ## 代码 22 | 23 | ```js 24 | function FindNumbersWithSum(array, sum) { 25 | let left = 0; 26 | let right = array.length - 1; 27 | while (left < right) { 28 | const s = array[left] + array[right]; 29 | if (s > sum) { 30 | right--; 31 | } else if (s < sum) { 32 | left++; 33 | } else { 34 | return [array[left], array[right]] 35 | } 36 | } 37 | return []; 38 | } 39 | ``` 40 | 41 | # 更多资料 42 | 43 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 44 | 45 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 46 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 47 | 48 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【剑指Offer 61】扑克牌中的顺子.md: -------------------------------------------------------------------------------- 1 | # 扑克牌中的顺子 2 | 3 | 剑指Offer61:[扑克牌中的顺子](https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) 4 | 5 | ## 题目 6 | 7 | 扑克牌中随机抽`5`张牌,判断是不是一个顺子,即这`5`张牌是不是连续的。 8 | 9 | `2-10`为数字本身,`A`为`1`,`J`为`11...`大小王可以看成任何数字,可以把它当作`0`处理。 10 | 11 | **示例 1:** 12 | 13 | ``` 14 | 输入: [1,2,3,4,5] 15 | 输出: True 16 | ``` 17 | 18 | **示例 2:** 19 | 20 | ``` 21 | 输入: [0,0,1,2,5] 22 | 输出: True 23 | ``` 24 | 25 | ## 思路 26 | 27 | - 1.数组排序 **排序时应使用numbers.sort((a, b) => a - b);,直接numbers.sort()将不对最后一个元素进行排序** 28 | - 2.遍历数组 29 | - 3.若为`0`,记录`0`的个数加`1` 30 | - 4.若不为`0`,记录和下一个元素的间隔 31 | - 5.最后比较`0`的个数和间隔数,间隔数`>0`的个数则不能构成顺子 32 | - 6.注意中间如果有两个元素相等则不能构成顺子 33 | 34 | ```js 35 | var isStraight = function (numbers) { 36 | if (numbers && numbers.length > 0) { 37 | numbers.sort((a, b) => a - b); 38 | let kingNum = 0; 39 | let spaceNum = 0; 40 | for (let i = 0; i < numbers.length - 1; i++) { 41 | if (numbers[i] === 0) { 42 | kingNum++; 43 | } else { 44 | //间距 45 | const space = numbers[i + 1] - numbers[i]; 46 | //如果存在相同数字,直接返回false 47 | if (space == 0) { 48 | return false; 49 | } else { 50 | //间距总和+=space-1 51 | spaceNum += space - 1; 52 | } 53 | } 54 | } 55 | //0的数量刚好能抵消间距 则true 56 | return kingNum - spaceNum >= 0; 57 | } 58 | return false; 59 | } 60 | ``` 61 | 62 | ![image-20210114103919035](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210114094622296.png) 63 | 64 | # 更多资料 65 | 66 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 67 | 68 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 69 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 70 | 71 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/【剑指Offer 66】构建乘积数组.md: -------------------------------------------------------------------------------- 1 | ## 构建乘积数组 2 | 3 | ### 题目 4 | 5 | 给定一个数组A`[0,1,...,n-1]`,请构建一个数组B`[0,1,...,n-1]`,其中B中的元素`B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]`。不能使用除法。 6 | 7 | ### 思路 8 | 9 | `B[i]`的值是`A`数组所有元素的乘积再除以`A[i]`,但是题目中给定不能用除法,我们换一个思路,将`B[i]`的每个值列出来,如下图: 10 | 11 | ![image-20210115140209008](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210115140209008.png) 12 | 13 | `B[i]`的值可以看作下图的矩阵中每行的乘积。 14 | 15 | 可以将`B`数组分为上下两个三角,先计算下三角,然后把上三角乘进去。 16 | 17 | ### 代码 18 | 19 | ```js 20 | function multiply(array) { 21 | const result = []; 22 | if (Array.isArray(array) && array.length > 0) { 23 | // 计算下三角 计算前i - 1个元素的乘积 24 | result[0] = 1; 25 | for (let i = 1; i < array.length; i++) { 26 | result[i] = result[i - 1] * array[i - 1]; 27 | } 28 | // 乘上三角(越过i) 计算后N - i个元素的乘积并连接 29 | let temp = 1; 30 | for (let i = array.length - 2; i >= 0; i--) { 31 | temp = temp * array[i + 1]; 32 | result[i] = result[i] * temp; 33 | } 34 | } 35 | return result; 36 | } 37 | ``` 38 | 39 | # 更多资料 40 | 41 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 42 | 43 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 44 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 45 | 46 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/合并有序数组.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruoruochen/front-end-note/bdfd11d32b610a5545808b1af3e222fad9b91b22/数据结构与算法/数组篇/合并有序数组.md -------------------------------------------------------------------------------- /数据结构与算法/数组篇/数组去重.md: -------------------------------------------------------------------------------- 1 | #### 1、简单的双层循环 2 | 3 | 创建一个新数组来存放去重的数组,遍历原数组中的元素在新数组中是否存在,不存在则存入新数组,否则不存。 4 | 5 | ```js 6 | var unique = function (array) { 7 | const newArr = []; 8 | 9 | let len = array.length; 10 | for (let i = 0; i < len; i++) { 11 | let flag = false; 12 | for (let j = 0; j < newArr.length; j++) { 13 | if (array[i] === newArr[j]) { 14 | flag = true; 15 | break; 16 | } 17 | } 18 | 19 | if (!flag) { 20 | newArr.push(array[i]); 21 | } 22 | } 23 | return newArr; 24 | } 25 | ``` 26 | 27 | #### 2、使用indexOf() 28 | 29 | 创建一个新数组来存放去重的数组。遍历原数组,使用indexOf()方法判断该元素第一个索引值是否为当前元素下标,是则加入新数组。 30 | 31 | ```js 32 | var unique = function (array) { 33 | const newArr = []; 34 | for (let i = 0; i < array.length; i++) { 35 | if (array.indexOf(array[i]) === i) { 36 | newArr.push(array[i]); 37 | } 38 | } 39 | return newArr; 40 | } 41 | ``` 42 | 43 | #### 3、对数组排序后去重 44 | 45 | 这个是我在算法里最常用的 46 | 47 | ```js 48 | var unique = function (array) { 49 | const newArr = []; 50 | array.sort((a, b) => a - b); 51 | for (let i = 0; i < array.length; i++) { 52 | if (i > 0 && array[i] === array[i - 1]) continue; 53 | newArr.push(array[i]); 54 | } 55 | return newArr; 56 | } 57 | ``` 58 | 59 | #### 4、使用includes()方法 60 | 61 | includes()方法 用来判断一个数组中是否包含某个元素。 62 | 63 | ```js 64 | var unique = function (array) { 65 | const newArr = []; 66 | for (let i = 0; i < array.length; i++) { 67 | if (!newArr.includes(array[i])) { 68 | newArr.push(array[i]); 69 | } 70 | } 71 | return newArr; 72 | } 73 | ``` 74 | 75 | #### 5、使用Set数据结构 76 | 77 | ```js 78 | var unique = function (array) { 79 | return [...new Set(array)]; 80 | } 81 | ``` 82 | 83 | #### 6、使用Map数据结构 84 | 85 | Map 地图,它类似于对象,也是以键值对的形式存储,但是“键”的范围不限于字符串,各种类型的值都可以当作键。 86 | 87 | ```js 88 | var unique = function (array) { 89 | const newArr = []; 90 | let map = new Map(); 91 | for (let i = 0; i < array.length; i++) { 92 | if (!map.has(array[i])) { 93 | map.set(array[i]); 94 | newArr.push(array[i]); 95 | } 96 | } 97 | return newArr; 98 | } 99 | ``` 100 | 101 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/斐波那契数列.md: -------------------------------------------------------------------------------- 1 | #### 斐波那契数列 2 | 3 | ```js 4 | function Fibonacci(n) 5 | { 6 | //递归或动态规划都行 求dp[n] 7 | let dp = new Array(n+1); 8 | dp[0]=0; 9 | dp[1]=1; 10 | for(let i =2;i<=n;i++){ 11 | dp[i]=dp[i-1]+dp[i-2]; 12 | } 13 | return dp[n]; 14 | } 15 | ``` 16 | 17 | ```js 18 | function Fibonacci(n) 19 | { 20 | //递归 21 | if(n<=1) return n; 22 | return Fibonacci(n-1)+Fibonacci(n-2); 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/比较版本号.md: -------------------------------------------------------------------------------- 1 | #### 比较版本号 2 | 3 | ```js 4 | function compare( version1 , version2 ) { 5 | //使用split('.')分割,一对一比较 6 | //比较规则:去除前导0 比较长度 7 | //长度相等,从前往后比较 > return1 < return -1 8 | //全部相同,另一个还有修订号,那个的大 9 | let arr1 = version1.split('.'),arr2 = version2.split('.'),str1,str2; 10 | while(arr1.lengtharr2.length){arr2.push('0')} 12 | //arr1 和 arr2 一对一比较 13 | for(let i =0;istr2.length){ 32 | return 1; 33 | }else if(str1.lengthnum2){ 40 | return 1; 41 | }else if(num1{ 10 | return a.start-b.start; 11 | }) 12 | let res = [],prev=intervals[0]; 13 | for (let i = 1; i < intervals.length; i++) { 14 | let cur = intervals[i]; 15 | if(prev.end>=cur.start){//有重合 16 | prev.end = Math.max(prev.end,cur.end) 17 | }else{ 18 | res.push(prev) //没有重合则推进 19 | prev = cur //更新 20 | } 21 | } 22 | res.push(prev) 23 | return res; 24 | } 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /数据结构与算法/数组篇/猿辅导笔试编程 逆时针输出.md: -------------------------------------------------------------------------------- 1 | [[编程题]发水果](https://www.nowcoder.com/questionTerminal/a3715db6eb1d428188d04f6fe2160ef7) 2 | 3 | ```js 4 | 5 | //牛客网的输入输入 6 | var firstline = readline().split(' '); 7 | var n = firstline[0]; 8 | var m = firstline[1]; 9 | var arr = []; 10 | for (let i = 0; i < n; i++) { 11 | var lines = readline().split(' '); 12 | arr.push(lines); 13 | } 14 | 15 | let top = 0; 16 | let bottom = n - 1; 17 | let left = 0; 18 | let right = m - 1; 19 | const res = []; 20 | 21 | while (top < bottom && left < right) { 22 | //逆时针 23 | //向下 24 | for (let i = top; i < bottom; i++) res.push(arr[i][left]); 25 | //向右 26 | for (let i = left; i < right; i++) res.push(arr[bottom][i]); 27 | //向上 28 | for (let i = bottom; i > top; i--)res.push(arr[i][right]); 29 | //向左 30 | for (let i = right; i > left; i--)res.push(arr[top][i]); 31 | 32 | //缩圈 33 | top++; 34 | bottom--; 35 | left++; 36 | right--; 37 | } 38 | 39 | //退出圈 40 | //向右 41 | if (top === bottom) { 42 | for (let i = left; i <= right; i++) { 43 | res.push(arr[top][i]); 44 | } 45 | } 46 | //向下 47 | else if (left === right) { 48 | for (let i = top; i <= bottom; i++) { 49 | res.push(arr[i][left]); 50 | } 51 | } 52 | 53 | var s = "" 54 | // 输出: 55 | for (let i = 0; i < res.length; i++) { 56 | s = s + res[i] + ' '; 57 | } 58 | console.log(s); 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /数据结构与算法/栈和队列/【leetcode 239】滑动窗口最大值.md: -------------------------------------------------------------------------------- 1 | # 滑动窗口最大值 2 | 3 | ## 题目 4 | 5 | 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 6 | 7 | 返回滑动窗口中的最大值。 8 | 9 | **示例 1:** 10 | 11 | ``` 12 | 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 13 | 输出:[3,3,5,5,6,7] 14 | 解释: 15 | 滑动窗口的位置 最大值 16 | --------------- ----- 17 | [1 3 -1] -3 5 3 6 7 3 18 | 1 [3 -1 -3] 5 3 6 7 3 19 | 1 3 [-1 -3 5] 3 6 7 5 20 | 1 3 -1 [-3 5 3] 6 7 5 21 | 1 3 -1 -3 [5 3 6] 7 6 22 | 1 3 -1 -3 5 [3 6 7] 7 23 | ``` 24 | 25 | **示例 2:** 26 | 27 | ``` 28 | 输入:nums = [1], k = 1 29 | 输出:[1] 30 | ``` 31 | 32 | **示例 3:** 33 | 34 | ``` 35 | 输入:nums = [9,11], k = 2 36 | 输出:[11] 37 | ``` 38 | 39 | **提示:** 40 | 41 | - `1 <= nums.length <= 105` 42 | - `-104 <= nums[i] <= 104` 43 | - `1 <= k <= nums.length` 44 | 45 | ## 思路 46 | 47 | ### 解法一:单调队列(双端队列) 48 | 49 | 单调队列中存储值的下标。 50 | 51 | - 核心思路:维护一个单调递减队列,最大值就是队列的第一个元素。 52 | - 维护单调队列:如果新加元素 >= 队尾元素,那么依次将队尾元素删除,保证队列头元素是最大值。 53 | - 当满足滑动窗口区间范围时,将队列头元素加入结果数组中。 54 | - 时间复杂度为O(N),空间复杂度为O(N) 55 | 56 | ![image.png](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/ef0119fb0034119b6f064297458b235ae0367f314baecd2269cabc50d33ec5c5-image.png) 57 | 58 | ```js 59 | var maxSlidingWindow = function (nums, k) { 60 | //数组长度 61 | const n = nums.length; 62 | //双端队列 63 | const q = []; 64 | //在队列中加入k个值,即第一个滑动窗口 65 | for (let i = 0; i < k; i++) { 66 | //如果队列不为空且新元素 >= 队尾元素,则删除队尾元素。 67 | while (q.length && nums[i] >= nums[q[q.length - 1]]) { 68 | q.pop(); 69 | } 70 | //否则将下标添加至队列 71 | q.push(i); 72 | } 73 | //结果数组 74 | const ans = [nums[q[0]]]; 75 | //移动滑动窗口 76 | for (let i = k; i < n; i++) { 77 | //队列不为空且新元素 >= 队尾元素,则删除队尾元素。 78 | while (q.length && nums[i] >= nums[q[q.length - 1]]) { 79 | q.pop(); 80 | } 81 | //加入队列 82 | q.push(i); 83 | //如果不满足滑动窗口区间范围,删除头元素, 84 | //寻找下一个满足范围的最大值。 85 | while (q[0] <= i - k) { 86 | q.shift(); 87 | } 88 | //将最大值加入结果数组中 89 | ans.push(nums[q[0]]); 90 | } 91 | return ans; 92 | }; 93 | ``` 94 | 95 | ![image-20210209180715514](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210209180715514.png) -------------------------------------------------------------------------------- /数据结构与算法/栈和队列/【剑指 Offer 30】包含min函数的栈.md: -------------------------------------------------------------------------------- 1 | # 包含min函数的栈 2 | 3 | ## 题目 4 | 5 | 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。 6 | 7 | **示例:** 8 | 9 | ``` 10 | MinStack minStack = new MinStack(); 11 | minStack.push(-2); 12 | minStack.push(0); 13 | minStack.push(-3); 14 | minStack.min(); --> 返回 -3. 15 | minStack.pop(); 16 | minStack.top(); --> 返回 0. 17 | minStack.min(); --> 返回 -2. 18 | ``` 19 | 20 | **提示:** 21 | 22 | 1. 各函数的调用总次数不超过 20000 次 23 | 24 | ## 思路 25 | 26 | 借助一个辅助栈,其中:辅助栈的栈顶元素,就是原栈中最小值。 27 | 28 | 原栈和辅助栈的处理过程: 29 | 30 | - 元素压入原栈时,如果辅助栈为空或`元素<=辅助栈的栈顶元素`,元素也压入辅助栈 31 | - 元素弹出原栈时,如果元素 = 辅助栈栈顶元素,辅助栈也弹出元素 32 | 33 | **注意:这里的判断条件是`元素<=辅助栈栈顶元素` 而不是`元素 < 辅助栈栈顶元素`,这是为了应对重复元素** 34 | 35 | ```javascript 36 | /** 37 | * initialize your data structure here. 38 | */ 39 | var MinStack = function () { 40 | this.dataStack = []; 41 | this.minStack = []; 42 | }; 43 | 44 | /** 45 | * @param {number} x 46 | * @return {void} 47 | */ 48 | MinStack.prototype.push = function (x) { 49 | this.dataStack.push(x); 50 | const length = this.minStack.length; 51 | if (!length || x <= this.minStack[length - 1]) { 52 | this.minStack.push(x); 53 | } 54 | }; 55 | 56 | /** 57 | * @return {void} 58 | */ 59 | MinStack.prototype.pop = function () { 60 | const { minStack, dataStack } = this; 61 | if (minStack[minStack.length - 1] === dataStack[dataStack.length - 1]) { 62 | minStack.pop(); 63 | } 64 | dataStack.pop(); 65 | }; 66 | 67 | /** 68 | * @return {number} 69 | */ 70 | MinStack.prototype.top = function () { 71 | const length = this.dataStack.length; 72 | if (!length) { 73 | return null; 74 | } else { 75 | return this.dataStack[length - 1]; 76 | } 77 | }; 78 | 79 | /** 80 | * @return {number} 81 | */ 82 | MinStack.prototype.min = function () { 83 | const length = this.minStack.length; 84 | if (!length) { 85 | return null; 86 | } else { 87 | return this.minStack[length - 1]; 88 | } 89 | }; 90 | 91 | /** 92 | * Your MinStack object will be instantiated and called as such: 93 | * var obj = new MinStack() 94 | * obj.push(x) 95 | * obj.pop() 96 | * var param_3 = obj.top() 97 | * var param_4 = obj.min() 98 | */ 99 | ``` 100 | 101 | 102 | 103 | ![image-20210209141240664](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210209141240664.png) -------------------------------------------------------------------------------- /数据结构与算法/栈和队列/【剑指 Offer 31】 栈的压入、弹出序列.md: -------------------------------------------------------------------------------- 1 | # 栈的压入、弹出序列 2 | 3 | ## 题目 4 | 5 | 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。 6 | 7 | **示例 1:** 8 | 9 | ``` 10 | 输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1] 11 | 输出:true 12 | 解释:我们可以按以下顺序执行: 13 | push(1), push(2), push(3), push(4), pop() -> 4, 14 | push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1 15 | ``` 16 | 17 | **示例 2:** 18 | 19 | ``` 20 | 输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2] 21 | 输出:false 22 | 解释:1 不能在 2 之前弹出。 23 | ``` 24 | 25 | ## 思路 26 | 27 | 借助一个辅助栈来模拟压入和弹出的过程,创建一个Index记录出栈序列当前元素。 28 | 29 | 1. 遍历入栈序列,将其数据依次入栈 30 | 31 | - 当辅助栈栈顶元素与当前出栈序列元素相同时,辅助栈出栈、出栈索引+1。因为出栈可能联系,所以使用while循环持续判断辅助栈栈顶元素与当前出栈序列元素。 32 | - 当....不相同,继续入栈。 33 | 34 | 2.全部入栈完毕后,若出栈顺序正确,那么辅助栈为空。 35 | 36 | ``` 37 | return stack.length == 0; 38 | ``` 39 | 40 | 41 | 42 | ```js 43 | /** 44 | * @param {number[]} pushed 45 | * @param {number[]} popped 46 | * @return {boolean} 47 | */ 48 | var validateStackSequences = function (pushed, popped) { 49 | if (pushed.length == 0 || popped.length == 0) { 50 | return true; 51 | } 52 | const stack = []; 53 | let index = 0; 54 | for (let i = 0; i < pushed.length; i++) { 55 | stack.push(pushed[i]); 56 | while (stack.length && stack[stack.length - 1] == popped[index]) { 57 | stack.pop(); 58 | index++; 59 | } 60 | } 61 | 62 | return stack.length == 0; 63 | }; 64 | ``` 65 | 66 | 67 | 68 | ![image-20210209145632525](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210209145632525.png) -------------------------------------------------------------------------------- /数据结构与算法/栈和队列/【剑指Offer 09】用两个栈实现队列.md: -------------------------------------------------------------------------------- 1 | # 用两个栈实现队列 2 | 3 | ## 题目 4 | 5 | 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 ) 6 | 7 | **示例 1:** 8 | 9 | ``` 10 | 输入: 11 | ["CQueue","appendTail","deleteHead","deleteHead"] 12 | [[],[3],[],[]] 13 | 输出:[null,null,3,-1] 14 | ``` 15 | 16 | **示例 2:** 17 | 18 | ``` 19 | 输入: 20 | ["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"] 21 | [[],[],[5],[2],[],[]] 22 | 输出:[null,-1,null,null,5,2] 23 | ``` 24 | 25 | **提示:** 26 | 27 | - `1 <= values <= 10000` 28 | - `最多会对 appendTail、deleteHead 进行 10000 次调用` 29 | 30 | ## 思路 31 | 32 | 使用两个栈,当插入元素时,push进stack1;当删除元素时,判断stack2的状态: 33 | 34 | - 若stack2 为空,遍历stack1的值pop出来,将其push进stack2中。 35 | - 若stack2不为空,直接pop出stack2的值。 36 | 37 | **注意,创建两个类使用this.xxx** 38 | 39 | ```js 40 | var CQueue = function () { 41 | this.stack1 = []; 42 | this.stack2 = []; 43 | }; 44 | 45 | /** 46 | * @param {number} value 47 | * @return {void} 48 | */ 49 | CQueue.prototype.appendTail = function (value) { 50 | this.stack1.push(value); 51 | }; 52 | 53 | /** 54 | * @return {number} 55 | */ 56 | CQueue.prototype.deleteHead = function () { 57 | if (this.stack2.length) { 58 | return this.stack2.pop(); 59 | } 60 | //将stack1放入stack2 61 | while (this.stack1.length) { 62 | this.stack2.push(this.stack1.pop()) 63 | } 64 | 65 | return this.stack2.pop() || -1; 66 | }; 67 | 68 | /** 69 | * Your CQueue object will be instantiated and called as such: 70 | * var obj = new CQueue() 71 | * obj.appendTail(value) 72 | * var param_2 = obj.deleteHead() 73 | */ 74 | ``` 75 | 76 | ![image-20210209134233535](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210209134233535.png) -------------------------------------------------------------------------------- /数据结构与算法/栈和队列/栈和队列前言.md: -------------------------------------------------------------------------------- 1 | # 栈和队列前言 2 | 3 | 在数组中,我们可以通过索引随机访问元素,但是在某些情况下,我们可能要限制数据的访问顺序,于是有了两种限制访问顺序的数据结构:栈(先进后出)、队列(先进先出) 4 | 5 | ![](https://i.loli.net/2019/08/18/xqbQD8UEW1cRPFs.jpg) 6 | 7 | [【剑指Offer 09】用两个栈实现队列 题解](https://blog.csdn.net/weixin_43786756/article/details/113769559) 8 | 9 | [【剑指 Offer 30】包含min函数的栈](https://blog.csdn.net/weixin_43786756/article/details/113769958) 10 | 11 | [【剑指 Offer 31】栈的压入、弹出序列](https://blog.csdn.net/weixin_43786756/article/details/113770860) 12 | 13 | [【leetcode 239】滑动窗口最大值](https://blog.csdn.net/weixin_43786756/article/details/113774095) -------------------------------------------------------------------------------- /数据结构与算法/栈和队列/表达式求值.md: -------------------------------------------------------------------------------- 1 | **逆波兰表达式(后缀表达式):**运算符位于操作数。 2 | 3 | 我们最熟悉的一种表达式1+2等都是中缀表示法。对于人们来说,也是最直观的一种求值方式,先算括号里的,然后算乘除,最后算加减,但是,计算机处理中缀表达式却并不方便。 4 | 5 | 所以就需要将一个普通的中序表达式转换为逆波兰表达式。 6 | 7 | #### 题解 8 | 9 | 1、中缀表达式转后缀表达式 10 | 11 | 1. 遇到数字直接输出 12 | 2. 遇到左括号,直接入栈 13 | 3. 遇到右括号,输出栈顶元素,直至栈空或遇到左括号,左括号出栈,自己不入栈。 14 | 4. 遇到运算符:输出优先级>=自己的栈顶元素,直至栈空或遇到左括号,然后自己入栈。 15 | - `+-`:处于优先级底层,` while(stack.length != 0 && stack[stack.length-1] != '(')`输出,最后自己入栈。 16 | - `*/`:比它优先级大的只有`)`,如果遇到`+-`或`(`,自己入栈。 17 | 18 | 2、根据后缀表达式计算 19 | 20 | ​ 从左到右遍历后缀表达式的每个数字和符号,遇到是数字进栈,遇到符号,就将处于栈顶的两个元素出栈进行运算,运算结果再进栈,直到处理到后缀表达式末尾,此时栈内元素为结果。 21 | 22 | ```js 23 | function solve( s ) { 24 | //使用正则匹配出数组 25 | let results = s.matchAll(/[+\-*()]|[0-9]+/g); 26 | console.log(results) 27 | //将数字从字符串转数字 28 | results =Array.from(results).map(item=>{ 29 | if(Number(item[0])){ 30 | return Number(item[0]); 31 | }else{ 32 | return item[0]; 33 | } 34 | }); 35 | //转换成逆波兰表达式 36 | let stack = [],postfix = []; 37 | for(let i =0;i= 容量 9 | if(map.size >= k) { 10 | //按顺序删除最不常用的 11 | map.delete(map.keys().next().value) 12 | //存入当前值 13 | map.set(key, value); 14 | } else { 15 | //缓存数 <容量 16 | //判断该key值是否存在,如果存在,则删除后重新添加 17 | if(map.has(key)) { 18 | map.delete(key) 19 | } 20 | map.set(key, value); 21 | } 22 | //查找 23 | } else if(op === 2) { 24 | //不存在key值,返回-1 25 | if(!map.has(key)) { 26 | res.push(-1); 27 | } else { 28 | //获取key值对应的value,并重新存储。 29 | let value = map.get(key); 30 | res.push(value); 31 | map.delete(key); 32 | map.set(key, value); 33 | } 34 | } 35 | } 36 | return res; 37 | } 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /数据结构与算法/设计LRU缓存结构.md: -------------------------------------------------------------------------------- 1 | ```js 2 | function LRU( operators , k ) { 3 | let res = []; 4 | let map = new Map(); 5 | for(let i = 0; i < operators.length; i++){ 6 | let [op, key, value] = operators[i]; 7 | if(op === 1) { 8 | //缓存数 >= 容量 9 | if(map.size >= k) { 10 | //按顺序删除最不常用的 11 | map.delete(map.keys().next().value) 12 | //存入当前值 13 | map.set(key, value); 14 | } else { 15 | //缓存数 <容量 16 | //判断该key值是否存在,如果存在,则删除后重新添加 17 | if(map.has(key)) { 18 | map.delete(key) 19 | } 20 | map.set(key, value); 21 | } 22 | //查找 23 | } else if(op === 2) { 24 | //不存在key值,返回-1 25 | if(!map.has(key)) { 26 | res.push(-1); 27 | } else { 28 | //获取key值对应的value,并重新存储。 29 | let value = map.get(key); 30 | res.push(value); 31 | map.delete(key); 32 | map.set(key, value); 33 | } 34 | } 35 | } 36 | return res; 37 | } 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/K 个一组翻转链表.md: -------------------------------------------------------------------------------- 1 | ## K 个一组翻转链表 2 | 3 | ```js 4 | var reverseKGroup = function(head, k) { 5 | //创建亚结点 作为第一组的pre结点 6 | const hair = new ListNode(-1); 7 | hair.next = head; 8 | let pre = hair; 9 | 10 | //遍历链表 11 | while(head){ 12 | //尾结点初始为pre 走k步后到达真正的尾结点 13 | let tail = pre; 14 | for(let i =0;i{ 29 | node.next = p; 30 | p = node; 31 | return p; 32 | },null) 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/NC69 链表中倒数最后k个节点.md: -------------------------------------------------------------------------------- 1 | #### NC69 链表中倒数最后k个节点 2 | 3 | ```js 4 | function FindKthToTail( pHead , k ) { 5 | // 两个指针一块走,相差k位 6 | // 如果count没数到k,p2优先为null了 则返回一个空链表 7 | let p1 = pHead,p2=pHead,count=0; 8 | while(p2){ 9 | count++; 10 | p2 = p2.next; 11 | if(count>k){ 12 | p1 = p1.next; 13 | } 14 | } 15 | //如果p2为空出来 count<=k 16 | if(!p2 &&counta.val-b.val); 11 | //结点连接 12 | return arr.reduceRight((p,node)=>{ 13 | node.next = p; 14 | p = node; 15 | return p; 16 | },null) 17 | } 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/NC96 判断一个链表是否为回文结构.md: -------------------------------------------------------------------------------- 1 | #### NC96 **判断一个链表是否为回文结构** 2 | 3 | 方法1 变数组 4 | 5 | ```js 6 | function isPail( head ) { 7 | const arr =[]; 8 | while(head){ 9 | arr.push(head); 10 | head = head.next; 11 | } 12 | let start = 0; 13 | let end = arr.length-1; 14 | while(start<=end){ 15 | if(arr[start].val!==arr[end].val){ 16 | return false; 17 | } 18 | start++; 19 | end--; 20 | ``` 21 | 22 | 方法2 利用栈先进后出 性能更优 23 | 24 | ```js 25 | function isPail( head ) { 26 | // write code here 27 | //使用栈的后入先出 28 | if(!head || !head.next) return true 29 | if(!head || !head.next) return true 30 | let arr = [] 31 | let slow = head,fast = head 32 | while(fast && fast.next) { 33 | arr.push(slow.val) 34 | fast = fast.next.next 35 | slow = slow.next 36 | } 37 | if(fast) { 38 | slow = slow.next 39 | } 40 | while(slow) { 41 | if(slow.val != arr.pop()) return false 42 | slow = slow.next 43 | } 44 | return true 45 | } 46 | 自己不优雅的写法 47 | ``` 48 | 49 | ```js 50 | function isPail( head ) { 51 | if(!head || !head.next) return true; 52 | //计算长度 53 | let len = getListLength(head); 54 | //利用栈 存一半的链表 55 | let halflen = parseInt(len/2); 56 | let count =0; 57 | const stack = []; 58 | while(head){ 59 | count++; 60 | if(count<=halflen){ 61 | stack.push(head.val); 62 | }else{ 63 | //中间位不参与比较 64 | if(len%2!==0){ 65 | head = head.next; 66 | len = len-1; 67 | continue; 68 | } 69 | //比较 70 | if(stack.pop() !== head.val){ 71 | return false; 72 | } 73 | } 74 | head = head.next; 75 | } 76 | return true; 77 | } 78 | 79 | function getListLength(head){ 80 | let len = 0; 81 | while(head){ 82 | len++; 83 | head = head.next; 84 | } 85 | return len; 86 | } 87 | ``` 88 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【1】链表前言.md: -------------------------------------------------------------------------------- 1 | # 链表前言 2 | 3 | ## 链表概述 4 | 5 | 用一组任意存储的单元来存储线性表的数据元素。一个对象存储着本身的值和下一个元素的地址。 6 | 7 | - 需要遍历才能查询到元素,查询慢。 8 | - 插入元素只需断开连接重新赋值,插入快。 9 | 10 | ## 基本应用 11 | 12 | > 主要是对链表基本概念和特性的应用,如果基础概念掌握牢靠,此类问题即可迎刃而解 13 | 14 | - [从尾到头打印链表](http://www.conardli.top/docs/dataStructure/链表/从尾到头打印链表.html) 15 | - [删除链表中的节点](http://www.conardli.top/docs/dataStructure/链表/删除链表中的节点or重复的节点.html#删除链表中的节点) 16 | - [反转链表](http://www.conardli.top/docs/dataStructure/链表/反转链表.html) 17 | - [复杂链表的复制](http://www.conardli.top/docs/dataStructure/链表/复杂链表的复制.html) 18 | 19 | ### 环类题目 20 | 21 | > 环类题目即从判断一个单链表是否存在循环而扩展衍生的问题 22 | 23 | - [环形链表](https://leetcode-cn.com/explore/learn/card/linked-list/194/two-pointer-technique/744/) 24 | - [环形链表 Ⅱ(链表环的入口节点)](http://www.conardli.top/docs/dataStructure/链表/链表中环的入口节点.html) 25 | - [约瑟夫环](http://www.conardli.top/docs/dataStructure/链表/圈圈中最后剩下的数字.html) 26 | 27 | ### 双指针 28 | 29 | > 双指针的思想在链表和数组中的题目都经常会用到,主要是利用两个或多个不同位置的指针,通过速度和方向的变换解决问题。 30 | 31 | - 两个指针从不同位置出发:一个从始端开始,另一个从末端开始; 32 | - 两个指针以不同速度移动:一个指针快一些,另一个指针慢一些。 33 | 34 | 对于单链表,因为我们只能在一个方向上遍历链表,所以第一种情景可能无法工作。然而,第二种情景,也被称为慢指针和快指针技巧,是非常有用的。 35 | 36 | - [两个链表的公共节点](http://www.conardli.top/docs/dataStructure/链表/两个链表的第一个公共节点.html) 37 | - [链表倒数第k个节点](http://www.conardli.top/docs/dataStructure/链表/链表倒数第k个节点.html) 38 | - [相交链表](https://leetcode-cn.com/explore/learn/card/linked-list/194/two-pointer-technique/746/) 39 | 40 | ### 双向链表 41 | 42 | 双链还有一个引用字段,称为`prev`字段。有了这个额外的字段,您就能够知道当前结点的前一个结点。 43 | 44 | - [扁平化多级双向链表](https://leetcode-cn.com/explore/learn/card/linked-list/197/conclusion/764/) 45 | 46 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 47 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【Leetcode 142】环形链表Ⅱ(链表环的入口节点).md: -------------------------------------------------------------------------------- 1 | # 环形链表Ⅱ(链表环的入口节点) 2 | 3 | ## 题目 4 | 5 | 给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。 6 | 7 | ## 思路 8 | 9 | 快慢指针法,声明两个指针 P1 P2 10 | 11 | - 1.判断链表是否有环: P1 P2 从头部出发,P1走两步,P2走一步,如果可以相遇,则环存在 12 | - 2.从环内某个节点开始计数,再回到此节点时得到链表环的长度 length 13 | - 3.P1、P2 回到head节点,让 P1 先走 length 步 ,当P2和P1相遇时即为链表环的起点 14 | 15 | ![image-20210109120936539](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210109120936539.png) 16 | 17 | 关于思路3的理解: 18 | 19 | 这一张图告诉我们,对于快慢指针,当slow和fast相遇时,相遇点在C, 同时推导出 =>2(AB+BC) = AB+BC+CB+BC => AB = CB 20 | 21 | ![Floyd1](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/20200804111327945.png)· 22 | 23 | 第二张图告诉我们,当slow走到入口节点B的时候,fast走到D,也就是说 => 2AB = AB+BC+CD =>AB = BC+CD 24 | 结合上图的结论 AB = CB 则可以推导出 => CB = BC+CD =>CD+DB = BC+CD => DB=BC 也就是为什么 BC,BD两个距离都是Y 25 | 26 | 所以根据 X,Y 距离的设置 结合之前的关系(AB = CB) 推导出 => CD = X-Y,CDB=X 27 | 然后,当slow和fast在C点相遇后,让slow指针的位置不变,也就是指向C,更改fast指针的指向为pHead,两个指针每次都走一步,下一次相遇就是入口节点 28 | 29 | ![floyd2](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210113172620510.png) 30 | 31 | ## 代码 32 | 33 | ```js 34 | function EntryNodeOfLoop(pHead) { 35 | if (!pHead || !pHead.next) { 36 | return null; 37 | } 38 | let P1 = pHead.next; 39 | let P2 = pHead.next.next; 40 | // 1.判断是否有环 41 | while (P1 != P2) { 42 | //p2走到终点,说明没有环 43 | if (P2 === null || P2.next === null) { 44 | return null; 45 | } 46 | P1 = P1.next; 47 | P2 = P2.next.next; 48 | } 49 | // 2.获取环的长度 50 | let temp = P1; 51 | let length = 1; 52 | P1 = P1.next; 53 | while (temp != P1) { 54 | P1 = P1.next; 55 | length++; 56 | } 57 | // 3.找公共节点 58 | P1 = P2 = pHead; 59 | // p2先走length步 60 | while (length-- > 0) { 61 | P2 = P2.next; 62 | } 63 | // p1 p2 一起走直至相遇 64 | while (P1 != P2) { 65 | P1 = P1.next; 66 | P2 = P2.next; 67 | } 68 | return P1; 69 | } 70 | ``` 71 | 72 | 更简便的写法: 73 | ```js 74 | function EntryNodeOfLoop(pHead) { 75 | if (!pHead || !pHead.next) { 76 | return null; 77 | } 78 | let P1 = pHead.next; 79 | let P2 = pHead.next.next; 80 | // 1.判断是否有环 81 | while (P1 != P2) { 82 | //p2走到终点,说明没有环 83 | if (P2 === null || P2.next === null) { 84 | return null; 85 | } 86 | P1 = P1.next; 87 | P2 = P2.next.next; 88 | } 89 | //p1为相遇结点 90 | P2 = pHead; 91 | //一起走 92 | while(P1!==P2){ 93 | P1 = P1.next; 94 | P2 = P2.next; 95 | } 96 | return P1; 97 | } 98 | ``` 99 | 100 | # 更多资料 101 | 102 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 103 | 104 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 105 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 106 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【Leetcode 237】删除链表中的节点.md: -------------------------------------------------------------------------------- 1 | # 删除链表中的节点 2 | 3 | 给定单链表的头指针和要删除的指针节点,在O(1)时间内删除该节点。 4 | 5 | - 1.删除的节点不是尾部节点 - 将next节点覆盖当前节点 6 | - 2.删除的节点是尾部节点且等于头节点,即只有一个节点 - 将头节点置为null 7 | - 3.删除的节点是尾节点且前面还有节点 - 遍历到末尾的前一个节点删除 8 | 9 | 只有第三种情况时间复杂度是O(n),且这种情况只会出现1/n次,所以算法时间复杂度是O(1) 10 | 11 | ```js 12 | var deleteNode = function (head, node) { 13 | if (node.next) { 14 | node.val = node.next.val; 15 | node.next = node.next.next; 16 | } else if (node === head) { 17 | node = null; 18 | head = null; 19 | } else { 20 | node = head; 21 | while (node.next.next) { 22 | node = node.next; 23 | } 24 | node.next = null; 25 | node = null; 26 | } 27 | return node; 28 | }; 29 | ``` 30 | 31 | ## 删除链表中重复的节点 32 | 33 | #### 方法1.存储链表中元素出现的次数 34 | 35 | - 1.用一个map存储每个节点出现的次数 36 | - 2.删除出现次数大于1的节点 37 | 38 | 此方法删除节点时可以使用上面总结的办法。 39 | 40 | 时间复杂度:O(n) 41 | 42 | 空间复杂度:O(n) 43 | 44 | ```js 45 | function deleteDuplication(pHead) { 46 | const map = {}; 47 | if (pHead && pHead.next) { 48 | let current = pHead; 49 | // 计数 50 | while (current) { 51 | //通过键获取值 52 | const val = map[current.val]; 53 | //如果不为0则加一;否则等于1 54 | map[current.val] = val ? val + 1 : 1; 55 | //取下一个节点 56 | current = current.next; 57 | } 58 | //链表处理 59 | current = pHead; 60 | while (current) { 61 | //获取当前节点的值 62 | const val = map[current.val]; 63 | if (val > 1) { 64 | // 删除节点 65 | console.log(val); 66 | if (current.next) { 67 | current.val = current.next.val; 68 | current.next = current.next.next; 69 | } else if (current === pHead) { 70 | current = null; 71 | pHead = null; 72 | } else { 73 | current = pHead; 74 | while (current.next.next) { 75 | current = current.next; 76 | } 77 | current.next = null; 78 | current = null; 79 | } 80 | 81 | } else { 82 | current = current.next; 83 | } 84 | } 85 | } 86 | return pHead; 87 | } 88 | ``` 89 | 90 | #### 方法2:重新比较连接数组 91 | 92 | 链表是排好顺序的,所以重复元素都会相邻出现 递归链表: 93 | 94 | - 1.当前节点或当前节点的next为空,返回该节点 95 | - 2.当前节点是重复节点:找到后面第一个不重复的节点 96 | - 3.当前节点不重复:将当前的节点的next赋值为下一个不重复的节点 97 | 98 | ```js 99 | function deleteDuplication(pHead) { 100 | if (!pHead || !pHead.next) { 101 | return pHead; 102 | } else if (pHead.val === pHead.next.val) { 103 | let tempNode = pHead.next; 104 | while (tempNode && pHead.val === tempNode.val) { 105 | tempNode = tempNode.next; 106 | } 107 | return deleteDuplication(tempNode); 108 | } else { 109 | pHead.next = deleteDuplication(pHead.next); 110 | return pHead; 111 | } 112 | } 113 | ``` 114 | 115 | 递归分析: 116 | 117 | 1. 定义递归函数的功能:删除重复节点 118 | 119 | 2. 结束条件:节点为空和next为空 120 | 121 | 3. 等价表达式,缩小参数范围。 122 | 123 | pHead.next = deleteDuplication(pHead.next); 124 | 125 | return pHead 126 | 127 | 时间复杂度:O(n) 128 | 129 | 空间复杂度:O(1) 130 | 131 | # 更多资料 132 | 133 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 134 | 135 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 136 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【Leetcode 430】扁平化多级双向链表.md: -------------------------------------------------------------------------------- 1 | # 扁平化多级双向链表 2 | 3 | ## 题目 4 | 5 | 多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。 6 | 7 | 给你位于列表第一级的头节点,请你扁平化列表,使所有结点出现在单级双链表中。 8 | 9 | **示例 1:** 10 | 11 | ``` 12 | 输入:head =[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12] 13 | 输出:[1,2,3,7,8,11,12,9,10,4,5,6] 14 | ``` 15 | 16 | 解释: 17 | 输入的多级列表如下图所示: 18 | 19 | ![image-20210110144738247](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210110144738247.png) 20 | 21 | 扁平化后的链表如下图: 22 | 23 | ![img](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/multilevellinkedlistflattened.png) 24 | 25 | **如何表示测试用例中的多级链表?** 26 | 27 | 以 **示例 1** 为例: 28 | 29 | ```js 30 | 1---2---3---4---5---6--NULL 31 | | 32 | 7---8---9---10--NULL 33 | | 34 | 11--12--NULL 35 | ``` 36 | 37 | 序列化其中的每一级之后: 38 | 39 | ```js 40 | [1,2,3,4,5,6,null] 41 | [7,8,9,10,null] 42 | [11,12,null] 43 | ``` 44 | 45 | 为了将每一级都序列化到一起,我们需要每一级中添加值为 null 的元素,以表示没有节点连接到上一级的上级节点。 46 | 47 | ```js 48 | [1,2,3,4,5,6,null] 49 | [null,null,7,8,9,10,null] 50 | [null,11,12,null] 51 | ``` 52 | 53 | 合并所有序列化结果,并去除末尾的 null 。 54 | 55 | ```js 56 | [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12] 57 | ``` 58 | 59 | ## 思路 60 | 61 | - 遇到child就递归, 把next和child都传递过去, 因为指针会遍历到后面, 正好可以拼接next和child 62 | - 递归返回之后要清掉child, 并且处理好prev指针 63 | 64 | ## 代码 65 | 66 | ```js 67 | // 优化前 68 | const flatten = (head, next) => { 69 | let curr = head 70 | while (curr && (curr.next || curr.child)) { 71 | if (curr.child) { 72 | curr.next = flatten(curr.child, curr.next) 73 | curr.child = null 74 | curr.next.prev = curr 75 | } 76 | curr = curr.next 77 | } 78 | if (next) { 79 | next.prev = curr 80 | curr.next = next 81 | } 82 | return head 83 | } 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【Leetcode 92】反转链表.md: -------------------------------------------------------------------------------- 1 | # 反转链表 2 | 3 | ## 题目 4 | 5 | 输入一个链表,反转链表后,输出新链表的表头。 6 | 7 | ## 思路 8 | 9 | #### 1、递归 10 | 11 | 状态变量,递归出口,递归列表 12 | 13 | 状态变量:头结点 14 | 15 | 递归出口:head.next == null 或空表 16 | 17 | 递归列表:当前反转 = 反转head.next 改变1,2节点指向。 18 | 19 | 20 | 21 | 递归三要素: 22 | 23 | - 定义递归函数的功能:反转链表 24 | - 寻找结束条件:head.next == null 或空表 25 | - 寻找等价关系,不断缩小参数范围 26 | 27 | 以下面例子为例 28 | 29 | ![image-20210109090749481](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210109090749481.png) 30 | 31 | reverseList(head.next)可以得到下图,这是我们只需让head与head.next指针交换一下即可。 32 | 33 | ![image-20210109090757782](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210109090757782.png) 34 | 35 | 所以我们得到等价条件: 36 | 37 | **reverseList(head)** 等价于 **reverseList(head.next)+ 改变一下1,2两个节点的指向** 。 38 | 39 | 写出递归式。 40 | 41 | #### 2、非递归 42 | 43 | 以链表的头部节点为基准节点 44 | 45 | 将基准节点的下一个节点挪到头部作为头节点 46 | 47 | 当基准节点的`next`为`null`,则其已经成为最后一个节点,链表已经反转完成 48 | 49 | ### 代码 50 | 51 | #### 代码1 52 | 53 | ```js 54 | //用递归的方法反转链表 55 | var reverseList = function (head) { 56 | // 1.递归结束条件 57 | if (head == null || head.next == null) { 58 | return head; 59 | } 60 | // 递归反转子链表 61 | var newList = reverseList2(head.next); 62 | // 改变 1,2节点的指向。 63 | // 通过 head.next获取节点2 64 | let t1 = head.next; 65 | // 让 2 的 next 指向 2 66 | t1.next = head; 67 | // 1 的 next 指向 null. 68 | head.next = null; 69 | // 把调整之后的链表返回。 70 | return newList; 71 | } 72 | ``` 73 | 74 | #### 代码2 75 | 76 | ```js 77 | var reverseList = function(head) { 78 | // 非递归反转 79 | let thead = head; 80 | let current = null; 81 | while(head && head.next){ 82 | //遍历head 获取每一个结点 83 | current = head.next 84 | head.next = current.next 85 | //交换节点指向和头结点 86 | current.next = thead; 87 | thead = current 88 | } 89 | return thead; 90 | }; 91 | ``` 92 | 93 | ```js 94 | function ReverseList(pHead) 95 | { 96 | //非递归反转 遍历链表 当前节点指向反转结点的头节点 97 | if(!pHead ||!pHead.next) return pHead; 98 | let reverseHead = pHead; 99 | let current = null; 100 | while(pHead && pHead.next){ 101 | current = pHead.next; 102 | pHead.next = current.next; 103 | //节点交换 104 | current.next = reverseHead; 105 | reverseHead = current; 106 | } 107 | return reverseHead; 108 | } 109 | ``` 110 | 111 | 进阶题目:[K个一组翻转链表](https://leetcode-cn.com/problems/reverse-nodes-in-k-group/) 112 | 113 | # 更多资料 114 | 115 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 116 | 117 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 118 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 119 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【剑指Offer 06】从尾到头打印链表.md: -------------------------------------------------------------------------------- 1 | # 从尾到头打印链表 2 | 3 | ## 题目 4 | 5 | 输入一个链表,按链表值从尾到头的顺序返回一个`ArrayList`。 6 | 7 | ## 分析 8 | 9 | - 链表的数据结构:`val`属性存储当前值,`next`属性存储下一个节点的引用。 10 | 11 | - 遍历链表即不断寻找当前节点的`next`节点,直至`next`节点为`null`。 12 | - 从尾到头顺序,使用一个队列来存储打印结果,每次从队列头部插入。(我觉得也可以使用栈来存储,用栈存储还需要一个一个pop出来存储,但麻烦) 13 | 14 | ## 代码 15 | 16 | ```js 17 | function printListFromTailToHead(head) { 18 | const array = []; 19 | while (head) { 20 | array.unshift(head.val); 21 | head = head.next; 22 | } 23 | return array; 24 | } 25 | ``` 26 | 27 | # 更多资料 28 | 29 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 30 | 31 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 32 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【剑指Offer 141】 环形链表.md: -------------------------------------------------------------------------------- 1 | # 链表环类题目 2 | 3 | > 环类题目即从判断一个单链表是否存在循环而扩展衍生的问题 4 | 5 | ## 环形链表 6 | 7 | 给定一个链表,判断链表中是否有环。 8 | 9 | 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 10 | 11 | 如果链表中存在环,则返回 true 。 否则,返回 false 。 12 | 13 | ## 题解 14 | 15 | 慢指针一次走一步,快指针一次走两步,快指针追上慢指针,说明有环 16 | 17 | 举例:400m 操场,小明一次跑 2 米,小梁一次跑 1 米,小梁跑一圈(400m)后小明(800m)追上小梁 18 | 19 | ```js 20 | const hasCycle = (head) => { 21 | 22 | // 至少 2 个节点才能构成一个环 23 | if (!head || !head.next) { 24 | return false; 25 | } 26 | 27 | // 设置快慢指针 28 | let slow = head; 29 | let fast = head.next; 30 | 31 | // 如果快指针一直没有追上慢指针 32 | while (slow !== fast) { 33 | // 如果没有环,则快指针会抵达终点 34 | if (!fast || !fast.next) { 35 | return false; 36 | } 37 | slow = slow.next; 38 | fast = fast.next.next; 39 | } 40 | 41 | // 如果有环,那么快指针会追上慢指针 42 | return true; 43 | }; 44 | 45 | ``` 46 | 47 | ![image-20210113162858240](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210113162858240.png) 48 | 49 | # 更多资料 50 | 51 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 52 | 53 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 54 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【剑指Offer 22】链表中倒数第k个节点.md: -------------------------------------------------------------------------------- 1 | # 链表倒数第k个节点 2 | 3 | Offer22 4 | 5 | ## 题目 6 | 7 | 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。 8 | 9 | ## 思路 10 | 11 | 简单思路: 循环到链表末尾找到 length 在找到length-k节点 需要循环两次。 12 | 13 | 优化: 14 | 15 | 设定两个节点,间距相差k个节点,当前面的节点到达终点,取后面的节点。 16 | 17 | 前面的节点到达k后,后面的节点才出发。 18 | 19 | 特判: 需要考虑head为null,k为0,k大于链表长度的情况。 20 | 21 | ## 代码 22 | 23 | ```js 24 | var getKthFromEnd = function (head, k) { 25 | //如果链表为空 或 k=0 26 | if (!head || !k) return null; 27 | var front = head; 28 | var behind = head; 29 | //记录链表长度 30 | var index = 1; 31 | while (front.next) { 32 | index++; 33 | front = front.next; 34 | //如果前面指针走过长度>k 后面指针开始移动 35 | if (index > k) { 36 | behind = behind.next; 37 | } 38 | } 39 | return (index >= k) && behind; 40 | 41 | }; 42 | 43 | ``` 44 | 45 | # 更多资料 46 | 47 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 48 | 49 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 50 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【剑指Offer 35】复杂链表的复制.md: -------------------------------------------------------------------------------- 1 | # 复杂链表的复制 2 | 3 | ## 题目 4 | 5 | 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。 6 | 7 | ## 思路 8 | 9 | 拆分成三步: 10 | 11 | 1.复制每一个节点,使得复制后的节点都在当前节点的下一个节点 12 | 13 | 2.原生链表的节点的指向任意节点,使复制的节点也都指向某一任意节点 14 | 15 | 3.重新连接节点,把原生节点重新连接起来,把克隆后的节点连接起来 16 | 17 | ## 代码 18 | 19 | ```js 20 | function Clone(pHead) { 21 | if (pHead === null) { 22 | return null; 23 | } 24 | //1.克隆节点,插在每个节点后面 25 | let current = pHead; 26 | while (current) { 27 | //复制节点 28 | var cloneNode = { 29 | label: current.label, 30 | next: current.next 31 | }; 32 | current.next = cloneNode; 33 | current = cloneNode.next; 34 | } 35 | 36 | //2.克隆Random指针 37 | current = pHead; 38 | while (current) { 39 | //当前节点的下一个节点为克隆节点 40 | var cloneNode = current.next; 41 | //复制Ramdom指针 42 | if (current.random) { 43 | cloneNode.random = current.random.next; 44 | } else { 45 | cloneNode.random = null; 46 | } 47 | //移动到下一个原生节点 48 | current = cloneNode.next; 49 | } 50 | 51 | //3.将克隆节点连接起来,并还原原生链表 52 | var cloneNode = pHead.next; 53 | var cloneHead = cloneNode; 54 | //当前节点 55 | current = pHead; 56 | while (current) { 57 | //还原原生链表 58 | current.next = cloneNode.next; 59 | //移动到下一个原生节点 60 | current = cloneNode.next; 61 | //如果下一个存在 62 | if (current) { 63 | //连接克隆节点 64 | cloneNode.next = current.next; 65 | //克隆节点移动 66 | cloneNode = current.next; 67 | } else { 68 | //到达末尾 69 | cloneNode.next = null; 70 | } 71 | } 72 | //返回克隆链表 73 | return cloneHead; 74 | } 75 | ``` 76 | 77 | # 更多资料 78 | 79 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 80 | 81 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 82 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【剑指Offer 52】两个链表的第一个公共节点.md: -------------------------------------------------------------------------------- 1 | # 两个链表的第一个公共节点 2 | 3 | 剑指Offer52 4 | 5 | ## 题目 6 | 7 | 输入两个链表,找出它们的第一个公共结点。 8 | 9 | **程序尽量满足O(n)时间复杂度,且仅用O(1)内存** 10 | 11 | 示例 1: 12 | 13 | ![img](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/160_example_1.png) 14 | 15 | ``` 16 | 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 17 | 输出:Reference of the node with value = 8 18 | 输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 19 | ``` 20 | 21 | ## 思路 22 | 23 | #### 快慢指针法 24 | 25 | - 1.先找到两个链表的长度`length1`、`length2` 26 | - 2.让长一点的链表先走`length2-length1`步,让长链表和短链表起点相同 27 | - 3.两个链表一起前进,比较获得第一个相等的节点 28 | - 时间复杂度`O(length1+length2)` 空间复杂度`O(0)` 29 | 30 | ![image-20210110134735970](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210110134735970.png) 31 | 32 | 33 | ```js 34 | var getIntersectionNode = function (headA, headB) { 35 | if (!headA || !headB) return null; 36 | var lengthA = getLength(headA); 37 | var lengthB = getLength(headB); 38 | var long, short, distance; 39 | if (lengthA > lengthB) { 40 | long = headA; 41 | short = headB; 42 | distance = lengthA - lengthB; 43 | } else { 44 | long = headB; 45 | short = headA; 46 | distance = lengthB - lengthA; 47 | } 48 | 49 | // 长的移动位置 50 | while (distance--) { 51 | long = long.next; 52 | } 53 | 54 | //一起走 55 | while (long && short) { 56 | if (long === short) { 57 | return long; 58 | } 59 | long = long.next; 60 | short = short.next; 61 | } 62 | return null; 63 | }; 64 | 65 | function getLength(head) { 66 | let count = 0; 67 | let current = head; 68 | while (current) { 69 | count++; 70 | current = current.next; 71 | } 72 | return count; 73 | } 74 | ``` 75 | 76 | ![image-20210114140418738](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210114140418738.png) 77 | 78 | #### 哈希集合法 79 | 80 | ```js 81 | var getIntersectionNode = function(headA, headB) { 82 | const set = new Set(); 83 | let temp = headA; 84 | while(temp){ 85 | set.add(temp); 86 | temp = temp.next; 87 | } 88 | temp = headB; 89 | while(temp){ 90 | if(set.has(temp)){ 91 | return temp; 92 | } 93 | temp = temp.next; 94 | } 95 | return null; 96 | }; 97 | ``` 98 | 99 | # 更多资料 100 | 101 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 102 | 103 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 104 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) 105 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/【剑指Offer 62】圆圈中最后剩下的数字(约瑟夫环).md: -------------------------------------------------------------------------------- 1 | # 约瑟夫环 2 | 3 | ## 题目 4 | 5 | `0,1,...,n-1`这`n`个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第`m`个数字。求出这个圆圈里剩下的最后一个数字。 6 | 7 | 其实这就是著名的约瑟夫环问题 8 | 9 | ## 思路 10 | 11 | #### **解法1:用链表模拟环** 12 | 13 | - 用链表模拟一个环 14 | - 模拟游戏场景 15 | - 记录头节点的前一个节点`current`,以保证我们找到的要删除的节点是`current.next` 16 | - 每次循环m次找到目标节点删除,直到链表只剩下一个节点 17 | - 时间复杂度`O(m*n)` 空间复杂度`O(n)` 18 | 19 | #### **解法2:用数组模拟** 20 | 21 | - 每次计算下标,需要考虑末尾条件 22 | 23 | #### **解法3:数学推导** 24 | 25 | - `f(n) = (f(n-1)+m)%n` 即 `f(n,m) = (f(n-1,m)+m)%n` 26 | - 使用递归求解 边界条件为 `n=1` 27 | 28 | #### **解法4** 29 | 30 | 大体思路: 31 | 32 | n个人编号0,1,2,...,n-1,每数m次删掉一个人 33 | 假设有函数f(n)表示n个人最终剩下人的编号 34 | n个人删掉1个人后可以看做n-1的状态,不过有自己的编号。 35 | n个人删掉的第一个人的编号是(m-1)%n,那么n个人时删掉第一个人的后面那个人(m-1+1)%n一定是n-1个人时候编号为0的那个人,即n个人时的编号m%n(这个编号是对于n个人来考虑的),n-1个人时编号为i的人就是n个人时(m+i)%n 36 | 所以f(n)=(m+f(n-1))%n 37 | f(1)=0,因为1个人时只有一个编号0。 38 | 因此可以将人数从2反推到n。 39 | 40 | 41 | 42 | 时间复杂度 `1>2>3>4` 43 | 44 | 易理解程度 `1>2>3>4` 45 | 46 | 47 | 48 | ## 代码 49 | 50 | **解法1leetcode上超时!** 51 | 52 | ```js 53 | // 解法1 54 | function LastRemaining_Solution(n, m) { 55 | //如果数量、次数小于1 56 | if (n < 1 || m < 1) { 57 | return -1; 58 | } 59 | //头指针指向第一个数字 60 | const head = { val: 0 } 61 | //模拟环 62 | let current = head; 63 | for (let i = 1; i < n; i++) { 64 | current.next = { val: i } 65 | current = current.next; 66 | } 67 | //尾部指向头节点 68 | current.next = head; 69 | //当环中的数量>1时进行循环 70 | while (current.next != current) { 71 | //循环找删除的节点 72 | for (let i = 0; i < m - 1; i++) { 73 | current = current.next; 74 | } 75 | //跨过删除节点 76 | current.next = current.next.next; 77 | } 78 | return current.val; 79 | } 80 | ``` 81 | 82 | ```js 83 | // 解法2 84 | function LastRemaining_Solution(n, m) { 85 | if (n < 1 || m < 1) { 86 | return -1; 87 | } 88 | const array = []; 89 | let index = 0; 90 | for (let i = 0; i < n; i++) { 91 | array[i] = i; 92 | } 93 | //长度>1时循环 94 | while (array.length > 1) { 95 | //计算删除节点的坐标 96 | index = (index + m) % array.length - 1; 97 | if (index >= 0) { 98 | //从Index坐标开始删除一个元素 99 | array.splice(index, 1); 100 | } else { 101 | //如果index<0,删除末尾元素 102 | array.splice(array.length - 1, 1); 103 | //指向第一个数字 104 | index = 0; 105 | } 106 | } 107 | return array[0]; 108 | } 109 | ``` 110 | 111 | ``` js 112 | // 解法3 113 | function LastRemaining_Solution(n, m) { 114 | if (n < 1 || m < 1) { 115 | return -1; 116 | } else { 117 | return joseoh(n, m); 118 | } 119 | 120 | } 121 | 122 | function joseoh(n, m) { 123 | //递归条件 124 | if (n === 1) { 125 | return 0; 126 | } 127 | //等价表达式 128 | return (joseoh(n - 1, m) + m) % n; 129 | } 130 | ``` 131 | 132 | ![image-20210113172656947](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210113172656947.png) 133 | 134 | ```js 135 | //解法4 136 | var lastRemaining = function (n, m) { 137 | let ans = 0; 138 | for (let i = 2; i <= n; i++) { 139 | ans = (ans + m) % i; 140 | } 141 | return ans; 142 | }; 143 | ``` 144 | 145 | ![image-20210113172925825](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210113172925825.png) 146 | 147 | # 更多资料 148 | 149 | 整理不易,若对您有帮助,请给个「关注+点赞」,您的支持是我更新的动力 👇 150 | 151 | 📖数据结构专栏:[剑指 Offer 题解 + JS 代码](https://blog.csdn.net/weixin_43786756/category_10716516.html) 152 | 🐱Github笔记 :[ruoruochen GitHub](https://github.com/ruoruochen/front-end-note) -------------------------------------------------------------------------------- /数据结构与算法/链表篇/两个链表生成相加链表.md: -------------------------------------------------------------------------------- 1 | #### NC40 两个链表生成相加链表 2 | 3 | ```js 4 | function addInList( head1 , head2 ) { 5 | // reverse后 相加 6 | let reverse1 = reverseList(head1); 7 | let reverse2 = reverseList(head2); 8 | let add = 0; 9 | //新链表 10 | let preHead = new ListNode(-1); 11 | let tail = preHead; 12 | while(reverse1 || reverse2){ 13 | let sum = add; 14 | if(reverse1){ 15 | sum += reverse1.val; 16 | reverse1 = reverse1.next; 17 | } 18 | if(reverse2){ 19 | sum += reverse2.val; 20 | reverse2 = reverse2.next; 21 | } 22 | //创建结点 23 | tail.next = new ListNode(sum%10); 24 | add = parseInt(sum/10); 25 | tail = tail.next; 26 | } 27 | //如果add还有 28 | if(add){ 29 | tail.next = new ListNode(add); 30 | } 31 | return reverseList(preHead.next); 32 | } 33 | 34 | function reverseList(head){ 35 | //反转数组 36 | let phead = head; 37 | let cur = null; 38 | while(head && head.next){ 39 | //拿到每一个结点 40 | cur = head.next; 41 | head.next = cur.next; 42 | //改变指向 43 | cur.next = phead; 44 | phead = cur; 45 | } 46 | return phead; 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/二叉树中的最大路径和.md: -------------------------------------------------------------------------------- 1 | ## 二叉树中的最大路径和 2 | 3 | 有点类似贪心算法,如果利益点<0则舍弃 4 | 5 | ```js 6 | var maxPathSum = function(root) { 7 | // maxSum 8 | //递归状态变量 root 9 | //递归出口 !root return; 10 | //当前路径和innermax = root.val + left + right innermax > maxSum maxSum替换 11 | //对外输出最大路径和 = root.val + Math.max(left,right),如果<0 则从0重新开始。 12 | let maxSum = -Infinity; 13 | let dfs = (root)=>{ 14 | if(!root) return 0 ; 15 | const left = dfs(root.left); 16 | const right = dfs(root.right); 17 | const innerMax = root.val + left + right; 18 | maxSum = Math.max(maxSum,innerMax) 19 | const outputMax = root.val + Math.max(left,right) 20 | return outputMax<0?0:outputMax; 21 | } 22 | dfs(root); 23 | return maxSum; 24 | }; 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/二叉树根节点到叶子节点的所有路径和.md: -------------------------------------------------------------------------------- 1 | ## **二叉树根节点到叶子节点的所有路径和** 2 | 3 | ```js 4 | function sumNumbers( root ) { 5 | //递归变量 root sum 6 | //出口 !root return.0 ; 叶子结点 sum*10 +root.val 7 | //列表 左+右 8 | let dfs = (root,sum)=>{ 9 | if(!root) return 0; 10 | if(!root.left && !root.right){ 11 | return sum*10 +root.val; 12 | } 13 | return dfs(root.left,sum*10+root.val) + dfs(root.right,sum*10+root.val); 14 | } 15 | return dfs(root,0); 16 | 17 | } 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/删除有序链表中重复的元素-I.md: -------------------------------------------------------------------------------- 1 | #### NC25 删除有序链表中重复的元素-I 2 | 3 | 解法1: 4 | ```js 5 | function deleteDuplicates( head ) { 6 | //去重 先放入数组中去重 再重新拼接 7 | const arr = []; 8 | while(head){ 9 | arr.push(head); 10 | head = head.next; 11 | } 12 | //数组去重 set map 13 | const res = []; 14 | const set = new Set(); 15 | for(let i =0;i{ 23 | node.next = p; 24 | p = node; 25 | return p; 26 | },null) 27 | } 28 | ``` 29 | 30 | 解法2: 31 | ```js 32 | function deleteDuplicates( head ) { 33 | //双指针移动 34 | if(!head) return null; 35 | let cur = head,next = head.next; 36 | while(next){ 37 | //比较 38 | if(cur.val === next.val){ 39 | //next移动 40 | cur.next = next.next; 41 | next = cur.next; 42 | }else{ 43 | //不相等 一起移动 44 | cur = cur.next; 45 | next = cur && cur.next; 46 | } 47 | } 48 | return head; 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/删除链表的倒数第 N 个结点.md: -------------------------------------------------------------------------------- 1 | #### 删除链表的倒数第 N 个结点 2 | 3 | ##### 笨方法 4 | 5 | 1、找到倒数第N个结点为正数第几个结点。 6 | 7 | 2、找到删除节点的前一个结点、删除节点,后一个节点。(需要进行边界判断,如果删除的为第一个结点,则不存在前一个结点) 8 | 9 | 3、前一个结点.next指向后一个节点。 10 | 11 | 需要走两趟 12 | 13 | ```js 14 | var removeNthFromEnd = function(head, n) { 15 | //找到倒数第n个节点是整数第几个 16 | let index =0; 17 | let count =1; 18 | let p1 = head,p2=head; 19 | while(p2.next){ 20 | count++; 21 | p2 = p2.next; 22 | if(count > n){ 23 | p1=p1.next; 24 | index++; 25 | } 26 | } 27 | let pre,last,current = head; 28 | count = 0; 29 | if(index == 0){ 30 | return head.next; 31 | }else{ 32 | while(current.next){ 33 | 34 | let temp = current; 35 | 36 | if(count == index-1){ 37 | pre = temp; 38 | }else if (count == index+1){ 39 | last = temp; 40 | break; 41 | } 42 | count++; 43 | current = current.next; 44 | } 45 | pre.next = p1.next; 46 | p1.next = null; 47 | return head; 48 | } 49 | }; 50 | ``` 51 | 52 | ##### 快慢指针法 53 | 54 | 亚结点的作用是什么? 55 | 去特殊化,我们删除任意一个结点,需要获取到他前面的结点,除了第一个结点没有前面的结点,所以我们需要创建一个亚结点去特殊化。 56 | 57 | ```js 58 | var removeNthFromEnd = function(head, n) { 59 | //创建哑结点 60 | let preHead = new ListNode('-'); 61 | preHead.next = head; 62 | //快慢指针 63 | let count =0; 64 | let p1 = preHead,p2=preHead; 65 | //删除节点的前一个节点 66 | let pre = null; 67 | while(p2){ 68 | p2 = p2.next; 69 | //如果p2不为空且中间隔了n,p1移动 70 | if(p2&&count >= n){ 71 | p1=p1.next; 72 | pre = p1; 73 | } 74 | count++; 75 | } 76 | 77 | //如果删除节点前一节点为空,说明删除的是第一个结点 78 | if(!pre) head = head.next; 79 | else{ 80 | pre.next = pre.next.next; 81 | } 82 | return head; 83 | }; 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/判断t1树中是否有与t2树拓扑结构完全相同的子树.md: -------------------------------------------------------------------------------- 1 | #### **判断t1树中是否有与t2树拓扑结构完全相同的子树** 2 | 3 | t1包含t2的情况:t1 == t2 t2在t1的左子树 t2在t1的右子树 4 | 5 | 如何树判断是否相等 root.val相等 左子树=左子树 右子树=右子树 6 | 7 | 递归: 8 | 9 | 1、状态变量 两棵树 10 | 2、出口 !root1 return false 11 | 3、列表 return isContains(roo1.left,root2) || isContains(roo1.right,root2) || isSameTree(root1,root2); 12 | 13 | ```js 14 | function isContains( root1 , root2 ) { 15 | //t1包含t2的情况:t1 == t2 t2在t1的左子树 t2在t1的右子树 16 | //如何树判断是否相等 root.val相等 左子树=左子树 右子树=右子树 17 | //递归判断 状态变量 两棵树 18 | //出口 !root1 return false 19 | //列表 return isContains(roo1.left,root2) || isContains(roo1.right,root2) || isSameTree(root1,root2); 20 | if(!root1) return false; 21 | return isContains(root1.left,root2) || isContains(root1.right,root2) || isSameTree(root1,root2); 22 | } 23 | 24 | function isSameTree(root1,root2){ 25 | if(!root1&&!root2) return true; 26 | if(!root1 || !root2 || root1.val !== root2.val) return false; 27 | return isSameTree(root1.left,root2.left) && isSameTree(root1.right,root2.right); 28 | } 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/判断该二叉树是否为搜索二叉树和完全二叉树。.md: -------------------------------------------------------------------------------- 1 | #### 判断该二叉树是否为搜索二叉树和完全二叉树 2 | 3 | ```js 4 | function judgeIt( root ) { 5 | var IsSearchTree = function(root,min,max){ 6 | if(!root) return true; 7 | if(root.val max) return false; 8 | return IsSearchTree(root.left,min,root.val) && IsSearchTree(root.right,root.val,max); 9 | } 10 | var IsFullTree = function(root){ 11 | if(!root){ 12 | return true; 13 | } 14 | const quene=[root]; 15 | let [leaf,left,right]=[false,false,false]; 16 | while(quene.length){ 17 | const head=quene.shift(); 18 | left=head.left; 19 | right=head.right; 20 | if((leaf&&(left||right))||(!left&&right)){ 21 | return false; 22 | } 23 | if(left){ 24 | quene.push(left); 25 | } 26 | if(right){ 27 | quene.push(right); 28 | } 29 | else{ 30 | leaf=true; 31 | } 32 | } 33 | return true; 34 | } 35 | let res = []; 36 | var res1= IsSearchTree(root,-Infinity, Infinity); 37 | var res2 = IsFullTree(root); 38 | res.push(res1); 39 | res.push(res2); 40 | return res; 41 | } 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/反转链表2: -------------------------------------------------------------------------------- 1 | #### 反转链表2 2 | 3 | ```js 4 | var reverseBetween = function(head, left, right) { 5 | const preHead = new ListNode(-1); 6 | preHead.next = head; 7 | let pre = preHead; 8 | // 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点 9 | for (let i = 0; i < left - 1; i++) { 10 | pre = pre.next; 11 | } 12 | 13 | // 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点 14 | let rightNode = pre; 15 | for (let i = 0; i < right - left + 1; i++) { 16 | rightNode = rightNode.next; 17 | } 18 | const nex = rightNode.next; 19 | //反转 20 | [phead,tail]=reverse(pre.next,rightNode); 21 | //拼接 22 | pre.next = phead; 23 | tail.next = nex; 24 | return preHead.next; 25 | }; 26 | 27 | var reverse = function(head,tail){ 28 | let nextLink = tail.next; 29 | let p = head; 30 | while(tail!==nextLink){ 31 | const nex = p.next; 32 | p.next = nextLink; 33 | nextLink = p; 34 | p = nex; 35 | } 36 | return [tail,head]; 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/反转链表2.md: -------------------------------------------------------------------------------- 1 | #### 反转链表2 2 | 3 | ```js 4 | var reverseBetween = function(head, left, right) { 5 | const preHead = new ListNode(-1); 6 | preHead.next = head; 7 | let pre = preHead; 8 | // 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点 9 | for (let i = 0; i < left - 1; i++) { 10 | pre = pre.next; 11 | } 12 | 13 | // 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点 14 | let rightNode = pre; 15 | for (let i = 0; i < right - left + 1; i++) { 16 | rightNode = rightNode.next; 17 | } 18 | const nex = rightNode.next; 19 | //反转 20 | [phead,tail]=reverse(pre.next,rightNode); 21 | //拼接 22 | pre.next = phead; 23 | tail.next = nex; 24 | return preHead.next; 25 | }; 26 | 27 | var reverse = function(head,tail){ 28 | let nextLink = tail.next; 29 | let p = head; 30 | while(tail!==nextLink){ 31 | const nex = p.next; 32 | p.next = nextLink; 33 | nextLink = p; 34 | p = nex; 35 | } 36 | return [tail,head]; 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/合并K个升序链表.md: -------------------------------------------------------------------------------- 1 | #### 合并K个升序链表 2 | 3 | ```js 4 | var mergeKLists = function(lists) { 5 | //把所有的结点放到一个数组 6 | const arr = lists.reduce((array,item)=>{ 7 | while(item){ 8 | array.push(item); 9 | item = item.next; 10 | } 11 | return array 12 | },[]) 13 | //排序 14 | arr.sort((a,b)=>a.val-b.val) 15 | //从右往左拼接链表 16 | return arr.reduceRight((p,node)=>{ 17 | node.next = p; 18 | p = node; 19 | return p; 20 | },null) 21 | }; 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/合并两个有序链表.md: -------------------------------------------------------------------------------- 1 | #### 合并两个有序链表 2 | 3 | ```js 4 | var mergeTwoLists = function(l1, l2) { 5 | //创建哑结点,并记录头结点 6 | let prehead = new ListNode(-1); 7 | //当前链表的尾结点 8 | let pre = prehead; 9 | while(l1 && l2){ 10 | if(l1.val<=l2.val){ 11 | pre.next = l1; 12 | l1 = l1.next 13 | }else{ 14 | pre.next =l2; 15 | l2 = l2.next; 16 | } 17 | pre = pre.next; 18 | } 19 | 20 | pre.next = l1 == null?l2:l1; 21 | //哑结点的下一节点 22 | return prehead.next; 23 | }; 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /数据结构与算法/链表篇/牛客NC132 环形链表的约瑟夫问题.md: -------------------------------------------------------------------------------- 1 | ### NC132 环形链表的约瑟夫问题 2 | 3 | #### 解法1 4 | 链表模拟环 5 | ```js 6 | function ysf( n , m ) { 7 | //链表模拟环 8 | const head = {val:1}; 9 | let cur = head; 10 | for(let i =2;i<=n;i++){ 11 | cur.next = {val:i}; 12 | cur = cur.next; 13 | } 14 | cur.next = head; 15 | //开始游戏 16 | while(cur.next !== cur){ 17 | //报数 找到前一个人 跨过后一个 18 | for(let i =0;i1){ 39 | //计算删除的位置 40 | index = (index+m-1)%arr.length; 41 | arr.splice(index,1); 42 | } 43 | return arr[0]; 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /读书笔记(其他)/你的灯亮着吗/你的灯亮着吗.md: -------------------------------------------------------------------------------- 1 | # 你的灯亮着吗 2 | 3 | ## 第一个问题:73 层大厦电梯不够用 排队太久 4 | 5 | **解决问题的思路:** 6 | 7 | 1. 谁碰到了问题?确定服务对象,解决问题是为了让谁满意。 8 | 2. 问题的本质是什么?到底是哪出了问题? 9 | 3. 我们可以做些什么? 10 | 11 | **分析问题** 12 | 13 | 1. 服务对象:房主、租户。 14 | 2. 问题的本质:理想状态和现实状态的差异。 15 | 16 | - 对于房主:希望以较小的成本,解决租户的投诉,吸引更多的租户。 17 | - 对于租户:等电梯的时间与预期不符,希望减少排队时间。 18 | 19 | 3. 我们可以做什么? 20 | 21 | 针对问题本质,我们有两个解决方案: 22 | 23 | 1. 减少租户对电梯排队的预期。 24 | 2. 改善现实中电梯排队的现状。 25 | 26 | 改善现状: 27 | 28 | - 新增电梯:对于房东来说需要消耗大量成本,属于下下策。 29 | - 提高电梯运行速度。 30 | - 错峰上班,减少同一时间排队人数。 31 | - 蹭其他楼的电梯,打通通道。 32 | 33 | 减少预期: 34 | 35 | - 让排队的人有事可干。增加镜子。 36 | 37 | 通过“让事情变得更糟糕”来找到解决问题的办法。 38 | 39 | 当增加镜子后,租户们会照镜子注意自己的仪态,与此同时有人会在镜子上涂鸦, 40 | 上级希望解决这个问题,如何解决? 41 | 42 | 1. 谁碰到了问题:上级。 43 | 2. 问题的本质是什么? 44 | 45 | 上级认为镜子涂鸦有辱大厦形象。 46 | 47 | 3. 我们可以做什么? 48 | 49 | “让事情变得更糟糕”,本质上镜子涂鸦能够更有效的减少租户对电梯速度的预期,那么我们可以主动增加涂鸦笔,让租户涂鸦,变成“艺术”。 50 | 51 | ## 问题该由谁解决? 52 | 53 | > “当别人可以妥善解决自己的问题时,不要越俎代庖。” 54 | 55 | ### 停车场不够问题 56 | 57 | 大学停车场位置不够,请问,问题该由谁解决?校长 58 | 59 | - 如果一个人处于解决问题的位置,却并不受问题困扰,那就采取一些行动使他能亲身体验到问题。 60 | - 为了改变局面,试着把责任归到自己身上——哪怕只有一会儿也行。(换个思路往好的方向想) 61 | 62 | > 从最后的情况看,想要真正解决问题的人并不是很多。 63 | -------------------------------------------------------------------------------- /读书笔记(其他)/小强升职记/小强升职记.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruoruochen/front-end-note/bdfd11d32b610a5545808b1af3e222fad9b91b22/读书笔记(其他)/小强升职记/小强升职记.pdf -------------------------------------------------------------------------------- /读书笔记(其他)/自控力/自控力.md: -------------------------------------------------------------------------------- 1 | # 自控力 2 | 3 | ## 一、什么是意志力? 4 | 5 | ### 意志力法则:认识你自己。 6 | 7 | > 在作决定的时候,你必须意识到自己此刻需要意志力。想戒烟的人需要第一时间意识到自己吸烟的冲动,也要知道哪里会让他有这种冲动(比如在室外、寒冷的环境里或摆弄打火机的时候)。他还得知道,如果自己这次投降了,明天很有可能会继续吸烟,未来很可能会疾病缠身。为了避免厄运降临,他必须有意识地戒烟。要是没有自我意识,他就完蛋了。 8 | 9 | 在做任何决定时,需要考虑三点: 10 | 11 | 1. 意识到自己当前的冲动。 12 | 2. 思考产生冲动的条件:时间、地点、人物。 13 | 3. 执行冲动的后果:今天、明天、未来。 14 | 15 | ### 意志力实验:回忆一天你的决定 16 | 17 | 分析哪些有利于你实现目标,哪些会消磨你的意志。 18 | 19 | 看看能否及早意识到自己在做什么,什么样的想法、感受和情况最容易让你有冲动,想些什么或暗示什么最容易让你放弃冲动。 20 | 21 | ## 二、锻炼自控力肌肉 22 | 23 | ### 意志力实验 24 | 25 | > 如果你想彻底改掉坏习惯,最好先找个简单的方式训练,提高意志力,而不是设定一个过高的目标。 26 | > 例如:想戒糖。可以把一罐糖放在自己周围,给自己下定一个目标:“不去吃这罐糖”,这是个具体的目标,而不是虚无的目标“戒糖”。 27 | 28 | TODO:设定一个期限,控制自己以前不会去控制的小事。 29 | 30 | - 增强我不要:不要跷二郎腿 31 | - 增强我想要: 32 | - 增强自我监控:监控支出、监控饮食、监控上网时间 33 | 34 | ## 三、渴望 !== 快乐 35 | 36 | ## 四、情绪低落为何会使人屈服于诱惑 37 | 38 | > 当你情绪低落的时候,你会怎么让自己高兴起来呢?如果你和大多数人一样,你就会选择奖励的承诺。 39 | > 40 | > 奖励的承诺并不总意味着我们会得到快乐。通常,我们缓解压力的办法反而会让我们更有压力。 41 | 42 | 因此,我们需要找到一种方法,既能让自己快乐又不屈服于诱惑。 43 | 44 | ### 意志力实验:尝试一种解压的方法 45 | 46 | > 真正能缓解压力的不是释放多巴胺或依赖奖励的承诺,而是增加大脑中改善情绪的化学物质,如血清素、γ-氨基丁酸和让人感觉良好的催产素。这些物质还会让大脑不再对压力产生反应,减少身体里的压力荷尔蒙,产生有治愈效果的放松反应。因为它们不像释放多巴胺的物质那样让人兴奋,所以我们往往低估了它们的作用. 47 | 48 | 当焦虑、注意力分散时,可以尝试以下几种解压方法: 49 | 50 | 1. 运动。腹肌运动、俯卧撑。 51 | 2. 阅读。**(不能阅读学习类书籍)**,一些故事类书籍。 52 | 53 | **小诀窍:** 54 | 55 | - 让自己鼓励自己,解压方法能让我感到更快乐。 56 | 57 | ### 意志力实验:失败的时候,请原谅自己 58 | 59 | > “每个人都会犯错误,都会遭遇挫折。既然失败无法避免,更重要的就是我们如何应对失败。” 60 | 61 | 自我谅解:“不要苛求自己,要记住每个人都有放纵自己的时候。” 62 | 那又如何:“那又如何,既然我已经破坏了 xx 计划,不如 xx 到底” 63 | 64 | 学会自我谅解,思考为什么会失败,而不是自我批评。 65 | -------------------------------------------------------------------------------- /项目总结/React个人博客/开发过程中遇到的问题及知识点.md: -------------------------------------------------------------------------------- 1 | #### react中`dangerouslySetInnerHTML`使用 2 | 3 | 场景:数据`content`中包含的是`markdown`语法的内容,如何让`React`显示`markdown` 4 | 5 | 解决方案: 6 | 7 | 使用`react-markdown`。 8 | 9 | [react-markdown官方链接](https://github.com/remarkjs/react-markdown) 10 | 11 | #### 锚点设计 12 | 13 | 如何不用手动加锚点Id?封装一个合理的锚点组件 14 | 15 | https://segmentfault.com/a/1190000020294373 16 | 17 | #### 路由集中式配置管理 18 | 19 | 随着页面的增多,需要对路由的进行集中式管理。使用`useRoutes`对路由进行渲染。 20 | 21 | ![image-20211204210925101](https://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/202112042109186.png) 22 | 23 | ![image-20211204211027526](https://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/202112042110580.png) 24 | 25 | #### 如何控制公共对话框的显示与隐藏。 26 | 27 | 通过eventEmitter监听事件,点击某个按钮时,触发事件,并把数据传递进对话框组件中,实现数据传递以及Visible控制。 28 | 29 | #### 后台管理系统复用之前使用Vue开发的,如何实现管理系统不用再次登录,直接拿到Token登录后台管理系统。 30 | 31 | 在React blog中,采用JWT进行鉴权,token存在storage中,vue管理系统和react blog系统不同源,不能访问到storage中的token,因此要重新输入账号密码登录。 32 | 33 | 预期:用户点击后台管理,直接进入后台管理系统。 34 | 35 | **解决方案:跨域存储。** 36 | 37 | 可跨域通信的机制: postMessage,它可以安全的实现跨域通信,不受同源策略限制。 38 | 39 | 语法: 40 | 41 | ```js 42 | otherWindow.postMessage('message', targetOrigin, [transfer]) 43 | ``` 44 | 45 | - otherWindow:窗口的一个引用,如:iframe 的 contentWindow 属性、当前 window 对象、window.open 返回的窗口对象等。 46 | - message 将要发送到 otherWindow 的数据 47 | - targetOrigin 通过窗口的 targetOrigin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串 “*”(表示无限制) 48 | 49 | **实现思路** 50 | 51 | 需求:有两个不同的域名(react bloghttp://localhost:3000 和 vue managehttp://localhost:8080)想共用本地存储中的同一个 token 作为统一登录凭证。 52 | 53 | 1. 在react blog中,跳转页面的同时,postMessage发送token。 54 | 55 | ![image-20211205221049864](https://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/202112052210112.png) 56 | 57 | 2. 在vue manage导航守卫中,监听message事件,拿到token,存入storage中进行使用。 58 | 59 | ![image-20211205221142479](https://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/202112052211532.png) 60 | -------------------------------------------------------------------------------- /项目总结/React仿网易云/01-React+TS项目环境搭建.md: -------------------------------------------------------------------------------- 1 | https://juejin.cn/post/6955664681306423327#heading-10 2 | 3 | Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it. 4 | React 5 | ![20220322164716](https://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/20220322164716.png) 6 | Route 的 element 需要为组件 7 | 8 | React+TS 环境配置 9 | 10 | prettier、eslint 配置 11 | 12 | 路径别名配置 13 | https://blog.csdn.net/weixin_43094619/article/details/123114581?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164794545516782094828859%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164794545516782094828859&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~times_rank-4-123114581.142^v3^pc_search_result_control_group,143^v4^control&utm_term=react%E9%85%8D%E7%BD%AE%E8%B7%AF%E5%BE%84%E5%88%AB%E5%90%8D&spm=1018.2226.3001.4187 14 | 15 | axios 封装 16 | -------------------------------------------------------------------------------- /项目总结/React仿网易云/开发调研.md: -------------------------------------------------------------------------------- 1 | # 项目实践:网易云 App 2 | 3 | ## 实践目的 4 | 5 | 1. 提高 React hooks 的使用熟练度。 6 | 2. 提高 TS 的使用熟练度 7 | 3. 提高发现问题、解决问题的能力 8 | 4. 提高优化能力。项目优化:包括性能优化、安全优化、组件封装、通用 hooks 封装、预加载等。 9 | 10 | ## 开发计划 11 | 12 | | 步骤 | 时间 | 计划内容 | 是否完成 | 13 | | ---- | --------- | ---------------------------------- | -------- | 14 | | 1 | 3.13~3.15 | 技术选型 、接口对接,输出需求文档 | | 15 | | 2 | 3.16~3.18 | 搭建项目框架、项目工程话 eslint 等 | | 16 | | 3 | 3.19~4.19 | 网易云功能开发 | | 17 | | 3 | 4.19~5.05 | 项目优化 | | 18 | 19 | ## 技术选型 20 | 21 | 技术栈: 22 | 23 | - 框架+状态管理:React、React-Router、Redux、Redux-thunk 24 | - JS:TS 25 | - UI 库:Antd 26 | - 请求:axios 27 | - 样式选型:Sass 28 | - 移动端适配: 29 | - 列表滚动:better-scroll 30 | 31 | 后期优化考虑: 32 | 33 | 1. immutable/immer,减少不必要渲染 34 | 2. react-lazyload: react 懒加载库 35 | 3. 35 条雅虎军规,性能优化 36 | 37 | ### 移动端适配方案 38 | 39 | **移动端适配基础知识** 40 | 41 | 视口:布局视口、视觉视口、理想视口 42 | 43 | 1. 布局视口:浏览器布局区域,通常为 980px 44 | 2. 视觉视口:通过能看到的区域。 45 | 3. 理想视口:布局视口===视觉视口 46 | 47 | **适配方案** 48 | 49 | 1. rem + 媒体查询 50 | 2. flexible.js 动态 rem 51 | 3. vw,vh 52 | 53 | **rem** 54 | 55 | ## 项目规范 56 | 57 | 1. 全面拥抱 hooks,统一用函数式组件。 58 | 59 | ## 功能点 60 | 61 | 1. 发现页 62 | 63 | - 搜索 64 | - 轮播图 65 | - icon 操作行 66 | - 推荐歌单 67 | - 流行精选 68 | - 下方 fix 播放器 69 | 70 | 1. 电台页 71 | 72 | 2. 我的页 73 | 74 | - 个人信息 75 | - 歌单信息 76 | 77 | 4. 搜索页 78 | 79 | 5. 个人中心状态页 80 | 81 | 6. 播放器页(核心页面) 82 | 83 | ## 开发 84 | 85 | ### 一、播放器页 86 | -------------------------------------------------------------------------------- /项目总结/React仿网易云/总结沉淀.md: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /项目总结/React仿网易云/接口文档.md: -------------------------------------------------------------------------------- 1 | # 网易云接口文档 2 | 3 | ## 1 播放器页 4 | 5 | ### 1.1 获取歌曲详情 6 | 7 | - 请求路径:/song/detail 8 | - 请求方法:get 9 | - 请求参数 10 | 11 | | 参数名 | 参数说明 | 备注 | 12 | | ------ | -------- | ------------------------ | 13 | | ids | 音乐 id | 必选。多个 id, 用 , 隔开 | 14 | 15 | - 响应数据 16 | 17 | ```js 18 | name: String, 歌曲标题 19 | id: u64, 歌曲ID 20 | pst: 0,功能未知 21 | t: enum, 22 | 0: 一般类型 23 | 1: 通过云盘上传的音乐,网易云不存在公开对应 24 | 如果没有权限将不可用,除了歌曲长度以外大部分信息都为null。 25 | 可以通过 `/api/v1/playlist/manipulate/tracks` 接口添加到播放列表。 26 | 如果添加到“我喜欢的音乐”,则仅自己可见,除了长度以外各种信息均为未知,且无法播放。 27 | 如果添加到一般播放列表,虽然返回code 200,但是并没有效果。 28 | 网页端打开会看到404画面。 29 | 属于这种歌曲的例子: https://music.163.com/song/1345937107 30 | 2: 通过云盘上传的音乐,网易云存在公开对应 31 | 如果没有权限则只能看到信息,但无法直接获取到文件。 32 | 可以通过 `/api/v1/playlist/manipulate/tracks` 接口添加到播放列表。 33 | 如果添加到“我喜欢的音乐”,则仅自己可见,且无法播放。 34 | 如果添加到一般播放列表,则自己会看到显示“云盘文件”,且云盘会多出其对应的网易云公开歌曲。其他人看到的是其对应的网易云公开歌曲。 35 | 网页端打开会看到404画面。 36 | 属于这种歌曲的例子: https://music.163.com/song/435005015 37 | ar: Vec, 歌手列表 38 | alia: Vec, 39 | 别名列表,第一个别名会被显示作副标题 40 | 例子: https://music.163.com/song/536623501 41 | pop: 小数,常取[0.0, 100.0]中离散的几个数值, 表示歌曲热度 42 | st: 0: 功能未知 43 | rt: Option, None、空白字串、或者类似`600902000007902089`的字符串,功能未知 44 | fee: enum, 45 | 0: 免费或无版权 46 | 1: VIP 歌曲 47 | 4: 购买专辑 48 | 8: 非会员可免费播放低音质,会员可播放高音质及下载 49 | fee 为 1 或 8 的歌曲均可单独购买 2 元单曲 50 | v: u64, 常为[1, ?]任意数字, 功能未知 51 | crbt: Option, None或字符串表示的十六进制,功能未知 52 | cf: Option, 空白字串或者None,功能未知 53 | al: Album, 专辑,如果是DJ节目(dj_type != 0)或者无专辑信息(single == 1),则专辑id为0 54 | dt: u64, 歌曲时长 55 | h: Option, 高质量文件信息 56 | m: Option, 中质量文件信息 57 | l: Option, 低质量文件信息 58 | a: Option, 常为None, 功能未知 59 | cd: Option, None或如"04", "1/2", "3", "null"的字符串,表示歌曲属于专辑中第几张CD,对应音频文件的Tag 60 | no: u32, 表示歌曲属于CD中第几曲,0表示没有这个字段,对应音频文件的Tag 61 | rtUrl: Option, 常为None, 功能未知 62 | rtUrls: Vec, 常为空列表, 功能未知 63 | dj_id: u64, 64 | 0: 不是DJ节目 65 | 其他:是DJ节目,表示DJ ID 66 | copyright: u32, 0, 1, 2: 功能未知 67 | s_id: u64, 对于t == 2的歌曲,表示匹配到的公开版本歌曲ID 68 | mark: u64, 功能未知 69 | originCoverType: enum 70 | 0: 未知 71 | 1: 原曲 72 | 2: 翻唱 73 | originSongSimpleData: Option, 对于翻唱曲,可选提供原曲简单格式的信息 74 | single: enum, 75 | 0: 有专辑信息或者是DJ节目 76 | 1: 未知专辑 77 | noCopyrightRcmd: Option, None表示可以播,非空表示无版权 78 | mv: u64, 非零表示有MV ID 79 | rtype: 常为0,功能未知 80 | rurl: Option, 常为None,功能未知 81 | mst: u32, 偶尔为0, 常为9,功能未知 82 | cp: u64, 功能未知 83 | publish_time: i64, 毫秒为单位的Unix时间戳 84 | pc: 云盘歌曲信息,如果不存在该字段,则为非云盘歌曲 85 | 86 | ``` 87 | 88 | ### 1.2 获取歌词 89 | 90 | - 请求路径:/lyric 91 | - 请求方式:get 92 | - 请求参数 93 | 94 | | 参数名 | 参数说明 | 备注 | 95 | | ------ | -------- | ---- | 96 | | id | 音乐 id | 必选 | 97 | 98 | - 响应数据 99 | 100 | ![20220313230243](https://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/20220313230243.png) 101 | 102 | ### 1.3 音乐评论 103 | 104 | - 请求路径:/comment/music 105 | - 请求方式:get 106 | - 请求参数 107 | 108 | | 参数名 | 参数说明 | 备注 | 109 | | ------ | -------- | -------------- | 110 | | id | 音乐 id | 必选 | 111 | | limit | 数量 | 可选,默认 200 | 112 | 113 | - 响应数据 114 | ![20220313230540](https://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/20220313230540.png) 115 | 116 | ### 1.4 音乐 url 117 | 118 | ```js 119 | https://music.163.com/song/media/outer/url?id=${id}.mp3; 120 | ``` 121 | -------------------------------------------------------------------------------- /项目总结/Vue电商管理系统项目总结/01电商后台管理系统概述.md: -------------------------------------------------------------------------------- 1 | # 电商后台管理系统——项目初始化篇 2 | 3 | ## 前言 4 | 5 | 前端用到的技术栈 6 | 7 | - Vue 8 | - Vue-router 9 | - Element-UI 10 | - Axios 11 | - Echarts 12 | 13 | ## 1 项目初始化 14 | 15 | ### 1.1 前端项目初始化步骤 16 | 17 | - 安装 Vue 脚手架 18 | - 通过 Vue 脚手架创建项目 19 | - 配置 Vue 路由 20 | - 配置 Element-UI 组件库 21 | - 配置 axios 库 22 | - 初始化 git 远程仓库 23 | - 将本地项目托管到Github或码云中 24 | 25 | 在本项目中,使用可视化项目管理ui界面进行项目创建和管理,在cmd中输入 26 | 27 | ``` 28 | vue ui 29 | ``` 30 | 31 | 即可开启UI界面。 32 | 33 | ## 2 项目管理 34 | 35 | ### 2.1 创建项目 36 | 37 | 点击左下角的更多→Vue项目管理器→创建→自行选择路径后,点击创建此项目→输入项目名,点击下一步 38 | 39 | ![image-20210106151942666](../img/image-20210106151942666.png) 40 | 41 | ![image-20210106152038981](../img/image-20210106152038981.png) 42 | 43 | ![image-20210106152129098](../img/image-20210106152129098.png) 44 | 45 | ![image-20210106152238548](../img/image-20210106152238548.png) 46 | 47 | 第一次开发的话,就没有原来的预设,这里我们就手动配置我们的项目了 48 | 49 | ![在这里插入图片描述](../img/20200115110701644.png) 50 | 51 | 依次将下面几个功能打开,Linter最好不要打开!!!最后那个使用配置文件是很重要的,一定要选上 52 | 53 | ![image-20210106152447627](../img/image-20210106152447627.png) 54 | 55 | ![image-20210106152449803](../img/image-20210106152449803.png) 56 | 57 | ![image-20210106152452020](../img/image-20210106152452020.png) 58 | 59 | ### 2.2 安装相关插件 60 | 61 | 进入项目仪表盘,然后点击插件,点击“添加插件”,输入以下内容 62 | 63 | ``` 64 | vue-cli-plugin-element 65 | ``` 66 | 67 | 选中之后,点击右下角安装按钮即可 68 | 69 | - 接着,就是我们对element进行插件配置,如下图: 70 | 对这个插件按需导入,否则会冗余 71 | 72 | ![在这里插入图片描述](../img/20200115120135111.png) 73 | 74 | ### 2.3 安装新的依赖(配置 axios 库) 75 | 76 | 返回之前的界面,我们选择依赖→安装依赖,然后输入axios,注意我们这里选的是运行依赖。 77 | 78 | ![image-20210106153014770](../img/image-20210106153014770.png) 79 | 80 | ## 最后 81 | 82 | 到此,我们Vue项目初始化就结束了,下篇博客将接着写关于如何托管项目到github或者码云上~ -------------------------------------------------------------------------------- /项目总结/Vue电商管理系统项目总结/02 配置API接口服务器并调试接口.md: -------------------------------------------------------------------------------- 1 | ## 前期准备 2 | 3 | - 下载MySql、Navicat。 4 | 5 | 在Navicat中创建一个mydb数据库,执行vueShop-api-server/db/mydb.sql,成功创建数据库。 6 | 7 | 电商管理后台Node服务器接口下载地址: 8 | 9 | 链接:https://pan.baidu.com/s/1oYsjFQzIxclvezljllbpkw 10 | 提取码:qzp0 11 | 复制这段内容后打开百度网盘手机App,操作更方便哦 12 | 13 | 内含服务器所有东西及API文档。 14 | 15 | ## 配置API接口并测试 16 | 17 | 1. 下载Postman调试工具,用于测试后台项目接口是否正常。自行注册登录。 18 | 2. 登陆成功后,进入界面如图所示。 19 | 20 | ![image-20210106194211383](../img/image-20210106194211383.png) 21 | 22 | 打开vueShop-api-server/api接口文档,我们先来测试登录验证接口 23 | 24 | ![image-20210106194355125](../img/image-20210106194355125.png) 25 | 26 | ![image-20210106194312295](../img/image-20210106194312295.png) 27 | 28 | 他使用的是post请求方法,路径是接口基准地址+请求路径,所以应该是: 29 | 30 | http://127.0.0.1:8888/api/private/v1/login,接着我们在Params中输入请求参数,点击send进行测试 31 | 32 | ![image-20210106194626489](../img/image-20210106194626489.png) 33 | 34 | 响应结果: 35 | 36 | ![image-20210106194654375](../img/image-20210106194654375.png) 37 | 38 | 我们再测试数据库中存在的账户: 39 | 40 | ![image-20210106195427257](../img/image-20210106195427257.png) 41 | 42 | 测试成功,说明后台接口没问题 43 | 44 | -------------------------------------------------------------------------------- /项目总结/Vue电商管理系统项目总结/07 参数管理.md: -------------------------------------------------------------------------------- 1 | ### 参数管理 2 | 3 | ## 1 参数管理概念 4 | 5 | ![image-20210113211620308](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210113211620308.png) 6 | 7 | ## 2 基本UI布局 8 | 9 | 略 10 | 11 | ## 3 获取商品分类列表数据并渲染至级联选择框 12 | 13 | 获取数据略,级联选择框与06类似。 14 | 15 | ## 4 Tab标签页的使用 16 | 17 | ![image-20210113215134858](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210113220051120.png) 18 | 19 | ```html 20 | 21 | 22 | 动态属性 23 | 静态属性 24 | 25 | ``` 26 | 27 | data: 28 | 29 | ```js 30 | // 被激活的页签名称 31 | activeName: "first" 32 | ``` 33 | 34 | ### 禁用按钮 35 | 36 | ![image-20210113220051120](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210113215134858.png) 37 | 38 | 使用计算属性,当selectKeys长度>0时,btn disabled false。 39 | 40 | ## 5 分类参数 41 | 42 | ### 5.1 获取参数列表数据 43 | 44 | ```js 45 | async getParams() { 46 | const { data: res } = await this.$http.get( 47 | `categories/${ 48 | this.selectCateKeys[this.selectCateKeys.length - 1] 49 | }/attributes`, 50 | { 51 | params: { 52 | sel: this.activeName 53 | } 54 | } 55 | ); 56 | console.log(res); 57 | if (res.meta.status !== 200) { 58 | return this.$message.error("获取分类参数失败"); 59 | } 60 | this.paramsList = res.data; 61 | } 62 | ``` 63 | 64 | ### 5.2 获取到的参数数据挂载到不同数据源上 65 | 66 | 对获取到的数据进行判断,分别存入不同的数据中 67 | 68 | ```js 69 | //动态 70 | if (res.data.attr_sel === "many") this.manyTableData = res.data; 71 | //静态 72 | else this.onlyTableData = res.data; 73 | ``` 74 | 75 | 使用Table表格渲染数据,略 76 | 77 | ### 5.3 渲染参数下的可选性。 78 | 79 | ![image-20210114173511344](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210114180232250.png) 80 | 81 | 获取参数数据时,将attr_vals从字符串转数组。 82 | 83 | **注意:需要对其是否为空进行判断,如果空直接返回空数组。不然会导致空字符串转化成【“”】的结果** 84 | 85 | ```js 86 | //将attr_val从字符串变为数组 87 | res.data.forEach(item => { 88 | item.attr_vals = item.attr_vals ? item.attr_vals.split(",") : []; 89 | }); 90 | ``` 91 | 92 | ```html 93 | 94 | 95 | 98 | 99 | ``` 100 | 101 | ### 5.4 可选性的添加按钮 102 | 103 | 使用Tag里面的动态编辑标签 104 | 105 | ![image-20210114180232250](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210114173511344.png) 106 | 107 | 完成以上功能后,发现多个参数的输入框联动改变,原因在于共用一个inputvalue 108 | 109 | ![image-20210114180508076](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210114180508076.png) 110 | 111 | **改进** 112 | 113 | 在获取参数列表书,给其添加inputValue属性 114 | 115 | ```js 116 | //将attr_val从字符串变为数组 117 | res.data.forEach(item => { 118 | //控制文本框的显示与隐藏 119 | item.inputVisible = false; 120 | item.inputValue = ""; 121 | item.attr_vals = item.attr_vals ? item.attr_vals.split(",") : []; 122 | }); 123 | ``` 124 | 125 | **让文本框获得焦点** 126 | 127 | ```js 128 | showInput(row) { 129 | row.inputVisible = true; 130 | this.$nextTick(_ => { 131 | this.$refs.saveTagInput.$refs.input.focus(); 132 | }); 133 | } 134 | ``` 135 | 136 | this.$nextTick当页面元素被重新渲染时,执行回调函数的代码。 -------------------------------------------------------------------------------- /项目总结/Vue电商管理系统项目总结/09 数据报表功能.md: -------------------------------------------------------------------------------- 1 | # 09 数据报表功能 2 | 3 | ## 渲染图表 4 | 5 | #### 安装echarts运行时依赖 6 | 7 | ![image-20210115230626233](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210115230626233.png) 8 | 9 | #### echarts使用 10 | 11 | 步骤: 12 | 13 | 1. 导入echarts和lodash 14 | 2. 创建Echarts容器Dom 15 | 3. 初始化Echarts实例 16 | 4. 设置配置项和数据 (使用lodash进行数据项合并) 17 | 5. 显示图表 18 | 19 | ```js 20 | template中 21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | script中 29 | //1.导入 30 | var echarts = require("echarts"); 31 | import _ from "lodash"; 32 | 33 | export default { 34 | name: "Report", 35 | data() { 36 | return { 37 | option: { 38 | title: { 39 | text: "ECharts 入门示例" 40 | }, 41 | tooltip: {}, 42 | legend: { 43 | data: ["销量"] 44 | }, 45 | xAxis: { 46 | data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"] 47 | }, 48 | yAxis: {}, 49 | series: [ 50 | { 51 | name: "销量", 52 | type: "bar", 53 | data: [5, 20, 36, 10, 10, 20] 54 | } 55 | ] 56 | } 57 | }; 58 | }, 59 | //此时dom元素渲染完毕 60 | async mounted() { 61 | // 3.基于准备好的dom,初始化echarts实例 62 | var myChart = echarts.init(document.getElementById("main")); 63 | // 4.指定图表的配置项和数据 64 | const { data: res } = await this.$http.get(`reports/type/1`); 65 | if (res.meta.status !== 200) return this.$message.error("获取数据失败"); 66 | 67 | const result = _.merge(res.data, this.option); 68 | // 5.使用刚指定的配置项和数据显示图表。 69 | myChart.setOption(result); 70 | } 71 | }; 72 | 73 | ``` 74 | 75 | ![image-20210115232150107](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210115232150107.png) -------------------------------------------------------------------------------- /项目总结/Vue电商管理系统项目总结/webpack打包优化数据记录.md: -------------------------------------------------------------------------------- 1 | # webpack打包优化数据记录 2 | 3 | 初始值 4 | 5 | ![image-20210401105741625](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210401105741625.png) 6 | 7 | `thread-loader`多进程构建 8 | 9 | ![image-20210401121230652](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210401121230652.png) 10 | 11 | 预编译资源模块 -------------------------------------------------------------------------------- /项目总结/Vue电商管理系统项目总结/使用pm2管理应用报错:.md: -------------------------------------------------------------------------------- 1 | # 使用pm2管理应用报错: 2 | 3 | ``` 4 | pm2 : 无法加载文件 C:\Users\Asus\AppData\Roaming\npm\pm2.ps1。 5 | 未对文件 C:\Users\Asus\AppData\Roaming\npm\pm2.ps1 进行数字签名。无法在当前系统上运行该脚本。有关运行脚本和设置执行策略的详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID =135170 中的 about_Execution_Policies。 6 | 所在位置 行:1 字符: 1 + pm2 start .\app 7 | ``` 8 | 9 | 解决方法: 10 | 1. 11 | 以管理员身份运行power shell 12 | 2. 13 | 输入set-ExecutionPolicy RemoteSigned 14 | 然后输入A 回车 15 | 问题解决 16 | 17 | ![image-20210117164531008](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210117164531008.png) -------------------------------------------------------------------------------- /项目总结/Vue电商管理系统项目总结/项目总结.md: -------------------------------------------------------------------------------- 1 | # 项目总结 2 | 3 | 整理不易,若对您有帮助,请给个star,您的支持是我更新的动力 👇 4 | 5 | [🚩在线体验地址1](http://118.31.171.210/#/welcome)体验账号:admin 密码:123456 已经默认设置啦,可以直接登录。 6 | 7 | [🚩在线体验地址2](http://8.136.180.108/#/login) 8 | 9 | 📖[开发全过程博客记录](https://blog.csdn.net/weixin_43786756/category_10716603.html) 10 | 11 | 🐱 [GitHub笔记](https://github.com/ruoruochen/front-end-note/tree/master/Vue%E7%94%B5%E5%95%86%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F%E9%A1%B9%E7%9B%AE) 12 | 13 | ### 项目使用指南: 14 | 15 | 1. 希望能给本项目一个`star`~,感谢。 16 | 2. 点击绿色按钮`Code`,DownLoad Zip并解压。 17 | 3. 在该项目下执行`npm install`安装依赖 18 | 4. 执行`npm run serve`打开本地服务 19 | 20 | ### 部署上线指南 21 | 22 | 📖[【超详细小白教学】Vue+nodejs电商项目部署指南](https://blog.csdn.net/weixin_43786756/article/details/112982951) 23 | 24 | ## 项目概述 25 | 26 | ### 1.1 电商项目基本业务概述 27 | 28 | 根据不同的应用场景,电商系统一般都提供了 PC 端、移动 APP、移动 Web、微信小程序等多种终端访问方式,我们主要是实现PC后台管理系统。 29 | 30 | ![image-20210309122856022](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210309122856022.png) 31 | 32 | ### 1.2 电商后台管理系统的功能 33 | 34 | 电商后台管理系统用于管理用户账号、商品分类、商品信息、订单、数据统计等业务功能。 35 | 36 | ![image-20210309122919582](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210309122919582.png) 37 | 38 | ### 1.3 电商后台管理系统的开发模式(前后端分离) 39 | 40 | 电商后台管理系统整体采用前后端分离的开发模式,其中前端项目是**基于 Vue 技术栈的 SPA 项目。** 41 | 42 | ![image-20210309122931478](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210309122931478.png) 43 | 44 | ## 电商后台管理系统的技术选型 45 | 46 | ### 前端用到的技术栈 47 | 48 | - Vue 49 | - Vue-router 50 | - Element-UI 51 | - Axios 52 | - Echarts 53 | 54 | ### 后端项目技术栈 55 | 56 | - Node.js 57 | - Express 58 | - Jwt 59 | - Mysql 60 | - Sequelize 61 | 62 | ## 项目效果: 63 | 64 | 贴上几张图,具体效果请在在线体验地址中体验哦~ 65 | 66 | ![image-20210120221628801](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210120221628801.png) 67 | 68 | ![image-20210120221647333](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210120221647333.png) 69 | 70 | ![image-20210120221706705](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210120221706705.png) -------------------------------------------------------------------------------- /项目总结/其他项目总结/品优购项目总结.md: -------------------------------------------------------------------------------- 1 | # 品优购项目总结 2 | 3 | 本次项目一共实现了7个界面,包括首页、登录页面、注册页面、商品秒杀页、商品推文页、商品抢购页、商品详情页等界面。 4 | 5 | ### 项目展示 6 | 7 | #### 首页 8 | 9 | ![image-20210106164351703](note/img/image-20210106164351703.png) 10 | 11 | ![image-20210106164416424](note/img/image-20210106164416424.png) 12 | 13 | ![image-20210106164436378](note/img/image-20210106164436378.png) 14 | 15 | ![image-20210106164457904](note/img/image-20210106164457904.png) 16 | 17 | ![image-20210106164840966](note/img/image-20210106164840966.png) 18 | 19 | #### 登录页 20 | 21 | ![image-20210106164947754](note/img/image-20210106164947754.png) 22 | 23 | #### 注册页 24 | 25 | ![image-20210106165013209](note/img/image-20210106165013209.png) 26 | 27 | #### 商品秒杀页 28 | 29 | ![image-20210106165117497](note/img/image-20210106165117497.png) 30 | 31 | ![image-20210106165140702](note/img/image-20210106165140702.png) 32 | 33 | #### 商品推文页 34 | 35 | ![image-20210106165226228](note/img/image-20210106165226228.png) 36 | 37 | #### 商品抢购页 38 | 39 | ![image-20210106165302289](note/img/image-20210106165302289.png) 40 | 41 | #### 商品详情页 42 | 43 | ![image-20210106165409239](note/img/image-20210106165409239.png) 44 | 45 | ![image-20210106165327943](note/img/image-20210106165327943.png) 46 | 47 | ### 项目技术概要 48 | 49 | #### SDO优化 50 | 51 | ​ 要做好一个成功的网站,一定要注意代码中的SDO优化,以下是我在品优购项目中所涉及到的SDO优化部分 52 | 53 | ![image-20210106165758682](note/img/image-20210106165758682.png) 54 | 55 | #### 字体图标的引入 56 | 57 | ​ 图片是有诸多优点的,但是缺点很明显,比如图片不但增加了总文件的大小,还增加了很多额外的"http请求",这都会大大降低网页的性能的。更重要的是图片不能很好的进行“缩放”,因为图片放大和缩小会失真。 我们后面会学习移动端响应式,很多情况下希望我们的图标是可以缩放的。此时,用字体图标来完成会更好。 58 | 59 | ##### 字体图标的优点 60 | 61 | ​ 字体图标可以做出跟图片一样可以做的事情,改变透明度、旋转度,等.. 但是本质其实是文字,可以很随意的改变颜色、产生阴影、透明效果等等... 本身体积更小,但携带的信息并没有削减。 62 | 63 | #### 图片懒加载 64 | 65 | 通过图片的懒加载,可以减少服务器的压力,优化页面加载时间。 66 | 67 | 实现方法: 68 | 69 | 在html标签中使用data-lazy-src 70 | 71 | ```html 72 | 73 | ``` 74 | 75 | 引用懒加载js文件 76 | 77 | ```js 78 | 79 | 94 | ``` 95 | 96 | #### js实现交互效果 97 | 98 | 使用js正则表达式进行用户注册输入数据验证、用户点击事件效果。 99 | 100 | 101 | 102 | 以上就是实现的基本效果啦~希望能够给大家带来帮助,一起学习一起进步。 -------------------------------------------------------------------------------- /项目总结/其他项目总结/星巴克购物系统项目总结.md: -------------------------------------------------------------------------------- 1 | # 项目总结 2 | 3 | 整理不易,若对您有帮助,请给个star,您的支持是我更新的动力 👇 4 | 5 | ## 项目概述 6 | 7 | #### 系统功能 8 | 9 | 本星巴克网上订餐系统实现了以下功能:用户通过注册登录网站进行网上在线选菜品点餐、下单、选择餐厅并用支付宝支付、查看订单、评论菜品,以及管理员通过后台系统进行菜品、用户、订单、评论、餐厅的管理功能。 10 | 11 | 系统将分为七部分: 12 | 13 | 1. 用户登录注册模块 14 | 2. 菜品模块 15 | 3. 购物车管理模块 16 | 4. 提交订单模块 17 | 5. 后台管理模块 18 | 6. 商品评论模块 19 | 7. 订单详情模块 20 | 21 | ![image-20210228140831074](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210228140831074.png) 22 | 23 | #### 项目业务流程 24 | 25 | 本项目的业务流程的参与者主要由普通顾客和管理员组成。顾客主要通过注册登录该网站后,进入网站首页查看菜品信息,选择查看菜品详情,还可以搜索想要的商品或根据分类查找商品,登录后才可以将菜品添加入购物车,并且继续提交订单、结账等操作。管理员登录后可以进入后台管理界面查看所有商品及详情,进行用户管理、菜品管理、订单管理、商品评论管理、餐厅管理、商品分类管理等操作。 26 | 27 | ![image-20210228140744382](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210228140744382.png) 28 | 29 | ### 项目展示 30 | 31 | #### 用户界面 32 | 33 | ![image-20210228140211496](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210228140211496.png) 34 | 35 | ![image-20210228140226087](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210228140226087.png) 36 | 37 | ![image-20210228140235174](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210228140235174.png) 38 | 39 | ![image-20210228140248032](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210228140248032.png) 40 | 41 | ![image-20210228140309190](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210228140309190.png) 42 | 43 | #### 管理员界面 44 | 45 | ![image-20210228140346820](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210228140346820.png) 46 | 47 | ![image-20210228140353353](http://ruoruochen-img-bed.oss-cn-beijing.aliyuncs.com/img/image-20210228140353353.png) 48 | 49 | --------------------------------------------------------------------------------