[
  {
    "path": ".gitattributes",
    "content": "*.md text=auto\n\n"
  },
  {
    "path": ".github/workflows/check_dead_links.yml",
    "content": "name: Check Markdown links\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  workflow_dispatch:\n\njobs:\n  test-and-build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          persist-credentials: false\n          fetch-depth: 0\n\n      - uses: gaurav-nelson/github-action-markdown-link-check@v1\n        with:\n          use-quiet-mode: 'yes'"
  },
  {
    "path": ".github/workflows/restructure_files.yml",
    "content": "name: Restructure files\n\non:\n  push:\n    branches: [ main ]\n  workflow_dispatch:\n\njobs:\n  test-and-build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          persist-credentials: false\n          fetch-depth: 0\n\n      - name: Install Prettier\n        run: npm install --save-dev --save-exact prettier\n\n      - name: Restructure files\n        run: node_modules/.bin/prettier --write README.md animation-simulation/\n\n      - name: Commit files\n        run: |\n          git config --local user.email \"41898282+github-actions[bot]@users.noreply.github.com\"\n          git config --local user.name \"github-actions[bot]\"\n          git diff-index --quiet HEAD -- || git commit -am \"代码重构 【Github Actions】\"\n\n      - name: Push changes\n        uses: ad-m/github-push-action@master\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          branch: ${{ github.ref }}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 算法基地\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# **algorithm-base**\n\n<div  align=\"left\">   <a href = \"https://www.zhihu.com/people/suan-fa-ji-di\"><img src=\"https://img.shields.io/badge/Zhihu-知乎-blue\" width = \"80px\" hight = \"50px\"/></a><span style=\"font-size:12px\">&nbsp@程序厨</span>&nbsp&nbsp&nbsp&nbsp\n    <a href = \"https://mp.weixin.qq.com/s/TJ_U9B3ttghwz_vWNdAjXw\"><img src=\"https://img.shields.io/badge/WX-公众号-green\" width = \"80px\" hight = \"50px\"/></a><span style=\"font-size:12px\">&nbsp@程序厨</span>\n    &nbsp&nbsp&nbsp&nbsp\n    <a href = \"https://github.com/chefyuan/algorithm-base\"><img src=\"https://img.shields.io/badge/GitHub-仓库-red\" width = \"80px\" hight = \"50px\"/></a><span style=\"font-size:12px\">&nbsp @算法基地</span> \n     </div>\n\n### **❤️ 致各位题友的一封信（使用仓库前必读）**\n\n推荐在线阅读，更稳定[www.chengxuchu.com](https://www.chengxuchu.com)\n\n![](https://files.mdnice.com/user/8139/e26facfd-4009-4bed-9009-8f2062b81bfd.png)\n\n![](https://files.mdnice.com/user/8139/da380ce8-d912-417d-ac4b-7cfbcd909105.png)\n\n如果想要贡献代码的大佬可以添加我的微信 **[iamchuzi](https://cdn.jsdelivr.net/gh/tan45du/test@master/美化.1kdnk85ce5c0.png)** 备注贡献仓库即可。\n\n在这里先替所有使用仓库的同学，谢谢各位贡献者啦。\n\n如果老哥觉得仓库很用心的话，麻烦大佬帮忙点个 star ，这也是我们一直更新下去的动力。\n\n感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助.\n\n如果你需要加入**刷题/秋招小队**的话，可以扫描下方二维码，点击与我联系/交流小队，该小队永不收费，也不会有人发广告，仅仅用作交流，但是希望大家进入时，可以备注自身情况，并做一个简短的自我介绍。\n\n<div  align=\"center\">  <img src=\"https://cdn.jsdelivr.net/gh/tan45du/test@master/美化.1kdnk85ce5c0.png\" width = \"150px\" hight = \"150px\"/> </div>\n\n## 另外如果你需要 C++ 项目的话，可以看下这些项目介绍 [www.chengxuchu.com/cppcamp.html](https://www.chengxuchu.com/cppcamp.html)\n\n### 📢 数据结构（前置知识）\n\n- [【动画模拟】哈希表详解，万字长文](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/Hash%E8%A1%A8%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B.md)\n- [【动画模拟】栈和队列详解](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%85%B3%E4%BA%8E%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B.md)\n- [【绘图解析】链表详解](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%85%B3%E4%BA%8E%E9%93%BE%E8%A1%A8%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B.md)\n- [【绘图描述】递归详解](https://mp.weixin.qq.com/s/A4xG9IbQUjFwQoy9YcneCw)\n- [【动画模拟】树](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%8F%89%E6%A0%91/%E4%BA%8C%E5%8F%89%E6%A0%91%E5%9F%BA%E7%A1%80.md)\n\n### 🔋 字符串匹配算法\n\n- [【动画模拟】字符串匹配 BF 算法](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/BF%E7%AE%97%E6%B3%95.md)\n- [【动画模拟】字符串匹配 BM 算法](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/BM.md)\n- [【动画模拟】字符串匹配 KMP 算法](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/KMP.md)\n\n### 🧮 排序算法\n\n- [【动画模拟】冒泡排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F.md)\n- [【动画模拟】简单选择排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E7%AE%80%E5%8D%95%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F.md)\n- [【动画模拟】插入排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E7%9B%B4%E6%8E%A5%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.md)\n- [【动画模拟】希尔排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F.md)\n- [【动画模拟】归并排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F.md)\n- [【动画模拟】快速排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F.md)\n- [【动画模拟】堆排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%A0%86%E6%8E%92%E5%BA%8F.md)\n- [【动画模拟】计数排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F.md)\n\n### 🍺 二叉树\n\n- [【动画模拟】前序遍历（迭代）](<https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%8F%89%E6%A0%91/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86(%E6%A0%88).md>)\n- [【动画模拟】前序遍历（Morris）](<https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%8F%89%E6%A0%91/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86(Morris).md>)\n- [【动画模拟】中序遍历（迭代）](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%8F%89%E6%A0%91/%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%88%E8%BF%AD%E4%BB%A3%EF%BC%89.md)\n- [【动画模拟】中序遍历（Morris）](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%8F%89%E6%A0%91/%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86%EF%BC%88Morris%EF%BC%89.md)\n- [【动画模拟】后序遍历（迭代）](<https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%8F%89%E6%A0%91/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E7%BB%AD%E9%81%8D%E5%8E%86%20(%E8%BF%AD%E4%BB%A3).md>)\n- [【动画模拟】后序遍历（Morris）](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%8F%89%E6%A0%91/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%8E%E7%BB%AD%E9%81%8D%E5%8E%86%EF%BC%88Morris%EF%BC%89.md)\n\n### 🍗 排序算法秒杀题目\n\n- [【动画模拟】荷兰国旗](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E8%8D%B7%E5%85%B0%E5%9B%BD%E6%97%97.md)\n- [【反证解决】数组合成最小的数，最大数](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%90%88%E6%88%90.md)\n- [【动画模拟】逆序对问题](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E9%80%86%E5%BA%8F%E5%AF%B9%E9%97%AE%E9%A2%98.md)\n- [【动画模拟】翻转对问题](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E7%BF%BB%E8%BD%AC%E5%AF%B9.md)\n- [【动画模拟】链表插入排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode147%E5%AF%B9%E9%93%BE%E8%A1%A8%E8%BF%9B%E8%A1%8C%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.md)\n\n### 🍖 数组篇\n\n- [【动画模拟】leetcode 1 两数之和](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode1%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md)\n- [【动画模拟】leetcode 27 移除元素](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode27%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.md)\n- [【动画模拟】leetcode 41 缺失的第一个正数](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode41%E7%BC%BA%E5%A4%B1%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%AD%A3%E6%95%B0.md)\n- [【动画模拟】leetcode 485 最大连续 1 的个数](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode485%E6%9C%80%E5%A4%A7%E8%BF%9E%E7%BB%AD1%E7%9A%84%E4%B8%AA%E6%95%B0.md)\n- [【绘图描述】leetcode 1052 爱生气的书店老板](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode1052%E7%88%B1%E7%94%9F%E6%B0%94%E7%9A%84%E4%B9%A6%E5%BA%97%E8%80%81%E6%9D%BF.md)\n- [【动画模拟】剑指 offer 3 数组中重复的数字](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/%E5%89%91%E6%8C%87offer3%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0.md)\n- [【动画模拟】leetcode 219 数组中重复元素 2](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode219%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A02.md)\n- [【动画模拟】leetcode 560 和为 K 的子数组](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode560%E5%92%8C%E4%B8%BAK%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md)\n- [【绘图描述】leetcode 66 加一](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode66%E5%8A%A0%E4%B8%80.md)\n- [【动画模拟】leetcode 75 颜色分类](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode75%E9%A2%9C%E8%89%B2%E5%88%86%E7%B1%BB.md)\n- [【动画模拟】leetcode 54 螺旋矩阵](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode54%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5.md)\n- [【动画模拟】leetcode 59 螺旋矩阵 2](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode59%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B52.md)\n- [【动画模拟】leetcode 233 数字 1 的个数](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%89%91%E6%8C%87offer/1%E7%9A%84%E4%B8%AA%E6%95%B0.md)\n\n### 🦞 求和问题\n\n- [【动画模拟】leetcode 01 两数之和](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%B1%82%E5%92%8C%E9%97%AE%E9%A2%98/%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.md)\n- [【动画模拟】leetcode 15 三数之和](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%B1%82%E5%92%8C%E9%97%AE%E9%A2%98/%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.md)\n- [【动画模拟】leetcode 18 四数之和](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%B1%82%E5%92%8C%E9%97%AE%E9%A2%98/%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.md)\n\n### 🍓 求次数问题\n\n- [【动画模拟】leetcode 136 只出现一次的数](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%B1%82%E6%AC%A1%E6%95%B0%E9%97%AE%E9%A2%98/%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B0.md)\n- [【动画模拟】leetcode 137 只出现一次的数字 II](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%B1%82%E6%AC%A1%E6%95%B0%E9%97%AE%E9%A2%98/%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B02.md)\n- [【动画模拟】leetcode 260 只出现一次的数字 III](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%B1%82%E6%AC%A1%E6%95%B0%E9%97%AE%E9%A2%98/%E5%8F%AA%E5%87%BA%E7%8E%B0%E4%B8%80%E6%AC%A1%E7%9A%84%E6%95%B03.md)\n\n### 🍅 链表篇\n\n- [【动画模拟】剑指 offer 22 倒数第 k 个节点](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/%E5%89%91%E6%8C%87offer22%E5%80%92%E6%95%B0%E7%AC%ACk%E4%B8%AA%E8%8A%82%E7%82%B9.md)\n- [【动画模拟】面试题 02.03. 链表中间节点](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.03.%20%E9%93%BE%E8%A1%A8%E4%B8%AD%E9%97%B4%E8%8A%82%E7%82%B9.md)\n- [【动画模拟】剑指 offer 52 两个链表的第一个公共节点 & leetcode 160 相交链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/%E5%89%91%E6%8C%87Offer52%E4%B8%A4%E4%B8%AA%E9%93%BE%E8%A1%A8%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%85%AC%E5%85%B1%E8%8A%82%E7%82%B9.md)\n- [【动画模拟】leetcode 234 回文链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/234.%20%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8.md)\n- [【动画模拟】leetcode 206 反转链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode206%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md)\n- [【动画模拟】leetcode 92 反转链表 2](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode92%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A82.md)\n- [【动画模拟】leetcode 141 环形链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode141%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.md)\n- [【动画模拟】leetcode 142 环形链表 2](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode142%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A82.md)\n- [【动画模拟】leetcode 86 分隔链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode86%E5%88%86%E9%9A%94%E9%93%BE%E8%A1%A8.md)\n- [【动画模拟】leetcode 328 奇偶链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode328%E5%A5%87%E5%81%B6%E9%93%BE%E8%A1%A8.md)\n- [【动画模拟】剑指 offer 25 合并两个排序链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/%E5%89%91%E6%8C%87Offer25%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%8E%92%E5%BA%8F%E7%9A%84%E9%93%BE%E8%A1%A8.md)\n- [【动画模拟】leetcode 82 删除排序链表的重复元素 2](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode82%E5%88%A0%E9%99%A4%E6%8E%92%E5%BA%8F%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0II.md)\n- [【动画模拟】leetcode 147 对链表进行插入排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode147%E5%AF%B9%E9%93%BE%E8%A1%A8%E8%BF%9B%E8%A1%8C%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.md)\n- [【动画模拟】面试题 02.05 链表求和](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/%E9%9D%A2%E8%AF%95%E9%A2%98%2002.05.%20%E9%93%BE%E8%A1%A8%E6%B1%82%E5%92%8C.md)\n\n### 🚁 双指针\n\n- [【动画模拟】二分查找详解](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%8F%8A%E5%85%B6%E5%8F%98%E7%A7%8D/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E8%AF%A6%E8%A7%A3.md)\n- [【动画模拟】leetcode 35 搜索插入位置](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%8F%8A%E5%85%B6%E5%8F%98%E7%A7%8D/leetcode35%E6%90%9C%E7%B4%A2%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE.md)\n- [【动画模拟】leetcode 27 移除元素](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode27%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.md)\n- [【动画模拟】leetcode 209 长度最小的子数组](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md)\n- [【动画模拟】leetcode 141 环形链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode141%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.md)\n- [【动画模拟】剑指 offer 52 两个链表的第一个公共节点 & leetcode 160 相交链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/%E5%89%91%E6%8C%87Offer52%E4%B8%A4%E4%B8%AA%E9%93%BE%E8%A1%A8%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%85%AC%E5%85%B1%E8%8A%82%E7%82%B9.md)\n- [【动画模拟】leetcode 328 奇偶链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode328%E5%A5%87%E5%81%B6%E9%93%BE%E8%A1%A8.md)\n\n### 🏳‍🌈 栈和队列\n\n- [【动画模拟】leetcode 225 队列实现栈](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/225.%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.md)\n- [【动画模拟】剑指 Offer 09. 用两个栈实现队列](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/%E5%89%91%E6%8C%87Offer09%E7%94%A8%E4%B8%A4%E4%B8%AA%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.md)\n- [【动画模拟】leetcode 20 有效的括号](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/leetcode20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.md)\n- [【动画模拟】leetcode1047 删除字符串中的所有相邻重复项](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/leetcode1047%20%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.md)\n- [【动画模拟】leetcode 402 移掉 K 位数字](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97/leetcode402%E7%A7%BB%E6%8E%89K%E4%BD%8D%E6%95%B0%E5%AD%97.md)\n\n### 🏬 二分查找及其变种\n\n- [【动画模拟】二分查找详解](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%8F%8A%E5%85%B6%E5%8F%98%E7%A7%8D/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E8%AF%A6%E8%A7%A3.md)\n- [【动画模拟】leetcode 35 搜索插入位置](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%8F%8A%E5%85%B6%E5%8F%98%E7%A7%8D/leetcode35%E6%90%9C%E7%B4%A2%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE.md)\n- [【动画模拟】leetcode 34 查找元素的第一个位置和最后一个位置](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%8F%8A%E5%85%B6%E5%8F%98%E7%A7%8D/leetcode34%E6%9F%A5%E6%89%BE%E7%AC%AC%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE.md)\n- [【绘图描述】找出第一个大于或小于目标元素的索引](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%8F%8A%E5%85%B6%E5%8F%98%E7%A7%8D/%E6%89%BE%E5%87%BA%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%A4%A7%E4%BA%8E%E6%88%96%E5%B0%8F%E4%BA%8E%E7%9B%AE%E6%A0%87%E7%9A%84%E7%B4%A2%E5%BC%95.md)\n- [【动画模拟】leetcode 33 旋转数组中查找目标元素（不含重复元素）](<https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%8F%8A%E5%85%B6%E5%8F%98%E7%A7%8D/leetcode33%E4%B8%8D%E5%AE%8C%E5%85%A8%E6%9C%89%E5%BA%8F%E6%9F%A5%E6%89%BE%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0(%E4%B8%8D%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%80%BC).md>)\n- [【绘图描述】leetcode 81 旋转数组中查找目标元素（包含重复元素）](<https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%8F%8A%E5%85%B6%E5%8F%98%E7%A7%8D/leetcode%2081%E4%B8%8D%E5%AE%8C%E5%85%A8%E6%9C%89%E5%BA%8F%E6%9F%A5%E6%89%BE%E7%9B%AE%E6%A0%87%E5%85%83%E7%B4%A0(%E5%8C%85%E5%90%AB%E9%87%8D%E5%A4%8D%E5%80%BC)%20.md>)\n- [【绘图描述】leetcode 153 寻找旋转数组中的最小值](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%8F%8A%E5%85%B6%E5%8F%98%E7%A7%8D/leetcode153%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC.md)\n- [【动画模拟】leetcode 74 二维数组的二分查找](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E5%8F%8A%E5%85%B6%E5%8F%98%E7%A7%8D/%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.md)\n\n### 💒 单调队列单调栈\n\n- [【动画模拟】剑指 Offer 59 - II. 队列的最大值](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97%E5%8D%95%E8%B0%83%E6%A0%88/%E5%89%91%E6%8C%87offer59%E9%98%9F%E5%88%97%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md)\n- [【动画模拟】剑指 Offer 59 - I. 滑动窗口的最大值](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97%E5%8D%95%E8%B0%83%E6%A0%88/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md)\n- [【动画模拟】leetcode 1438 绝对值不超过限制的最长子数组](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E7%BB%84%E7%AF%87/leetcode1438%E7%BB%9D%E5%AF%B9%E5%80%BC%E4%B8%8D%E8%B6%85%E8%BF%87%E9%99%90%E5%88%B6%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84.md)\n- [【动画模拟】leetcode 155 最小栈](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97%E5%8D%95%E8%B0%83%E6%A0%88/%E6%9C%80%E5%B0%8F%E6%A0%88.md)\n- [【动画模拟】leetcode 739 每日温度](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97%E5%8D%95%E8%B0%83%E6%A0%88/leetcode739%E6%AF%8F%E6%97%A5%E6%B8%A9%E5%BA%A6.md)\n- [【动画模拟】leetcode 42 接雨水](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97%E5%8D%95%E8%B0%83%E6%A0%88/%E6%8E%A5%E9%9B%A8%E6%B0%B4.md)\n\n### 🛳 前缀和\n\n- [【动画模拟】leetcode 724 寻找数组的中心索引](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%89%8D%E7%BC%80%E5%92%8C/leetcode724%E5%AF%BB%E6%89%BE%E6%95%B0%E7%BB%84%E7%9A%84%E4%B8%AD%E5%BF%83%E7%B4%A2%E5%BC%95.md)\n- [【动画模拟】leetcode 523 连续的子数组和](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%89%8D%E7%BC%80%E5%92%8C/leetcode523%E8%BF%9E%E7%BB%AD%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84%E5%92%8C.md)\n- [【动画模拟】leetcode 560 和为 K 的子数组](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%89%8D%E7%BC%80%E5%92%8C/leetcode560%E5%92%8C%E4%B8%BAK%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md)\n- [【绘图描述】leetcode1248 统计「优美子数组」](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%89%8D%E7%BC%80%E5%92%8C/leetcode1248%E5%AF%BB%E6%89%BE%E4%BC%98%E7%BE%8E%E5%AD%90%E6%95%B0%E7%BB%84.md)\n- [【绘图描述】leetcode 974 和可被 K 整除的子数组](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E5%89%8D%E7%BC%80%E5%92%8C/leetcode974%E5%92%8C%E5%8F%AF%E8%A2%ABK%E6%95%B4%E9%99%A4%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.md)\n\n### 🥥 递归\n\n- 敬请期待。。。\n\n### 🍒 贪心\n\n- 敬请期待。。。\n\n### 🚃 回溯\n\n- 敬请期待。。。\n\n### 🌆 分治\n\n- 敬请期待。。。\n\n### 🧭 动态规划\n\n- 敬请期待。。。\n\n### 🌋 并查集\n\n- 敬请期待。。。\n\n> > > > > > >\n\n---\n\n<div  align=\"center\">  <img src=\"https://cdn.jsdelivr.net/gh/tan45du/photobed@master/赞赏码.2mrhxsmxexa0.png\" width = \"200px\" hight = \"200px\"/> </div>\n"
  },
  {
    "path": "animation-simulation/Leetcode常用类和函数.md",
    "content": "# Leetcode 常用函数\n\n## 链表篇\n\n一些节点，除了最后一个节点以外的每一个节点都存储着下一个节点的地址，依据这种方法依次连接， 构成一个链式结构。\n\n### ListNode\n\n```java\nListNode list=new ListNode(0)\n```\n\n初始化一个值为 0 的空节点，提倡的写法\n\n### HashSet\n\nHashSet 基于 HashMap 来实现的，是一个不允许有重复元素的集合但是允许有 null 值，HashSet 是无序的，即不会记录插入的顺序。HashSet 不是线程安全的， 如果多个线程尝试同时修改 HashSet，则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。\n\n```java\nHashSet<String> sites = new HashSet<String>();\n```\n\n#### add()\n\n往 HashSet 里添加元素\n\n```\nsites.add(\"我是袁厨，大家快快关注我吧\");\n```\n\n#### size()\n\n#### remove()\n\nremover()size()也是会用到的函数，具体用法和 ArrayList 一样\n\n#### contains()\n\n判断元素是否存在\n\n```\nSystem.out.println(sites.contains(\"我是袁厨，大家快快关注我吧\"));\n```\n\n> 输出：true；\n\n## 数组篇\n\n### length\n\n该函数是用来得到数组长度的函数，这里需要注意的是 length 后面没有括号\n\n### sort()\n\n该函数用于给数组进行排序，这两个函数用的比较多\n\n### Arrays.fill()\n\n用于填充数组\n\n一共有四个参数，分别是数组，开始索引，结束索引，填充数值。\n\n```\nArrays.fill(nums, 0, 2, 0);\nfor(int x:nums){\n  System.out.println(x);\n}\n```\n\n> 输出：0,0\n\n### Arrays.sort()\n\n给数组排序,也可以做到部分排序 ，在括号中添加索引即可\n\n```\nint[] array = {1,6,3,4};\nArrays.sort(array);\nreturn array;\n```\n\n> array : 1,3,4,6;\n\n### Arrays.copyOfRange()\n\n将一个原始的数组，从下标 0 开始复制，复制到上标 2，生成一个新的数组\n\n```\nint[] array = {1,2,3,4};\nint[] ar= Arrays.copyOfRange(intersection, 0, 2);\nreturn ar;\n\n```\n\n> array2: 1 , 2 ;\n\n### System.arraycopy();\n\n```java\nSystem.arraycopy(targetnums,beganindex, newnums, newindex, length);\n```\n\ntargetnums:目标数组，想要复制的数组\n\nbeganindex:目标数组开始索引\n\nnewsnums:复制到的新数组\n\nnewindex:开始索引\n\nlength：想要复制的长度\n\n```java\n int[] array = {1,2,3,4};\n int[] newarray  =  new int[2];\n System.arraycopy(array,0,newarray,0,2);\n for(int x : newarray){\n     System.out.println(x)\n }\n```\n\n> 输出：1，2\n\n### 逻辑运算符\n\n#### x | 0\n\n得到的仍然是他本身，\n\n例：1001|0000=1001；或运算代表的是如果两位其中有一个 1 则返回 1，否则为 0；\n\n```java\npublic static void main(String[] args) {\n        int x =10 ;\n        System.out.println(x|0);\n    }\n```\n\n> 输出：10\n\n#### x & 0\n\n无论任何数都会输出 0，这个也很好理解。\n\n例：1001&0000=0000;两位都为 1 才能返回 1\n\n```\npublic static void main(String[] args) {\n        int x =10 ;\n        System.out.println(x&0);\n    }\n```\n\n> 输出：0\n\n#### x ^ 0\n\n得到的还是他本身，这个也很好理解，异或的含义就是如果相同输出 0，如果不同输出 1\n\n例：0111^0000=0111 第一位相同，其余位不同\n\n```java\npublic static void main(String[] args) {\n    int x =-10 ;\n    System.out.println(x^0);\n}\n```\n\n> 输出：-10\n\n#### x | 1\n\n如果是奇数的话，还是它本身，偶数的话则加 1；\n\n```java\nint x =-9 ;\nint y = -10;\nSystem.out.println(x|1);\nSystem.out.println(y|1);\n```\n\n> 输出：-9，-9\n\n#### x ^ 1\n\n如果是偶数则加 1，如果是奇数则减 1；\n\n```java\nint x =-9 ;\nint y = -10;\nSystem.out.println(x^1);\nSystem.out.println(y^1);\n```\n\n> 输出：-10，-9\n\n#### x & 1\n\n得出最后一位是 0 还是 1，通常会用来判断奇偶\n\n```java\nint x =-9 ;\nint y = -10;\nSystem.out.println(x&1);\nSystem.out.println(y&1);\n```\n\n> 输出：1，0\n\n#### 1<<3\n\n代表的含义是将 1 左移 3 位，即 0001 ---->1000 则为 2^3 为 8\n\n```java\nSystem.out.println(1<<3);\n```\n\n> 输出：8\n\n#### HashMap\n\n创建一个 HashMap,两种数据类型\n\n```\nHashMap<Integer,Integer> map = new HashMap<Integer,Integer>();\n```\n\n往 hashmap 里面插入数据\n\n```java\nfor (int num : arr){\n     map.put(num, map.getOrDefault(num, 0) + 1);//如果没有则添加，如果有则加1\n }\n```\n\n遍历 Hashmap,查询值为 k 的元素\n\n```\nfor (int k : hashmap.keySet())\n      if (hashmap.get(k) == 1) return k;\n\n```\n\n遍历 HashSet\n\n```\nset.iterator().next();//迭代器\n```\n\n## 树篇\n\n### ArrayList<List<对象类型>>\n\n```java\nList<List<Integer>> arr = new  ArrayList<List<Integer>>();\n```\n\n用来创建二维的动态数组，层次遍历用到这个。\n\n### ArrayList\n\n```java\nList<Integer> array = new ArrayList<>();\n```\n\nArrayList 类是一个可以动态修改的数组，与普通数组的区别就是它是没有固定大小的限制，我们可以添加或删除元素。<>代表的数组类型\n\n#### add()元素,括号内为需要添加的元素\n\n```java\npublic class Test {\n    public static void main(String[] args) {\n        List<String> array = new ArrayList<>();\n        array.add(\"大家好我是袁厨\");\n        System.out.println(array);\n    }\n}\n```\n\n> 输出：大家好我是袁厨\n\n#### get()\n\nget()函数用于获取动态数组的元素，括号内为索引值\n\n```java\npublic class Test {\n    public static void main(String[] args) {\n        List<String> array = new ArrayList<>();\n        array.add(\"大家好我是袁厨\");\n        System.out.println(array.get(0));//获取第一个元素\n    }\n}\n\n```\n\n> 输出：大家好我是袁厨\n\n#### set()\n\nset()用于修改元素，括号内为索引值\n\n```\npublic class Test {\n    public static void main(String[] args) {\n        List<String> array = new ArrayList<>();\n        array.add(\"大家好我是袁厨\");\n        array.set(0,\"祝大家天天开心\")\n        System.out.println(array.get(0));//获取第一个元素\n    }\n}\n```\n\n> 输出：祝大家天天开心\n\n#### remove()\n\n用来删除数组内的元素\n\n```\npublic class Test {\n    public static void main(String[] args) {\n        List<String> array = new ArrayList<>();\n        array.add(\"大家好我是袁厨\");\n        array.add(\"祝大家天天开心\");\n        array.remove(0);\n        System.out.println(array);//获取第一个元素\n    }\n}\n```\n\n> 输出：祝大家天天开心\n\n#### isEmpty()\n\nisEmpty()函数判断是否为空，这个函数用到的地方很多，队列和栈的时候总会用。总是会在 while 循环中使用\n\nwhile(!queue.isEmpty()){\n\n//用来判断队列是否为空的情况\n\n}\n\n```\npublic class Test {\n    public static void main(String[] args) {\n        List<String> array = new ArrayList<>();\n        array.add(\"大家好我是袁厨\");\n        array.add(\"祝大家天天开心\");\n        array.remove(0);\n        System.out.println(array.isEmpty());//获取第一个元素\n    }\n}\n```\n\n输出：false\n\n#### clear()\n\n该函数用来清空动态数组\n\n```\nArrayList<String> sites = new ArrayList<>();\n        sites.add(\"袁厨不帅\");\n        sites.add(\"袁厨不帅\");\n        sites.add(\"袁厨不帅\");\n        System.out.println(sites);\n        // 删除所有元素\n        sites.clear();\n        System.out.println(sites);\n```\n\n> ```\n> 删除前：[袁厨不帅，袁厨不帅，袁厨不帅]删除后：[]\n> ```\n\n#### sort()\n\n该函数用来给动态数组排序，这个有时也会用到。\n\n```\npublic class leetcode {\n    public static void main(String[] args) {\n         int[] arr = {4,5,1,3,6};\n         Arrays.sort(arr);\n         for(int x : arr){\n             System.out.println(x);\n         }\n\n    }\n}\n```\n\n> 输出：1，3，4，5，6\n\n## 字符串篇\n\n### StringBuffer\n\nStringBuilder 类在 Java 5 中被提出，它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的（不能同步访问）。由于 StringBuilder 相较于 StringBuffer 有速度优势，所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下，则必须使用 StringBuffer 类。\n\n```Java\npublic class Test{\n  public static void main(String args[]){\n    StringBuffer sBuffer = new StringBuffer(\"我的\");\n    sBuffer.append(\"名字\");\n    sBuffer.append(\"是\");\n    sBuffer.append(\"袁厨\");\n    sBuffer.append(\"大家点个关注吧\");\n    System.out.println(sBuffer);\n  }\n}\n```\n\n> 输出：我的名字是袁厨大家点个关注吧\n\nString 中的字符串是不允许修改的，这个 StringBuffer 可以进行修改，做字符串的题目时会经常用到，树的题目中也偶尔会遇到\n\n### charAt(i)\n\ncharAt() 方法用于返回指定索引处的字符。索引范围为从 0 到 length() - 1。\n\n```java\npublic class Test {\n    public static void main(String[] args) {\n        String s = \"大家好我是袁厨，点个关注哦\";\n        char result = s.charAt(6);\n        System.out.println(result);\n    }\n}\n```\n\n> 输出：厨\n\n这个函数的用法，就跟我们根据数组的索引输出值一样。在字符串题目中也比较常用。\n\n### s.charAt(index)-'0'\n\n这个函数的用途是将字符串索引值变成 int 型。知道这个可以大大提高刷题效率。大家可以掌握一下。\n\n```java\npublic class Test {\n    public static String getType(Object test) {\n        return test.getClass().getName();\n    }\n    public static void main(String[] args) {\n\n        String s = \"祝大家永不脱发，点个关注哦\";\n        System.out.println(getType(s.charAt(6)-'0'));\n    }\n}\n```\n\n> 输出：java.lang.Integer\n\n### Integer.toString()\n\n该函数用于将 int 型变为 string 型，比如这个**第 9 题求回文数**的题目，我们就是先将 x 变为字符串，然后再遍历字符串\n\n```java\nclass Solution {\n    public boolean isPalindrome(int x) {\n       if(x<0){\n           return false;\n       }\n       //将int型变成string型，然后遍历字符串，不再需要使用额外数组进行存储\n       String t = Integer.toString(x);\n       int i = 0;\n       int j = t.length()-1;\n        //双指针遍历\n       while(i<j){\n           if(t.charAt(i)!=t.charAt(j)){\n               return false;\n           }\n           else{\n               i++;\n               j--;\n           }\n       }\n       return true;\n    }\n}\n```\n\n### substring()\n\nsubstring() 方法返回字符串的子字符串\n\n```java\npublic String substring(int beginIndex);\npublic String substring(int beginIndex, int endIndex);\n```\n\n表示两种情况，一种是从 beginIndex 到结尾，一种是从 beginIndex ->endIndex;\n\n```\n   String Str = new String(\"程序员爱做饭\");\n   System.out.println(Str.substring(3) );\n   System.out.println(Str.substring(4, 5) );\n```\n\n> 输出：爱做饭，做\n\n### equals()\n\nequals() 方法用于判断 Number 对象与方法的参数和类型是否相等。\n\n```\npublic static void main(String args[]){\n        Integer x = 5;\n        Integer y = 10;\n        Integer z =5;\n        Short a = 5;\n        System.out.println(x.equals(y));\n        System.out.println(x.equals(z));\n        System.out.println(x.equals(a));\n    }\n```\n\n> 输出：false,true,false\n\n### toCharArray()\n\n```java\nString num = \"12345\";\nchar[] s = num.toCharArray();//将字符串变为char数组\nSystem.out.println(s);\n```\n\n> 输出：12345\n\n### char 数组变为 String\n\n```java\nString newstr = new String (arr2,start,end);\n```\n\n### indexOf\n\n- **int indexOf(String str):** 返回指定字符在字符串中第一次出现处的索引，如果此字符串中没有这样的字符，则返回 -1。\n- **int indexOf(String str, int fromIndex):** 返回从 fromIndex 位置开始查找指定字符在字符串中第一次出现处的索引，如果此字符串中没有这样的字符，则返回 -1\n\n```\nString s = \"LkLLAAAOOO\";\nreturn s.indexOf(\"LLL\");\n```\n\n> 返回-1\n\n## 栈（Stack）\n\n#### 创建栈\n\n```\nStack<TreeNode> stack = new Stack<TreeNode>();//创建栈\n```\n\n上面的是创建新栈，栈的变量类型为 TreeNode,我们用深度优先遍历树来举例\n\n#### push()\n\n把项压入栈顶部\n\n```\n stack.push(root);//将根节点入栈\n```\n\n#### pop()\n\n移除堆栈顶部的对象，并作为此函数的值返回该对象。\n\n```java\nTreeNode temp = stack.pop();//将栈顶元素出栈，赋值TreeNode变量temp\n```\n\npeek()\n\n查看栈顶元素但是不移除它\n\n```java\n stack.push(root);\n TreeNode temp2 = stack.peek();\n System.out.println(temp2.val);\n```\n\n#### 出栈操作\n\n```\nStringBuilder str = new StringBuilder();\n//遍历栈\nwhile(!stack.isEmpty()){\n    str.append(stack.pop());\n }\n //反转并变为字符串\nreturn str.reverse().toString();\n```\n"
  },
  {
    "path": "animation-simulation/一些分享/区块链详解.md",
    "content": "最近总是能在一些网站上看到比特币大涨的消息，诺，这不都涨破 20000 美元啦。\n\n最近比特币涨势喜人，牵动着每一位股民的心，持有的老哥后悔说当时我咋就没多买点呢，不然明天早饭又能多加个鸡蛋啦，没持有的呢，就在懊恼后悔当时为啥就没买入呢？这不我女朋友也看到新闻了，说比特币最近涨那么厉害，咱们要不买两个呀！然后这个总是听到的比特币到底是什么东西呀？\n\n你说那个比特币呀，我也不是很懂，知道一点点，我给你讲一下我知道的吧。\n\n**注：本文和股票无关，单纯的介绍一下比特币原理，投资有风险，入场需谨慎**\n\n> 关键词 ：比特币，去中心化，挖矿，区块链，双重支付，最长链原则，工作量证明\n\n我先给你说一下比特币的历史吧。\n\n> 2008 年爆发全球金融危机，同年 11 月 1 日，一个自称中本聪（Satoshi Nakamoto）的人在 P2P foundation 网站上发布了比特币白皮书《比特币：一种点对点的电子现金系统》 陈述了他对电子货币的新设想——比特币就此面世。2009 年 1 月 3 日，比特币创世区块诞生。\n\n你平时不是会把每天的收入和支出记在自己的小本本上，我们称之为记账。我们平常在消费的时候，银行也会为我们记录这条交易记录及交易后银行卡里的余额。然后我们会通过银行卡里数字来评估自己拥有的财富。所以我们拥有多少财富都通过银行的记账本来决定的。\n\n**中本聪**在**2008**年提出，其实我们可以不需要一个**中心化**的记账系统，不需要以某个人或者机构为中心来帮我们记账，我们可以**去中心化**，每一个人的账本都是透明公开的，这就叫做**去中心化电子记账系统**。下面我们通过一个例子来进行描述。\n\n![区块](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/区块.3wdvlln33q60.png)\n\n### 1.那你说的那个区块链到底是什么东西呀，我不是很懂哎？\n\n我们对上图进行解析，A,B,C,D，四个小伙伴进行交易，首先 A 支付 5 个比特币给 B，那么他需要将这条交易信息发送给每位小伙伴，同理 B 和 C，C 和 D 的交易也要传送给所有的小伙伴，用户会将这些交易信息记录下来，并打包成块，我们称之为**区块**，（区块大小约为 1M，约 4000 条左右交易记录），当块存满时我们将这个块接到以前的交易记录上，形成一条链，过一段时间再把新的块接到它后面，我们称这条链为**区块链**，如下图。\n\n![区块链](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/区块链.2s1tuiwa4ba0.png)\n\n好啦，我们大概了解什么是区块链了。\n\n### 2.好啦我知道什么是区块链了，但是那些用户为什么要记录交易信息呢？\n\n记账奖励：每个用户都可以去记账，如果某个用户进行记帐则会奖励他一些手续费，比如 A 和 B 交易 10 个比特币，A 就需要多支出一点点给为其记录的人。其实现实生活中，我们使用银行卡时也会有手续费,这里的手续费是支付给银行。\n\n打包（将交易记录打包成块）奖励：打包者只能有一位，完成打包的那一位能够获得**打包奖励**，\n\n### 3.哦，知道了，那打包一次能获得多少奖励呢？\n\n2008 年刚提出这个系统时，奖励方案如下\n\n每十分钟打一个包，最开始的时候，每打一个包会奖励打包者 50 个比特币，过了四年之后，每打一个包奖励 25 个比特币，再过四年的则奖励 12.5 个比特币，以此类推。\n\n### 4.哇，那么多，那世界上一共有多少个比特币呢？\n\n一个包奖励 50 个比特币，一个小时 6 个包，一天 24 小时，一年 365 天 ，每隔四年减半，则计算公式如下\n\n![微信图片_20201218150122](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/微信图片_20201218150122.1hx8euiaa9uo.png)\n\n总数大概为 2100 万个比特币。\n\n### 5.因为我们有手续费和打包费的奖励机制，所以大家都抢着打包，但是打包者只能有一个人，那么我们应该让谁打包呢？\n\n中本聪提出了一个**工作量证明**的办法，说白了就是想打包的用户都要去做一个很难的数学题，谁先做出来，谁就能获得这个打包的权力。打包者就能够获得奖励，但是这个题目很特别，就是我们任何人都不能用脑子把他做出来，我们只能一个数一个数的去尝试，直到你把这个数尝试出来，那么你就获得了奖励，这个过程就是我们经常说的挖矿。\n\n### 6.你说的那个挖矿的原理是怎样的呢，我想不通？\n\n刚才我们说挖矿的原理其实是让我们做一道数学题，谁先做出来算谁的，这个题目还不拼智商，需要我们一个一个的试，取决于咱们 CPU 的运行速度。那么具体原理是什么呢？\n\n**这里可以选择性阅读，不感兴趣可以直接跳到第 8 个问题**\n\n介绍原理之前，我们先来了解一下哈希函数，大家可以去看一下我之前之前的文章《[学生物的女朋友都能看懂的哈希表总结！](http://mp.weixin.qq.com/s?__biz=Mzg3NDQ2NDY3MA==&mid=2247486429&idx=1&sn=449b0482f89a4b2778cbd5c5d6dcc67f&chksm=ced11f2cf9a6963ab9ce6331c4bec69775e347ef03e4bae46e93113f6e99d18c83f45359a04c&scene=21#wechat_redirect)》，里面对哈希函数做出了简要描述。下面我们再来了解一下数字摘要。\n\n数字摘要就是采用**单向 Hash 函数**将需要加密的明文“摘要”成一串固定长度的密文这一串密文又称为数字指纹，它有固定的长度，而且不同的明文摘要成密文，其结果总是不同的，而同样的明文其摘要必定一致。\n\n通俗点说就是，一个字符串，我们通过 hash 函数计算，得到一个固定长度的密文，不同的字符串得到的密文不同，哪怕仅仅是两个字符串相差一个 0 最后的得到的密文也可能完全不同，相同的字符串会得到相同的密文。通过明文得到密文很容易，我们通过特定的哈希函数就可以，但是反过来是极其难的。\n\n下面我们简单描述一下 著名的哈希函数 SHA256 的生成摘要的过程\n\n![sha256](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/sha256.5veoxktednk0.png)\n\n我们已经了解了生成摘要的过程，那么挖矿的具体原理是什么样呢？\n\n刚才我们说到，区块链其实是一大堆交易信息，其实我们的区块里面不只有交易信息，还有头部。目前有很多人记录了系统的交易信息，然后想把自己记录的交易信息打包成块，并连接到区块链上，获得打包费。那么多人想打包，但是只能有一个人可以获得打包权，那么具体是解决了怎样的数学问题获得打包权的呢？\n\n刚才我们描述了生成密文过程，那么我们的明文，也就是输入字符串，在这里主要由什么组成呢？\n\n字符串 ：**前块头部 + 账单的信息 + 时间 + 随机数**\n\n主要有以上信息组成，前块的头部，你所记录的账单信息，时间戳，随机数组成。那么我们看，这里的组成部分对于所用用户来说，只有前块头部是固定的，账单信息因为每个人记录顺序不同也是不固定的，每个人开始的时间不一样，那么时间也是不固定的，随机数也不固定，那么既然我们的输入都是不固定的，那这个题应该怎么答呀，那怎么保证公平呢？主要通过以下方法\n\n刚才我们也说了，经过 SHA256 加密之后会得到一个 256 位的二进制数。\n\n获得打包权的那个难题就是让我们把字符串经过两次 SHA256 运算之后得到一个哈希值，哈希值要求**前 n 位**为 0，意思就是谁先算出那个前 n 位为 0 的哈希值，谁就能获得打包权。\n\n因为每个人的输入是不固定的，但是对于个人来说，他开始运算的时间是固定的，头部也是固定的，他所记录内容也是固定的，所以他只能依靠调整**随机数**来修改最后的哈希值，只能挨个试，但是如果人品爆发可能试的第一个数就能得到符合要求的哈希值，但是总的来说还是一个考察算力的题目。\n\n![两次哈希函数](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/两次哈希函数.3dv6ep2rqh00.png)\n\n### 7.那哈希值前 n 位为 0 ，这个 n 是依据什么决定的呢？\n\n这个 n 越大计算难度就越大，因为我们不能反算，只能挨个去试，每一位上出现 0 或 1 的概率都为 1/2，那么我们获得前 n 位为 0 的哈希值概率也就是 1/2 的 n 次方。\n\n当时中本聪在设计时，为了保证每十分钟出一个块，所以就会适当的调整 n, 比特币系统每过 2016 个区块之后，就会自动调整一次难度目标。如果上一个难度目标调整周期（也就是之前 2016 个区块），平均出块时间大于 10 分钟，说明挖矿难度偏高，需要降低挖矿难度；反之，前一个难度目标调整周期，平均出块时间小于 10 分钟，说明挖矿难度偏低，需要提高挖矿难度。难度目标上调和下调的范围都有 4 倍的限制。\n\n所以这个 n 是根据挖矿难度（算力）进行调整的，也就是我们矿机的算力和矿机数量等进行调整。\n\n### 8.哦，我懂了，那如果有人冒充咱们咋办，偷偷花咱们的比特币！\n\n这个问题问的好\n\n说到防止假冒，我们先来说一下身份认证，身份验证又称“验证”、“鉴权”，是指通过一定的手段，完成对用户身份的确认。指纹，人脸，签名等都是传统的认证手段，另外我们说一下比特币系统的电子签名。\n\n比特币用户在注册时会生成一个随机数，通过随机数会产生一个私钥的字符串，这个私钥又可以产生一个公钥字符串和地址，私钥和公钥是对应的，并且私钥是保密的，别人向你交易时，你只需要把你的地址发过去即可，如果你给别人交易时，则需要将你的公钥和地址一起发过去。流程图如下\n\n![公钥私钥](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/公钥私钥.1z7tkrhr4q4g.png)\n\n我们在传输记录时通过私钥加密，然后通过公钥解密，加密和解密的钥匙不一样，所以我们称之为非对称加密\n\n具体交易流程如下，例 A 支付 5 个比特币给 B\n\n![广播](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/广播.3y66ai91qa00.png)\n\n我们其他用户接收到了这个支付消息，那其他用户怎么判断这条信息是不是 A 发出的呢？不是他人冒充 A 发的呢？具体流程如下\n\n![广播对比](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/广播对比.76soh60sm2s0.png)\n\n其他用户进行对比，如果一致则认可这条消息是 A 发的，不一致则认为是别人冒充，所有用户则会拒绝这条消息。这里可能会不明白了，公钥和私钥你都发出来了解密肯定的呀，刚才我们说公钥的公开的，但是公钥是由私钥加密得到的，私钥是私密的唯一的，只有 A 用户知道自己的私钥。\n\n### 9.哇，好神奇啊，我知道了，那要是我只有 5 个比特币，同时支付给两个人咋办，每个人五个，那我岂不是赚了呀。\n\n厉害呀，这你都能想到，但是你想多啦。\n\n比如 A 只有五个比特币，他同时发了两个消息，分别是给 B 五个比特币，给 C 五个比特币，但是他总数只有 5 个，这样显然是不行的，我们称之为**双重支付**。\n\n那么我们如何解决呢？\n\n##### 余额检查\n\n![追溯](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/追溯.4lbdktlb5re0.png)追溯\n\n用户在接收到这个消息时，会先从区块链里，进行查询 A 的交易记录，得出 A 的余额是否大于交易数额，如果大于则接收，反之则拒绝。\n\n##### 解决双重支付\n\n首先我们来了解下什么是双重支付，打个比方哈，袁记菜馆第 963 家分店因为店长经营不善，要进行出售，出售的时候店长将这个房子同时卖给了两个人，但是只有一个房子，这就是**双重支付**。\n\n![双重支付](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/双重支付.2ff4ejsp3esk.png)双重支付\n\n在比特币系统中是如何解决双重支付问题的呢？我们 A 用户只有 5 个比特币，但是他几乎同时发布了两条广播，此时有些用户会先接收到第一条广播，然后进行追溯，发现 A 只有 5 个比特币，则会拒绝第二条。同理先接收到第二条广播的用户也会如此。就好比形成了两个阵营，然后两个阵营的用户进行答题，然后获得了打包权，则会将自己打的包接到区块链上，那么他所接收到的那条消息则会被整个系统认可。另一条则会放弃。\n\n比如用户 D 先接收到了第二条广播 ，A 支付给 C，然后 D 用户获得了打包权，则 D 将包接到链上，那么其余用户则会放弃自己的包，全部都认可 D 所记录的交易信息。所以此时 C 收入 5 个比特币，B 没有收入。所以我们接收到别人交易消息时，不能认为当时已经到账，要等新的块已经形成，消息被记录到主链上才可以。\n\n### 10.那如果有人偷偷篡改交易信息，那他不就成比特币最多的人了吗？\n\n想的挺全面呀，厉害呀你\n\n我们考虑一下这种情况，A 已经支付给了 B 五个比特币，但是他想把这个记录删掉，伪造一下记录。有这种可能吗？\n\n说之前我们先来描述一下比特币系统遵循的**最长链原则**，那什么是最长链原则呢？\n\n![最长链](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/最长链.608rywpq68g0.png)最长链\n\n比如上图，我们同时有两个块接到了链上，那么会有两拨人，他们都会以第一个接收到的块为准，然后两拨人继续运算，当某一拨中的某个人获得打包权之后则会将新块接到他接收的块后面，那么此时他的这个链是整个系统最长的链，则会被所有人认可，另一拨人也会来到这个最长链下面继续打包。之前的那个分支则会废弃。如果说某个人他就不想转移阵容，非得死守那个相对短的链，这样也是可以的，只要你一己之力可以对抗所有人，把你这个链变成最长链，大家则会都认可你这条链。\n\n那么我们来说一下，如何防止篡改\n\n![红色块分支](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/红色块分支.72gt7kf4gzo0.png)\n\nA 此时想要修改红色块里的交易记录，则 A 需要重新计算重新打包，创造出一个支链来，但是大家不会承认你这个支链，因为这个支链不是最长的，所以不会承认你伪造的信息，如果你非要继续往下算，什么时候你自己创造的支链长度大于世界上所有人的打包的链的长度，那么恭喜你，你伪造成功了，大家都认可你的伪造信息了，所以说理论上是可以篡改的，但是你改了之后不会被大家承认，除非你的计算能力超过了世界上其余所有的人。大家试想一下一个掌握全世界一半以上算力的人，会去干这种无聊的事吗？\n\n这下我全都懂了，那咱们快去买两个吧！\n\n你看看现在一个多少钱啦，买不起呀咱们。\n\n<u></u>\n\n另外给大家建了一个寒假刷题互相监督群，需要的可以联系我，公众号内点击一起刷题即可，为了防止广告党进入，大家记得备注【刷题】，目前人数大概有 200 来人。大家可以自行组成小队，也可以一起商量题目。多个人一起走，才能走得更远\n"
  },
  {
    "path": "animation-simulation/一些分享/厨子的2020.md",
    "content": "## 我的那些牛 X 的事\n\n在火车上无聊，写下了这篇随笔，2020 年已经过去了一段时间，这篇年度总结好像来的略微晚了一些，因为实在不知道写些什么，感觉这一年没有特别突出的成绩，也没有特别大的突破，平平淡淡。下面我试着说一下我的 2020，希望不会浪费你们的这几分钟。\n\n### **2020 印象最难忘的一件事**\n\n印象最深的一件事，应该是我疫情期间参加志愿者吧，做一些很简单的工作，站岗，登记，测温，消毒，宣传抗疫知识，虽没有什么技术含量，但也要细心严谨。\n\n参加志愿者的时间是在一月下旬，算是疫情最严重的时候，爸妈虽然担心，但还算支持，就这样安然无恙的工作了两个多星期，但是某天晚上睡觉前，总感觉身体不舒服，当时心想，我去，该不是发烧了吧，\n\n我就拿起体温计，测了一下体温，37.8！不对不对，肯定是温度计整错了，再来。我就又测了一遍,38.1！坏了，我该不会被感染了吧！说实话，当时我真以为我被感染了，真的把自己吓坏了。\n\n那天的夜格外漫长，一夜无眠，思考了很多，在半梦半醒度过了忐忑的一夜。现在想想当时可真幼稚啊。哈哈\n\n第二天一大早，我就全副武装，把自己包的严严的，来到医院后，医生问了我各种信息之后开始验血，检验科的医生一听我 38.1，着实吓了一跳，抽血时，手止不住的发抖，两次才抽血成功，\n\n抽完之后，地上湿了一小片，不是我的血，是因为医生额头上豆大的汗珠滴落在地。（没有夸张，因为我们是先在小医院初检，所以医生的心理素质没有那么高）我当时的心情却格外平静，（可能该吓得都吓完了）之后又做了一些别的检查，最后发现我没有被传染，而是得了水痘。\n\n就是那种小朋友容易得的小水泡，我也想不明白我 20 多岁的大小伙子，咋就得了水痘了。不过万幸没有被传染。然后在家休息了两个星期，痊愈之后，又继续干了一段时间的志愿者，直到解封。\n\n这件事算是给我上了一课，现在想想当时真的太幼稚啦，不过也算是给我的 2020 增添了一些别样的色彩。\n\n### **2020 最正确的一件事**\n\n最正确的事应该是学会书写，在此之前我是以读者的角度思考问题，想方设法将别人的知识搞懂，搞懂之后也就不在理会。\n\n现在的我想方设法的让别人弄懂，努力将一些不是特别容易理解的问题，包装加工，让其变的生动活泼，尽全力让其变的通俗易懂。在这个过程中我的收获是巨大的，让我对问题的理解更加透彻，注意到了之前忽略掉的一些细节。\n\n写文章从来不是一件容易的事情，把文章写好更是如此，这是之前的我没有体会到的。每次写完一篇文章，都会给自己带来满满的自豪感，就好像辛苦拉扯大了一个孩子。\n\n前辈们的每一次转载，读者的每一次点赞，都会让我感觉到努力得到了认可，促使我更加积极，用心的去输出，所以希望读者以后遇到对你们有帮助的文章（不仅仅是我的），不要吝啬你的点赞，多多鼓励一下他们。借用小林的一句话，利他必利己。\n\n### **2020 最遗憾的一件事**\n\n2020 最遗憾的事，是自己没有利用好疫情居家期间那段可以自由支配的时间，没有那时候就开始写作。\n\n在家里学习的效率是很低的，没有很强的自制力，只是按部就班的上网课，写实验报告，做课设。因为上半学期有一门课考的很差劲，所以下半学期就需要使劲学，去填那一门的坑。\n\n虽然最后如愿获得了奖学金和评优资格。但是技术上的东西学的很少，很是后悔。开学后，时间开始不受自己控制，看论文，做展板，开组会，汇报实验进度，那些不是我喜欢的，却是我不得不去做的，写东西是我喜欢做的，甚至可以在图书馆连续写 6，7 个小时，期间不看一次手机。即使这样仍感觉十分有趣。\n\n不过人成长的标志就是开始学着“身不由己”，做一些自己不是那么想做，甚至说有点讨厌的事。所以希望我以后可以合理利用我能利用的时间。控制好我所能控制的事。\n\n以上就是我的 2020，在你们看来或许都是很平常的事，但做为亲身经历者的我而言，就是我的那些牛 X 的事。\n\n### **2021 想要做的一些事**\n\n多打打球，自从写了公众号之后，把运动和娱乐的时间都用到了写作上，虽然体 型变化不大，但是明显感觉到了打球时的体力下降。\n\n- 2021 保证一周打两次球。\n\n- 和女朋友毕业后能在同一座城市找到满意的工作。\n- 坚持写作，希望 2021 可以完成 70 篇高质量原创。\n- 理财收益 2020 大概百分之 27，希望 2021 可以继续保持。\n- 完成一次半马，一直想参加，但还没有参加过，希望今年可以实现\n\n好啦，就这么些吧，多了我也完成不了，感谢各位阅读，拜了个拜。\n"
  },
  {
    "path": "animation-simulation/一些分享/学习.md",
    "content": "不知道各位有没有遇到过下面这种情况\n\n每天早上醒来，先给自己打个气，今天我一定要好好学习。打完气之后，就开始躺在床上玩手机。吃了睡，睡了吃，好不容易打开电脑学个习，看了半个小时就再次倒下。\n\n还有的时候呢？2：45 啦，我再玩一会吧，凑个整，到 3 点再学习，玩着玩着，那边你妈喊你吃晚饭了。\n\n晚上，躺在床上，再一次给自己打气，我明天一定要早起好好学习，然后一个没忍住继续玩手机，第二天又睡过头了，继续复制昨天的生活。\n\n相信大家放假之前都有在假期里大干一场的想法，可谁知回到家之后，身体开始不受控制，总想躺着。给大家总结了一波小技巧，希望对大家有一丢丢帮助。\n\n**拒绝舒适**\n\n**换掉睡衣**，或许我们不能好好学习的**罪魁祸首之一**就是身上的睡衣，行为影响态度，如果我们穿着睡衣葛优躺在沙发上的时候，我们就倾向于刷刷视频，找朋友开开黑，做一些放松的活动。但是如果你**穿上上学的衣服**，甚至说穿上校服，你离学习就更近一些，**更容易进入到类似于学校的学习状态**，**成为一个没有感情的学习机器。**\n\n**欺骗自己**\n\n有的时候我们好不容易坐到电脑前，然后我们开始纠结，翻两下书又开始背单词，事情太多，先做哪个呢？\n\n要不还是先看会手机吧，总的来说就是**计划太多，无从下手，犹豫太久，啥也没有**。**重要的不是学什么，而是学。**\n\n对我们来说更难的不是继续做，而是开始做。就比如我们**离开舒适区**，进入书房，坐在电脑前敲下第一行代码的过程，往往比我们已经开始，继续行动，要难很多很多，所以**我们可以欺骗自己，我就学十分钟，只听一节网课，只做一道算法题，总的来说就是先开始再说。**\n\n**然后刚开始学习时我们可以先做些简单的工作，提高我们的学习积极性，**帮助我们进入学习状态，**循序渐进**。如果刚一开始就做些困难工作，我们内心会对其抗拒，渐渐失去对学习的耐心。\n\n**整齐的学习环境**\n\n一个整齐的学习环境能让你。。。算了，这个我也没做到，不说了，反正还是整洁一点好。\n\n**学会休息**\n\n个人认为这一点格外重要，在家里同样需要管理好作息，管理作息不一定非要起的特别早来学习，而是**保持一个较规律的作息习惯**。你可以给自己**设定一个睡觉时间和起床时间**，完全按照这个时间执行。这样你前一天做好的规划就不会被打乱。\n\n告诉大家一个我经常用的小窍门，那就是**不躺在床上玩手机，看手机的时候坐在桌子旁，到了睡觉时间，则把手机放在桌子上，躺到床上迅速入睡**。那么你的起床时间和睡觉时间就能得到保证。因为我们**晚睡晚起的大多数情况都是赖床玩手机。**\n\n我们被闹钟吵醒之后肯定还是很困，可能会关掉桌子上的闹钟继续睡觉，这时我们可以**做几个俯卧撑，听听音乐**，就会让你困意全无。起床成功。\n\n**制定任务清单**\n\n在前一天晚上**按照轻重缓急**制定第二天的任务清单，第二天我们就可以直接按照任务清单进行工作，这样能让你快速进入学习状态。\n\n另外我建议大家尽量将**任务拆解细化**，因为我们大脑在面对庞大的工作时内心是拒绝的，**任务量越大，我们越难开始行动**，所以我们**将任务拆解细化之后，更容易让我们开始做**。其实很多时候我们迷惑的不是怎样继续当下的学习任务，而是怎样开始当下的学习任务。\n\n**不要用时间做计划**\n\n因为我们是在家里学习，**没有学校那么严格的束缚**，这时我们该想着那我得自己约束自己呀，今天我要学习六个小时，明天七个，后天八个。但是结果是这样的吗？\n\n**帕金森定理：只要还有时间，工作就会自动地占满所有可用的时间；如果你给自己安排了充裕的时间从事一项活动，你会放慢你的节奏以使用掉所有分配的时间。**\n\n当我们给我们的某一项工作规划了固定的时间之后，我们可能会出现两种情况\n\n1.拖延反正还有那么多时间可以用，先玩会手机等会再做吧。\n\n2.痛苦比如我们给自己规划了今天要学 6 个小时，这时我们可能就在想，哇，今天还有六个小时要学，好煎熬，这样或许就**会打击我们学习的积极性**，总之我们不要低估我们拖延的能力，你会发现自己真的很牛批。\n\n更好的方法是**用学习量来做规划**，比如今天刷几道题，记几个单词，看几页书等，**一个清晰明确的目标或许会让我们事半功倍。**\n\n既然都读到了这里，还不赶快行动起来，**快去学习吧**。\n\n好啦，现在我们坐到桌子前准备学习啦，那么我们有没有遇到过这种情况呢？\n\n我们学习的时候**总会走神**，一会转转笔，一会抠抠手，刷刷手机，大家**不要太自责**，这不是我们一个人的问题《单核工作法》的作者说，这是人的生理本能惹的货，**大脑会鼓励我们走神**，每次我们走神之后，大脑就会获得一份多巴胺奖励，让你爽一下。\n\n那么有没有什么办法帮助提升学习时的专注力呢？下面是一些小技巧希望能够帮到大家。\n\n**舒缓音乐**\n\n个人习惯会在工作时**听一些舒缓的音乐**，帮助我**提高专注度**，但是不一定适用所有人，大家可以试一下，另外这个歌单的名字是清华大学自习室歌单，不知道真假，俺也没去过清华，不过歌是真不错，大家可以去听一下。\n\n<img src=\"https://pic2.zhimg.com/80/v2-ed02d6887fa21ee94815240a7c40cd9d_1440w.jpeg\" alt=\"img\" style=\"zoom: 25%;\" />\n\n**动手实践**\n\n动手实践在结合我们手和脑方面非常有效，我们可以使用这种方法让自己专注，**边看书，边实践**可以带动我们的思考。比起只看书可以更好的集中我们的注意力，当我们**接受知识的速度和处理知识的速度是趋同的时候**，我们就会**忘记时间，超级专注**。\n\n为啥我们看电视的时候可以一看看几个小时，忘记时间，是因为我们接收剧情的速度和大脑处理的速度是趋同的，看的时候不用思考，越看越上瘾。\n\n**巧用工具**\n\n大家不要排斥利用软件帮助我们学习，毕竟人都是有惰性的，我们不能控制住自己的时候，可以**使用一些工具来帮助我们**，下面我给大家介绍几个我之前读大学的时候用过的软件，个人认为是有一些帮助的。\n\n<img src=\"https://pic4.zhimg.com/80/v2-74e41ddace28ffe7449da82bf329ae95_1440w.jpeg\" alt=\"img\" style=\"zoom:25%;\" />\n\n**软件名称：达目标**\n\n这是一款**设定目标的软件**，我们可以在上面设定我们想要完成的目标，然后我们每天打卡，如果**忘记打卡的次数超过我们设定的休假天数，则会挑战失败**。失败的代价是金钱。是的，我们就失去了我们的挑战金。所以这个软件可以让我们强制打卡，毕竟钱没了还是很心疼的。我们还可以围观别人的目标，**监督别人打卡，如果他挑战失败则可以分得他的挑战金。**\n\n<img src=\"https://pic2.zhimg.com/80/v2-cd03cec563095eb03e74880ff3bf936b_1440w.jpeg\" alt=\"img\" style=\"zoom:25%;\" />\n\n**软件名称：番茄 ToDo**\n\n这是一款锁机软件，我们可以用它来**强制锁机，只保留通话功能，**设定好锁机时间之后，手机就变成了一块板砖，只有等到时间结束之后，才能使用手机。如果手机**没有禅定模式**的话，则可以用这个软件代替\n\n<img src=\"https://pic1.zhimg.com/80/v2-41cbd40c65ee96ac81768b757f024f58_1440w.jpeg\" alt=\"img\" style=\"zoom:25%;\" />\n\n**软件名称：Forest**\n\n这也是一款能够**帮助我们自律**的软件，我们可以设定学习时长，**如果期间玩手机，则会让你的小树枯萎，完成目标则会让小树长大**，有点类似养成型游戏。这是同学推荐的，他经常用，听说效果还不错，感兴趣的同学可以试试。\n\n好啦，我知道的大概就这么多啦，希望大家可以利用好寒假，冲冲冲！\n"
  },
  {
    "path": "animation-simulation/一些分享/考研分享.md",
    "content": "## 终于轮到我了，关于计算机考研的一点点经验\n\n之前有虎扑老哥让我写一下考研经验分享，但是距离我考研已经好几年了，记忆有些模糊，所以就请了一位跨考浙大计算机并成功上岸的学弟为大家分享。希望对准备考研的学弟学妹有一丢丢帮助。\n\n下面是学弟的分享\n\n---\n\n先说明一下自己的情况，南开材料跨考浙大计算机学硕，初试 411 分，已经拟录取，接受算法基地老哥的邀请，分享一下自己关于考研的一点点经验\n\n主要分三个部分来说吧，择校，初试科目复习以及整个过程中的心态。\n\n## 择校\n\n说来惭愧，择校这个事情，我好像真没什么经验可以分享，知道了自己平时成绩不够保研，然后对于本科材料学科也不是很感兴趣，\n\n加上从小就对计算机很有兴趣，高中时候也参加过一段时间的信息竞赛，所以决定跨考计算机专业，\n\n但刚开始定的目标是本校的计算机学院，六月份返校之后深入了解发现本校计算机招生人数很少（一年就两三个），感觉风险还挺大的，于是和山大计算机本科的高中同学询问，知道浙大计算机每年招收考研生挺多。\n\n自己是南方人也对浙大印象挺好，于是便决定了报考浙大。（马后炮：希望大家不要学我，现在回想起来还是觉得有点悬，信息收集的太少会让自己整个复习过程都有压力）。\n\n**信息收集对于考研来说是非常重要的，大家考研之前要充分调查自己的目标院校，主要途径，该校的师兄师姐，论坛，官网，官方公众号，考研群等。**\n\n![微信截图_20210406121324](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210406121324.1sw682g6oabk.png)\n\n## 初试科目复习\n\n我的初试科目是政治、英语一、数学一、408\n\n因为疫情，去年上半年直接在家里，而我又是一个在家里从来不学的人，一本张宇的基础三十讲，从过年到六月份看了十二章，\n\n408 也是只买了王道的课压根就没打开，在家里每天就是打游戏和吃东西，等到 6.8 号返校开始全面复习时，才发现自己的进度非常的慢，\n\n导致一直都有一点点担心自己的进度（但是也不要一味的赶进度，我还是踏踏实实的学，后来的结果也没有很差）开始全面复习之后作息就比较规律了。\n\n早上七点二十起床，八点钟开始上午自习，十二点结束自习，开始吃饭午休，下午两点开始自习，六点吃饭，休息，七点开始晚上的自习，一直到晚上十一点，一天十二个小时，从六月八号到十二月二十五号，每天雷打不动。\n\n### 政治\n\n关于具体的每个学科的复习，首先，政治，我的政治很差，一千题刷了两遍，知识点精讲精练也过了三遍，但是最后客观题还是扣了十五分，导致只有 67，不过主要是错在时政的选择题（我个人属于有点喜欢钻牛角尖的，所以会纠结一些很奇怪的方面），\n\n虽然我政治考的很差，但我觉得 **1000 题和肖四肖八 **还是很重要的，毕竟大题接近于进场默写，时政方面我确实没有去管于是也在这里吃了亏。\n\n![u=478002692,2367127772&fm=26&gp=0](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/u=478002692,2367127772&fm=26&gp=0.6x1r5kdl55c0.jpg)\n\n### 英语\n\n然后就是英语一，我的英语很烂，虽然高考的时候有 137 分，但是就属于那种，短时间的应试，并不是自己语言能力的提高，四级四百多分，六级考了两遍最终也是四百九十多分，英语就死抓着真题硬啃就够了，再搭配上单词的背记。\n\n但我太懒了，单词背了一遍就没背了最终也是吃了亏，刚开始刷真题的时候，一套题客观题基本就拿个二十几分，那段时间每天做完英语对答案就会心慌，害怕自己因为英语拖后腿考不上研究生，我是两天一个周期，第一天做完对答案，第二天整张卷子翻译并且找出生词。\n\n刚开始从一张卷子有一两百个不认识的慢慢的到几十个到最后基本都认识，可以感受到自己对于考研词汇的掌握是在不断提升的。\n\n第二轮刷真题就不怎么在意选项是啥了，毕竟题都背的下来，主要就是看自己能否顺畅在心中翻译理解卷子中的文章，再有就是分析阅读题出题逻辑的角度，其实如果文章意思能读懂，每个人会出错的方面基本不会变，多分析自己错的题，找出自己逻辑与正确答案逻辑的差异，并且让自己的逻辑往正确答案靠，养成试卷的思维，我认为是很重要的。\n\n我把 97 年到 15 年的英语真题都刷了两边，最后冲刺刷 16 到 20 的卷子每张卷子客观题都只错了十分之内，不过今年的题还是给我上了一课 hhhh，惩罚了我不去背单词，搞得自己阅读题很多都没有看懂，最终拿了 70 分也算是很一般了，大家一定可以做的比我更好。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210406105202.4xbxgtbai5q0.png)\n\n### 数学\n\n接下来就是数学，我高中的数学倒是学的挺不错，但是上了大学数学完全没有学，开始复习数学的时候，啥都忘了，连三角函数的基本值那些都已经忘了，于是花了很长一段的时间看基础的高数视频和高数基础教程。\n\n哦对了，关于考研跟哪位数学老师，我觉得这是很个人的事情，市面上比较有名的老师教学肯定都是没问题的，得看你自己喜欢什么样的风格，我最终选择了张宇，因为他讲课很有意思，不会让我打瞌睡，我从六月份开始花了一个半月把基础课程看完。\n\n这期间跟着视频做例题和书上的习题，然后第二轮看提高的教材和提高的视频，这期间除了做提高的教材，还搭配刷配套的 1000 题，关于题集的选择也是很个人的，我刚开始选的是 1800 题，但是做的太不顺了，后来换成了 1000 题，1000 题刚开始只做 AB，最后冲刺的时候才做的 C。\n\n第三轮就是开始刷真题和模拟卷以及开始收集错题，因为我的基础太差了，导致我的数学进度一直很赶，最后半个月才把错题整理完，导致我线代复习的还不是很好，最后也确实错了一个选择题和线代大题的第二问，真题我从 87 年还是多少年开始刷的，一直刷到 20 年，一天一套，做完就对答案然后整理错题，整理错题是很重要的一环.\n\n我觉得对于应试数学的提高还是很有效的，毕竟题是做不完的，但是题型只有这么多，如果能做到做一题而通一类，对于数学分数的提高还是很有作用的，模拟题我做了李林和张宇的模拟题，但是还有其他的模拟题比如**合工大**，但我实在是时间太赶了，所以就没有再做，最后的结果 141 也算还不错。\n\n![图片来源于网络](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210406105545.5fnxma7jbs80.png)\n\n### 专业课\n\n最后是 408，408 我也是从六月份开始复习的，刚开始拿着王道的教材和王道的视频过一遍，做王道书上的选择题，因为 408 的知识很系统，所以刚开始做题痛苦的一比，一章错一半是常有，第二轮自己细读教材，把知识点自己提炼写在笔记本上.\n\n同时二刷选择题然后把王道书上的大题过一遍，最后第三轮的时候刷 408 真题和王道的模拟题，我觉得 408 很像理综，数据结构和计组像物理，有思维逻辑思考的过程，操作系统和计网就和化学生物一样，一定要基础的知识稳固才能做出题，对了，如果时间充裕，基础教材也是很重要的.\n\n我就因为完全没看过基础教材，搞得今年那些很基础很基础的概念题错了，408 只要选择题不出问题，大题基本上不会错多少分，最多是数据结构的算法题有一些障碍，但直接用暴力算法也只会扣几分无伤大雅，最终我错了六个选择题 ORZ，大家不要学习我基础薄弱，最后 133 分也算不错。\n\n噢对了，如果目标院校有机试的兄弟，机试也是很重要的一部分，建议尽早复习，机试复习对于数据结构的复习帮助也是很大的。\n\n而关于复习时的时间安排，我是完全按照考试时间来的，**所有的上午都是用来复习数学，下午用来复习 408，晚上则前期安排英语卷子，后面就是政治背记和写题（晚上经常因为数学学不完被挪用时间写数学）所以我觉得大伙可以比我更早开始，比如四月五月，也不至于像我一样到最后手忙脚乱。**\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210406120028.uw608tnatkw.png)\n\n## 心态\n\n第三点就是整个过程中的心态，我觉得考研比起高考难顶的最重要的一点就是考研属于盲人摸象。\n\n你永远不知道自己到底是什么水平，只要做题时哪个地方出现瑕疵，心理就会紧张，就会想自己的竞争对手是不是会完美的做出这一题，而且长时间高强度的学习对于身心都是很大的挑战。\n\n我在考研期间压力很大老是想吃宵夜导致现在重了二十斤，而且会有无数次想要给自己放松一下，复习考研的时候正常情况不会像高考一样一直有人督促，所以自己如果想偷懒，一下一天就被荒废了，所以我觉得自己内心的渴望必须要十分强烈，才能够真的咬牙撑下这一段孤独旅程。\n\n![来源于网络](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210406120555.6q8a7ghh3kw0.png)\n\n其实整个复习过程我也不是完完全全没有放松，初期我每周日都会有一个半天啥事不干就放松，而且中间有段时间压力实在太大，晚上十二点多躺下来还在床上看火影忍者看到一两点。\n\n但是却没有缺席任何一个我计划中学习的半天，风雨无阻，当时我给自己心里说的就是“尽人事，听天命”，我不想之后再来后悔，所以我想做到能利用的每一秒钟都把握住，最后发生什么结果也都接受，每个人调节的方式都不太一样，我抛砖引玉仅供大家参考。\n\n考前一个月其实压力已经很大了，经常自己一个人在食堂吃早饭啃着个包子眼泪就止不住的往下掉，和家里人电话的时候，只要他们一问自己累不累就会哽咽，但是这些都是必经的，既然自己选择了目标，就应该为之付出应有的努力，才配得上能够实现目标。\n\n之前复习的时候还 yy 如果上岸了，该怎么来装逼，到了现在只想说，考研确实挺辛苦的，但做出选择就得有所行动，对于年级还低的兄弟，我的建议只有一个，好好学习，争取保研，考研太苦了，我这辈子不想再体验一次这样的备考，就这么多了，祝福各位心想事成，早日上岸！\n\n---\n\n厨子寄语：考研备考的过程真的很苦，深有体会，尽人事，听天命，你越渴望，机会越大。愿努力考研的你们，都能成功上岸心仪的院校。\n"
  },
  {
    "path": "animation-simulation/一些分享/软件分享.md",
    "content": "这两天读者让我介绍一下写东西用到的工具，那咱们就来看看我都用了什么工具吧。\n\nPS：这期很多利器，大家不要错过哈，总有一款适合你。\n\n---\n\n## **Typora**\n\n这个大家肯定都不陌生，我的所有文章都是用这个软件写出来的，非常喜欢这个软件，界面简洁，功能强大。\n\n是一款 markdown 神器，支持代码、表格、公式插入，支持导出各种格式文件 pdf,doc 等。\n\n![img](https://img-blog.csdnimg.cn/img_convert/1311c53c99e3792b8aa35a7b2e21c6af.gif)img\n\n上面就是软件实况，大家可以试一下，是不是看起来很不错，另外还有一些我没用过的功能，欢迎大家尝试，下面为官方网站\n\nhttps://www.typora.io/\n\n另外还有 notion 也是贼好用的软件，我哥们一直在用，我也早就下载了，一直放着没用，得抽空学学，真的很不错。\n\n还有一个国产软件 wolai，是读者推荐的，功能和 notion 差不多，也非常不错，大家可以都试一下，挑选适合自己的。\n\n推荐指数：⭐⭐⭐⭐⭐\n\n---\n\n## draw.io\n\n这个也是我写东西的主力军，从林总那里知道的，非常牛批，原来我还用 PPT 和 Process 画图，自从有了这个，其他的再也没用过了，\n\n画的图贼好看，这也是你们问的最多的，呐，大家快拿去用吧，具体功能我就不介绍啦，大家一看便知。\n\n![img](https://img-blog.csdnimg.cn/img_convert/465734f03a28312cc06f6cf3a1d77d4d.png)\n\n我平常画图都是在这个网站上，真的很不错，强烈推荐。\n\nhttps://app.diagrams.net/\n\n大家也可以试试 Process ，之前也一直用它，也是一个很不错的在线画图网站。\n\n推荐指数：⭐⭐⭐⭐⭐\n\n---\n\n## picx\n\n我是用的 GitHub 当的图床，所以这个神器可帮了我大忙了，是一款贼好用的图床神器，\n\n图片外链使用 jsDelivr 进行 CDN 加速。不用下载和安装，只需要一个 Github 账号，打开 PicX 官网即可配置使用\n\n![img](https://img-blog.csdnimg.cn/img_convert/f59d97a8d786309d2e822d07b66ec208.png)img\n\n![img](https://img-blog.csdnimg.cn/img_convert/a3283d30d51d4855afaf58b53e8762d3.png)\n\n刚开始用的时候才几百位小伙伴使用，当时还怕用的人太少，管理员不再继续维护，现在看来担心是多余的，越来越多的人发现这个网站啦，大家也可以试试，真的不错，大家如果有更好的可以在评论区说一下呀。\n\npicx.xpoet.cn\n\nGitHub 配置图床大家可以搜索一哈，跟着做就行啦，很简单的。\n\n推荐指数：⭐⭐⭐⭐⭐\n\n---\n\n## mdnice\n\n这个网站也帮助到我很多，我主要用它来排版，在 typora 编辑好文章之后，导入到 mdnice ，然后一键复制到公众号，省掉了排版的时间， mdnice 和 draw.io 算是号主们必备网站，站如其名真的很 nice。\n\n![img](https://img-blog.csdnimg.cn/img_convert/9633bfc05227f485a05fc51ea83d9d19.png)\n\n算是我的排版主力，一个免费好使的网站\n\nwww.mdnice.com\n\n另外还有另一个排版网站 http://md.aclickall.com/ 大家可以选择自己喜欢的。\n\n推荐指数：⭐⭐⭐⭐⭐\n\n---\n\n## carbon\n\n这个网站我刚开始用的贼多，可以把代码转化成图片，大家可以不用滑动，就能看清所有代码，但是代码比较小，后来吴师兄建议我换成代码形式，要照顾下电脑阅读的读者，图片的话大家也不好复制，后面就没有再使用了，\n\n![img](https://img-blog.csdnimg.cn/img_convert/114c73d4ef97b062b8ebeb6c724c0cc3.gif)\n\n大家可以先收藏一哈，后面需要的话就就拿出来用用，很多主题，都挺好看。\n\ncarbon.now.sh\n\n推荐指数：⭐⭐⭐⭐\n\n---\n\n## **Free Video to GIF Converter**\n\n这时一款将视频转为 gif 图的软件，比如上面的动画我就是先录屏然后再将视频转化为 gif，平常用的也比较多，另外还有一款可以直接录屏保存为 gif 的软件 ScreenToGif，不过这个用的不太多。\n\n![img](https://img-blog.csdnimg.cn/img_convert/8b5385e4f24a509b62d21719e2212875.png)\n\n这个软件的使用率也挺高，也是一块免费的软件，用着很顺手，需要的老哥可以试一哈。\n\n推荐指数：⭐⭐⭐⭐\n\n好啦，上面的软件我都给留下了网站，大家可以自己下载或者在线使用，另外我给大家打包了一份，\n\n大家可以自己下载，拜了个拜。\n\n链接：https://pan.baidu.com/s/1oQi1OECYagZyCjKtxPog9A\n提取码：jrnb\n"
  },
  {
    "path": "animation-simulation/二分查找及其变种/leetcode 81不完全有序查找目标元素(包含重复值) .md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n## **查找目标元素（含重复元素）**\n\n我们通过刚才的例子了解了，如果在不完全有序的数组中查找目标元素，但是我们的不完全有序数组中是不包含重复元素的，那如果我们的数组中包含重复元素我们应该怎么做呢？见下图\n\n![640](https://img-blog.csdnimg.cn/img_convert/9f77a33a7ff5b3fd8bbb98d77cb8a499.png)\n\n如上图，如果我们继续使用刚才的代码，则会报错这是为什么呢?我们来分析一下。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210321134336356.png)\n\n所以我们需要对其进行改进，我们只需将重复元素去掉即可，当我们的 nums[left] == nums[mid] 时，让 left ++ 即可，比如 1，3，1，1，1 此时 nums[mid] == nums[left] 则 left ++,那我们此时会不会错过目标值呢？其实并不会，只是去掉了某些重复元素，如果此时我们的目标元素是 3，则我们 left++，之后情况就变为了上题中的情况。\n\n#### [81. 搜索旋转排序数组 II](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/)\n\n#### **题目描述**\n\n假设按照升序排序的数组在预先未知的某个点上进行了旋转。\n\n( 例如，数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。\n\n编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true，否则返回 false。\n\n示例 1：\n\n> 输入：nums = [2,5,6,0,0,1,2], target = 0 输出：true\n\n示例 2：\n\n> 输入：nums = [2,5,6,0,0,1,2], target = 3 输出：false\n\n#### **题目解析**\n\n这个题目就比刚才的不含重复元素的题目多了一个去除某些重复元素的情况，当 nums[mid] == nums[left] 时，让 left++，并退出本次循环，其余部分完全相同，大家可以结合代码和图片进行理解。\n\n#### **题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public boolean search(int[] nums, int target) {\n        int left = 0;\n        int right = nums.length - 1;\n        while (left <= right) {\n            int mid = left+((right-left)>>1);\n            if (nums[mid] == target) {\n                return true;\n            }\n            if (nums[mid] == nums[left]) {\n                left++;\n                continue;\n            }\n            if (nums[mid] > nums[left]) {\n                if (nums[mid] > target && target >= nums[left]) {\n                       right = mid - 1;\n                } else if (target > nums[mid] || target < nums[left]) {\n                       left = mid + 1;\n                }\n\n            }else if (nums[mid] < nums[left]) {\n                if (nums[mid] < target && target <= nums[right]) {\n                    left = mid + 1;\n                } else if (target < nums[mid] || target > nums[right]) {\n                    right = mid - 1;\n                }\n            }\n        }\n        return false;\n    }\n}\n```\n"
  },
  {
    "path": "animation-simulation/二分查找及其变种/leetcode153搜索旋转数组的最小值.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n## **寻找最小值**\n\n这种情况也很容易处理，和咱们的 leetcode33 搜索旋转排序数组，题目类似，只不过一个需要搜索目标元素，一个搜索最小值，我们搜索目标元素很容易处理，但是我们搜索最小值应该怎么整呢？见下图\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210321134701939.png)\n\n我们需要在一个旋转数组中，查找其中的最小值，如果我们数组是完全有序的很容易，我们只需要返回第一个元素即可，但是此时我们是旋转过的数组。\n\n我们需要考虑以下情况\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/2021032113472644.png)\n\n我们见上图，我们需要考虑的情况是\n\n- 数组完全有序 nums[left] < nums[right]，此时返回 nums[left] 即可\n\n- left 和 mid 在一个都在前半部分，单调递增区间内，所以需要移动 left，继续查找，left = mid + 1；\n\n- left 在前半部分，mid 在后半部分，则最小值必在 left 和 mid 之间（见下图）。则需要移动 right ，right = mid，我们见上图，如果我们 right = mid - 1，则会漏掉我们的最小值，因为此时 mid 指向的可能就是我们的最小值。所以应该是 right = mid 。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210321134748668.png)\n\n#### [153. 寻找旋转排序数组中的最小值](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/)\n\n#### **题目描述**\n\n假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如，数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。\n\n请找出其中最小的元素。\n\n示例 1：\n\n> 输入：nums = [3,4,5,1,2]输出：1\n\n示例 2：\n\n> 输入：nums = [4,5,6,7,0,1,2] 输出：0\n\n示例 3：\n\n> 输入：nums = [1] 输出:1\n\n#### **题目解析**\n\n我们在上面的描述中已经和大家分析过几种情况，下面我们一起来看一下，[5,6,7,0,1,2,3]的执行过程，相信通过这个例子，大家就能把这个题目整透了。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210321134814233.png)\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public int findMin(int[] nums) {\n\n        int left = 0;\n        int right = nums.length - 1;\n\n        while (left < right) {\n\n            if (nums[left] < nums[right]) {\n                return nums[left];\n            }\n            int mid = left + ((right - left) >> 1);\n            if (nums[left] > nums[mid]) {\n                right = mid;\n            } else {\n                left = mid + 1;\n            }\n        }\n        return nums[left];\n\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int findMin(vector <int> & nums) {\n        int left = 0;\n        int right = nums.size() - 1;\n        while (left < right) {\n            if (nums[left] < nums[right]) {\n                return nums[left];\n            }\n            int mid = left + ((right - left) >> 1);\n            if (nums[left] > nums[mid]) {\n                right = mid;\n            } else {\n                left = mid + 1;\n            }\n        }\n        return nums[left];\n    }\n};\n```\n"
  },
  {
    "path": "animation-simulation/二分查找及其变种/leetcode33不完全有序查找目标元素(不包含重复值).md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n# **不完全有序**\n\n## **查找目标元素（不含重复元素）**\n\n之前我们说二分查找需要在完全有序的数组里使用，那么不完全有序时可以用吗？\n\n例：\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/案例1.2wan88b4sdk0.png)\n\n上面的新数组虽然不是完全有序，但是也可以看成是由一个完全有序的数组翻折得到的。或者可以理解成两个有序数组，且第二个数组的最大值小于第一的最小值，我们将其拼接，拼接成了一个不完全有序的数组，在这个数组中我们需要找到 target ，找到后返回其索引，如果没有找到则返回 -1；\n\n我们第一次看到这种题目时，可能会想到，我们只需要挨个遍历就好啦，发现后返回索引即可，这样做当然是可以滴，那么我们可不可以使用二分查找呢？\n\n下面我们看一下解决该题的具体思路。\n\n首先我们设想一下 mid 值会落到哪里，我们一起来想一下。\n\n是不是只有两种情况，和 left 在一个数组，同时落在 数组 1 或同时在 数组 2，或者不在一个数组， left 在数组 1，mid 在数组 2。想到这里咱们这个题目已经完成一半了。\n\n![mid值情况](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/mid值情况.3879bq8s3xk0.png)\n\n那么我们先来思考一下，？我们可以根据 nums[mid] 和 nums[left] 判断，是因为我们的 mid 一定是会落在 left 和 right 之间，那如果 nums[mid] >= nums[left] 时，说明他俩落在一个数组里了，如果 nums[mid] < nums[left] 时，说明他俩落在了不同的数组，此时 left 在数组 1 mid 在数组 2.\n\n注：left 和 mid 落在同一数组时，不能是 left 在 数组 2 ，mid 在 数组 1 呢？因为咱们的 mid 是通过 left 和 right 的下标求得，所以应该在 left 和 right 中间\n\n如果我们的 mid 和 left 在同一个数组内时？咱们的 target 会有几种情况呢？我们通过都落在 数组 1 举例。\n\n![left左](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/left左.6kl90uroee40.png)\n\n无非也是两种情况，用我们上面的例子来说，\n\n1.**落在 mid 的左边**，当前例子中 情况是落在 [4,7）区间内，即 4 <= target < 7 ，也就是 target >= nums[left] && target < nums[mid]，此时我们让 right = mid -1，让 left 和 right 都落到数组 1 中，下次查找我们就是在数组 1 中进行了，完全有序，\n\n2.**落在 mid 的右边**，此时例子中 target 不落在 [4,7）区间内，那就 target = 8 或 0 <= target <= 2 （此时我们的 target 均小于 nums[left]） 两种情况，也就是 target > nums[mid] || target < nums[left] 此时我们让 left = mid + 1 即可，也是为了慢慢将 left 和 right 指针赶到一个有序数组内。\n\n那我们在来思考一下当 mid 值落在 **数组 2** 中时，target 会有几种情况呢？其实和上面的例子思路一致，情况相反而已。\n\n![right右](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/right右.3yvrwxloi3c0.png)\n\n1. target <= nums[right] && target > nums[mid]\n\n   > 这里和上面的对应，此时的情况就是整个落在右半部分，我们下次就可以在数组 2 内进行查找。\n\n2. target > nums[right] || target < nums[mid]\n\n   > 这里就是和上面的第二种情况对应，落在 mid 的左半部分，我们尽量将两个指针赶到一起\n\n希望我的表达能够让大家对这个变种理解透彻，如果没能让各位理解，或者有表达不当的地方欢迎各位批评指导。然后我们一起来做一下 leetcode 33 题吧。\n\n#### [33. 搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/)\n\n#### 题目描述\n\n给你一个整数数组 nums ，和一个整数 target 。\n\n该整数数组原本是按升序排列，但输入时在预先未知的某个点上进行了旋转。（例如，数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] ）。\n\n请你在数组中搜索 target ，如果数组中存在这个目标值，则返回它的索引，否则返回 -1 。\n\n示例 1：\n\n> 输入：nums = [4,5,6,7,0,1,2], target = 0\n> 输出：4\n\n示例 2：\n\n> 输入：nums = [4,5,6,7,0,1,2], target = 3\n> 输出：-1\n\n示例 3：\n\n> 输入：nums = [1], target = 0\n> 输出：-1\n\n#### 题目解析\n\n这个题目的解答方法，咱们在上面已经有所描述，下面我们来看一下下面这个例子的代码执行过程吧.\n\n> 输入 nums = [4,5,6,7,8,0,1,2] target = 8\n\n下面我们看题目代码吧，如果还没有完全理解的同学，可以仔细阅读 if ，else if 里面的语句，还有注释，一定可以整透的。\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int search(int[] nums, int target) {\n        //左右指针\n        int left = 0;\n        int right = nums.length - 1;\n        while (left <= right) {\n            int mid = left+((right-left)>>1);\n            if (nums[mid] == target) {\n                return mid;\n            }\n            //落在同一数组的情况，同时落在数组1 或 数组2\n            if (nums[mid] >= nums[left]) {\n                //target 落在 left 和 mid 之间，则移动我们的right，完全有序的一个区间内查找\n                if (nums[mid] > target && target >= nums[left]) {\n                       right = mid - 1;\n                // target 落在right和 mid 之间，有可能在数组1， 也有可能在数组2\n                } else if (target > nums[mid] || target < nums[left]) {\n                       left = mid + 1;\n                }\n            //不落在同一数组的情况，left 在数组1， mid 落在 数组2\n            }else if (nums[mid] < nums[left]) {\n                //有序的一段区间，target 在 mid 和 right 之间\n                if (nums[mid] < target && target <= nums[right]) {\n                    left = mid + 1;\n                // 两种情况，target 在left 和 mid 之间\n                } else if (target < nums[mid] || target > nums[right]) {\n                    right = mid - 1;\n                }\n            }\n        }\n        //没有查找到\n        return -1;\n\n    }\n}\n```\n"
  },
  {
    "path": "animation-simulation/二分查找及其变种/leetcode34查找第一个位置和最后一个位置.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n## 查找元素第一个位置和最后一个位置\n\n上面我们说了如何使用二分查找在数组或区间里查出特定值的索引位置。但是我们刚才数组里面都没有重复值，查到返回即可，那么我们思考一下下面这种情况\n\n![二分查找变种一](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找变种一.3axfe8rq0ei0.png)\n\n此时我们数组里含有多个 5 ，我们查询是否含有 5 可以很容易查到，但是我们想获取第一个 5 和 最后一个 5 的位置应该怎么实现呢？哦！我们可以使用遍历，当查询到第一个 5 时，我们设立一个指针进行定位，然后到达最后一个 5 时返回，这样我们就能求的第一个和最后一个五了？因为我们这个文章的主题就是二分查找，我们可不可以用二分查找来实现呢？当然是可以的。\n\n#### [34. 在排序数组中查找元素的第一个和最后一个位置](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)\n\n#### 题目描述\n\n> 给定一个按照升序排列的整数数组 nums，和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。\n>\n> 如果数组中不存在目标值 target，返回 [-1, -1]。\n\n示例 1：\n\n> 输入：nums = [5,7,7,8,8,10], target = 8\n> 输出：[3,4]\n\n示例 2：\n\n> 输入：nums = [5,7,7,8,8,10], target = 6\n> 输出：[-1,-1]\n\n示例 3：\n\n> 输入：nums = [], target = 0\n> 输出：[-1,-1]\n\n#### 题目解析\n\n这个题目很容易理解，我们在上面说了如何使用遍历解决该题，但是这个题目的目的就是让我们使用二分查找，我们来逐个分析，先找出目标元素的下边界，那么我们如何找到目标元素的下边界呢？\n\n我们来重点分析一下刚才二分查找中的这段代码\n\n```java\n  if (nums[mid] == target) {\n       return mid;\n  }else if (nums[mid] < target) {\n      //这里需要注意，移动左指针\n      left  = mid + 1;\n  }else if (nums[mid] > target) {\n      //这里需要注意，移动右指针\n      right = mid - 1;\n  }\n```\n\n我们只需在这段代码中修改即可，我们再来剖析一下这块代码，nums[mid] == target 时则返回，nums[mid] < target 时则移动左指针，在右区间进行查找， nums[mid] > target 时则移动右指针，在左区间内进行查找。\n\n那么我们思考一下，如果此时我们的 nums[mid] = target ,但是我们不能确定 mid 是否为该目标数的左边界，所以此时我们不可以返回下标。例如下面这种情况。![二分查找下边界](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找下边界.m9m083jempc.png)\n\n此时 mid = 4 ，nums[mid] = 5,但是此时的 mid 指向的并不是第一个 5，所以我们需要继续查找 ，因为我们要找\n\n的是数的下边界，所以我们需要在 mid 的值的左区间继续寻找 5 ，那我们应该怎么做呢？我们只需在\n\ntarget <= nums[mid] 时，让 right = mid - 1 即可，这样我们就可以继续在 mid 的左区间继续找 5 。是不是听着有点绕，我们通过下面这组图进行描述。\n\n![左边界1](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/左边界1.5o2ihokjfg80.png)\n\n![左边界2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/左边界2.5wazlfm298s0.png)\n\n其实原理很简单，就是我们将小于和等于合并在一起处理，当 target <= nums[mid] 时，我们都移动右指针，也就是 right = mid -1，还有一个需要注意的就是，我们计算下边界时最后的返回值为 left ，当上图结束循环时，left = 3，right = 2，返回 left 刚好时我们的下边界。我们来看一下求下边界的具体执行过程。\n\n**动图解析**\n\n![二分查找下边界](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找下边界.u1cidx99yio.gif)\n\n```java\nint lowerBound(int[] nums, int target) {\n        int left = 0, right = nums.length - 1;\n        while (left <= right) {\n            //这里需要注意，计算mid\n            int mid = left + ((right - left) >> 1);\n            if (target <= nums[mid]) {\n                //当目标值小于等于nums[mid]时，继续在左区间检索，找到第一个数\n                right = mid - 1;\n\n            }else if (target > nums[mid]) {\n                //目标值大于nums[mid]时，则在右区间继续检索，找到第一个等于目标值的数\n                left = mid + 1;\n\n            }\n        }\n        return left;\n    }\n```\n\n计算上边界时算是和计算上边界时条件相反，\n\n计算下边界时，当 target <= nums[mid] 时，right = mid -1；target > nums[mid] 时，left = mid + 1；\n\n计算上边界时，当 target < nums[mid] 时，right = mid -1; target >= nums[mid] 时 left = mid + 1;刚好和计算下边界时条件相反，返回 right。\n\n**计算上边界代码**\n\n```java\nint upperBound(int[] nums, int target) {\n        int left = 0, right = nums.length - 1;\n        while (left <= right) {\n            //求mid\n            int mid = left + ((right - left) >> 1);\n            //移动左指针情况\n            if (target >= nums[mid]) {\n                 left = mid + 1;\n            //移动右指针情况\n            }else if (target < nums[mid]) {\n                right = mid - 1;\n            }\n\n        }\n        return left;\n    }\n```\n\n#### **题目完整代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] searchRange (int[] nums, int target) {\n         int upper = upperBound(nums,target);\n         int low = lowerBound(nums,target);\n         //不存在情况\n         if (upper < low) {\n             return new int[]{-1,-1};\n         }\n         return new int[]{low,upper};\n    }\n    //计算下边界\n    int lowerBound(int[] nums, int target) {\n        int left = 0, right = nums.length - 1;\n        while (left <= right) {\n            //这里需要注意，计算mid\n            int mid = left + ((right - left) >> 1);\n            if (target <= nums[mid]) {\n                //当目标值小于等于nums[mid]时，继续在左区间检索，找到第一个数\n                right = mid - 1;\n\n            }else if (target > nums[mid]) {\n                //目标值大于nums[mid]时，则在右区间继续检索，找到第一个等于目标值的数\n                left = mid + 1;\n\n            }\n        }\n        return left;\n    }\n    //计算上边界\n    int upperBound(int[] nums, int target) {\n        int left = 0, right = nums.length - 1;\n        while (left <= right) {\n            int mid = left + ((right - left) >> 1);\n            if (target >= nums[mid]) {\n                 left = mid + 1;\n            }else if (target < nums[mid]) {\n                right = mid - 1;\n            }\n        }\n        return right;\n    }\n}\n```\n"
  },
  {
    "path": "animation-simulation/二分查找及其变种/leetcode35搜索插入位置.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [35. 搜索插入位置](https://leetcode-cn.com/problems/search-insert-position/)\n\n#### 题目描述\n\n> 给定一个排序数组和一个目标值，在数组中找到目标值，并返回其索引。如果目标值不存在于数组中，返回它将会被按顺序插入的位置。\n>\n> 你可以假设数组中无重复元素。\n\n示例 1:\n\n> 输入: [1,3,5,6], 5\n> 输出: 2\n\n示例 2:\n\n> 输入: [1,3,5,6], 2\n> 输出: 1\n\n示例 3:\n\n> 输入: [1,3,5,6], 7\n> 输出: 4\n\n示例 4:\n\n> 输入: [1,3,5,6], 0\n> 输出: 0\n\n#### 题目解析\n\n这个题目完全就和咱们的二分查找一样，只不过有了一点改写，那就是将咱们的返回值改成了 left，具体实现过程见下图\n\n![搜索插入位置](https://img-blog.csdnimg.cn/img_convert/d806cb5199c4baeebc62bebe29d7eded.gif)\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int searchInsert(int[] nums, int target) {\n\n        int left = 0, right = nums.length-1;\n        //注意循环条件\n        while (left <= right) {\n            //求mid\n            int mid = left + ((right - left ) >> 1);\n            //查询成功\n            if (target == nums[mid]) {\n                return mid;\n            //右区间\n            } else if (nums[mid] < target) {\n                left = mid + 1;\n            //左区间\n            } else if (nums[mid] > target) {\n                right = mid - 1;\n            }\n        }\n        //返回插入位置\n        return left;\n    }\n}\n```\n\nGo Code:\n"
  },
  {
    "path": "animation-simulation/二分查找及其变种/二分查找详解.md",
    "content": "﻿> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### 什么是二分？\n\n废话不多说，让导演帮我们把镜头切到袁记菜馆吧！\n\n袁记菜馆内。。。。\n\n> 店小二：掌柜的，您进货回来了呀，哟！今天您买这鱼挺大呀！\n>\n> 袁厨：那是，这是我今天从咱们江边买的，之前一直去菜市场买，那里的老贵了，你猜猜我今天买的多少钱一条。\n>\n> 店小二：之前的鱼，30 个铜板一条，今天的我猜 26 个铜板。\n>\n> 袁厨：贵了。\n>\n> 店小二：还贵呀！那我猜 20 个铜板！\n>\n> 袁厨：还是贵了。\n>\n> 店小二：15 个铜板。\n>\n> 袁厨：便宜了\n>\n> 店小二：18 个铜板\n>\n> 袁厨：恭喜你猜对了\n\n上面的例子就用到了我们的二分查找思想，如果你玩过类似的游戏，那二分查找理解起来肯定很轻松啦，下面我们一起征服二分查找吧！\n\n# **完全有序**\n\n## 二分查找\n\n> 二分查找也称折半查找（Binary Search），是一种在有序数组中查找某一特定元素的搜索算法。我们可以从定义可知，运用二分搜索的前提是数组必须是有序的，这里需要注意的是，我们的输入不一定是数组，也可以是数组中某一区间的起始位置和终止位置\n\n通过上面二分查找的定义，我们知道了二分查找算法的作用及要求，那么该算法的具体执行过程是怎样的呢？\n\n下面我们通过一个例子来帮助我们理解。我们需要在 nums 数组中，查询元素 8 的索引\n\n```java\nint[ ]  nums = {1,3,4,5,6,8,12,14,16}; target = 8\n```\n\n> （1）我们需要定义两个指针分别指向数组的头部及尾部，这是我们在整个数组中查询的情况，当我们在数组\n>\n> 某一区间进行查询时，可以输入数组，起始位置，终止位置进行查询。\n\n![二分查找1](https://img-blog.csdnimg.cn/img_convert/b58d55a34b32a342f652792297a1e4d7.png)\n\n> （2）找出 mid，该索引为 mid =（left + right）/ 2，但是这样写有可能溢出，所以我们需要改进一下写成\n>\n> mid = left +（right - left）/ 2 或者 left + ((right - left ) >> 1) 两者作用是一样的，都是为了找到两指针的中\n>\n> 间索引，使用位运算的速度更快。那么此时的 mid = 0 + (8-0) / 2 = 4\n\n![二分查找2](https://img-blog.csdnimg.cn/img_convert/5354d4c9ea5e5bd28a77a202b4dd3445.png)\n\n> （3）此时我们的 mid = 4，nums[mid] = 6 < target,那么我们需要移动我们的 left 指针，让 left = mid + 1，下次则可以在新的 left 和 right 区间内搜索目标值，下图为移动前和移动后\n\n![](https://img-blog.csdnimg.cn/img_convert/97c584c48d6c1c06dffb94c6481f82c6.png)\n\n> （4）我们需要在 left 和 right 之间计算 mid 值，mid = 5 + （8 - 5）/ 2 = 6 然后将 nums[mid] 与 target 继续比较，进而决定下次移动 left 指针还是 right 指针，见下图\n\n![二分查找3](https://img-blog.csdnimg.cn/img_convert/471b4093db0233e41d4875fc6b2e4359.png)\n\n> （5）我们发现 nums[mid] > target，则需要移动我们的 right 指针， 则 right = mid - 1；则移动过后我们的 left 和 right 会重合，这里是我们的一个重点大家需要注意一下，后面会对此做详细叙述。\n\n![二分查找4](https://img-blog.csdnimg.cn/img_convert/2145730bf3a6373f1cf60da628bf85e6.png)\n\n> （6）我们需要在 left 和 right 之间继续计算 mid 值，则 mid = 5 +（5 - 5）/ 2 = 5 ，见下图，此时我们将 nums[mid] 和 target 比较，则发现两值相等，返回 mid 即可 ，如果不相等则跳出循环，返回 -1。\n\n![二分查找6](https://img-blog.csdnimg.cn/img_convert/0aba81887cfbc25ce9a859ba8137cd4a.png)\n\n二分查找的执行过程如下\n\n1.从已经排好序的数组或区间中，取出中间位置的元素，将其与我们的目标值进行比较，判断是否相等，如果相等\n\n则返回。\n\n2.如果 nums[mid] 和 target 不相等，则对 nums[mid] 和 target 值进行比较大小，通过比较结果决定是从 mid\n\n的左半部分还是右半部分继续搜索。如果 target > nums[mid] 则右半区间继续进行搜索，即 left = mid + 1; 若\n\ntarget < nums[mid] 则在左半区间继续进行搜索，即 right = mid -1；\n\n**动图解析**\n\n![二分查找2](https://img-blog.csdnimg.cn/img_convert/eb648f86b4ada5b32afc7a52e78d9953.gif)\n\n下面我们来看一下二分查找的代码，可以认真思考一下 if 语句的条件，每个都没有简写。\n\nJava Code:\n\n```java\npublic static int binarySearch(int[] nums,int target,int left, int right) {\n    //这里需要注意，循环条件\n    while (left <= right) {\n        //这里需要注意，计算mid\n        int mid = left + ((right - left) >> 1);\n        if (nums[mid] == target) {\n            return mid;\n        }else if (nums[mid] < target) {\n            //这里需要注意，移动左指针\n            left  = mid + 1;\n        }else if (nums[mid] > target) {\n            //这里需要注意，移动右指针\n            right = mid - 1;\n        }\n    }\n    //没有找到该元素，返回 -1\n    return -1;\n}\n```\n\nGo Code:\n\n```go\nfunc binarySearch(nums []int, target, left, right int) int {\n\t//这里需要注意，循环条件\n\tfor left <= right {\n\t\t//这里需要注意，计算mid\n\t\tmid := left + ((right - left) >> 1)\n\t\tif nums[mid] == target {\n\t\t\treturn mid\n\t\t} else if nums[mid] < target {\n\t\t\t//这里需要注意，移动左指针\n\t\t\tleft = mid + 1\n\t\t} else if nums[mid] > target {\n\t\t\t//这里需要注意，移动右指针\n\t\t\tright = mid - 1\n\t\t}\n\t}\n\t//没有找到该元素，返回 -1\n\treturn -1\n}\n```\n\n二分查找的思路及代码已经理解了，那么我们来看一下实现时容易出错的地方\n\n1.计算 mid 时 ，不能使用 （left + right ）/ 2,否则有可能会导致溢出\n\n2.while (left < = right) { } 注意括号内为 left <= right ,而不是 left < right ，我们继续回顾刚才的例子，如果我们设置条件为 left < right 则当我们执行到最后一步时，则我们的 left 和 right 重叠时，则会跳出循环，返回 -1，区间内不存在该元素，但是不是这样的，我们的 left 和 right 此时指向的就是我们的目标元素 ，但是此时 left = right 跳出循环\n\n3. left = mid + 1,right = mid - 1 而不是 left = mid 和 right = mid。我们思考一下这种情况,见下图，当我们的 target 元素为 16 时，然后我们此时 left = 7 ，right = 8，mid = left + (right - left) = 7 + (8-7) = 7，那如果设置 left = mid 的话，则会进入死循环，mid 值一直为 7 。\n\n![二分查找出差](https://img-blog.csdnimg.cn/img_convert/d7ff6aba9a1e9d673ae24667823d5770.png)\n\n下面我们来看一下二分查找的递归写法\n\nJava Code:\n\n```java\npublic static int binarySearch(int[] nums,int target,int left, int right) {\n\n    if (left <= right) {\n        int mid = left + ((right - left) >> 1);\n        if (nums[mid] == target) {\n            //查找成功\n            return  mid;\n        }else if (nums[mid] > target) {\n            //新的区间,左半区间\n            return binarySearch(nums,target,left,mid-1);\n        }else if (nums[mid] < target) {\n            //新的区间，右半区间\n            return binarySearch(nums,target,mid+1,right);\n        }\n    }\n    //不存在返回-1\n    return -1;\n}\n```\n\nGo Code:\n\n```go\nfunc binarySearch(nums []int, target, left, right int) int {\n\n\tif left <= right {\n\t\tmid := left + ((right - left) >> 1)\n\t\tif nums[mid] == target {\n\t\t\t//查找成功\n\t\t\treturn mid\n\t\t} else if nums[mid] > target {\n\t\t\t//新的区间,左半区间\n\t\t\treturn binarySearch(nums, target, left, mid-1)\n\t\t} else if nums[mid] < target {\n\t\t\t//新的区间，右半区间\n\t\t\treturn binarySearch(nums, target, mid+1, right)\n\t\t}\n\t}\n\t//不存在返回-1\n\treturn -1\n}\n```\n"
  },
  {
    "path": "animation-simulation/二分查找及其变种/二维数组的二分查找.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [74. 搜索二维矩阵](https://leetcode-cn.com/problems/search-a-2d-matrix/)\\*\\*\\*\\*\n\n下面我们来看一下另外一种变体，如何在二维矩阵里使用二分查找呢？\n\n其实这个很简单，只要学会了二分查找，这个完全可以解决，我们先来看一个例子\n\n我们需要从一个二维矩阵中，搜索是否含有元素 7，我们如何使用二分查找呢？其实我们可以完全将二维矩阵想象成一个有序的一维数组，然后用二分，，比如我们的二维矩阵中，共有 9 个元素，那定义我们的 left = 0，right = 9 - 1= 8，是不是和一维数组定义相同，然后我们求我们的 mid 值， mid = left +（(right - left) >> 1）此时 mid = 4 ，但是我们的二维矩阵下标最大是，nums[2,2]呀，你这求了一个 4 ，让我们怎么整呀。如果我们理解了二分查找，那么这个题目考察我们的应该是如何将一维数组的下标，变为 二维坐标。其实也很简单，咱们看哈，此时咱们的 mid = 4，咱们的二维矩阵共有 3 行， 3 列，那我们 mid =4，肯定在第二行，那么这个应该怎么求得呢?\n\n我们可以直接用 （mid/列数），即可，因为我们 mid = 4，4 /3 = 1,说明在 在第二行，那如果 mid = 7 ，7/3=2，在第三行，我们第几行知道了，那么我们如何知道第几列呢？我们可以直接根据 （mid % 列数 ）来求得呀，比如我们此时 mid = 7，7%3 = 1，那么在我们一维数组索引为 7 的元素，其处于二维数组的第 2 列，大家看看下图是不是呀！\n\n![二维数组](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二维数组.63nd4jlj0v00.png)\n\n下面我们来看一下 leetcode 74 题，让我们给他整个通透\n\n### 搜索二维矩阵\n\n#### 题目描述\n\n编写一个高效的算法来判断 m x n 矩阵中，是否存在一个目标值。该矩阵具有如下特性：\n\n每行中的整数从左到右按升序排列。\n每行的第一个整数大于前一行的最后一个整数。\n\n示例 1\n\n> 输入：matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 3\n> 输出：true\n\n示例 2\n\n> 输入：matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,50]], target = 13\n> 输出：false\n\n示例 3\n\n> 输入：matrix = [], target = 0\n> 输出：false\n\n#### 题目解析\n\n在上面我们已经解释了如何在二维矩阵中进行搜索，这里我们再对其进行一个总结，就是我们凭空想象一个一维数组，这个数组是有二维数组一层一层拼接来的，也是完全有序，然后我们定义两个指针一个指向一维数组头部，一个指向尾部，我们求得 mid 值然后将 mid 变成二维坐标，然后和 target 进行比较，如果大于则移动 left ，如果小于则移动 right 。\n\n动图解析\n\n![](https://img-blog.csdnimg.cn/20210318133244216.gif)\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public boolean searchMatrix(int[][] matrix, int target) {\n\n           if (matrix.length == 0) {\n               return false;\n           }\n           //行数\n           int row = matrix.length;\n           //列数\n           int col = matrix[0].length;\n           int left = 0;\n           //行数乘列数 - 1，右指针\n           int right = row * col - 1;\n           while (left <= right) {\n               int mid = left+ ((right-left) >> 1);\n               //将一维坐标变为二维坐标\n               int rownum = mid / col;\n               int colnum = mid % col;\n               if (matrix[rownum][colnum] == target) {\n                    return true;\n               } else if (matrix[rownum][colnum] > target) {\n                   right = mid - 1;\n               } else if (matrix[rownum][colnum] < target) {\n                   left = mid + 1;\n               }\n           }\n           return false;\n    }\n}\n```\n"
  },
  {
    "path": "animation-simulation/二分查找及其变种/找出第一个大于或小于目标的索引.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n## 找出第一个大于目标元素的索引\n\n我们在上面的变种中，描述了如何找出目标元素在数组中的上下边界，然后我们下面来看一个新的变种，如何从数组或区间中找出第一个大于或最后一个小于目标元素的数的索引，例 nums = {1,3,5,5,6,6,8,9,11} 我们希望找出第一个大于 5 的元素的索引，那我们需要返回 4 ，因为 5 的后面为 6，第一个 6 的索引为 4，如果希望找出最后一个小于 6 的元素，那我们则会返回 3 ，因为 6 的前面为 5 最后一个 5 的索引为 3。好啦题目我们已经了解，下面我们先来看一下如何在数组或区间中找出第一个大于目标元素的数吧。\n\n找出第一个大于目标元素的数，大概有以下几种情况\n\n![模糊边界情况](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/模糊边界情况.4k45gb16fhy0.png)\n\n1.数组包含目标元素，找出在他后面的第一个元素\n\n2.目标元素不在数组中，数组内的部分元素大于它，此时我们需要返回第一个大于他的元素\n\n3.目标元素不在数组中，且数组中的所有元素都大于它，那么我们此时返回数组的第一个元素即可\n\n4.目标元素不在数组中，且数组中的所有元素都小于它，那么我们此时没有查询到，返回 -1 即可。\n\n既然我们已经分析完所有情况，那么这个题目对咱们就没有难度了，下面我们描述一下案例的执行过程\n\n> nums = {1,3,5,5,6,6,8,9,11} target = 7\n\n上面的例子中，我们需要找出第一个大于 7 的数，那么我们的程序是如何执行的呢？\n\n![二分查找模糊边界目标值](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/二分查找模糊边界目标值.4d27nsldwcy0.png)\n\n上面的例子我们已经弄懂了，那么我们看一下，当 target = 0 时，程序应该怎么执行呢？\n\n![模糊边界目标0](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/模糊边界目标0.1n579314c8ao.png)\n\nOK!我们到这一步就能把这个变种给整的明明白白的了，下面我们看一哈程序代码吧，也是非常简单的。\n\nJava Code:\n\n```java\npublic static int lowBoundnum(int[] nums,int target,int left, int right) {\n\n    while (left <= right) {\n        //求中间值\n        int mid = left + ((right - left) >> 1);\n        //大于目标值的情况\n        if (nums[mid] > target) {\n                //返回 mid\n            if (mid == 0 || nums[mid-1] <= target) {\n                return mid;\n            }\n            else{\n                right = mid -1;\n            }\n\n        } else if (nums[mid] <= target){\n            left = mid + 1;\n        }\n    }\n    //所有元素都小于目标元素\n    return -1;\n}\n```\n\nGo Code:\n\n```go\nfunc lowBoundnum(nums []int, target, left, right int) int {\n\n    for (left <= right) {\n        //求中间值\n        mid := left + ((right - left) >> 1);\n        //大于目标值的情况\n        if (nums[mid] > target) {\n                //返回 mid\n            if (mid == 0 || nums[mid-1] <= target) {\n                return mid\n            }else{\n                right = mid -1\n            }\n\n        } else if (nums[mid] <= target){\n            left = mid + 1\n        }\n    }\n    //所有元素都小于目标元素\n    return -1\n}\n```\n\n## **找出最后一个小于目标元素的索引**\n\n通过上面的例子我们应该可以完全理解了那个变种，下面我们继续来看以下这种情况，那就是如何找到最后一个小于目标数的元素。还是上面那个例子\n\n> nums = {1,3,5,5,6,6,8,9,11} target = 7\n\n查找最后一个小于目标数的元素，比如我们的目标数为 7 ，此时他前面的数为 6，最后一个 6 的索引为 5，此时我们返回 5 即可，如果目标数元素为 12，那么我们最后一个元素为 11，仍小于目标数，那么我们此时返回 8，即可。这个变种其实算是上面变种的相反情况，上面的会了，这个也完全可以搞定了，下面我们看一下代码吧。\n\nJava Code:\n\n```java\npublic static int upperBoundnum(int[] nums,int target,int left, int right) {\n\n    while (left <= right) {\n\n        int mid = left + ((right - left) >> 1);\n            //小于目标值\n        if (nums[mid] < target) {\n            //看看是不是当前区间的最后一位，如果当前小于，后面一位大于，返回当前值即可\n            if (mid == right || nums[mid+1] >= target) {\n                return mid;\n            }\n            else{\n                left = mid + 1;\n            }\n\n        } else if (nums[mid] >= target){\n            right = mid - 1;\n        }\n    }\n    //没有查询到的情况\n    return -1;\n}\n```\n\nGo Code:\n\n```go\nfunc upperBoundnum(nums []int, target, left, right int) int {\n\n\tfor left <= right {\n\n\t\tmid := left + ((right - left) >> 1)\n\t\t//小于目标值\n\t\tif nums[mid] < target {\n\t\t\t//看看是不是当前区间的最后一位，如果当前小于，后面一位大于，返回当前值即可\n\t\t\tif mid == right || nums[mid+1] >= target {\n\t\t\t\treturn mid\n\t\t\t} else {\n\t\t\t\tleft = mid + 1\n\t\t\t}\n\n\t\t} else if nums[mid] >= target {\n\t\t\tright = mid - 1\n\t\t}\n\t}\n\t//没有查询到的情况\n\treturn -1\n}\n```\n"
  },
  {
    "path": "animation-simulation/二叉树/二叉树中序遍历（Morris）.md",
    "content": "### **Morris**\n\n我们之前说过，前序遍历的 Morris 方法，如果已经掌握，今天中序遍历的 Morris 方法也就没有什么难度，仅仅修改了一丢丢。\n\n我们先来回顾一下前序遍历 Morris 方法的代码部分。\n\n**前序遍历 Morris 代码**\n\n```java\nclass Solution {\n    public List<Integer> preorderTraversal(TreeNode root) {\n\n        List<Integer> list = new ArrayList<>();\n        if (root == null) {\n            return list;\n        }\n        TreeNode p1 = root; TreeNode p2 = null;\n        while (p1 != null) {\n            p2 = p1.left;\n            if (p2 != null) {\n                //找到左子树的最右叶子节点\n                while (p2.right != null && p2.right != p1) {\n                    p2 = p2.right;\n                }\n                //添加 right 指针，对应 right 指针为 null 的情况\n                //标注 1\n                if (p2.right == null) {\n                    list.add(p1.val);\n                    p2.right = p1;\n                    p1 = p1.left;\n                    continue;\n                }\n                //对应 right 指针存在的情况，则去掉 right 指针\n                p2.right = null;\n                //标注2\n            } else {\n                list.add(p1.val);\n            }\n            //移动 p1\n            p1 = p1.right;\n        }\n        return list;\n    }\n}\n```\n\n我们先来看标注 1 的部分，这里的含义是，当找到 p1 指向节点的左子树中的最右子节点时。也就是下图中的情况，此时我们需要将 p1 指向的节点值，存入 list。\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.3h60vcjhqo80.png)\n\n上述为前序遍历时的情况，那么中序遍历应该如何操作嘞。\n\n前序遍历我们需要移动 p1 指针，`p1 = p1.left` 这样做的原因和上述迭代法原理一致，找到我们当前需要遍历的那个节点。\n\n我们还需要修改哪里呢？见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.44fk4hw4maw0.png)\n\n我们在前序遍历时，遇到 `p2.right == p1`的情况时，则会执行 `p2.right == null` 并让 `p1 = p1.right`,这样做是因为，我们此时 p1 指向的值已经遍历完毕，为了防止重复遍历。\n\n但是呢，在我们的中序 Morris 中我们遇到`p2.right == p1`此时 p1 还未遍历，所以我们需要在上面两条代码之间添加一行代码`list.add(p1.val);`\n\n好啦，到这里我们就基本上就搞定了中序遍历的 Morris 方法，下面我们通过动画来加深一下印象吧，当然我也会把前序遍历的动画放在这里，大家可以看一下哪里有所不同。\n\n![二叉树中序](https://img-blog.csdnimg.cn/20210622155624486.gif)\n\n![二叉树前序Morris](https://img-blog.csdnimg.cn/20210622155959185.gif)\n\n**参考代码：**\n\n```java\n//中序 Morris\nclass Solution {\n    public List<Integer> inorderTraversal(TreeNode root) {\n        List<Integer>  list = new ArrayList<Integer>();\n        if (root == null) {\n            return list;\n        }\n        TreeNode p1 = root;\n        TreeNode p2 = null;\n        while (p1 != null) {\n            p2  = p1.left;\n            if (p2 != null) {\n                while (p2.right != null && p2.right != p1) {\n                    p2 = p2.right;\n                }\n                if (p2.right == null) {\n                    p2.right = p1;\n                    p1 = p1.left;\n                    continue;\n                } else {\n                    p2.right  = null;\n                }\n            }\n            list.add(p1.val);\n            p1 = p1.right;\n        }\n        return list;\n    }\n}\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func inorderTraversal(_ root: TreeNode?) -> [Int] {\n        var list:[Int] = []\n        guard root != nil else {\n            return list\n        }\n        var p1 = root, p2: TreeNode?\n        while p1 != nil {\n            p2 = p1!.left\n            if p2 != nil {\n                while p2!.right != nil && p2!.right !== p1 {\n                    p2 = p2!.right\n                }\n                if p2!.right == nil {\n                    p2!.right = p1\n                    p1 = p1!.left\n                    continue\n                } else {\n                    p2!.right = nil\n                }\n            }\n            list.append(p1!.val)\n            p1 = p1!.right\n        }\n        return list\n    }\n}\n```\n"
  },
  {
    "path": "animation-simulation/二叉树/二叉树中序遍历（迭代）.md",
    "content": "哈喽大家好，我是厨子，之前我们说了二叉树前序遍历的迭代法和 Morris，今天咱们写一下中序遍历的迭代法和 Morris。\n\n> 注：数据结构掌握不熟练的同学，阅读该文章之前，可以先阅读这两篇文章，二叉树基础，前序遍历另外喜欢电脑阅读的同学，可以在小屋后台回复仓库地址，获取 Github 链接进行阅读。\n\n中序遍历的顺序是, `对于树中的某节点,先遍历该节点的左子树, 然后再遍历该节点, 最后遍历其右子树`。老规矩，上动画，我们先通过动画回忆一下二叉树的中序遍历。\n\n![中序遍历](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/中序遍历.7gct7ztck8k0.gif)\n\n注：二叉树基础总结大家可以阅读这篇文章，点我。\n\n## 迭代法\n\n我们二叉树的中序遍历迭代法和前序遍历是一样的，都是借助栈来帮助我们完成。\n\n我们结合动画思考一下，该如何借助栈来实现呢？\n\n我们来看下面这个动画。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210608010104232.gif)\n\n用栈实现的二叉树的中序遍历有两个关键的地方。\n\n- 指针不断向节点的左孩子移动，为了找到我们当前需要遍历的节点。途中不断执行入栈操作\n- 当指针为空时，则开始出栈，并将指针指向出栈节点的右孩子。\n\n这两个关键点也很容易理解，指针不断向左孩子移动，是为了找到我们此时需要节点。然后当指针指向空时，则说明我们此时已经找到该节点，执行出栈操作，并将其值存入 list 即可，另外我们需要将指针指向出栈节点的右孩子，迭代执行上诉操作。\n\n大家是不是已经知道怎么写啦，下面我们看代码吧。\n\n```java\nclass Solution {\n    public List<Integer> inorderTraversal(TreeNode root) {\n        List<Integer> arr = new ArrayList<>();\n        TreeNode cur = new TreeNode(-1);\n        cur = root;\n        Stack<TreeNode> stack = new Stack<>();\n        while (!stack.isEmpty() || cur != null) {\n               //找到当前应该遍历的那个节点\n               while (cur != null) {\n                 stack.push(cur);\n                 cur = cur.left;\n               }\n               //此时指针指向空，也就是没有左子节点，则开始执行出栈操作\n               TreeNode temp = stack.pop();\n               arr.add(temp.val);\n               //指向右子节点\n               cur = temp.right;\n        }\n        return arr;\n    }\n}\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func inorderTraversal(_ root: TreeNode?) -> [Int] {\n        var arr:[Int] = []\n        var cur = root\n        var stack:[TreeNode] = []\n\n        while !stack.isEmpty || cur != nil {\n            //找到当前应该遍历的那个节点\n            while cur != nil {\n                stack.append(cur!)\n                cur = cur!.left\n            }\n            //此时指针指向空，也就是没有左子节点，则开始执行出栈操作\n            if let temp = stack.popLast() {\n                arr.append(temp.val)\n                //指向右子节点\n                cur = temp.right\n            }\n        }\n        return arr\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc inorderTraversal(root *TreeNode) []int {\n    res := []int{}\n    if root == nil {\n        return res\n    }\n    stk := []*TreeNode{}\n    cur := root\n    for len(stk) != 0 || cur != nil {\n        // 找到当前应该遍历的那个节点，并且把左子节点都入栈\n        for cur != nil {\n            stk = append(stk, cur)\n            cur = cur.Left\n        }\n        // 没有左子节点，则开始执行出栈操作\n        temp := stk[len(stk) - 1]\n        stk = stk[: len(stk) - 1]\n        res = append(res, temp.Val)\n        cur = temp.Right\n    }\n    return res\n}\n```\n\n###\n"
  },
  {
    "path": "animation-simulation/二叉树/二叉树基础.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n这假期咋就唰的一下就没啦，昨天还跟放假第一天似的，今天就开始上班了。\n\n既然开工了，那咱们就随遇而安呗，继续努力搬砖吧。\n\n下面我们将镜头切到袁记菜馆。\n\n小二：掌柜的，最近大家都在忙着种树，说是要保护环境。\n\n老板娘：树 ? 咱们店有呀，前几年种的那棵葡萄树，不是都结果子了吗？就数你吃的最多。\n\n小儿：这.......。\n\n大家应该猜到，咱们今天要唠啥了。\n\n之前给大家介绍了`链表`,`栈`，`哈希表` 等数据结构\n\n今天咱们来看一种新的数据结构，树。\n\nPS：本篇文章内容较基础，对于没有学过数据结构的同学会有一些帮助，如果你已经学过的话，也可以复习一下，查缺补漏，后面会继续更新这个系列。\n\n**文章大纲**\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.1ein9cz4oips.png)\n\n> 注：可能有的同学不喜欢手机阅读，所以将这篇同步在了我的仓库，大家可以去 Github 进行阅读，点击文章最下方的阅读原文即可\n\n## 树\n\n我们先来看下百度百科对树的定义\n\n> 树是 n （n >= 0） 个节点的有限集。 n = 0 时 我们称之为空树, 空树是树的特例。\n\n在`任意一棵非空树`中：\n\n- 有且仅有一个特定的节点称为根（Root）的节点\n- 当 n > 1 时，其余节点可分为 m （m > 0）个`互不相交的有限集` T1、T2、........Tm，其中每一个集合本身又是一棵树，并且称为根的子树。\n\n我们一起来拆解一下上面的两句话，到底什么是子树呢？见下图\n\n![二叉树](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/二叉树.6w6xnvay3v40.png)\n\n例如在上面的图中\n\n有且仅有一个特定的节点称为根节点，也就是上图中的`橙色节点`。\n\n当节点数目大于 1 时，除根节点以外的节点，可分为 m 个`互不相交`的有限集 T1,T2........Tm。\n\n例如上图中，我们将根节点以外的节点，分为了 T1 （2，3，4，5，6，7），T2（8，9）两个有限集。\n\n那么 T1 （绿色节点）和 T2（蓝色节点）就是根节点（橙色节点）的子树。\n\n我们拆解之后发现，我们上图中的例子符合树的要求，另外不知道大家有没有注意到这个地方。\n\n除根节点以外的节点，可分为 m 个`互不相交`的有限集。\n\n那么这个互不相交又是什么含义呢？见下图。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/幻灯片1.3wt8kt6ewj20.PNG)\n\n我们将 (A) , (B) , (C) , (D) 代入上方定义中发现，(A) , (B) 符合树的定义，（C）, (D) 不符合，这是因为 (C) , (D) 它们都有相交的子树。\n\n好啦，到这里我们知道如何辨别树啦，下面我们通过下面两张图再来深入了解一下树。\n\n主要从节点类型，节点间的关系下手。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/幻灯片2.4gvv5tql9cw0.PNG)\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/幻灯片3.17o6v5lqd9xc.PNG)\n\n这里节点的高度和深度可能容易记混，我们代入到现实即可。\n\n我们求深度时，从上往下测量，求高度时，从下往上测量，节点的高度和深度也是如此。\n\n## 二叉树\n\n我们刷题时遇到的就是二叉树啦，下面我们一起来了解一下二叉树\n\n二叉树前提是一棵树，也就是`需要满足我们树的定义的同时`，还需要满足以下要求\n\n每个节点`最多`有两个子节点，分别是左子节点和右子节点。\n\n注意我们这里提到的是`最多`，所以二叉树并不是`必须要求每个节点都有两个子节点`，也可以有的仅有一个左子节点，有的节点仅有一个右子节点。\n\n下面我们来总结一下二叉树的特点\n\n- 每个节点最多有两棵子树，也就是说二叉树中不存在度大于 2 的节点，节点的度可以为 0，1，2。\n- 左子树和右子树是有顺序的,有左右之分。\n- 假如只有一棵子树 ，也要区分它是左子树还是右子树\n\n好啦，我们已经了解了二叉树的特点，那我们分析一下，下图中的树是否满足二叉树定义，共有几种二叉树。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/二叉树举例.1mavhkdbs8xs.png)\n\n上图共为 5 种不同的二叉树，在二叉树的定义中提到，二叉树的左子树和右子树是有顺序的，所以 B，C 是两个不同的二叉树，故上图为 5 种不同的二叉树。\n\n## 特殊的二叉树\n\n下面我们来说几种比较特殊的二叉树，可以`帮助我们刷题时，考虑到特殊情况`。\n\n### 满二叉树\n\n满二叉树：在一棵二叉树中，`所有分支节点都存在左子树和右子树`，并且`所有的叶子都在同一层`,这种树我们称之为满二叉树.见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/image.2k1tlbtywzu0.png)\n\n我们发现只有 (B) 符合满二叉树的定义,我们发现其实满二叉树也为完全二叉树的一种。\n\n### 完全二叉树\n\n完全二叉树：叶子结点只能出现在最下层和次下层，且最下层的叶子结点集中在树的左部。\n\n哦！我们可以这样理解，除了最后一层，其他层的节点个数都是满的，而且最后一层的叶子节点必须靠左。\n\n下面我们来看一下这几个例子\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/image.2f3i4soptvi8.png)\n\n上面的几个例子中，（A）（B）为完全二叉树，（C）（D）不是完全二叉树\n\n### 斜二叉树\n\n这个就很好理解啦,斜二叉树也就是斜的二叉树,所有的节点只有左子树的称为左斜树,所有节点只有右子树的二叉树称为右斜树.\n\n诺,下面这俩就是.\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/image.6u1n2j3jmu80.png)\n\n另外还有 一些二叉树的性质, 比如第 i 层至多有多少节点,通过叶子节点求度为 2 的节点, 通过节点树求二叉树的深度等, 这些是考研常考的知识, 就不在这里进行赘述,需要的同学可以看一下王道或者天勤的数据结构, 上面描述的很具体, 并附有证明过程.\n\n好啦，我们已经了解了二叉树，那么二叉树如何存储呢？\n\n## 如何存储二叉树\n\n二叉树多采用两种方法进行存储,基于数组的顺序存储法和基于指针的二叉链式存储法\n\n我们在之前说过的堆排序中,其中对堆的存储采用的则是顺序存储法,具体细节可以看这篇文章\n\n**一个破堆排我搞了 4 个动画?**\n\n这里我们再来回顾一下如何用数组存储完全二叉树.\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210223223621.3juf4t4hc9a0.png)\n\n我们首先看根节点，也就是值为 1 的节点，它在数组中的下标为 1 ,它的左子节点，也就是值为 4 的节点，此时索引为 2，右子节点也就是值为 2 的节点，它的索引为 3。\n\n我们发现其中的关系了吗？\n\n数组中，某节点（非叶子节点）的下标为 i , 那么其`左子节点下标为 2*i `（这里可以直接通过相乘得到左孩子, 也就是为什么空出第一个位置, 如果从 0 开始存，则需要 2*i+1 才行）, 右子节点为 2*i+1，其父节点为 i/2 。既然我们完全可以根据索引找到某节点的 `左子节点` 和` 右子节点`，那么我们用数组存储是完全没有问题的。\n\n但是,我们再考虑一下这种情景,如果我们用数组存储`斜树`时会出现什么情况?\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.780as9g3ofs0.png)\n\n通过 2\\*i 进行存储左子节点的话,如果遇到斜树时,则会浪费很多的存储空间,这样显然是不合适的,\n\n所以说当存储完全二叉树时,我们用数组存储,无疑是最省内存的，但是存储斜树时，则就不太合适。\n\n所以我们下面介绍一下另一种存储结构,链式存储结构.\n\n因为二叉树的每个节点, 最多有两个孩子, 所以我们只需为每个节点定义一个数据域,两个指针域即可\n\nval 为节点的值, left 指向左子节点, right 指向右子节点.\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/image.2m6tju8ruoo0.png)\n\n下面我们对树 1, 2, 3, 4, 5, 6, 7 使用链式存储结构进行存储,即为下面这种情况.\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/二叉树链式存储.3cctqhi5ll40.png)\n\n**二叉链表的节点结构定义代码**\n\n```java\npublic class BinaryTree {\n    int val;\n    BinaryTree left;\n    BinaryTree right;\n    BinaryTree() {}\n    BinaryTree(int val) { this.val = val; }\n    BinaryTree(int val, BinaryTree left, BinaryTree right) {\n        this.val = val;\n        this.left = left;\n        this.right = right;\n    }\n}\n```\n\n另外我们在刷题的时候, 可以`自己实现一下数据结构`, 加深我们的理解, 提升基本功, 而且面试考的也越来越多.\n\n好啦,下面我们说一下树的遍历,\n\n下面我会用动图的形式进行描述,很容易理解, 我也会为大家总结对应的题目,欢迎各位阅读.\n\n## 遍历二叉树\n\n二叉树的遍历指`从根节点出发,按照某种次序依次访问二叉树的所有节点`,使得每个节点都被访问且访问一次.\n\n我们下面介绍二叉树的几种遍历方法及其对应的题目, 前序遍历, 中序遍历 , 后序遍历 , 层序遍历 .\n\n### 前序遍历\n\n前序遍历的顺序是, 对于树中的某节点,`先遍历该节点,然后再遍历其左子树,最后遍历其右子树`.\n\n只看文字有点生硬, 下面我们直接看动画吧\n\n![前序遍历](https://img-blog.csdnimg.cn/20210504155755565.gif)\n\n**测试题目: leetcode 144. 二叉树的前序遍历**\n\n**代码实现(递归版)**\n\n```java\nclass Solution {\n    public List<Integer> preorderTraversal(TreeNode root) {\n        List<Integer> arr = new ArrayList<>();\n        preorder(root,arr);\n        return arr;\n\n    }\n    public void preorder(TreeNode root,List<Integer> arr) {\n        if (root == null) {\n            return;\n        }\n        arr.add(root.val);\n        preorder(root.left,arr);\n        preorder(root.right,arr);\n    }\n}\n```\n\n时间复杂度 : O(n) 空间复杂度 : O(n) 为递归过程中栈的开销,平均为 O(logn),但是当二叉树为斜树时则为 O(n)\n\n为了控制文章篇幅, 二叉树的迭代遍历形式, 会在下篇文章进行介绍。\n\n### 中序遍历\n\n中序遍历的顺序是, `对于树中的某节点,先遍历该节点的左子树, 然后再遍历该节点, 最后遍历其右子树`\n\n继续看动画吧, 如果有些遗忘或者刚开始学数据结构的同学可以自己模拟一下执行步骤.\n\n![中序遍历](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/中序遍历.7gct7ztck8k0.gif)\n\n**测试题目: leetcode 94 题 二叉树的中序遍历**\n\n**代码实现(递归版)**\n\n```java\nclass Solution {\n    public List<Integer> inorderTraversal(TreeNode root) {\n\n         List<Integer> res = new ArrayList<>();\n         inorder(root, res);\n         return res;\n\n    }\n    public void inorder (TreeNode root, List<Integer> res) {\n        if (root == null) {\n            return;\n        }\n        inorder(root.left, res);\n        res.add(root.val);\n        inorder(root.right, res);\n\n    }\n}\n```\n\n时间复杂度 : O(n) 空间复杂度 : O(n)\n\n### 后序遍历\n\n后序遍历的顺序是,` 对于树中的某节点, 先遍历该节点的左子树, 再遍历其右子树, 最后遍历该节点`.\n\n哈哈,继续看动画吧,看完动画就懂啦.\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/后序遍历.2bx6qccr1q1w.gif)\n\n**测试题目: leetcode 145 题 二叉树的后序遍历**\n\n**代码实现(递归版)**\n\n```java\nclass Solution {\n    public List<Integer> postorderTraversal(TreeNode root) {\n         List<Integer> res = new ArrayList<>();\n         postorder(root,res);\n         return res;\n    }\n\n    public void postorder(TreeNode root, List<Integer> res) {\n        if (root == null) {\n            return;\n        }\n        postorder(root.left, res);\n        postorder(root.right, res);\n        res.add(root.val);\n    }\n}\n```\n\n时间复杂度 : O(n) 空间复杂度 : O(n)\n\n### 层序遍历\n\n顾名思义,一层一层的遍历.\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/image.4ypjzygovms0.png)\n\n比如刚才那棵二叉树的层序遍历序列即为 1 ~ 9.\n\n二叉树的层序, 这里我们需要借助其他数据结构来实现, 我们思考一下, 我们需要对二叉树进行层次遍历, 从上往下进行遍历, 我们可以借助什么数据结构来帮我们呢 ?\n\n我们可以利用队列先进先出的特性，使用队列来帮助我们完成层序遍历, 具体操作如下\n\n让二叉树的每一层入队, 然后再依次执行出队操作,\n\n对`该层节点执行出队操作时, 需要将该节点的左孩子节点和右孩子节点进行入队操作`,\n\n这样当该层的所有节点出队结束后, 下一层也就入队完毕,\n\n不过我们需要考虑的就是, 我们`需要通过一个变量来保存每一层节点的数量`.\n\n这样做是为了防止, 一直执行出队操作, 使输出不能分层\n\n好啦,下面我们直接看动画吧,\n\n![](https://img-blog.csdnimg.cn/20210504155603953.gif)\n\n**测试题目: leetcode 102 二叉树的层序遍历**\n\nJava Code:\n\n```java\nclass Solution {\n    public List<List<Integer>> levelOrder(TreeNode root) {\n\n      List<List<Integer>> res = new ArrayList<>();\n      if (root == null) {\n          return res;\n      }\n      //入队 root 节点，也就是第一层\n      Queue<TreeNode> queue = new LinkedList<>();\n      queue.offer(root);\n      while (!queue.isEmpty()) {\n          List<Integer> list = new ArrayList<>();\n          int size = queue.size();\n          for (int i = 0; i < size; ++i) {\n              TreeNode temp = queue.poll();\n              //孩子节点不为空，则入队\n              if (temp.left != null)  queue.offer(temp.left);\n              if (temp.right != null) queue.offer(temp.right);\n              list.add(temp.val);\n          }\n          res.add(list);\n      }\n      return res;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    vector<vector<int>> levelOrder(TreeNode* root) {\n      vector<vector<int>> res;\n      if (!root) return res;\n      queue<TreeNode *> q;\n      q.push(root);\n      while (!q.empty()) {\n          vector <int> t;\n          int size = q.size();\n          for (int i = 0; i < size; ++i) {\n              TreeNode * temp = q.front();\n              q.pop();\n              if (temp->left != nullptr)  q.push(temp->left);\n              if (temp->right != nullptr) q.push(temp->right);\n              t.emplace_back(temp->val);\n          }\n          res.push_back(t);\n      }\n      return res;\n    }\n};\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func levelOrder(_ root: TreeNode?) -> [[Int]] {\n        var res:[[Int]] = []\n        guard root != nil else {\n            return res\n        }\n        var queue:[TreeNode?] = []\n        queue.append(root!)\n\n        while !queue.isEmpty {\n            let size = queue.count\n            var list:[Int] = []\n\n            for i in 0..<size {\n                guard let node = queue.removeFirst() else {\n                    continue\n                }\n                if node.left != nil {\n                    queue.append(node.left)\n                }\n                if node.right != nil {\n                    queue.append(node.right);\n                }\n                list.append(node.val)\n            }\n            res.append(list)\n        }\n\n        return res\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc levelOrder(root *TreeNode) [][]int {\n    res := [][]int{}\n    if root == nil {\n        return res\n    }\n    // 初始化队列时，记得把root节点加进去。\n    que := []*TreeNode{root}\n    for len(que) != 0 {\n        t := []int{}\n        // 这里一定要先求出来que的长度，因为在迭代的过程中，que的长度是变化的。\n        l := len(que)\n        for i := 0; i < l; i++ {\n            temp := que[0]\n            que = que[1:]\n            // 子节点不为空，就入队\n            if temp.Left != nil { que = append(que, temp.Left) }\n            if temp.Right != nil { que = append(que, temp.Right) }\n            t = append(t, temp.Val)\n        }\n        res = append(res, t)\n    }\n    return res\n}\n```\n\n时间复杂度：O（n） 空间复杂度：O（n）\n\n大家如果吃透了二叉树的层序遍历的话，可以顺手把下面几道题目解决掉，思路一致，甚至都不用拐弯\n\n- **leetcode 107. 二叉树的层序遍历 II**\n\n- **leetcode 103. 二叉树的锯齿形层序遍历**\n\n上面两道题仅仅是多了翻转\n\n- **leetcode 199. 二叉树的右视图**\n- **leetcode 515. 在每个树行中找最大值**\n- **leetcode 637. 二叉树的层平均值**\n\n这三道题，仅仅是加了一层的一些操作\n\n- **leetcode 116 填充每个节点的下一个右侧**\n- **leetcode 117 填充每个节点的下一个右侧 2**\n\n这两个题对每一层的节点进行链接即可。\n\n大家可以去顺手解决这些题目，但是也要注意一下其他解法，把题目吃透。不要为了数目而刷题，好啦，今天的节目就到这里啦，我们下期见！\n"
  },
  {
    "path": "animation-simulation/二叉树/二叉树的前序遍历(Morris).md",
    "content": "### Morris\n\nMorris 遍历利用树的左右孩子为空（大量空闲指针），实现空间开销的极限缩减。这个遍历方法，稍微有那么一丢丢难理解，不过结合动图，也就一目了然啦，下面我们先看动画吧。\n\n![Morris前序](https://img-blog.csdnimg.cn/20210622155959185.gif)\n\n看完视频，是不是感觉自己搞懂了，又感觉自己没搞懂，哈哈，咱们继续往下看。\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.1u3at0ckvn34.png)\n\n我们之前说的，Morris 遍历利用了`树中大量空闲指针的特性`，我们需要`找到当前节点的左子树中的最右边的叶子节点`，将该叶子节点的 right 指向当前节点。例如当前节点为 2，其左子树中的最右节点为 9 ，则在 9 节点添加一个 right 指针指向 2。\n\n其实上图中的 Morris 遍历遵循两个原则，我们在动画中也能够得出。\n\n1. 当 p1.left == null 时，p1 = p1.right。(这也就是我们为什么要给叶子节点添加 right 指针的原因)\n\n2. 如果 p1.left != null，找到 p1 左子树上最右的节点。(也就是我们的 p2 最后停留的位置)，此时我们又可以分为两种情况，一种是叶子节点添加 right 指针的情况，一种是去除叶子节点 right 指针的情况。\n\n3. - 如果 p2 的 right 指针指向空，让其指向 p1，p1 向左移动,即 p1 = p1.left\n   - 如果 p2 的 right 指针指向 p1，让其指向空，（为了防止重复执行，则需要去掉 right 指针）p1 向右移动，p1 = p1.right。\n\n这时你可以结合咱们刚才提到的两个原则，再去看一遍动画，并代入规则进行模拟，差不多就能完全搞懂啦。\n\n下面我们来对动画中的内容进行拆解 ，\n\n首先 p1 指向 root 节点\n\np2 = p1.left，下面我们需要通过 p2 找到 p1 的左子树中的最右节点。即节点 5，然后将该节点的 right 指针指向 root。并记录 root 节点的值。\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.3h60vcjhqo80.png)\n\n向左移动 p1，即 p1 = p1.left\n\np2 = p1.left ，即节点 4 ，找到 p1 的左子树中的最右叶子节点，也就是 9，并将该节点的 right 指针指向 2。\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.zq91mdjkyzk.png)\n\n继续向左移动 p1,即 p1 = p1.left，p2 = p1.left。 也就是节点 8。并将该节点的 right 指针指向 p1。\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.5vsh71yrzxs0.png)\n\n我们发现这一步给前两步是一样的，都是找到叶子节点，将其 right 指针指向 p1,此时我们完成了添加 right 指针的过程，下面我们继续往下看。\n\n我们继续移动 p1 指针，p1 = p1.left。p2 = p.left。此时我们发现 p2 == null,即下图\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.zk7nxrjdgr.png)\n\n此时我们需要移动 p1, 但是不再是 p1 = p1.left 而是 p1 = p1.right。也就是 4，继续让 p2 = p1.left。此时则为下图这种情况\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.1pjni9r6tkps.png)\n\n此时我们发现 p2.right != null 而是指向 4，说明此时我们已经添加过了 right 指针，所以去掉 right 指针，并让 p1 = p1.right\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.17t7n8yy340w.png)\n\n下面则继续移动 p1 ,按照规则继续移动即可，遇到的情况已经在上面做出了举例，所以下面我们就不继续赘述啦，如果还不是特别理解的同学，可以再去看一遍动画加深下印象。\n\n时间复杂度 O（n），空间复杂度 O（1）\n\n下面我们来看代码吧。\n\n#### 代码\n\n```java\nclass Solution {\n    public List<Integer> preorderTraversal(TreeNode root) {\n\n        List<Integer> list = new ArrayList<>();\n        if (root == null) {\n            return list;\n        }\n        TreeNode p1 = root; TreeNode p2 = null;\n        while (p1 != null) {\n            p2 = p1.left;\n            if (p2 != null) {\n                //找到左子树的最右叶子节点\n                while (p2.right != null && p2.right != p1) {\n                    p2 = p2.right;\n                }\n                //添加 right 指针，对应 right 指针为 null 的情况\n                if (p2.right == null) {\n                    list.add(p1.val);\n                    p2.right = p1;\n                    p1 = p1.left;\n                    continue;\n                }\n                //对应 right 指针存在的情况，则去掉 right 指针\n                p2.right = null;\n            } else {\n                list.add(p1.val);\n            }\n            //移动 p1\n            p1 = p1.right;\n        }\n        return list;\n    }\n}\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func preorderTraversal(_ root: TreeNode?) -> [Int] {\n        var list:[Int] = []\n        guard root != nil else {\n            return list\n        }\n        var p1 = root, p2: TreeNode?\n        while p1 != nil {\n            p2 = p1!.left\n            if p2 != nil {\n                //找到左子树的最右叶子节点\n                while p2!.right != nil && p2!.right !== p1 {\n                    p2 = p2!.right\n                }\n                //添加 right 指针，对应 right 指针为 null 的情况\n                if p2!.right == nil {\n                    list.append(p1!.val)\n                    p2!.right = p1\n                    p1 = p1!.left\n                    continue\n                }\n                //对应 right 指针存在的情况，则去掉 right 指针\n                p2!.right = nil\n            } else {\n                list.append(p1!.val)\n            }\n            //移动 p1\n            p1 = p1!.right\n        }\n        return list\n    }\n}\n```\n\n好啦，今天就看到这里吧，咱们下期见！\n"
  },
  {
    "path": "animation-simulation/二叉树/二叉树的前序遍历(栈).md",
    "content": "我们之前说了二叉树基础及二叉的几种遍历方式及练习题，今天我们来看一下二叉树的前序遍历非递归实现。\n\n前序遍历的顺序是, 对于树中的某节点,`先遍历该节点,然后再遍历其左子树,最后遍历其右子树`.\n\n我们先来通过下面这个动画复习一下二叉树的前序遍历。\n\n![前序遍历](https://img-blog.csdnimg.cn/20210504155755565.gif)\n\n### 迭代\n\n我们试想一下，之前我们借助队列帮我们实现二叉树的层序遍历，\n\n那么可不可以，也借助数据结构，帮助我们实现二叉树的前序遍历。\n\n见下图\n\n![image](https://cdn.jsdelivr.net/gh/tan45du/test@master/image.622242fm7dc0.png)\n\n假设我们的二叉树为 [1,2,3]。我们需要对其进行前序遍历。其遍历顺序为\n\n当前节点 1，左孩子 2，右孩子 3。\n\n这里可不可以用栈，帮我们完成前序遍历呢？\n\n> 栈和队列的那些事\n\n我们都知道栈的特性是先进后出，我们借助栈来帮助我们完成前序遍历的时候。\n\n则需要注意的一点是，我们应该`先将右子节点入栈，再将左子节点入栈`。\n\n这样出栈时，则会先出左节点，再出右子节点，则能够完成树的前序遍历。\n\n见下图。\n\n![](https://img-blog.csdnimg.cn/20210512205822221.gif)\n\n我们用一句话对上图进行总结，`当栈不为空时，栈顶元素出栈，如果其右孩子不为空，则右孩子入栈，其左孩子不为空，则左孩子入栈`。还有一点需要注意的是，我们和层序遍历一样，需要先将 root 节点进行入栈，然后再执行 while 循环。\n\n看到这里你已经能够自己编写出代码了，不信你去试试。\n\n时间复杂度：O(n) 需要对所有节点遍历一次\n\n空间复杂度：O(n) 栈的开销，平均为 O(logn) 最快情况，即斜二叉树时为 O(n)\n\n**参考代码**\n\n```java\nclass Solution {\n    public List<Integer> preorderTraversal(TreeNode root) {\n        List<Integer> list = new ArrayList<>();\n        Stack<TreeNode> stack = new Stack<>();\n        if (root == null)  return list;\n        stack.push(root);\n        while (!stack.isEmpty()) {\n            TreeNode temp = stack.pop();\n            if (temp.right != null) {\n                stack.push(temp.right);\n            }\n            if (temp.left != null) {\n                stack.push(temp.left);\n            }\n            //这里也可以放到前面\n            list.add(temp.val);\n        }\n        return list;\n    }\n}\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n\n    func preorderTraversal(_ root: TreeNode?) -> [Int] {\n        var list:[Int] = []\n        var stack:[TreeNode] = []\n\n        guard root != nil else {\n            return list\n        }\n        stack.append(root!)\n        while !stack.isEmpty {\n            let temp = stack.popLast()\n            if let right = temp?.right {\n                stack.append(right)\n            }\n            if let left = temp?.left {\n                stack.append(left)\n            }\n            //这里也可以放到前面\n            list.append((temp?.val)!)\n        }\n        return list\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc preorderTraversal(root *TreeNode) []int {\n    res := []int{}\n    if root == nil {\n        return res\n    }\n    stk := []*TreeNode{root}\n    for len(stk) != 0 {\n        temp := stk[len(stk) - 1]\n        stk = stk[: len(stk) - 1]\n        if temp.Right != nil {\n            stk = append(stk, temp.Right)\n        }\n        if temp.Left != nil {\n            stk = append(stk, temp.Left)\n        }\n        res = append(res, temp.Val)\n    }\n    return res\n}\n```\n"
  },
  {
    "path": "animation-simulation/二叉树/二叉树的后续遍历 (迭代).md",
    "content": "之前给大家介绍了二叉树的[前序遍历]()，[中序遍历]()的迭代法和 Morris 方法，今天咱们来说一下二叉后序遍历的迭代法及 Morris 方法。\n\n注：阅读该文章前，建议各位先阅读之前的三篇文章，对该文章的理解有很大帮助。\n\n## 迭代\n\n后序遍历的相比前两种方法，难理解了一些，所以这里我们需要认真思考一下，每一行的代码的作用。\n\n我们先来复习一下，二叉树的后序遍历\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/后序遍历.2bx6qccr1q1w.gif)\n\n我们知道后序遍历的顺序是,` 对于树中的某节点, 先遍历该节点的左子树, 再遍历其右子树, 最后遍历该节点`。\n\n那么我们如何利用栈来解决呢？\n\n我们直接来看动画，看动画之前，但是我们`需要带着问题看动画`，问题搞懂之后也就搞定了后序遍历。\n\n1.动画中的橙色指针发挥了什么作用\n\n2.为什么动画中的某节点，为什么出栈后又入栈呢?\n\n好啦，下面我们看动画吧！\n\n![后序遍历迭代](https://img-blog.csdnimg.cn/20210622160754912.gif)\n\n相信大家看完动画之后，也能够发现其中规律。\n\n我们来对其中之前提出的问题进行解答\n\n1.动画中的橙色箭头的作用？\n\n> 用来定位住上一个访问节点，这样我们就知道 cur 节点的 right 节点是否被访问，如果被访问，我们则需要遍历 cur 节点。\n\n2.为什么有的节点出栈后又入栈了呢？\n\n> 出栈又入栈的原因是，我们发现 cur 节点的 right 不为 null ，并且 cur.right 也没有被访问过。因为 `cur.right != preNode `，所以当前我们还不能够遍历该节点，应该先遍历其右子树中的节点。\n>\n> 所以我们将其入栈后，然后`cur = cur.right`\n\n```java\nclass Solution {\n    public List<Integer> postorderTraversal(TreeNode root) {\n        Stack<TreeNode> stack = new Stack<>();\n        List<Integer> list = new ArrayList<>();\n        TreeNode cur = root;\n        //这个用来记录前一个访问的节点，也就是橙色箭头\n        TreeNode preNode = null;\n        while (cur != null || !stack.isEmpty()) {\n            //和之前写的中序一致\n            while (cur != null) {\n                stack.push(cur);\n                cur = cur.left;\n            }\n            //1.出栈，可以想一下，这一步的原因。\n            cur = stack.pop();\n            //2.if 里的判断语句有什么含义？\n            if (cur.right == null || cur.right == preNode) {\n                list.add(cur.val);\n                //更新下 preNode，也就是定位住上一个访问节点。\n                preNode = cur;\n                cur = null;\n            } else {\n                //3.再次压入栈，和上面那条 1 的关系？\n                stack.push(cur);\n                cur = cur.right;\n            }\n        }\n        return list;\n    }\n}\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func postorderTraversal(_ root: TreeNode?) -> [Int] {\n        var list:[Int] = []\n        var stack:[TreeNode] = []\n        var cur = root, preNode: TreeNode?\n        while !stack.isEmpty || cur != nil {\n            //和之前写的中序一致\n            while cur != nil {\n                stack.append(cur!)\n                cur = cur!.left\n            }\n            //1.出栈，可以想一下，这一步的原因。\n            cur = stack.popLast()\n            //2.if 里的判断语句有什么含义？\n            if cur!.right === nil || cur!.right === preNode  {\n                list.append(cur!.val)\n                //更新下 preNode，也就是定位住上一个访问节点。\n                preNode = cur\n                cur = nil\n            } else {\n                //3.再次压入栈，和上面那条 1 的关系？\n                stack.append(cur!)\n                cur = cur!.right\n            }\n        }\n        return list\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc postorderTraversal(root *TreeNode) []int {\n    res := []int{}\n    if root == nil {\n        return res\n    }\n    stk := []*TreeNode{}\n    cur := root\n    var pre *TreeNode\n    for len(stk) != 0 || cur != nil {\n        for cur != nil {\n            stk = append(stk, cur)\n            cur = cur.Left\n        }\n        // 这里符合本文最后的说法，使用先获取栈顶元素但是不弹出，根据栈顶元素的情况进行响应的处理。\n        temp := stk[len(stk) - 1]\n        if temp.Right == nil || temp.Right == pre {\n            stk = stk[: len(stk) - 1]\n            res = append(res, temp.Val)\n            pre = temp\n        } else {\n            cur = temp.Right\n        }\n    }\n    return res\n}\n```\n\n当然也可以修改下代码逻辑将 `cur = stack.pop()` 改成 `cur = stack.peek()`，下面再修改一两行代码也可以实现，这里这样写是方便动画模拟，大家可以随意发挥。\n\n时间复杂度 O（n）, 空间复杂度 O（n）\n\n这里二叉树的三种迭代方式到这里就结束啦，大家可以进行归纳总结，三种遍历方式大同小异，建议各位，掌握之后，自己手撕一下，从搭建二叉树开始。\n"
  },
  {
    "path": "animation-simulation/二叉树/二叉树的后续遍历（Morris）.md",
    "content": "之前给大家介绍了二叉树的[前序遍历]()，[中序遍历]()的迭代法和 Morris 方法，今天咱们来说一下二叉后序遍历的迭代法及 Morris 方法。\n\n注：阅读该文章前，建议各位先阅读之前的三篇文章，对该文章的理解有很大帮助。\n\n## Morris\n\n后序遍历的 Morris 方法也比之前两种代码稍微长一些，看着挺唬人,其实不难，和我们之前说的没差多少。下面我们一起来干掉它吧。\n\n我们先来复习下之前说过的[中序遍历]()，见下图。\n\n![](https://img-blog.csdnimg.cn/20210622155624486.gif)\n\n另外我们来对比下，中序遍历和后序遍历的 Morris 方法，代码有哪里不同。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210622142148928.png)\n\n由上图可知，仅仅有三处不同，后序遍历里少了 `list.add()`，多了一个函数`postMorris()` ，那后序遍历的 list.add() 肯定是在 postMorris 函数中的。所以我们搞懂了 postMorris 函数，也就搞懂了后序遍历的 Morris 方法（默认大家看了之前的文章，没有看过的同学，可以点击文首的链接）\n\n下面我们一起来剖析下 postMorris 函数.代码如下\n\n```java\npublic void postMorris(TreeNode root) {\n        //反转转链表，详情看下方图片\n        TreeNode reverseNode = reverseList(root);\n        //遍历链表\n        TreeNode cur = reverseNode;\n        while (cur != null) {\n            list.add(cur.val);\n            cur = cur.right;\n        }\n        //反转回来\n        reverseList(reverseNode);\n    }\n\n//反转链表\npublic TreeNode reverseList(TreeNode head) {\n      TreeNode cur = head;\n      TreeNode pre = null;\n      while (cur != null) {\n          TreeNode next = cur.right;\n          cur.right = pre;\n          pre = cur;\n          cur = next;\n      }\n      return pre;\n    }\n```\n\n上面的代码，是不是贼熟悉，和我们的倒序输出链表一致，步骤为，反转链表，遍历链表，将链表反转回原样。只不过我们将 ListNode.next 写成了 TreeNode.right 将树中的遍历右子节点的路线，看成了一个链表，见下图。\n\n![](https://img-blog.csdnimg.cn/20210622145335283.png)\n\n上图中的一个绿色虚线，代表一个链表，我们根据序号进行倒序遍历，看下是什么情况\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210622145805876.png)\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210622145846117.png)\n\n到这块是不是就整懂啦，打完收工！\n\n```java\nclass Solution {\n    List<Integer> list;\n    public List<Integer> postorderTraversal(TreeNode root) {\n        list = new ArrayList<>();\n        if (root == null) {\n            return list;\n        }\n        TreeNode p1 = root;\n        TreeNode p2 = null;\n        while (p1 != null) {\n            p2 = p1.left;\n            if (p2 != null) {\n                 while (p2.right != null && p2.right != p1) {\n                     p2 = p2.right;\n                 }\n                 if (p2.right == null) {\n                     p2.right = p1;\n                     p1 = p1.left;\n                     continue;\n                 } else {\n                     p2.right = null;\n                     postMorris(p1.left);\n                 }\n            }\n            p1 = p1.right;\n        }\n        //以根节点为起点的链表\n        postMorris(root);\n        return list;\n    }\n    public void postMorris(TreeNode root) {\n        //翻转链表\n        TreeNode reverseNode = reverseList(root);\n        //从后往前遍历\n        TreeNode cur = reverseNode;\n        while (cur != null) {\n            list.add(cur.val);\n            cur = cur.right;\n        }\n        //翻转回来\n        reverseList(reverseNode);\n    }\n    public TreeNode reverseList(TreeNode head) {\n        TreeNode cur = head;\n        TreeNode pre = null;\n        while (cur != null) {\n            TreeNode next = cur.right;\n            cur.right = pre;\n            pre = cur;\n            cur = next;\n        }\n        return pre;\n    }\n\n}\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    var list:[Int] = []\n    func postorderTraversal(_ root: TreeNode?) -> [Int] {\n        guard root != nil else {\n            return list\n        }\n        var p1 = root, p2: TreeNode?\n        while p1 != nil {\n            p2 = p1!.left\n            if p2 != nil {\n                while p2!.right != nil && p2!.right !== p1 {\n                    p2 = p2!.right\n                }\n                if p2!.right == nil {\n                    p2!.right = p1\n                    p1 = p1!.left\n                    continue\n                } else {\n                    p2!.right = nil\n                    postMorris(p1!.left)\n                }\n            }\n            p1 = p1!.right\n        }\n        //以根节点为起点的链表\n        postMorris(root!)\n        return list\n    }\n\n    func postMorris(_ root: TreeNode?) {\n        let reverseNode = reverseList(root)\n        //从后往前遍历\n        var cur = reverseNode\n        while cur != nil {\n            list.append(cur!.val)\n            cur = cur!.right\n        }\n        reverseList(reverseNode)\n    }\n\n    func reverseList(_ head: TreeNode?) -> TreeNode? {\n        var cur = head, pre: TreeNode?\n        while cur != nil {\n            let next = cur?.right\n            cur?.right = pre\n            pre = cur\n            cur = next\n        }\n        return pre\n    }\n}\n```\n\n时间复杂度 O（n）空间复杂度 O（1）\n\n总结：后序遍历比起前序和中序稍微复杂了一些，所以我们解题的时候，需要好好注意一下，迭代法的核心是利用一个指针来定位我们上一个遍历的节点，Morris 的核心是，将某节点的右子节点，看成是一条链表，进行反向遍历。\n\n好啦，今天就唠到这吧，拜了个拜。\n"
  },
  {
    "path": "animation-simulation/二叉树/前序序列和中序构建二叉树.md",
    "content": ""
  },
  {
    "path": "animation-simulation/位运算/空.md",
    "content": ""
  },
  {
    "path": "animation-simulation/写写水文/书单.md",
    "content": ""
  },
  {
    "path": "animation-simulation/写写水文/如何学习.md",
    "content": "如何面向面试学习？\n\n我们提到面试，大多数人脑子里蹦出的第一个词，那就是八股文。但是面试真的可以**只**靠八股文吗？\n\n那面试八股文重要吗？重要，非常重要！\n\n那你这不是前后矛盾吗？一会说不能只靠八股文，一会又说八股文非常重要。\n\n哎嘛，不要着急，听我慢慢说。\n\n以下仅仅是我的一家之言。\n\n我们先来看一下，一位 Javaer 校招需要准备的东西有哪些。\n\n- 数据结构与算法\n\n- 操作系统\n\n- 计算机网络\n\n- Java 基础\n\n- MySQL\n\n- Redis\n\n- Java 并发编程\n\n- Spring 全家桶\n\n- Linux\n\n- 设计模式\n- 1-2 两个能拿得出手的项目。\n\n上面的内容或多或少会在面试中问到，有的面试官侧重于项目，有的面试官喜欢问基础知识，也就是我们常说的八股，还有的面试官喜欢问实际开发中遇到的问题也就是场景题。但是我认为面试官在提问之前，他们心里已经有他们的答案，你如果能说出他心里的那个点，然后再对其延伸，则有可能让面试官眼前一亮的。但是如果你一直没有说出他想要的那个点，一昧的对其拓展，这个答案或许就有点些冗余。\n\n或许面试时，面试官想要的状态是，看到你对技术的自信，知其然知其所以然。这样自然而然能够得到一个好的面评。\n\n那么我们如何才能做到上面提到的呢？那就是看书，你会发现看过某个科目 2-3 遍书之后，你对这个科目是有自信的，因为你有这门科目的知识架构，有自己的理解，知道它们之间的联系，那么你回答时则会得心应手。记住是看 2-3 遍哦，一遍的话，只能大概了解大致脉络，不能让自己深刻理解，所以到重复看，你会发现那些好书，每次看的时候都会有新的收获。\n\n那么面向面试，我们应该如何学习一项新科目呢？我们就以 MySQL（高频考点）来举例吧。\n\n第一步：调研\n\n这一步很好理解，我们需要了解该项技术的经典书籍，这样能我们学习时，事半功倍。我一般是自己进行搜索。现在是开源的时代，大家都很喜欢分享自己的心得，你可以通过知乎，论坛等搜索到某项科目的经典书籍，但是不要只看一个帖子，多看几个，这些帖子中多次提到的书籍。就是我们的学习目标。\n\n![](https://img-blog.csdnimg.cn/a3bc62b23f994897a01d2f3a55b7463a.png)\n\n另外你也可以问师兄师姐们，毕竟他们是过来人，知道哪些书籍值得读。\n\n这里给大家推荐几本我读过的 MySQL 书籍，没有基础的同学可以按这个路线学习。\n\n- MySQL 必知必会\n\n  一本小册子，一天就能搞定，帮你快速入门 MySQL，另外大家在学习时，可以自己下载一下 MySQL 官方的学习库，然后自己动手实践一下，虽然速度慢了一些，但是能够让你学习的更加扎实。\n\n  ![在这里插入图片描述](https://img-blog.csdnimg.cn/94505d023f6e4cf9ab179925ac7420a6.png)\n\n  官方的 employees 库，我们可以用来练习一下，连接，explains 命令等。\n\n- 数据库系统概论\n\n  玫红色书皮的那本书，很多学校用来当作教材，这本书对数据库基础知识，查询语句，范式等讲的比较详细。这本书因为我之前学过几遍，后面再看的时候很快就读完了。个人认为还不错的一本书。有的学校研究生复试会考数据库，那么可以看下这本书，考点全面覆盖。\n\n- 高性能 MySQL\n\n  非常牛皮的一本书，很多知识点在里面讲的很细，适合进阶的同学，如果你看了这本书，面试时，常考的那些知识点，你就可以得心应手啦。\n\n- MySQL 技术内幕\n\n  这本书我没有完整的看下来，看了部分章节，比如那些常考的知识点，事务，索引等。也是非常棒的一本书，推荐给面试的同学。\n\n- MySQL 45 讲\n\n  这门课我看了大概百分之七十，前面的十几讲 看了大概 3-4 遍，每次都有新收获，这门课适合有一定基础的同学，如果没有学过 MySQL 的话，看的时候可能会有些吃力。\n\n- 从根上理解 MySQL\n\n  这个是掘金小册，也非常棒，但是我发现的有点晚了，后面抽着看了大概 1/2 吧。小册子对某个知识点说的很细，很透。\n\n视频的话，我看的比较少，之前看过 MOOC 哈工大，战德臣 老师的课程，非常牛的一位老师，讲课风格也很棒，没有基础的同学可以看一下这个视频。\n\n好啦，第一步一不小心扯多了，下面我们来说第二步。\n\n第二步：看面经（八股）\n\n啥？你刚才还说不能只看八股，这刚调研完经典书籍，就开始看八股了？这不是自己打自己脸吗？先别骂，先别骂，听我接着往下说。\n\n这时的八股和面试题，是为了让你知道面试时的重点，哪些知识点常考，这样我们就可以重点看那些常考的章节。\n\n那些不常考的知识点就不用看了吗？当然也是需要看的，因为每个章节之间是有联系的，通过前面的章节引出后面的，可以帮助我们更好的理解，形成自己的体系结构。不过这些不是重点的章节，可以粗略学习，了解即可。\n\n第三步：看书\n\n这一步我建议大家看纸质书，我们可以在书上标注，后面二刷三刷的时候，也可以根据标注帮我们回忆。大家可以在看书的时候，自己做一下思维导图，帮助我们构建自己的知识体系。推荐的软件是 Xmind，ProcessOn。\n\n第四步：看面经和八股\n\n注意，这里是看不是背，我们通过面经里的问题来进行归纳整理，对面经的问题进行分类，然后自己通过翻阅书籍和文章来找到答案进行整理，记住哈，记得分类，后面便于补充，也有利于搭建我们的知识体系。比如下面这样\n\n![](https://img-blog.csdnimg.cn/92c846fe20ac4162960927a964b29bac.png)\n\n第五步：回溯\n\n哈哈，这个回溯不是我们刷题的那个回溯，而是我们对每次面试的总结，建议大家刚开始面试的时候可以对自己的面试过程进行录屏，面试结束后，查看录像，看看自己的言行举止等，是否有摇头晃脑，回答不自信等情况。\n\n后面的话则只需录音即可，思考一下自己哪块回答的不太好，需要迭代，思考一下某个问题，面试官想要考察的点是什么。经历几次之后，就能找到自己的面试节奏和风格。\n\n大家是不是发现学好一门课并不容易，也比较耗时，所以我们需要尽早的准备面试，早就是优势！\n\n好啦，我要说的大概就这些啦，希望可以对学弟学妹们有一丢丢帮助。大家可以在评论区进行补充，推荐一下自己认为不错的书籍，今天就唠到这吧，拜了个拜。如果你需要我整理的面经 PDF ，可以添加我的微信，备注你需要的科目和 PDF ，例如 数据库 PDF。\n"
  },
  {
    "path": "animation-simulation/写写水文/学弟问了我一个问题.md",
    "content": "一位学弟，问了我一个问题。\n\n![问题描述](https://img-blog.csdnimg.cn/93cb8f9ccbe442a1bec05fff68a2e8e3.png)\n\n我在这里说一下我的看法，希望能够对有相同问题的学弟学妹，有一丢丢帮助。\n\n回想自身\n\n我似乎从来没想过这个问题？\n\n读大学的时候，每天的想法不是，不是今天学点啥，吃点啥，玩个啥游戏开心开心，每天想的是，我怎么练球才能把我哥们打爆，斗牛时，说垃圾话，怎么才能不落下风。寒暑假的时候，能够大冬天搁水泥地（不是篮球场，是一块空地）拍球，拍两三个小时，就是为了开学的时候，把他们斩于马下。\n\n就这样打着打着篮球，忽然就到大三下学期了，然后就听到谁谁谁去哪个大厂实习啦，谁谁谁参加什么比赛得奖啦。\n\n好家伙，我慌了啊。\n\n突然不知道自己该干啥了，打球打球，打个锤子球，马上就毕业啦，心里咋就没数呢？\n\n那天晚上我失眠了，也在那天晚上，做了一个可能影响我一生的决定。\n\n嗯，我决定考研！\n\n其实说白了，也就是为了逃避就业，为自己的菜找个借口。\n\n第二天醒来，就直接背着书去了自习室，开始了朝 8 晚 10 的复习之路。从决定考研到考研前夕，为期八个多月的备考，我打球的次数不会超过 5 次，休息的总天数不会超过 3 天。\n\n每次哥们叫我打球，我总是找一些借口推掉了，渐渐的他们打球的时候也就不喊我了，我也就安心准备考研啦。\n\n备考的这段时间，我一直没有忘记锻炼，我会隔一天去一次健身房，每次锻炼一个半小时。我觉得我能坚持到考研，健身房也有很大的功劳。\n\n毕竟备考还是很累的，一坐就是一整天，中午也不回宿舍睡觉，就在桌子上趴一会。所以要有一个好身体，才能扛得住。\n\n读者：你这说了一堆，和你学弟问的问题也没啥关系啊！\n\n厨子：哎嘛，别急别急，即然说啦，肯定是有用的的，马上就说到重点啦。\n\n通过上面我的例子，我认为完成某个目标需要具备这几个特点。\n\n- 抹平信息差\n- 执行力\n- 持之以恒\n\n**抹平信息差**\n\n其实很多时候，我们和别人都有信息差距，进而导致思维上的差距，然而思维上的差距是最难缩短的。\n\n我们为什么考大学考研究生时，想要考好大学，毕业后想去大公司。\n\n大公司好呀，钱多，福利好，技术氛围好。\n\n嗯是的，进入好学校和好公司都可以理解成进入了一个好的平台。之前和一位 HR 聊了挺久，交谈中的一句话，让我仍记忆犹新。\n\n应届生入职时，平台带给你的，远远大于你自身技术带给你的。\n\n暂且不论这句话是对是错，聊天结束后我思考了这句话的含义，以下是我对这句话，某一个方面的理解。\n\n在好的平台里，你会多了很多和大牛们交流的机会，相当于进入了一个好的圈子。**在什么样的圈子里，以后就做什么样的事情，但是你现在所做的事，决定你以后能够进入什么样的圈子。**\n\n这个过程中，你可能意识不到你的进步，但是如果你养成记录的习惯，回过头来再看的话，你会发现自己真的进步很大，而且进步的过程本来就是悄无声息，而是在之后的某一时刻，你才会发现你进步了。\n\n当然好的平台带给我们的远远不止这一点，而且我们每个人对 “好” 的定义也不相同，就不在这详细说啦。\n\n我认为抹平信息差是完成某个目标的要做的第一件事，完成目标前，我们要先定下目标。\n\n本科的时候，我们很多人甚至都不知道有保研，秋招，比赛这一说，你敢信？\n\n**作为大学生的你们则可以通过一下几种方法帮助你们抹平信息差**\n\n1.请教往届的师兄师姐，他们的经验分享或许对你帮助很大。\n\n2.通过某些途径，看一些前辈的分享求职分享或者学习路线等，比如知乎，牛客，脉脉等。\n\n就拿考研来说，如果你看过，其他师兄师姐的考研心得，那么你就有可能少走很多弯路，复习的更加充分，上岸的几率则更大。\n\n有的时候，我们缺少和前辈面对面交流的机会，但是从他们的文字中，也能够学到很多。\n\n3.多和比你优秀的人交流。\n\n就比如《孔子家语》中的一句话，与善人居，如入芝兰之室，久而不闻其香，即与之化矣。这句话的意思是，与品行优良的人共同居住，就好比是进入栽满了芝兰香草的雅室，时间久了就闻不到香味了，因为他已经被同化了，变得同样的品行优良。\n\n思维高度亦是如此。\n\n**执行力**\n\n如果我们将抹平信息差，看成是通过前辈们的指导，找到了满是成熟果实的果园，那么执行力就可以比做摘果子的竹竿。\n\n我们不要想着我们找到了果园，就能够硕果累累。果实是有成熟期的，如果在特定的时间不摘的话，就会腐烂（机会流逝）。\n\n所以我们想到什么事有搞头，就抓紧去做吧，不然，只会让机会白白流走。\n\n种一棵树最好的时间是十年前，其次是现在。\n\n这句话，没毛病，但是我们为什么不把握最好的时间，而是要退而求其次呢？\n\n很多时候，我们都是通过和身边人的比较，而知道自身水平。\n\n所以如果身边的人都在进步，你停滞不前，到你发现的时候， 欲望更大，需要的更多，所承受的压力则会更大。\n\n所以现在觉得有搞头，就快闷着头往前冲吧！\n\n**持之以恒**\n\n写到这的时候，感觉要写不下去了，说的这些感觉大家都知道，但是没有经历过的话，很难有深刻理解。本科时，我浪费掉了太多时间，所以真的希望学弟学妹们能够把握当下，这四年真的能够学到太多东西。\n\n还是接着往下说吧。\n\n我们现在已经在**满是成熟果实**的果园里打果子啦。如果我们三天打鱼两天晒网，别人果园的果实都打完了，开始找下一个果园了，你的这个还没打一半，这可咋整。\n\n所以我们在给自己定下目标之后，坚持下去。\n\n我们可以通过一下几种方法帮助我们坚持。\n\n**正反馈**\n\n设立奖励机制，我们完成某个小目标之后，则可以奖励给自己某些东西，比如吃些自己想吃的，买些自己之前想买，但是没买的东西。就比如我之前考研的时候，我会在每天早上，给自己设定好，自己今天要做的题目。\n\n设定过今天的学习目标之后，则能让我学习效率大幅度提升，努力完成今天的目标，完成之后，则会在回宿舍的时候，奖励自己好吃的。\n\n**闭关**\n\n是的，闭关。无论是考研的时候，还是准备秋招的时候，我都是自己一人去图书馆，然后将手机锁机，开始学习。\n\n我觉得最好的学习状态就是，保证作息规律，每天重复同样的事，吃饭，学习，睡觉。\n\n进入这个状态之后，你会非常的想学习，对学习之外的事，兴趣就变得没有之前那么大，自然而然的就坚持下来啦。\n\n**回溯**\n\n其实这个也和正反馈一致，我们可以每天晚上，整理今日所学。整理过后，你会发现你今天学到了很多东西，就会非常充实和踏实。感觉今天一天没有白过。自然而然的形成良性循环，帮助你坚持下去。\n\n当然我们一定要注意锻炼，身体是革命的本钱，身体和脑子有一个动就好啦。\n\n授人以鱼，不如授人以渔。希望这篇文章，能够对迷茫的学弟学妹有一丢丢帮助。这篇文章不是假大空，是我经历过，感觉浪费掉的大把时间真的可惜。\n\n虽然人生是个长跑，不是冲刺跑。某个时期对你的影响，没有你想象的那么大，只要我们保持向上的心就好。\n\n如果给我重新读大学的机会，我仍然会和哥们们好好打球，不过我会努力抹平信息差，对自己的职业生涯好好规划。\n\n如果觉得这篇文章对你有点用，那就帮忙转发给你的朋友吧。好啦，今天就唠到这吧，拜了个拜。\n"
  },
  {
    "path": "animation-simulation/写写水文/常看的UP主.md",
    "content": "今天不和大家吹牛玩了，唠唠嗑吧，说几个我逛 B 站经常看的几位 UP 主吧。看看里面有没有你关注滴。我知道在做的各位，很多都是在 B 站学跳舞的 🐶，我看的比较多的则是搞笑区 UP，他们可都太逗了。\n\n### 导演小策\n\n入坑作品，是那个贼牛的《一块劳力士的回家路》，现在已经一千多万播放了，当时感觉小策真的太有才了，短短三分钟，剧情跌宕起伏，既隐晦又深刻。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/ee00701440eb42b7a993fbd1a7dc4a32.png)\n\n后面他又拍了《广场往事》系列，每个视频都让我笑出鹅叫，甚至连恰饭都恰的那么清新脱俗。\n\n![](https://img-blog.csdnimg.cn/27b3da9294ee4378a8b392adae3bfe5f.png)\n\n广场舞巨头鹅姨，鹅姨的跟班二花，会说 rap 的烫嘴阿姨，爱和三舅合影的三炮。每个人物都个性鲜明，绝了。\n\n![2021-09-13 21.56.41](/Users/yuanbangjie/Documents/动画/2021-09-13 21.56.41.gif)\n\n### 才疏学浅的才浅\n\n这个真的是巨佬，也是我关注特别久的 UP 刚开始关注的时候才几千粉丝，现在已经 350 万了。\n\n当时还跟着他的视频，给女朋友做了两个印章，（不知道咋回事，视频找不到了）。\n\n![我做的印章](https://img-blog.csdnimg.cn/e2294dac7350447b8fe0d572e2cd9d34.png)\n\n可是后来，他开始做刀了。\n\n![](https://img-blog.csdnimg.cn/d7f25b635d80406eb406bd34e4cc55f1.png)\n\n再后来，他开始做面具，开始制杖了！\n\n![](/Users/yuanbangjie/Library/Application Support/typora-user-images/image-20210913224757736.png)\n\n这下我真的搞不定了，才浅做面具和制杖的视频还登上了央视，真的太牛了，也喜欢手工的老哥可以去看一哈，非常牛的一位 UP 主。\n\n### 小潮院长\n\n哈哈，小潮 TEAM 的大哥大，他的不要做挑战真的太好笑啦，来回看了好几遍，每次都笑的肚子痛。\n\n这个还掀起了 B 站的模仿热潮。真的是一个既有才又有趣的 UP。\n\n![](https://img-blog.csdnimg.cn/5f0d27231c5b4d46b8a85d0f14c52683.png)\n\n### 张若宇\n\n张若宇一个喜欢敷面膜的老哥，他的视频有一个特点，那就是短，短短一分钟的视频，节奏把握的特别好，BGM 配的也恰到好处，属实让他玩明白了。视频中最喜欢看的就是陶陶，一个酷爱吃肘子的 “活宝”。\n\n![](https://img-blog.csdnimg.cn/9d9034a4af114924bdf7516164eeef7e.png)\n\n亲切的口音和热闹的家庭氛围，让我很喜欢看他们的视频。也是一个非常有才 UP 主。\n\n### 野球帝\n\n很久很久之前就关注了野球帝，当时的我还很小，一看就看到现在。\n\n野球帝团队的人也越来越多，也越来越热闹。\n\n说实话真的很羡慕他们那种氛围，既可以和兄弟们一起打球，又能一起工作。\n\n和喵哥说的似的，等退休之后，开个篮球场和烧烤店，和兄弟们打完球，一起撸撸串吹吹牛，好不惬意。\n\n![](https://img-blog.csdnimg.cn/fd31c23d431b470f99a769a62ec332f6.png)\n\n依旧干货满满，另外多说一句，别让通演傻子啦，他快改不过来啦。哈哈\n\n喜欢篮球的哥们可以关注一波。\n\nKBT 篮球训练营\n\n这不和喵哥约了国庆节决一死战，我俩每天都在群里说一些垃圾话，都觉得能把对方打爆。不能光吹牛批不干活，所以咱们得把训练安排上。一位干货满满的 UP ，为你指出平常没有注意到的细节。都是很实用的动作，打喵哥应该足够了。\n\n![](https://img-blog.csdnimg.cn/059dfed9cd2b4fd3bd16cc28f926b429.png)\n\n好啦，今天是纯唠嗑局，大家也可以把自己常看的优质 UP 打在评论区。\n\n后面会继续给大家更新一些关于面试事，另外多说一句，2023 秋招的学弟学妹们，要尽快准备起来啦，早就是优势。\n\n今天就唠到这吧，拜了个拜。\n"
  },
  {
    "path": "animation-simulation/写写水文/送书.md",
    "content": "好久不见\n\n哈喽大家好，我是厨子，好久不见啊。\n\n主要是这段时间太忙啦，所以没有进行更新，不过后面会慢慢更新起来，继续更之前的专题。\n\n那么我今天是来干什么的呢？给大家送点福利，送几本我们经常用的《剑指 offer》。呐，就是下面这一本啦。\n\n《剑指 offer 专项突破版》\n\n感谢博文视点杨老师的赠书\n\n大概翻了一下，这本书的目录和内容，这本书不仅仅是根据专题来进行编写，另外还将每个专题的解题方法进行了总结，个人感觉是非常不错的，能够帮助我们高效刷题。书中的题目也都是比较经典，高频的题目，对于我们面试也很有帮助。\n\n下面是专项版和经典版的一些对比。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/e5ee04d996d24fad9804749557f8e87b.png)\n\n杨老师这里赞助了我六本，送给读者朋友，大家需要的可以参与下。\n"
  },
  {
    "path": "animation-simulation/分治/空.md",
    "content": ""
  },
  {
    "path": "animation-simulation/前缀和/leetcode1248寻找优美子数组.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [1248. 统计「优美子数组」](https://leetcode-cn.com/problems/count-number-of-nice-subarrays/)\n\n**题目描述**\n\n> 给你一个整数数组 nums 和一个整数 k。\n>\n> 如果某个 连续 子数组中恰好有 k 个奇数数字，我们就认为这个子数组是「优美子数组」。\n>\n> 请返回这个数组中「优美子数组」的数目。\n\n**示例 1：**\n\n> 输入：nums = [1,1,2,1,1], k = 3\n> 输出：2\n> 解释：包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。\n\n**示例 2：**\n\n> 输入：nums = [2,4,6], k = 1\n> 输出：0\n> 解释：数列中不包含任何奇数，所以不存在优美子数组。\n\n**示例 3：**\n\n> 输入：nums = [2,2,2,1,2,2,1,2,2,2], k = 2\n> 输出：16\n\n如果上面那个题目我们完成了，这个题目做起来，分分钟的事，不信你去写一哈，百分百就整出来了，我们继续按上面的思想来解决。\n\n**HashMap**\n\n**解析**\n\n上个题目我们是求和为 K 的子数组，这个题目是让我们求 恰好有 k 个奇数数字的连续子数组，这两个题几乎是一样的，上个题中我们将前缀区间的和保存到哈希表中，这个题目我们只需将前缀区间的奇数个数保存到区间内即可，只不过将 sum += x 改成了判断奇偶的语句，见下图。\n\n![微信截图_20210114222339](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210114222339.c0gwtdh8m94.png)\n\n我们来解析一下哈希表，key 代表的是含有 1 个奇数的前缀区间，value 代表这种子区间的个数，含有两个，也就是 nums[0],nums[0,1].后面含义相同，那我们下面直接看代码吧，一下就能读懂。\n\nJava Code:\n\n```java\nclass Solution {\n    public int numberOfSubarrays(int[] nums, int k) {\n\n        if (nums.length == 0) {\n            return 0;\n        }\n        HashMap<Integer,Integer> map = new HashMap<>();\n        //统计奇数个数，相当于我们的 presum\n        int oddnum = 0;\n        int count = 0;\n        map.put(0,1);\n        for (int x : nums) {\n            // 统计奇数个数\n            oddnum += x & 1;\n            // 发现存在，则 count增加\n            if (map.containsKey(oddnum - k)) {\n             count += map.get(oddnum - k);\n            }\n            //存入\n            map.put(oddnum,map.getOrDefault(oddnum,0)+1);\n        }\n        return count;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int numberOfSubarrays(vector<int>& nums, int k) {\n        if (nums.size() == 0) {\n            return 0;\n        }\n        map <int, int> m;\n        //统计奇数个数，相当于我们的 presum\n        int oddnum = 0;\n        int count = 0;\n        m.insert({0,1});\n        for (int & x : nums) {\n            // 统计奇数个数\n            oddnum += x & 1;\n            // 发现存在，则 count增加\n            if (m.find(oddnum - k) != m.end()) {\n             count += m[oddnum - k];\n            }\n            //存入\n            if(m.find(oddnum) != m.end()) m[oddnum]++;\n            else m[oddnum] = 1;\n        }\n        return count;\n    }\n};\n```\n\n但是也有一点不同，就是我们是统计奇数的个数，数组中的奇数个数肯定不会超过原数组的长度，所以这个题目中我们可以用数组来模拟 HashMap ，用数组的索引来模拟 HashMap 的 key，用值来模拟哈希表的 value。下面我们直接看代码吧。\n\nJava Code:\n\n```java\nclass Solution {\n    public int numberOfSubarrays(int[] nums, int k) {\n        int len = nums.length;\n        int[] map = new int[len + 1];\n        map[0] = 1;\n        int oddnum = 0;\n        int count = 0;\n        for (int i = 0; i < len; ++i) {\n            //如果是奇数则加一，偶数加0，相当于没加\n            oddnum += nums[i] & 1;\n            if (oddnum - k >= 0) {\n                count += map[oddnum-k];\n            }\n            map[oddnum]++;\n        }\n        return count;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int numberOfSubarrays(vector<int>& nums, int k) {\n        int len = nums.size();\n        vector <int> map(len + 1, 0);\n        map[0] = 1;\n        int oddnum = 0;\n        int count = 0;\n        for (int i = 0; i < len; ++i) {\n            //如果是奇数则加一，偶数加0，相当于没加\n            oddnum += nums[i] & 1;\n            if (oddnum - k >= 0) {\n                count += map[oddnum-k];\n            }\n            map[oddnum]++;\n        }\n        return count;\n    }\n};\n```\n"
  },
  {
    "path": "animation-simulation/前缀和/leetcode523连续的子数组和.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [523. 连续的子数组和](https://leetcode-cn.com/problems/continuous-subarray-sum/)\n\n**题目描述**\n\n> 给定一个包含 非负数 的数组和一个目标 整数 k，编写一个函数来判断该数组是否含有连续的子数组，其大小至少为 2，且总和为 k 的倍数，即总和为 n\\*k，其中 n 也是一个整数。\n\n**示例 1：**\n\n> 输入：[23,2,4,6,7], k = 6\n> 输出：True\n\n解释：[2,4] 是一个大小为 2 的子数组，并且和为 6。\n\n**示例 2：**\n\n> 输入：[23,2,6,4,7], k = 6\n> 输出：True\n\n解释：[23,2,6,4,7]是大小为 5 的子数组，并且和为 42。\n\n**前缀和 + HashMap**\n\n这个题目算是对刚才那个题目的升级，前半部分是一样的，都是为了让你找到能被 K 整除的子数组，但是这里加了一个限制，那就是子数组的大小至少为 2，那么我们应该怎么判断子数组的长度呢？我们可以根据索引来进行判断，见下图。\n\n![微信截图_20210115174825](https://img-blog.csdnimg.cn/img_convert/953d09fbfffab9298152e143a39c85c0.png)\n\n此时我们 K = 6, presum % 6 = 4 也找到了相同余数的前缀子数组 [0,1] 但是我们此时指针指向为 2，我们的前缀子区间 [0,1]的下界为 1，所以 2 - 1 = 1，但我们的中间区间的长度小于 2，所以不能返回 true，需要继续遍历，那我们有两个区间[0,1],[0,2]都满足 presum % 6 = 4，那我们哈希表中保存的下标应该是 1 还是 2 呢？我们保存的是 1，如果我们保存的是较大的那个索引，则会出现下列情况，见下图。\n\n![微信截图_20210115175122](https://img-blog.csdnimg.cn/img_convert/7bbd04ac578074d5fbccae7ab384f061.png)\n\n此时，仍会显示不满足子区间长度至少为 2 的情况，仍会继续遍历，但是我们此时的 [2,3]区间已经满足该情况，返回 true，所以我们往哈希表存值时，只存一次，即最小的索引即可。下面我们看一下该题的两个细节\n\n细节 1：我们的 k 如果为 0 时怎么办，因为 0 不可以做除数。所以当我们 k 为 0 时可以直接存到数组里，例如输入为 [0,0] , K = 0 的情况\n\n细节 2：另外一个就是之前我们都是统计个数，value 里保存的是次数，但是此时我们加了一个条件就是长度至少为 2，保存的是索引，所以我们不能继续 map.put(0,1)，应该赋初值为 map.put(0,-1)。这样才不会漏掉一些情况，例如我们的数组为[2,3,4],k = 1,当我们 map.put(0,-1) 时，当我们遍历到 nums[1] 即 3 时，则可以返回 true，因为 1-（-1）= 2，5 % 1=0 , 同时满足。\n\n**视频解析**\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210318094237943.gif#pic_center)\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public boolean checkSubarraySum(int[] nums, int k) {\n        HashMap<Integer,Integer> map = new HashMap<>();\n        //细节2\n        map.put(0,-1);\n        int presum = 0;\n        for (int i = 0; i < nums.length; ++i) {\n            presum += nums[i];\n            //细节1，防止 k 为 0 的情况\n            int key = k == 0 ? presum : presum % k;\n            if (map.containsKey(key)) {\n                if (i - map.get(key) >= 2) {\n                     return true;\n                }\n                //因为我们需要保存最小索引，当已经存在时则不用再次存入，不然会更新索引值\n                continue;\n            }\n            map.put(key,i);\n        }\n        return false;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    bool checkSubarraySum(vector<int>& nums, int k) {\n        map <int, int> m;\n        //细节2\n        m.insert({0,-1});\n        int presum = 0;\n        for (int i = 0; i < nums.size(); ++i) {\n            presum += nums[i];\n            //细节1，防止 k 为 0 的情况\n            int key = k == 0 ? presum : presum % k;\n            if (m.find(key) != m.end()) {\n                if (i - m[key] >= 2) {\n                     return true;\n                }\n                //因为我们需要保存最小索引，当已经存在时则不用再次存入，不然会更新索引值\n                continue;\n            }\n            m.insert({key, i});\n        }\n        return false;\n    }\n};\n```\n\nGo Code:\n\n```go\nfunc checkSubarraySum(nums []int, k int) bool {\n    m := map[int]int{}\n    // 由于前缀和%k可能为0，所以需要给出没有元素的时候，索引位置，即-1\n    m[0] = -1\n    sum := 0\n    for i, num := range nums {\n        sum += num\n        key := sum % k\n        /*\n        // 题目中告诉k >= 1\n        key := sum\n        if k != 0 {\n            key = sum % k\n        }\n        */\n        if v, ok := m[key]; ok {\n            if i - v >= 2 {\n                return true\n            }\n            // 避免更新最小索引\n            continue\n        }\n        // 保存的是最小的索引\n        m[key] = i\n    }\n    return false\n}\n```\n"
  },
  {
    "path": "animation-simulation/前缀和/leetcode560和为K的子数组.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [560. 和为 K 的子数组](https://leetcode-cn.com/problems/subarray-sum-equals-k/)\n\n**题目描述**\n\n> 给定一个整数数组和一个整数 k，你需要找到该数组中和为 k 的连续的子数组的个数。\n\n**示例 1 :**\n\n> 输入:nums = [1,1,1], k = 2\n> 输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。\n\n**暴力法**\n\n**解析**\n\n这个题目的题意很容易理解，就是让我们返回和为 k 的子数组的个数，所以我们直接利用双重循环解决该题，这个是很容易想到的。我们直接看代码吧。\n\n```java\nclass Solution {\n    public int subarraySum(int[] nums, int k) {\n         int len = nums.length;\n         int sum = 0;\n         int count = 0;\n         for (int i = 0; i < len; ++i) {\n             for (int j = i; j < len; ++j) {\n                 sum += nums[j];\n                 if (sum == k) {\n                     count++;\n                 }\n             }\n             sum = 0;\n         }\n         return count;\n    }\n}\n```\n\n下面我们我们使用前缀和的方法来解决这个题目，那么我们先来了解一下前缀和是什么东西。其实这个思想我们很早就接触过了。见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210113193831.4wk2b9zc8vm0.png)\n\n我们通过上图发现，我们的 presum 数组中保存的是 nums 元素的和，presum[1] = presum[0] + nums[0];\n\npresum [2] = presum[1] + nums[1],presum[3] = presum[2] + nums[2] ... 所以我们通过前缀和数组可以轻松得到每个区间的和，\n\n例如我们需要获取 nums[2] 到 nums[4] 这个区间的和，我们则完全根据 presum 数组得到，是不是有点和我们之前说的字符串匹配算法中 BM,KMP 中的 next 数组和 suffix 数组作用类似。\n\n那么我们怎么根据 presum 数组获取 nums[2] 到 nums[4] 区间的和呢？见下图\n\n![前缀和](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/前缀和.77twdj3gpkg0.png)\n\n所以我们 nums[2] 到 nums[4] 区间的和则可以由 presum[5] - presum[2] 得到。\n\n也就是前 5 项的和减去前 2 项的和，得到第 3 项到第 5 项的和。那么我们可以遍历 presum 就能得到和为 K 的子数组的个数啦。\n\n直接上代码。\n\n```java\nclass Solution {\n    public int subarraySum(int[] nums, int k) {\n        //前缀和数组\n        int[] presum = new int[nums.length+1];\n        for (int i = 0; i < nums.length; i++) {\n            //这里需要注意，我们的前缀和是presum[1]开始填充的\n            presum[i+1] = nums[i] + presum[i];\n        }\n        //统计个数\n        int count = 0;\n        for (int i = 0; i < nums.length; ++i) {\n            for (int j = i; j < nums.length; ++j) {\n                //注意偏移，因为我们的nums[2]到nums[4]等于presum[5]-presum[2]\n                //所以这样就可以得到nums[i,j]区间内的和\n                if (presum[j+1] - presum[i] == k) {\n                    count++;\n                }\n            }\n        }\n        return count;\n    }\n}\n```\n\n我们通过上面的例子我们简单了解了前缀和思想，那么我们如果继续将其优化呢？\n\n**前缀和 + HashMap**\n\n**解析**\n\n其实我们在之前的两数之和中已经用到了这个方法，我们一起来回顾两数之和 HashMap 的代码.\n\n```java\nclass Solution {\n    public int[] twoSum(int[] nums, int target) {\n\n        HashMap<Integer,Integer> map  = new HashMap<>();\n        //一次遍历\n        for (int i = 0; i < nums.length; ++i) {\n            //存在时，我们用数组得值为 key，索引为 value\n            if (map.containsKey(target - nums[i])){\n               return new int[]{i,map.get(target-nums[i])};\n            }\n            //存入值\n            map.put(nums[i],i);\n        }\n        //返回\n        return new int[]{};\n    }\n}\n```\n\n上面代码中，我们将数组的值和索引存入 map 中，当我们遍历到某一值 x 时，判断 map 中是否含有 target - x，即可。其实我们现在这个题目和两数之和原理是一致的，只不过我们是将**所有的前缀和**该**前缀和出现的次数**存到了 map 里。下面我们来看一下代码的执行过程。\n\n**动图解析**\n\n![](https://img-blog.csdnimg.cn/2021031809231883.gif#pic_center)\n\n**题目代码**\n\nJava Code：\n\n```java\nclass Solution {\n    public int subarraySum(int[] nums, int k) {\n        if (nums.length == 0) {\n            return 0;\n        }\n        HashMap<Integer,Integer> map = new HashMap<>();\n        //细节，这里需要预存前缀和为 0 的情况，会漏掉前几位就满足的情况\n        //例如输入[1,1,0]，k = 2 如果没有这行代码，则会返回0,漏掉了1+1=2，和1+1+0=2的情况\n        //输入：[3,1,1,0] k = 2时则不会漏掉\n        //因为presum[3] - presum[0]表示前面 3 位的和，所以需要map.put(0,1),垫下底\n        map.put(0, 1);\n        int count = 0;\n        int presum = 0;\n        for (int x : nums) {\n            presum += x;\n            //当前前缀和已知，判断是否含有 presum - k的前缀和，那么我们就知道某一区间的和为 k 了。\n            if (map.containsKey(presum - k)) {\n                count += map.get(presum - k);//获取presum-k前缀和出现次数\n            }\n            //更新\n            map.put(presum,map.getOrDefault(presum,0) + 1);\n        }\n        return count;\n    }\n}\n```\n\nC++ Code：\n\n```cpp\npublic:\n    int subarraySum(vector<int>& nums, int k) {\n         if (nums.size() == 0) {\n            return 0;\n        }\n        map <int, int> m;\n        //细节，这里需要预存前缀和为 0 的情况，会漏掉前几位就满足的情况\n        //例如输入[1,1,0]，k = 2 如果没有这行代码，则会返回0,漏掉了1+1=2，和1+1+0=2的情况\n        //输入：[3,1,1,0] k = 2时则不会漏掉\n        //因为presum[3] - presum[0]表示前面 3 位的和，所以需要m.insert({0,1}),垫下底\n        m.insert({0, 1});\n        int count = 0;\n        int presum = 0;\n        for (int x : nums) {\n            presum += x;\n            //当前前缀和已知，判断是否含有 presum - k的前缀和，那么我们就知道某一区间的和为 k 了。\n            if (m.find(presum - k) != m.end()) {\n                count += m[presum - k];//获取presum-k前缀和出现次数\n            }\n            //更新\n           if(m.find(presum) != m.end()) m[presum]++;\n           else m[presum] = 1;\n        }\n        return count;\n    }\n};\n```\n\nGo Code:\n\n```GO\nfunc subarraySum(nums []int, k int) int {\n    m := map[int]int{}\n    // m存的是前缀和，没有元素的时候，和为0，且有1个子数组(空数组)满足条件，即m[0] = 1\n    m[0] = 1\n    sum := 0\n    cnt := 0\n    for _, num := range nums {\n        sum += num\n        if v, ok := m[sum - k]; ok {\n            cnt += v\n        }\n        // 更新满足前缀和的子数组数量\n        m[sum]++\n    }\n    return cnt\n}\n```\n"
  },
  {
    "path": "animation-simulation/前缀和/leetcode724寻找数组的中心索引.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### 前缀和详解\n\n今天我们来说一下刷题时经常用到的前缀和思想，前缀和思想和滑动窗口会经常用在求子数组和子串问题上，当我们遇到此类问题时，则应该需要想到此类解题方式，该文章深入浅出描述前缀和思想，读完这个文章就会有属于自己的解题框架，遇到此类问题时就能够轻松应对。\n\n下面我们先来了解一下什么是前缀和。\n\n前缀和其实我们很早之前就了解过的，我们求数列的和时，Sn = a1+a2+a3+...an; 此时 Sn 就是数列的前 n 项和。例 S5 = a1 + a2 + a3 + a4 + a5; S2 = a1 + a2。所以我们完全可以通过 S5-S2 得到 a3+a4+a5 的值，这个过程就和我们做题用到的前缀和思想类似。我们的前缀和数组里保存的就是前 n 项的和。见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210113193831.4wk2b9zc8vm0.png)\n\n我们通过前缀和数组保存前 n 位的和，presum[1]保存的就是 nums 数组中前 1 位的和，也就是 **presum[1]** = nums[0], **presum[2]** = nums[0] + nums[1] = **presum[1]** + nums[1]. 依次类推，所以我们通过前缀和数组可以轻松得到每个区间的和。\n\n例如我们需要获取 nums[2] 到 nums[4] 这个区间的和，我们则完全根据 presum 数组得到，是不是有点和我们之前说的字符串匹配算法中 BM,KMP 中的 next 数组和 suffix 数组作用类似。那么我们怎么根据 presum 数组获取 nums[2] 到 nums[4] 区间的和呢？见下图\n\n![前缀和](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/前缀和.77twdj3gpkg0.png)\n\n好啦，我们已经了解了前缀和的解题思想了，我们可以通过下面这段代码得到我们的前缀和数组，非常简单\n\n```java\n for (int i = 0; i < nums.length; i++) {\n      presum[i+1] = nums[i] + presum[i];\n }\n```\n\n好啦，我们开始实战吧。\n\n#### [724. 寻找数组的中心下标](https://leetcode-cn.com/problems/find-pivot-index/)\n\n**题目描述**\n\n> 给定一个整数类型的数组 nums，请编写一个能够返回数组 “中心索引” 的方法。\n>\n> 我们是这样定义数组 中心索引 的：数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。\n>\n> 如果数组不存在中心索引，那么我们应该返回 -1。如果数组有多个中心索引，那么我们应该返回最靠近左边的那一个。\n\n**示例 1：**\n\n> 输入：\n> nums = [1, 7, 3, 6, 5, 6]\n> 输出：3\n\n解释：\n索引 3 (nums[3] = 6) 的左侧数之和 (1 + 7 + 3 = 11)，与右侧数之和 (5 + 6 = 11) 相等。\n同时, 3 也是第一个符合要求的中心索引。\n\n**示例 2：**\n\n> 输入：\n> nums = [1, 2, 3]\n> 输出：-1\n\n解释：\n数组中不存在满足此条件的中心索引。\n\n理解了我们前缀和的概念（不知道好像也可以做，这个题太简单了哈哈）。我们可以一下就能把这个题目做出来，先遍历一遍求出数组的和，然后第二次遍历时，直接进行对比左半部分和右半部分是否相同，如果相同则返回 true，不同则继续遍历。\n\nJava Code:\n\n```java\nclass Solution {\n    public int pivotIndex(int[] nums) {\n        int presum = 0;\n        //数组的和\n        for (int x : nums) {\n           presum += x;\n        }\n        int leftsum = 0;\n        for (int i = 0; i < nums.length; ++i) {\n            //发现相同情况\n            if (leftsum == presum - nums[i] - leftsum) {\n                return i;\n            }\n            leftsum += nums[i];\n        }\n        return -1;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int pivotIndex(vector<int>& nums) {\n        int presum = 0;\n        //数组的和\n        for (int x : nums) {\n           presum += x;\n        }\n        int leftsum = 0;\n        for (int i = 0; i < nums.size(); ++i) {\n            //发现相同情况\n            if (leftsum == presum - nums[i] - leftsum) {\n                return i;\n            }\n            leftsum += nums[i];\n        }\n        return -1;\n    }\n};\n```\n\nGo Code:\n\n```go\nfunc pivotIndex(nums []int) int {\n    presum := 0\n    for _, num := range nums {\n        presum += num\n    }\n    var leftsum int\n    for i, num := range nums {\n        // 比较左半和右半是否相同\n        if presum - leftsum - num == leftsum {\n            return i\n        }\n        leftsum += num\n    }\n    return -1\n}\n```\n"
  },
  {
    "path": "animation-simulation/前缀和/leetcode974和可被K整除的子数组.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [974. 和可被 K 整除的子数组](https://leetcode-cn.com/problems/subarray-sums-divisible-by-k/)\n\n**题目描述**\n\n> 给定一个整数数组 A，返回其中元素之和可被 K 整除的（连续、非空）子数组的数目。\n\n**示例：**\n\n> 输入：A = [4,5,0,-2,-3,1], K = 5\n> 输出：7\n\n**解释：**\n\n> 有 7 个子数组满足其元素之和可被 K = 5 整除：\n> [4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]\n\n**前缀和+HashMap**\n\n**解析**\n\n我们在该文的第一题 **和为 K 的子数组 **中，我们需要求出满足条件的区间，见下图\n\n![微信截图_20210115194113](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210115194113.5e56re9qdic0.png)\n\n我们需要找到满足，和为 K 的区间。我们此时 presum 是已知的，k 也是已知的，我们只需要找到 presum - k 区间的个数，就能得到 k 的区间个数。但是我们在当前题目中应该怎么做呢？见下图。\n\n![微信截图_20210115150520](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210115150520.3kh5yiwwmlm0.png)\n\n我们在之前的例子中说到，presum[j+1] - presum[i] 可以得到 nums[i] + nums[i+1]+.... nums[j]，也就是[i,j]区间的和。\n\n那么我们想要判断区间 [i,j] 的和是否能整除 K，也就是上图中紫色那一段是否能整除 K，那么我们只需判断\n\n(presum[j+1] - presum[i] ) % k 是否等于 0 即可，\n\n我们假设 (presum[j+1] - presum[i] ) % k == 0；则\n\npresum[j+1] % k - presum[i] % k == 0;\n\npresum[j +1] % k = presum[i] % k ;\n\n我们 presum[j +1] % k 的值 key 是已知的，则是当前的 presum 和 k 的关系，我们只需要知道之前的前缀区间里含有相同余数 （key）的个数。则能够知道当前能够整除 K 的区间个数。见下图\n\n![微信截图_20210115152113](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210115152113.606bcpexpww0.png)\n\n**题目代码**\n\n```java\nclass Solution {\n    public int subarraysDivByK(int[] A, int K) {\n        HashMap<Integer,Integer> map = new HashMap<>();\n        map.put(0,1);\n        int presum = 0;\n        int count = 0;\n        for (int x : A) {\n             presum += x;\n             //当前 presum 与 K的关系，余数是几，当被除数为负数时取模结果为负数，需要纠正\n             int key = (presum % K + K) % K;\n             //查询哈希表获取之前key也就是余数的次数\n             if (map.containsKey(key)) {\n                 count += map.get(key);\n             }\n             //存入哈希表当前key，也就是余数\n             map.put(key,map.getOrDefault(key,0)+1);\n        }\n        return count;\n    }\n}\n```\n\n我们看到上面代码中有一段代码是这样的\n\n```java\nint key = (presum % K + K) % K;\n```\n\n这是为什么呢？不能直接用 presum % k 吗？\n\n这是因为当我们 presum 为负数时，需要对其纠正。纠正前(-1) %2 = (-1)，纠正之后 ( (-1) % 2 + 2) % 2=1 保存在哈希表中的则为 1.则不会漏掉部分情况，例如输入为 [-1,2,9],K = 2 如果不对其纠正则会漏掉区间 [2] 此时 2 % 2 = 0，符合条件，但是不会被计数。\n\n那么这个题目我们可不可以用数组，代替 map 呢？当然也是可以的，因为此时我们的哈希表存的是余数，余数最大也只不过是 K-1 所以我们可以用固定长度 K 的数组来模拟哈希表。\n\nJava Code:\n\n```java\nclass Solution {\n    public int subarraysDivByK(int[] A, int K) {\n        int[] map = new int[K];\n        map[0] = 1;\n        int len = A.length;\n        int presum = 0;\n        int count = 0;\n        for (int i = 0; i < len; ++i) {\n            presum += A[i];\n            //求key\n            int key = (presum % K + K) % K;\n            //count添加次数，并将当前的map[key]++;\n            count += map[key]++;\n        }\n        return count;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int subarraysDivByK(vector<int>& A, int K) {\n        vector <int> map (K, 0);\n      \tint len = A.size();\n        int count = 0;\n        int presum = 0;\n        map[0] = 1;\n         for (int i = 0; i < len; ++i) {\n            presum += A[i];\n            //求key\n            int key = (presum % K + K) % K;\n            //count添加次数，并将当前的map[key]++;\n            count += (map[key]++);\n        }\n        return count;\n    }\n};\n```\n\nGo Code:\n\n```go\nfunc subarraysDivByK(nums []int, k int) int {\n    m := make(map[int]int)\n    cnt := 0\n    sum := 0\n    m[0] = 1\n    for _, num := range nums {\n        sum += num\n        key := (sum % k + k) % k\n        cnt += m[key]\n        m[key]++\n    }\n    return cnt\n}\n```\n"
  },
  {
    "path": "animation-simulation/剑指offer/1的个数.md",
    "content": "# 我太喜欢这个题了\n\n> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n今天我们来看一道贼棒的题目，题目不长，很经典，也很容易理解，我们一起来看一哈吧，\n\n大家也可能做过这道题，那就再复习一下，如果没做过的话，可以看完文章，自己去 AC 一下，不过写代码的时候，要自己完全写出来，这样才能有收获，下面我们看题目吧。\n\n## leetcode 233. 数字 1 的个数\n\n**题目描述**\n\n给定一个整数 n，计算所有小于等于 n 的非负整数中数字 1 出现的个数。\n\n示例 1：\n\n> 输入：n = 13\n> 输出：6\n\n示例 2：\n\n> 输入：n = 0\n> 输出：0\n\n太喜欢这种简洁的题目啦，言简意赅，就是让咱们找出**小于等于 n 的非负整数中数字 1 出现的个数**。\n\n大家看到这个题目的第一印象，可能会这样想，哦，让我们求 1 的个数。\n\n呐我们直接逐位遍历每个数的每一位，当遇到 1 的时候，计数器 +1，不就行了。\n\n嗯，很棒的方法，可惜会超时。（我试了）\n\n或者说，我们可以先将所有数字拼接起来，然后再逐位遍历，这样仍会超时。（我也试了）\n\n大家再思考一下还有没有别的方法呢？\n\n既然题目让我们统计小于等于 n 的非负整数中数字 1 出现的个数。\n\n那我们可以不可这样统计。\n\n我们假设 n = abcd，某个四位数。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/1的次数1.1s5l5k3qy3y8.png)\n\n那我们完全可以统计每一位上 1 出现的次数，个数上 1 出现的次数，十位上 1 出现的次数，百位 ，千位。。。\n\n也就是说**小于等于 n 的所有数字中**，个位上出现 1 的次数 + 十位出现 1 的次数 + 。。。最后得到的就是总的出现次数。\n\n见下图\n\n我们假设 n = 13 (用个小点的数，比较容易举例)\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/1的次数2.1horkktykr7k.png)\n\n我们需要统计小于等于 13 的数中，出现 1 的次数，\n\n通过上图可知，个位上 1 出现 2 次，十位上 1 出现 4 次\n\n那么总次数为 2 + 4 = 6 次。\n\n> 另外我们发现 11 这个数，会被统计 2 次，它的十位和个位都为 1 ，\n>\n> 而我们这个题目是要统计 1 出现的次数，而不是统计包含 1 的整数，所以上诉方法不会出现重复统计的情况。\n\n我们题目已经有大概思路啦，下面的难点就是如何统计每一位中 1 出现的次数呢？\n\n我们完全可以通过遍历 n 的每一位来得到总个数，见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/1的次数3.21nr01qnlz40.png)\n\n假设我们想要得到十位上 1 出现的次数，当前我们指针指向十位，\n\n我们称之为当前位。num 则代表当前位的位因子，当前位为个位时 num = 1，十位时为 10，百位时为 100....\n\n那我们将**当前位左边的定义为高位**，**当前位右边的定义位低位**。\n\n> 例：n = 1004 ，此时指针指向十位（当前位）num = 10，高位为百位，千位，低位为个位\n\n而且我们某一位的取值范围为 0 ~ 9,那么我们可以将这 10 个数分为 3 类，小于 1 （当前位数字为 0 ），等于 1（当前位数字为 1 ） ，大于 1（当前位上数字为 2 ~ 9），下面我们就来分别考虑三种情况。\n\n> **我们进行举例的 n 为 1004，1014，1024。重点讨论十位上 3 种不同情况**。大家阅读下方文字之前，先想象自己脑子里有一个行李箱的滚轮密码锁，我们固定其中的某一位，然后可以随意滑动其他位，这样可以帮助大家理解。\n>\n> 注：该比喻来自与网友 ryan0414，看到的时候，不禁惊呼可太贴切了！\n\n### **n = 1004**\n\n我们想要计算出**小于等于 1004 的非负整数中**，十位上出现 1 的次数。\n\n也就是当前位为十位，数字为 0 时，十位上出现 1 的次数。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/位数1.2x7xcbxtkjo0.png)\n\n> 解析：为什么我们可以直接通过高位数字 \\* num，得到 1 出现的次数\n>\n> 因为我们高位为 10，可变范围为 0 ~ 10，但是我们的十位为 0 ，所以高位为 10 的情况取不到，所以共有 10 种情况。\n>\n> 又当前位为十位，低位共有 1 位，可选范围为 0 ~ 9 共有 10 种情况，所以直接可以通过 10 \\* 10 得到。\n\n其实不难理解，我们可以设想成行李箱的密码盘，在一定范围内，也就是上面的 0010 ~ 0919 ， 固定住一位为 1 ，只能移动其他位，看共有多少种组合。\n\n好啦，这个情况我们已经搞明白啦，下面我们看另一种情况。\n\n### n = 1014\n\n我们想要计算出**小于等于 1014 的非负整数中**，十位上出现 1 的次数。\n\n也就是当前位为十位，数字为 1 时，十位上出现 1 的次数。\n\n我们在小于 1014 的非负整数中，十位上为 1 的最小数字为 10，最大数字为 1014，所以我们需要在 10 ~ 1014 这个范围内固定住十位上的 1 ，移动其他位。\n\n其实然后我们可以将 1014 看成是 1004 + 10 = 1014\n\n则可以将 10 ~ 1014 拆分为两部分 0010 ~ 0919 （小于 1004 ），1010 ~ 1014。\n\n见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/次数为1十位.4e6s2zqwtsw0.png)\n\n> 解析：为什么我们可以直接通过 高位数字 _ num + 低位数字 + 1 即 10 _ 10 + 4 + 1\n>\n> 得到 1 出现的次数\n>\n> 高位数字 \\* num 是得到第一段的次数，第二段为 低位数字 + 1，求第二段时我们高位数字和当前位已经固定，\n>\n> 我们可以改变的只有低位。\n\n可以继续想到密码盘，求第二段时，把前 3 位固定，只能改变最后一位。最后一位最大能到 4 ，那么共有几种情况？\n\n### n = 1024\n\n我们想要计算出**小于等于 1024 的非负整数中**，十位上出现 1 的次数。\n\n也就是当前位为十位，数字为 2 ~ 9 时，十位上出现 1 的次数。其中最小的为 0010，最大的为 1019\n\n我们也可以将其拆成两段 0010 ~ 0919，1010 ~ 1019\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/高位.1wn8di6g1t6.png)\n\n> 解析：为什么我们可以直接通过高位数字 _ num + num， 10 _ 10 + 10 得到 1 出现的次数\n>\n> 第一段和之前所说一样，第二段的次数，我们此时已经固定了高位和当前位，当前位为 1，低位可以随意取值，上诉例子中，当前位为 10，低位为位数为 1，则可以取值 0 ~ 9 的任何数，则共有 10 (num) 种可能。\n\n好啦，这个题目大家应该理解的差不多啦，\n\n下面我们通过动画模拟一下，是怎样一步一步的计算出，小于等于 1014 的数中 1 出现的次数。\n\n> 注：蓝色高位，橙色当前位，绿色低位\n>\n> 初始化：low = 0， cur = n % 10, num = 1, count = 0, high = n / 10;\n\n![1的个数](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/1的个数.5yccejufzc80.gif)\n\n好啦，下面我们看一下题目代码吧\n\n注：下方代码没有简写，也都标有注释，大家可以结合动画边思考边阅读。\n\n**题目代码**\n\n```java\nclass Solution {\n    public int countDigitOne(int n) {\n        //高位\n        int high = n;\n        //低位\n        int low = 0;\n        //当前位\n        int cur = 0;\n        int count = 0;\n        int num = 1;\n        while (high != 0 || cur != 0) {\n            cur = high % 10;\n            high /= 10;\n            //这里我们可以提出 high * num 因为我们发现无论为几，都含有它\n            if (cur == 0) count += high * num;\n            else if (cur == 1) count += high * num + 1 + low;\n            else count += (high + 1) * num;\n            //低位\n            low = cur * num + low;\n            num *= 10;\n        }\n        return count;\n    }\n}\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func countDigitOne(_ n: Int) -> Int {\n        var high = n, low = 0, cur = 0, count = 0, num = 1\n        while high != 0 || cur != 0 {\n            cur = high % 10\n            high /= 10\n            //这里我们可以提出 high * num 因为我们发现无论为几，都含有它\n            if cur == 0 {\n                count += high * num\n            } else if cur == 1 {\n                count += high * num + 1 + low\n            } else {\n                count += (high + 1) * num\n            }\n            low = cur * num + low\n            num *= 10\n        }\n        return count\n    }\n}\n```\n\n时间复杂度 : O(logn) 空间复杂度 O(1)\n\nC++ Code:\n\n```C++\nclass Solution\n{\npublic:\n    int countDigitOne(int n)\n    {\n        //  高位,      低位,    当前位\n        int high = n, low = 0, cur = 0;\n        int count = 0, num = 1;\n\n        //数字是0的时候完全没必要继续计算\n        while (high != 0)\n        {\n            cur = high % 10;\n            high /= 10;\n            //这里我们可以提出 high * num 因为我们发现无论为几，都含有它\n            if (cur == 0)\n                count += (high * num);\n            else if (cur == 1)\n                count += (high * num + 1 + low);\n            else\n                count += ((high + 1) * num);\n            //低位\n            low = cur * num + low;\n            //提前检查剩余数字, 以免溢出\n            if (high != 0)\n                num *= 10;\n        }\n        return count;\n    }\n};\n```\n"
  },
  {
    "path": "animation-simulation/动态规划/空.md",
    "content": ""
  },
  {
    "path": "animation-simulation/单调队列单调栈/leetcode739每日温度.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [739. 每日温度](https://leetcode-cn.com/problems/daily-temperatures/)\n\n题目描述：\n\n> 请根据每日 气温 列表，重新生成一个列表。对应位置的输出为：要想观测到更高的气温，至少需要等待的天数。如果气温在这之后都不会升高，请在该位置用 0 来代替。\n\n示例 1：\n\n> 输入： temperatures = [73, 74, 75, 71, 69, 72, 76, 73]\n>\n> 输出：arr = [1, 1, 4, 2, 1, 1, 0, 0]\n\n示例 2：\n\n> 输入：temperatures = [30,30,31,45,31,34,56]\n>\n> 输出：arr = [2,1,1,3,1,1,0]\n\n#### 题目解析\n\n其实我们可以换种方式理解这个题目，比如我们 temperatures[0] = 30,则我们需要找到后面第一个比 30 大的数，也就是 31，31 的下标为 2，30 的下标为 0 ，则我们的返回数组 arr[0] = 2。\n\n理解了题目之后我们来说一下解题思路。\n\n遍历数组，数组中的值为待入栈元素，待入栈元素入栈时会先跟栈顶元素进行对比，如果小于该值则入栈，如果大于则将栈顶元素出栈，新的元素入栈。\n\n例如栈顶为 69，新的元素为 72，则 69 出栈，72 入栈。并赋值给 arr，69 的索引为 4，72 的索引为 5，则 arr[4] = 5 - 4 = 1，这个题目用到的是单调栈的思想，下面我们来看一下视频解析。\n\n![](https://img-blog.csdnimg.cn/20210319163137996.gif)\n\n注：栈中的括号内的值，代表索引对应的元素，我们的入栈的为索引值，为了便于理解将其对应的值写在了括号中\n\n```java\nclass Solution {\n    public int[] dailyTemperatures(int[] T) {\n        int len = T.length;\n        if (len == 0) {\n            return T;\n        }\n        Stack<Integer> stack = new Stack<>();\n        int[] arr = new int[len];\n        int t = 0;\n        for (int i = 0; i < len; i++) {\n            //单调栈\n            while (!stack.isEmpty() && T[i] > T[stack.peek()]){\n                  arr[stack.peek()] = i - stack.pop();\n            }\n            stack.push(i);\n        }\n        return arr;\n\n    }\n}\n```\n\nGO Code:\n\n```go\nfunc dailyTemperatures(temperatures []int) []int {\n    l := len(temperatures)\n    if l == 0 {\n        return temperatures\n    }\n    stack := []int{}\n    arr   := make([]int, l)\n    for i := 0; i < l; i++ {\n        for len(stack) != 0 && temperatures[i] > temperatures[stack[len(stack) - 1]] {\n            idx := stack[len(stack) - 1]\n            arr[idx] = i - idx\n            stack = stack[: len(stack) - 1]\n        }\n        // 栈保存的是索引\n        stack = append(stack, i)\n    }\n    return arr\n}\n```\n"
  },
  {
    "path": "animation-simulation/单调队列单调栈/剑指offer59队列的最大值.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n今天我们好好说说单调栈和单调队列。其实很容易理解，单调栈就是单调递增或单调递减的栈，栈内元素是有序的，单调队列同样也是。\n\n下面我们通过几个题目由浅入深，一点一点挖透他们吧！\n\n## 单调队列\n\n#### [剑指 Offer 59 - II. 队列的最大值](https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/)\n\n#### 题目描述\n\n请定义一个队列并实现函数 max_value 得到队列里的最大值\n\n若队列为空，pop_front 和 max_value 需要返回 -1\n\n**示例 1：**\n\n> 输入: [\"MaxQueue\",\"push_back\",\"push_back\",\"max_value\",\"pop_front\",\"max_value\"] > [[],[1],[2],[],[],[]]\n> 输出: [null,null,null,2,1,2]\n\n**示例 2：**\n\n> 输入:\n> [\"MaxQueue\",\"pop_front\",\"max_value\"] > [[],[],[]]\n> 输出: [null,-1,-1]\n\n#### 题目解析：\n\n我们先来拆解下上面的示例 1\n\n![队列的最大值](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/队列的最大值.6bfapy4zf1g0.png)\n\n其实我觉得这个题目的重点在理解题意上面，可能刚开始刷题的同学，对题意理解不够透彻，做起来没有那么得心应手，通过上面的图片我们简单了解了一下题意，那我们应该怎么做才能实现上述要求呢？\n\n下面我们来说一下双端队列。我们之前说过的队列，遵守先进先出的规则，双端队列则可以从队头出队，也可以从队尾出队。我们先通过一个视频来简单了解下双端队列。\n\n![](https://img-blog.csdnimg.cn/20210319154950406.gif)\n\n我们可以用双端队列做辅助队列，用辅助队列来保存当前队列的最大值。我们同时定义一个普通队列和一个双端单调队列。普通队列就正常执行入队，出队操作。max_value 操作则返回咱们的双端队列的队头即可。下面我们来看一下代码的具体执行过程吧。\n\n![](https://img-blog.csdnimg.cn/20210319154716931.gif)\n\n我们来对视频进行解析\n\n1.我们需要维护一个单调双端队列，上面的队列则执行正常操作，下面的队列队头元素则为上面队列的最大值\n\n2.出队时，我们需要进行对比两个队列的队头元素是否相等，如果相等则同时出队，则出队后的双端队列的头部仍未上面队列中的最大值。\n\n3.入队时，我们需要维持一个单调递减的双端队列，因为我们需要确保队头元素为最大值嘛。\n\n```java\nclass MaxQueue {\n    //普通队列\n    Queue<Integer> que;\n    //双端队列\n    Deque<Integer> deq;\n    public MaxQueue() {\n        que = new LinkedList<>();\n        deq = new LinkedList<>();\n    }\n    //获取最大值值，返回我们双端队列的对头即可，因为我们双端队列是单调递减的嘛\n    public int max_value() {\n        return deq.isEmpty() ? -1 : deq.peekFirst();\n    }\n    //入队操作\n    public void push_back(int value) {\n        que.offer(value);\n        //维护单调递减\n        while (!deq.isEmpty() && value > deq.peekLast()){\n            deq. pollLast();\n        }\n        deq.offerLast(value);\n\n    }\n    //返回队头元素，此时有个细节，我们需要用equals\n    //这里需要使用 equals() 代替 == 因为队列中存储的是 int 的包装类 Integer\n    public int pop_front() {\n        if(que.isEmpty()) return -1;\n        if (que.peek().equals(deq.peekFirst())) {\n            deq.pollFirst();\n        }\n        return que.poll();\n    }\n}\n```\n\nGO Code:\n\n```go\ntype MaxQueue struct {\n    que []int\t// 普通队列\n    deq []int\t// 双端队列\n    size int\t// que的队列长度\n}\n\n\nfunc Constructor() MaxQueue {\n    return MaxQueue{\n        que: []int{},\n        deq: []int{},\n    }\n}\n\n// Is_empty 表示队列是否为空\nfunc (mq *MaxQueue) Is_empty() bool {\n    return mq.size == 0\n}\n\n// Max_value 取最大值值，返回我们双端队列的对头即可，因为我们双端队列是单调递减的嘛\nfunc (mq *MaxQueue) Max_value() int {\n    if mq.Is_empty() { return -1 }\n    return mq.deq[0]\n}\n\n// Push_back 入队\nfunc (mq *MaxQueue) Push_back(value int)  {\n    mq.que = append(mq.que, value)\n    // 维护单调递减队列\n    for len(mq.deq) != 0 && mq.deq[len(mq.deq) - 1] < value {\n        mq.deq = mq.deq[:len(mq.deq) - 1]\n    }\n    mq.deq = append(mq.deq, value)\n    mq.size++\n}\n\n// Pop_front 弹出队列头元素，并且返回其值。\nfunc (mq *MaxQueue) Pop_front() int {\n    if mq.Is_empty() { return -1 }\n    ans := mq.que[0]\n    mq.que = mq.que[1:]\n    if mq.deq[0] == ans {\n        mq.deq = mq.deq[1:]\n    }\n    mq.size--\n    return ans\n}\n```\n\n###\n"
  },
  {
    "path": "animation-simulation/单调队列单调栈/接雨水.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [42. 接雨水](https://leetcode-cn.com/problems/trapping-rain-water/)\n\n这道接雨水也是一道特别经典的题目，一道必刷题目，我们也用单调栈来解决。下面我们来看一下题目吧\n\n给定 n 个非负整数表示每个宽度为 1 的柱子的高度图，计算按此排列的柱子，下雨之后能接多少雨水。\n\n示例 1：\n\n```\n输入：height = [0,1,0,2,1,0,1,3,2,1,2,1]\n输出：6\n```\n\n示例 2：\n\n```\n输入：height = [4,2,0,3,2,5]\n输出：9\n```\n\n示例 3：\n\n```\n输入：[4,3,2,0,1,1,5]\n输出：13\n```\n\n> 上面是由数组 [4,3,2,0,1,1,5]表示的高度图，在这种情况下，可以接 13 个单位的雨水（见下图）。\n\n### 题目解析：\n\n看了上面的示例刚开始刷题的同学可能有些懵逼，那我们结合图片来理解一下，我们就用示例 3 的例子进行举例，他的雨水到底代表的是什么。\n\n![](https://img-blog.csdnimg.cn/2021032013412768.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70)\n\n上图则为我们的题目描述，是不是理解了呢？你也可以这样理解我们在地上放置了若干高度的黄色箱子，他们中间有空隙，然后我们想在他们里面插入若干蓝色箱子，并保证插入之后，这些箱子的左视图和右视图都不能看到蓝色箱子。\n\n好啦题目我们已经理解了，下面我们看一下解题思路。做这个这前我们可以先去看一下我们之前做过的另一道题目每日温度。这两道题目的思路差不多，都是利用了单调栈的思想，下面我们来看一下具体思路吧。\n\n这里我们也系统的说一下单调栈，单调栈含义就是栈内的元素是单调的，我们这两个题目用到的都是递减栈（相同也可以），我们依次将元素压入栈，如果当前元素小于等于栈顶元素则入栈，如果大于栈顶元素则先将栈顶不断出栈，直到当前元素小于或等于栈顶元素为止，然后再将当前元素入栈。就比如下图的 4，想入栈的话则需要 2，3 出栈之后才能入栈，因为 4 大于他俩。\n\n<img src=\"https://img-blog.csdnimg.cn/20210320134154434.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70\" alt=\"在这里插入图片描述\" style=\"zoom:80%;\" />\n\n我们了解单调栈的含义下面我们来看一下接雨水问题到底该怎么做，其实原理也很简单，我们通过我们的例 3 来进行说明。\n\n首先我们依次入栈 4，3，2，0 我们的数组前四个元素是符合单调栈规则的。但是我们的第五个 1，是大于 0 的。那我们就需要 0 出栈 1 入栈。但是我们这样做是为了什么呢？有什么意义呢？别急我们来看下图。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210320134213324.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70)\n\n上图我们的，4，3，2，0 已经入栈了，我们的另一个元素为 1，栈顶元素为 0，栈顶下的元素为 2。那么我们在这一层接到的雨水数量怎么算呢？2，0，1 这三个元素可以接住的水为一个单位(见下图)这是我们第一层接到水的数量。\n\n注：能接到水的情况，肯定是中间低两边高，这样才可以。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210320134228696.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70)\n\n因为我们需要维护一个单调栈，所以我们则需要将 0 出栈 1 入栈，那么此时栈内元素为 4，3，2，1。下一位元素为 1，我们入栈，此时栈内元素为 4，3，2，1，1。下一元素为 5，栈顶元素为 1，栈顶的下一元素仍为 1，则需要再下一个元素，为 2，那我们求当前层接到的水的数量。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210320134249605.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70)\n\n我们是通过 2，1，1，5 这四个元素求得第二层的接水数为 1\\*3=3;1 是因为 min(2-1,5-1)=min(1,4)得来的，大家可以思考一下木桶效应。装水的多少，肯定是按最短的那个木板来的，所以高度为 1，3 的话是因为 5 的索引为 6，2 的索引为 2，他们之间共有三个元素（3，4，5）也就是 3 个单位。所以为 6-2-1=3。\n\n将 1 出栈之后，我们栈顶元素就变成了 2，下一元素变成了 3，那么 3，2，5 这三个元素同样也可以接到水。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210320134307389.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70)\n\n这是第三层的接水情况，能够接到 4 个单位的水，下面我们继续出栈 2，那么我们的 4，3，5 仍然可以接到水啊。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210320134319646.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70)\n\n这是我们第四层接水的情况，一共能够接到 5 个单位的水，那么我们总的接水数加起来，那就是\n\n1+3+4+5=13。你学会了吗？别急还有视频我们，我们再来深入理解一哈。\n\n![](https://img-blog.csdnimg.cn/20210319163622150.gif)\n\n### 题目代码：\n\n```java\nclass Solution {\n    public int trap(int[] height) {\n         Stack<Integer> stack = new Stack<Integer>();\n         int water = 0;\n         //特殊情况\n         if(height.length <3){\n             return 0;\n         }\n         for(int i = 0; i < height.length; i++){\n             while(!stack.isEmpty() && height[i] > height[stack.peek()]){\n                 //栈顶元素\n                 int popnum = stack.pop();\n                 //相同元素的情况例1，1\n                 while(!stack.isEmpty()&&height[popnum] == height[stack.peek()]){\n                     stack.pop();\n                 }\n                 //计算该层的水的单位\n                 if(!stack.isEmpty()){\n                     int temp = height[stack.peek()];//栈顶元素值\n                     //高\n                     int hig = Math.min(temp-height[popnum],height[i]-height[popnum]);\n                     //宽\n                     int wid = i-stack.peek()-1;\n                     water +=hig * wid;\n                 }\n\n             }\n             //这里入栈的是索引\n             stack.push(i);\n         }\n         return water;\n    }\n}\n```\n\nGO Code:\n\n```go\nfunc trap(height []int) int {\n    stack := []int{}\n    water := 0\n    // 最左边部分不会接雨水，左边持续升高时，stack都会弹出所有元素。\n    for i := 0; i< len(height); i++ {\n        for len(stack) != 0 && height[i] > height[stack[len(stack) - 1]] {\n            popnum := stack[len(stack) - 1]\n            // 出现相同高度的情况（其实也可以不用处理，如果不处理，相同高度时后面的hig为0，会产生很多无效的计算）\n            for len(stack) != 0 && height[popnum] == height[stack[len(stack) - 1]] {\n                stack = stack[:len(stack) - 1]\n            }\n            if len(stack) == 0 { break }\n            le, ri := stack[len(stack) - 1], i\n            hig := min(height[ri], height[le]) - height[popnum]\n            wid := ri - le - 1\n            water += wid * hig\n        }\n        stack = append(stack, i)\n    }\n    return water\n}\n\nfunc min(a, b int) int {\n    if a < b { return a }\n    return b\n}\n```\n\n###\n"
  },
  {
    "path": "animation-simulation/单调队列单调栈/最小栈.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [155. 最小栈](https://leetcode-cn.com/problems/min-stack/)\n\n设计一个支持 push ，pop ，top 操作，并能在常数时间内检索到最小元素的栈。\n\n- push(x) —— 将元素 x 推入栈中。\n- pop() —— 删除栈顶的元素。\n- top() —— 获取栈顶元素。\n- getMin() —— 检索栈中的最小元素。\n\n输入：\n\n> [\"MinStack\",\"push\",\"push\",\"push\",\"getMin\",\"pop\",\"top\",\"getMin\"] > [[],[-2],[0],[-3],[],[],[],[]]\n\n输出：\n\n> [null,null,null,null,-3,null,0,-2]\n\n#### 题目解析\n\n感觉这个题目的难度就在读懂题意上面，读懂之后就没有什么难的了，我们在上面的滑动窗口的最大值已经进行了详细描述，其实这个题目和那个题目思路一致。该题让我们设计一个栈，该栈具有的功能有，push，pop，top 等操作，并且能够返回栈的最小值。比如此时栈中的元素为 5，1，2，3。我们执行 getMin() ，则能够返回 1。这块是这个题目的精髓所在，见下图。\n\n![单调栈](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/单调栈.46hlqk2xqza0.png)\n\n我们一起先通过一个视频先看一下具体解题思路，通过视频一定可以整懂的，我们注意观察栈 B 内的元素。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210319162722440.gif)\n\n我们来对视频进行解析 1.我们执行入栈操作时，先观察需要入栈的元素是否小于栈 B 的栈顶元素，如果小于则两个栈都执行入栈操作。\n\n2.栈 B 的栈顶元素则是栈 A 此时的最小值。则 getMin() 只需返回栈 B 的栈顶元素即可。\n\n3.出栈时，需要进行对比，若栈 A 和栈 B 栈顶元素相同，则同时出栈，出栈好 B 的栈顶保存的仍为此时栈 A 的最小元素\n\n#### 题目代码\n\n```java\nclass MinStack {\n    //初始化\n    Stack<Integer> A,B;\n    public MinStack() {\n          A = new Stack<>();\n          B = new Stack<>();\n    }\n    //入栈，如果插入值，当前插入值小于栈顶元素，则入栈，栈顶元素保存的则为当前栈的最小元素\n    public void push(int x) {\n        A.push(x);\n        if (B.isEmpty() || B.peek() >= x) {\n            B.push(x);\n        }\n\n    }\n    //出栈，如果A出栈等于B栈顶元素，则说明此时栈内的最小元素改变了。\n    //这里需要使用 equals() 代替 == 因为 Stack 中存储的是 int 的包装类 Integer\n    public void pop() {\n        if (A.pop().equals(B.peek()) ) {\n            B.pop();\n        }\n    }\n    //A栈的栈顶元素\n    public int top() {\n        return A.peek();\n    }\n    //B栈的栈顶元素\n    public int getMin() {\n        return B.peek();\n    }\n}\n```\n\nGO Code:\n\n```go\ntype MinStack struct {\n    stack  []int\n    minStk []int\n}\n\n/** initialize your data structure here. */\nfunc Constructor() MinStack {\n    return MinStack{\n        stack:  []int{},\n        minStk: []int{},\n    }\n}\n\n// Push 入栈，如果插入值，当前插入值小于栈顶元素，则入栈，栈顶元素保存的则为当前栈的最小元素\nfunc (m *MinStack) Push(x int)  {\n    m.stack = append(m.stack, x)\n    if len(m.minStk) == 0 || m.minStk[len(m.minStk) - 1] >= x {\n        m.minStk = append(m.minStk, x)\n    }\n}\n\n// Pop 出栈，如果stack出栈等于minStk栈顶元素，则说明此时栈内的最小元素改变了。\nfunc (m *MinStack) Pop()  {\n    temp := m.stack[len(m.stack) - 1]\n    m.stack = m.stack[: len(m.stack) - 1]\n    if temp == m.minStk[len(m.minStk) - 1] {\n        m.minStk = m.minStk[: len(m.minStk) - 1]\n    }\n}\n\n// Top stack的栈顶元素\nfunc (m *MinStack) Top() int {\n    return  m.stack[len(m.stack) - 1]\n}\n\n// GetMin minStk的栈顶元素\nfunc (m *MinStack) GetMin() int {\n    return  m.minStk[len(m.minStk) - 1]\n}\n```\n\n###\n"
  },
  {
    "path": "animation-simulation/单调队列单调栈/滑动窗口的最大值.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [剑指 Offer 59 - I. 滑动窗口的最大值](https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/)\n\n这个题目，算是很经典的类型，我们的滑动窗口主要分为两种，一种的可变长度的滑动窗口，一种是固定长度的滑动窗口，这个题目算是固定长度的代表。今天我们用双端队列来解决我们这个题目，学会了这个题目的解题思想你可以去解决一下两道题目 [剑指 Offer 59 - II. 队列的最大值](https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/)，[155. 最小栈](https://leetcode-cn.com/problems/min-stack/)，虽然这两个题目和该题类型不同，但是解题思路是一致的，都是很不错的题目，我认为做题，那些考察的很细的，解题思路很难想，即使想到，也不容易完全写出来的题目，才是能够大大提高我们编码能力的题目，希望能和大家一起进步。\n\n这个题目我们用到了**双端队列**，队列里面保存的则为每段滑动窗口的最大值，我给大家做了一个动图，先来看一下代码执行过程吧。\n\n我们先来了解下双端队列吧，队列我们都知道，是先进先出，双端队列呢？既可以从队头出队，也可以从队尾出队，则不用遵循先进先出的规则。\n\n下面我们通过一个动图来了解一下吧。\n\n![](https://img-blog.csdnimg.cn/20210319154950406.gif)\n\n好啦，我们了解双端队列是什么东东了，下面我们通过一个动画，来看一下代码的执行过程吧，相信各位一下就能够理解啦。\n\n我们就通过题目中的例子来表述。nums = [1,3,-1,-3,5,3,6,7], k = 3\n\n![](https://img-blog.csdnimg.cn/20210319162114967.gif)\n\n不知道通过上面的例子能不能给各位描述清楚，如果不能的话，我再加把劲，各位看官，请接着往下看。\n\n我们将执行过程进行拆解。\n\n1.想将我们第一个窗口的所有值存入单调双端队列中，单调队列里面的值为单调递减的。如果发现队尾元素小于要加入的元素，则将队尾元素出队，直到队尾元素大于新元素时，再让新元素入队，目的就是维护一个单调递减的队列。\n\n2.我们将第一个窗口的所有值，按照单调队列的规则入队之后，因为队列为单调递减，所以队头元素必为当前窗口的最大值，则将队头元素添加到数组中。\n\n3.移动窗口，判断当前**窗口前的元素**是否和队头元素相等，如果相等则出队。\n\n4.继续然后按照规则进行入队，维护单调递减队列。\n\n5.每次将队头元素存到返回数组里。\n\n5.返回数组\n\n是不是懂啦，再回去看一遍视频吧。祝大家新年快乐，天天开心呀！\n\n```java\nclass Solution {\n    public int[] maxSlidingWindow(int[] nums, int k) {\n        int len = nums.length;\n        if (len == 0) {\n            return nums;\n        }\n        int[] arr = new int[len - k + 1];\n        int arr_index = 0;\n        //我们需要维护一个单调递增的双向队列\n        Deque<Integer> deque = new LinkedList<>();\n        for (int i = 0; i < k; i++) {\n             while (!deque.isEmpty() && deque.peekLast() < nums[i]) {\n               deque.removeLast();\n            }\n            deque.offerLast(nums[i]);\n        }\n        arr[arr_index++] = deque.peekFirst();\n        for (int j = k; j < len; j++) {\n            if (nums[j - k] == deque.peekFirst()) {\n                deque.removeFirst();\n            }\n            while (!deque.isEmpty() && deque.peekLast() < nums[j]) {\n                deque.removeLast();\n            }\n            deque.offerLast(nums[j]);\n            arr[arr_index++] = deque.peekFirst();\n        }\n        return arr;\n    }\n}\n```\n\nGO Code:\n\n```go\nfunc maxSlidingWindow(nums []int, k int) []int {\n    l := len(nums)\n    if l == 0 {\n        return nums\n    }\n\n    arr   := []int{}\n    // 维护一个单调递减的双向队列\n    deque := []int{}\n    for i := 0; i < k; i++ {\n        for len(deque) != 0 && deque[len(deque) - 1] < nums[i] {\n            deque = deque[:len(deque) - 1]\n        }\n        deque = append(deque, nums[i])\n    }\n\n    arr = append(arr, deque[0])\n    for i := k; i < l; i++ {\n        if nums[i - k] == deque[0] {\n            deque = deque[1:]\n        }\n        for len(deque) != 0 && deque[len(deque) - 1] < nums[i] {\n            deque = deque[:len(deque) - 1]\n        }\n        deque = append(deque, nums[i])\n        arr = append(arr, deque[0])\n    }\n    return arr\n}\n```\n"
  },
  {
    "path": "animation-simulation/哈希表篇/空.md",
    "content": ""
  },
  {
    "path": "animation-simulation/回溯/空.md",
    "content": ""
  },
  {
    "path": "animation-simulation/并查集/空.md",
    "content": ""
  },
  {
    "path": "animation-simulation/数据结构和算法/BF算法.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n> 为保证代码严谨性，文中所有代码均在 leetcode 刷题网站 AC ，大家可以放心食用。\n\n皇上生辰之际，举国同庆，袁记菜馆作为天下第一饭店，所以被选为这次庆典的菜品供应方，这次庆典对于袁记菜馆是一项前所未有的挑战，毕竟是第一次给皇上庆祝生辰，稍有不慎就是掉脑袋的大罪，整个袁记菜馆内都在紧张的布置着。此时突然有一个店小二慌慌张张跑到袁厨面前汇报，到底发生了什么事，让店小二如此慌张呢？\n\n袁记菜馆内\n\n店小二：不好了不好了，掌柜的，出大事了。\n\n袁厨：发生什么事了，慢慢说，如此慌张，成何体统。（开店开久了，架子出来了哈）\n\n店小二：皇上按照咱们菜单点了 666 道菜，但是咱们做西湖醋鱼的师傅请假回家结婚了，不知道皇上有没有点这道菜，如果点了这道菜，咱们做不出来，那咱们店可就完了啊。\n\n（袁厨听了之后，吓得一屁股坐地上了，缓了半天说道）\n\n袁厨：别说那么多了，快给我找找皇上点的菜里面，有没有这道菜！\n\n找了很久，并且核对了很多遍，最后确认皇上没有点这道菜。菜馆内的人都松了一口气\n\n通过上面的一个例子，让我们简单了解了字符串匹配。\n\n字符串匹配：设 S 和 T 是给定的两个串，在主串 S 中找到模式串 T 的过程称为字符串匹配，如果在主串 S 中找到 模式串 T ，则称匹配成功，函数返回 T 在 S 中首次出现的位置，否则匹配不成功，返回 -1。\n\n例：\n\n![字符串匹配](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/字符串匹配.3q9wqbh8ws40.png)\n\n在上图中，我们试图找到模式 T = baab,在主串 S = abcabaabcabac 中第一次出现的位置，即为红色阴影部分， T 第一次在 S 中出现的位置下标为 4 （ 字符串的首位下标是 0 ），所以返回 4。如果模式串 T 没有在主串 S 中出现，则返回 -1。\n\n解决上面问题的算法我们称之为字符串匹配算法，今天我们来介绍三种字符串匹配算法，大家记得打卡呀，说不准面试的时候就问到啦。\n\n## BF 算法（Brute Force）\n\n这个算法很容易理解，就是我们将模式串和主串进行比较，一致时则继续比较下一字符，直到比较完整个模式串。不一致时则将模式串后移一位，重新从模式串的首位开始对比，重复刚才的步骤下面我们看下这个方法的动图解析，看完肯定一下就能搞懂啦。\n\n![请添加图片描述](https://img-blog.csdnimg.cn/20210319193924425.gif)\n\n通过上面的代码是不是一下就将这个算法搞懂啦，下面我们用这个算法来解决下面这个经典题目吧。\n\n### leetcdoe 28. 实现 strStr()\n\n#### 题目描述\n\n给定一个 haystack 字符串和一个 needle 字符串，在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从 0 开始)。如果不存在，则返回 -1。\n\n示例 1:\n\n> 输入: haystack = \"hello\", needle = \"ll\"\n> 输出: 2\n\n示例 2:\n\n> 输入: haystack = \"aaaaa\", needle = \"bba\"\n> 输出: -1\n\n#### 题目解析\n\n其实这个题目很容易理解，但是我们需要注意的是一下几点，比如我们的模式串为 0 时，应该返回什么，我们的模式串长度大于主串长度时，应该返回什么，也是我们需要注意的地方。下面我们来看一下题目代码吧。\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int strStr(String haystack, String needle) {\n        int haylen = haystack.length();\n        int needlen = needle.length();\n        //特殊情况\n        if (haylen < needlen) {\n            return -1;\n        }\n        if (needlen == 0) {\n            return 0;\n        }\n        //主串\n        for (int i = 0; i < haylen - needlen + 1; ++i) {\n            int j;\n            //模式串\n            for (j = 0; j < needlen; j++) {\n                //不符合的情况，直接跳出，主串指针后移一位\n                if (haystack.charAt(i+j) != needle.charAt(j)) {\n                    break;\n                }\n            }\n            //匹配成功\n            if (j == needlen) {\n                return i;\n            }\n\n        }\n        return -1;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def strStr(self, haystack: str, needle: str)->int:\n        haylen = len(haystack)\n        needlen = len(needle)\n        # 特殊情况\n        if haylen < needlen:\n            return -1\n        if needlen == 0:\n            return 0\n        # 主串\n        for i in range(0, haylen - needlen + 1):\n            # 模式串\n            j = 0\n            while j < needlen:\n                if haystack[i + j] != needle[j]:\n                    break\n                j += 1\n            # 匹配成功\n            if j == needlen:\n                return i\n        return -1\n```\n\n我们看一下 BF 算法的另一种算法（显示回退），其实原理一样，就是对代码进行了一下修改，只要是看完咱们的动图，这个也能够一下就能看懂，大家可以结合下面代码中的注释和动图进行理解。\n\nJava Code:\n\n```java\nclass Solution {\n    public int strStr(String haystack, String needle) {\n        //i代表主串指针，j模式串\n        int i,j;\n        //主串长度和模式串长度\n        int halen = haystack.length();\n        int nelen = needle.length();\n        //循环条件，这里只有 i 增长\n        for (i = 0 , j = 0; i < halen && j < nelen; ++i) {\n            //相同时，则移动 j 指针\n            if (haystack.charAt(i) == needle.charAt(j)) {\n                ++j;\n            } else {\n                //不匹配时，将 j 重新指向模式串的头部，将 i 本次匹配的开始位置的下一字符\n                i -= j;\n                j = 0;\n            }\n        }\n        //查询成功时返回索引，查询失败时返回 -1；\n        int renum = j == nelen ? i - nelen : -1;\n        return renum;\n\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def strStr(self, haystack: str, needle: str)->int:\n        # i代表主串指针，j模式串\n        i = 0\n        j = 0\n        # 主串长度和模式串长度\n        halen = len(haystack)\n        nelen = len(needle)\n        # 循环条件，这里只有 i 增长\n        while i < halen and j < nelen:\n            # 相同时，则移动 j 指针\n            if haystack[i] == needle[j]:\n                j += 1\n            else:\n                # 不匹配时，将 j 重新只想模式串的头部，将 i 本次匹配的开始位置的下一字符\n                i -= j\n                j = 0\n            i += 1\n            # 查询成功时返回索引，查询失败时返回 -1\n            renum = i - nelen if j == nelen else -1\n        return renum\n```\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/BM.md",
    "content": "## BM 算法(Boyer-Moore)\n\n> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n我们刚才说过了 BF 算法，但是 BF 算法是有缺陷的，比如我们下面这种情况\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210401200433751.png)\n\n如上图所示，如果我们利用 BF 算法，遇到不匹配字符时，每次右移一位模式串，再重新从头进行匹配，我们观察一下，我们的模式串 abcdex 中每个字符都不一样，但是我们第一次进行字符串匹配时，abcde 都匹配成功，到 x 时失败，又因为模式串每位都不相同，所以我们不需要再每次右移一位，再重新比较，我们可以直接跳过某些步骤。如下图\n\n![BM2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/BM2.141fhslg6vek.png)\n\n我们可以跳过其中某些步骤，直接到下面这个步骤。那我们是依据什么原则呢？\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210401200635476.png)\n\n### 坏字符规则\n\n我们之前的 BF 算法是从前往后进行比较 ，BM 算法是从后往前进行比较，我们来看一下具体过程，我们还是利用上面的例子。\n\n![BM4](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/BM4.2mayfaccj3i0.png)\n\nBM 算法是从后往前进行比较，此时我们发现比较的第一个字符就不匹配，我们将**主串**这个字符称之为**坏字符**，也就是 f ,我们发现坏字符之后，模式串 T 中查找是否含有该字符（f），我们发现并不存在 f，此时我们只需将模式串右移到坏字符的后面一位即可。如下图\n\n![BM5](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/BM5.31j3sja7vsq0.png)\n\n那我们在模式串中找到坏字符该怎么办呢？\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210401200838199.png)\n\n此时我们的坏字符为 f ,我们在模式串中，查找发现含有坏字符 f,我们则需要移动模式串 T ,将模式串中的 f 和坏字符对齐。见下图。\n\n![坏字符移动](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/坏字符移动.kl5k3nnzkcg.png)\n\n然后我们继续从右往左进行比较，发现 d 为坏字符，则需要将模式串中的 d 和坏字符对齐。\n\n![换字符对其2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/换字符对其2.4xdb38am9e60.png)\n\n![坏字符原则](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/坏字符原则.781vhv3vm280.png)\n\n那么我们在来思考一下这种情况，那就是模式串中含有多个坏字符怎么办呢？\n\n![两个坏字符](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/两个坏字符.1a6hcs8ildkw.png)\n\n那么我们为什么要让**最靠右的对应元素与坏字符匹配**呢？如果上面的例子我们没有按照这条规则看下会产生什么问题。\n\n![坏字符匹配不按规则](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/坏字符匹配不按规则.1y45278xg1vk.png)\n\n如果没有按照我们上述规则，则会**漏掉我们的真正匹配**。我们的主串中是**含有 babac** 的，但是却**没有匹配成功**，所以应该遵守**最靠右的对应字符与坏字符相对**的规则。\n\n我们上面一共介绍了三种移动情况，分别是下方的模式串中没有发现与坏字符对应的字符，发现一个对应字符，发现两个。这三种情况我们分别移动不同的位数，那我们是根据依据什么来决定移动位数的呢？下面我们给图中的字符加上下标。见下图\n\n![坏字符移动规则](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/坏字符移动规则.48oh1msdypy0.png)\n\n下面我们来考虑一下这种情况。\n\n![换字符bug](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/换字符bug.24av6jslzh40.png)\n\n此时这种情况肯定是不行的，不往右移动，甚至还有可能左移，那么我们有没有什么办法解决这个问题呢？继续往下看吧。\n\n### 好后缀规则\n\n好后缀其实也很容易理解，我们之前说过 BM 算法是从右往左进行比较，下面我们来看下面这个例子。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210401201215799.png)\n\n这里如果我们按照坏字符进行移动是不合理的，这时我们可以使用好后缀规则，那么什么是好后缀呢？\n\nBM 算法是从右往左进行比较，发现坏字符的时候此时 cac 已经匹配成功，在红色阴影处发现坏字符。此时已经匹配成功的 cac 则为我们的好后缀，此时我们拿它在模式串中查找，如果找到了另一个和好后缀相匹配的串，那我们就将另一个和**好后缀相匹配**的串 ，滑到和好后缀对齐的位置。\n\n是不是感觉有点拗口，没关系，我们看下图，红色代表坏字符，绿色代表好后缀\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210401201254453.png)\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/好后缀中间.7b6m6ki25l00.png)\n\n上面那种情况搞懂了，但是我们思考一下下面这种情况\n\n![比较](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/比较.4m9ci1x1c1e0.png)\n\n上面我们说到了，如果在模式串的**头部**没有发现好后缀，发现好后缀的子串也可以。但是为什么要强调这个头部呢？\n\n我们下面来看一下这种情况\n\n![不完全重合](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/不完全重合.6oayqd0dre00.png)\n\n但是当我们在头部发现好后缀的子串时，是什么情况呢？\n\n![](https://img-blog.csdnimg.cn/20210319204004219.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70#pic_center)\n\n下面我们通过动图来看一下某一例子的具体的执行过程\n\n![请添加图片描述](https://img-blog.csdnimg.cn/202103191939263.gif)\n\n说到这里，坏字符和好后缀规则就算说完了，坏字符很容易理解，我们对好后缀总结一下\n\n1.如果模式串**含有好后缀**，无论是中间还是头部可以按照规则进行移动。如果好后缀在模式串中出现多次，则以**最右侧的好后缀**为基准。\n\n2.如果模式串**头部含有**好后缀子串则可以按照规则进行移动，中间部分含有好后缀子串则不可以。\n\n3.如果在模式串尾部就出现不匹配的情况，即不存在好后缀时，则根据坏字符进行移动，这里有的文章没有提到，是个需要特别注意的地方，我是在这个论文里找到答案的，感兴趣的同学可以看下。\n\n> Boyer R S，Moore J S. A fast string searching algorithm［J］. Communications of the ACM，1977，10： 762-772.\n\n之前我们刚开始说坏字符的时候，是不是有可能会出现负值的情况，即往左移动的情况，所以我们为了解决这个问题，我们可以分别计算好后缀和坏字符往后滑动的位数**（好后缀不为 0 的情况）**，然后取两个数中最大的，作为模式串往后滑动的位数。\n\n![五好后缀](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/五好后缀.6wvqxa4um040.png)\n\n这破图画起来是真费劲啊。下面我们来看一下算法代码，代码有点长，我都标上了注释也在网站上 AC 了，如果各位感兴趣可以看一下，不感兴趣理解坏字符和好后缀规则即可。可以直接跳到 KMP 部分\n\nJava Code:\n\n```java\nclass Solution {\n    public int strStr(String haystack, String needle) {\n        char[] hay = haystack.toCharArray();\n        char[] need = needle.toCharArray();\n        int haylen = haystack.length();\n        int needlen = need.length;\n        return bm(hay,haylen,need,needlen);\n    }\n    //用来求坏字符情况下移动位数\n    private static void badChar(char[] b, int m, int[] bc) {\n        //初始化\n        for (int i = 0; i < 256; ++i) {\n            bc[i] = -1;\n        }\n        //m 代表模式串的长度，如果有两个 a,则后面那个会覆盖前面那个\n        for (int i = 0; i < m; ++i) {\n            int ascii = (int)b[i];\n            bc[ascii] = i;//下标\n        }\n    }\n    //用来求好后缀条件下的移动位数\n    private static void goodSuffix (char[] b, int m, int[] suffix,boolean[] prefix) {\n        //初始化\n        for (int i = 0; i < m; ++i) {\n            suffix[i] = -1;\n            prefix[i] = false;\n        }\n        for (int i = 0; i < m - 1; ++i) {\n            int j = i;\n            int k = 0;\n            while (j >= 0 && b[j] == b[m-1-k]) {\n                --j;\n                ++k;\n                suffix[k] = j + 1;\n            }\n            if (j == -1) prefix[k] = true;\n        }\n    }\n    public static int bm (char[] a, int n, char[] b, int m) {\n\n        int[] bc = new int[256];//创建一个数组用来保存最右边字符的下标\n        badChar(b,m,bc);\n        //用来保存各种长度好后缀的最右位置的数组\n        int[] suffix_index = new int[m];\n        //判断是否是头部，如果是头部则true\n        boolean[] ispre = new boolean[m];\n        goodSuffix(b,m,suffix_index,ispre);\n        int i = 0;//第一个匹配字符\n        //注意结束条件\n        while (i <= n-m) {\n            int j;\n            //从后往前匹配，匹配失败，找到坏字符\n            for (j = m - 1; j >= 0; --j) {\n                if (a[i+j] != b[j]) break;\n            }\n            //模式串遍历完毕，匹配成功\n            if (j < 0) {\n                return i;\n            }\n            //下面为匹配失败时，如何处理\n            //求出坏字符规则下移动的位数，就是我们坏字符下标减最右边的下标\n            int x = j - bc[(int)a[i+j]];\n            int y = 0;\n            //好后缀情况，求出好后缀情况下的移动位数,如果不含有好后缀的话，则按照坏字符来\n            if (y < m-1 && m - 1 - j > 0) {\n                y = move(j, m, suffix_index,ispre);\n            }\n            //移动\n            i = i + Math.max(x,y);\n\n        }\n        return -1;\n    }\n    // j代表坏字符的下标\n    private static int move (int j, int m, int[] suffix_index, boolean[] ispre) {\n        //好后缀长度\n        int k = m - 1 - j;\n        //如果含有长度为 k 的好后缀，返回移动位数，\n        if (suffix_index[k] != -1) return j - suffix_index[k] + 1;\n        //找头部为好后缀子串的最大长度，从长度最大的子串开始\n        for (int r = j + 2; r <= m-1; ++r) {\n            //如果是头部\n            if (ispre[m-r] == true) {\n                return r;\n            }\n        }\n        //如果没有发现好后缀匹配的串，或者头部为好后缀子串，则移动到 m 位，也就是匹配串的长度\n        return m;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def strStr(self, haystack: str, needle: str)->int:\n        haylen = len(haystack)\n        needlen = len(needle)\n        return self.bm(haystack, haylen, needle, needlen)\n\n    # 用来求坏字符情况下移动位数\n    def badChar(self, b: str, m: int, bc: List[int]):\n        # 初始化\n        for i in range(0, 256):\n            bc[i] = -1\n        # m 代表模式串的长度，如果有两个 a，则后面那个会覆盖前面那个\n        for i in range(0, m,):\n            ascii = ord(b[i])\n            bc[ascii] = i# 下标\n\n    # 用来求好后缀条件下的移动位数\n    def goodSuffix(self, b: str, m: int, suffix: List[int], prefix: List[bool]):\n        # 初始化\n        for i in range(0, m):\n            suffix[i] = -1\n            prefix[i] = False\n        for i in range(0, m - 1):\n            j = i\n            k = 0\n            while j >= 0 and b[j] == b[m - 1 - k]:\n                j -= 1\n                k += 1\n                suffix[k] = j + 1\n            if j == -1:\n                prefix[k] = True\n\n    def bm(self, a: str, n: int, b: str, m: int)->int:\n        bc = [0] * 256# 创建一个数组用来保存最右边字符的下标\n        self.badChar(b, m, bc)\n        # 用来保存各种长度好后缀的最右位置的数组\n        suffix_index = [0] * m\n        # 判断是否是头部，如果是头部则True\n        ispre = [False] * m\n        self.goodSuffix(b, m, suffix_index, ispre)\n        i = 0# 第一个匹配字符\n        # 注意结束条件\n        while i <= n - m:\n            # 从后往前匹配，匹配失败，找到坏字符\n            j = m - 1\n            while j >= 0:\n                if a[i + j] != b[j]:\n                    break\n                j -= 1\n            # 模式串遍历完毕，匹配成功\n            if j < 0:\n                return i\n            # 下面为匹配失败时，如何处理\n            # 求出坏字符规则下移动的位数，就是我们坏字符下标减最右边的下标\n            x = j - bc[ord(a[i + j])]\n            y = 0\n            # 好后缀情况，求出好后缀情况下的移动位数,如果不含有好后缀的话，则按照坏字符来\n            if y < m - 1 and m - 1 - j > 0:\n                y = self.move(j, m, suffix_index, ispre)\n            # 移动\n            i += max(x, y)\n        return -1\n\n    # j代表坏字符的下标\n    def move(j: int, m: int, suffix_index: List[int], ispre: List[bool])->int:\n        # 好后缀长度\n        k = m - 1 - j\n        # 如果含有长度为 k 的好后缀，返回移动位数\n        if suffix_index[k] != -1:\n            return j - suffix_index[k] + 1\n        # 找头部为好后缀子串的最大长度，从长度最大的子串开始\n        for r in range(j + 2, m):\n            # //如果是头部\n            if ispre[m - r] == True:\n                return r\n        # 如果没有发现好后缀匹配的串，或者头部为好后缀子串，则移动到 m 位，也就是匹配串的长度\n        return m\n```\n\n我们来理解一下我们代码中用到的两个数组，因为两个规则的移动位数，只与模式串有关，与主串无关，所以我们可以提前求出每种情况的移动情况，保存到数组中。\n\n![头缀函数](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/头缀函数.145da63ig3s0.png)\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/Hash表的那些事.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n# 散列（哈希）表总结\n\n之前给大家介绍了**链表**，**栈和队列**今天我们来说一种新的数据结构散列（哈希）表，散列是应用非常广泛的数据结构，在我们的刷题过程中，散列表的出场率特别高。所以我们快来一起把散列表的内些事给整明白吧。文章框架如下\n\n![脑图](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/脑图.1pk584kfatxc.png)\n\n说散列表之前，我们先设想以下场景。\n\n> 袁厨穿越回了古代，凭借从现代学习的做饭手艺，开了一个袁记菜馆，正值开业初期，店里生意十分火爆，但是顾客结账时就犯难了，每当结账的时候，老板娘总是按照菜单一个一个找价格（遍历查找），每次都要找半天，所以结账的地方总是排起长队，顾客们表示用户体验不咋滴。袁厨一想这不是办法啊，让顾客老是等着，太影响客户体验啦。所以袁厨就先把菜单按照首字母排序（二分查找），然后查找的时候根据首字母查找，这样结账的时候就能大大提高检索效率啦！但是呢？工作日顾客不多，老板娘完全应付的过来，但是每逢节假日，还是会排起长队。那么有没有什么更好的办法呢？对呀！我们把所有的价格都背下来不就可以了吗？每个菜的价格我们都了如指掌，结账的时候我们只需简单相加即可。所以袁厨和老板娘加班加点的进行背诵。下次再结账的时候一说吃了什么菜，我们立马就知道价格啦。自此以后收银台再也没有出现过长队啦，袁记菜馆开着开着一不小心就成了天下第一饭店了。\n\n下面我们来看一下袁记菜馆老板娘进化史。\n\n![image-20201117132633797](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201117132633797.5dlsgarvplc0.png)\n\n上面的后期结账的过程则模拟了我们的散列表查找，那么在计算机中是如何使用进行查找的呢？\n\n### 散列表查找步骤\n\n散列表-------最有用的基本数据结构之一。是根据关键码的值儿直接进行访问的数据结构，散列表的实现常常叫做**散列（hasing）**。散列是一种用于以**常数平均时间**执行插入、删除和查找的技术，下面我们来看一下散列过程。\n\n我们的整个散列过程主要分为两步\n\n（1）通过**散列函数**计算记录的散列地址，并按此**散列地址**存储该记录。就好比麻辣鱼我们就让它在川菜区，糖醋鱼，我们就让它在鲁菜区。但是我们需要注意的是，无论什么记录我们都需要用**同一个散列函数**计算地址，再存储。\n\n（2)当我们查找时，我们通过**同样的散列函数**计算记录的散列地址，按此散列地址访问该记录。因为我们存和取得时候用的都是一个散列函数，因此结果肯定相同。\n\n刚才我们在散列过程中提到了散列函数，那么散列函数是什么呢？\n\n我们假设某个函数为 **f**，使得\n\n​ **存储位置 = f (关键字)**\n\n**输入：关键字** **输出：存储位置(散列地址)**\n\n那样我们就能通过查找关键字**不需要比较**就可获得需要的记录的存储位置。这种存储技术被称为散列技术。散列技术是在通过记录的存储位置和它的关键字之间建立一个确定的对应关系 **f** ,使得每个关键字 **key** 都对应一个存储位置 **f(key)**。见下图\n\n![image-20201117145348616](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201117145348616.7l7om0vd7ww0.png)\n\n这里的 **f** 就是我们所说的散列函数（哈希）函数。我们利用散列技术将记录存储在一块连续的存储空间中，这块连续存储空间就是我们本文的主人公------**散列(哈希)表**\n\n上图为我们描述了用散列函数将关键字映射到散列表，但是大家有没有考虑到这种情况，那就是将关键字映射到同一个槽中的情况，即 **f(k4) = f(k3)** 时。这种情况我们将其称之为**冲突**，**k3** 和 **k4**则被称之为散列函数 **f** 的**同义词**，如果产生这种情况，则会让我们查找错误。幸运的是我们能找到有效的方法解决冲突。\n\n首先我们可以对哈希函数下手，我们可以精心设计哈希函数，让其尽可能少的产生冲突，所以我们创建哈希函数时应遵循以下规则\n\n（1）**必须是一致的**，假设你输入辣子鸡丁时得到的是**在看**，那么每次输入辣子鸡丁时，得到的也必须为**在看**。如果不是这样，散列表将毫无用处。\n\n（2）**计算简单**，假设我们设计了一个算法，可以保证所有关键字都不会冲突，但是这个算法计算复杂，会耗费很多时间，这样的话就大大降低了查找效率，反而得不偿失。所以咱们**散列函数的计算时间不应该超过其他查找技术与关键字的比较时间**，不然的话我们干嘛不使用其他查找技术呢?\n\n（3）**散列地址分布均匀**我们刚才说了冲突的带来的问题，所以我们最好的办法就是让**散列地址尽量均匀分布在存储空间中**，这样即保证空间的有效利用，又减少了处理冲突而消耗的时间。\n\n现在我们已经对散列表，散列函数等知识有所了解啦，那么我们来看几种常用的散列函数构造规则。这些方法的共同点为都是将原来的数字按某种规律变成了另一个数字。\n\n### 散列函数构造方法\n\n#### 直接定址法\n\n如果我们对盈利为 0-9 的菜品设计哈希表，我们则直接可以根据作为地址，则 **f(key) = key**;\n\n即下面这种情况。\n\n![直接定址法](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/直接定址法.b74l6bhzm0w.png)\n\n有没有感觉上面的图很熟悉，没错我们经常用的数组其实就是一张哈希表，关键码就是数组的索引下标，然后我们通过下标直接访问数组中的元素。\n\n另外我们假设每道菜的成本为 50 块，那我们还可以根据盈利+成本来作为地址，那么则 f(key) = key + 50。也就是说我们可以根据线性函数值作为散列地址。\n\n​ **f(key) = a \\* key + b** **a,b 均为常数**\n\n优点：简单、均匀、无冲突。\n\n应用场景：需要事先知道关键字的分布情况，适合查找表较小且连续的情况\n\n#### 数字分析法\n\n该方法也是十分简单的方法，就是分析我们的关键字，取其中一段，或对其位移，叠加，用作地址。比如我们的学号，前 6 位都是一样的，但是后面 3 位都不相同，我们则可以用学号作为键，后面的 3 位做为我们的散列地址。如果我们这样还是容易产生冲突，则可以对抽取数字再进行处理。我们的目的只有一个，提供一个散列函数将关键字合理的分配到散列表的各位置。这里我们提到了一种新的方式，抽取，这也是在散列函数中经常用到的手段。\n\n![image-20201117161754010](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201117161754010.7k9ilkvmcfk0.png)\n\n优点：简单、均匀、适用于关键字位数较大的情况\n\n应用场景：关键字位数较大，知道关键字分布情况且关键字的若干位较均匀\n\n#### 折叠法\n\n其实这个方法也很简单，也是处理我们的关键字然后用作我们的散列地址，主要思路是将关键字从左到右分割成位数相等的几部分，然后叠加求和，并按散列表表长，取后几位作为散列地址。\n\n比如我们的关键字是 123456789，则我们分为三部分 123 ，456 ，789 然后将其相加得 1368 然后我们再取其后三位 368 作为我们的散列地址。\n\n优点：事先不需要知道关键字情况\n\n应用场景：适合关键字位数较多的情况\n\n#### 除法散列法\n\n在用来设计散列函数的除法散列法中，通过取 key 除以 p 的余数，将关键字映射到 p 个槽中的某一个上，对于散列表长度为 m 的散列函数公式为\n\n​ **f(k) = k mod p (p <= m)**\n\n例如，如果散列表长度为 12，即 m = 12 ，我们的参数 p 也设为 12，**那 k = 100 时 f(k) = 100 % 12 = 4**\n\n由于只需要做一次除法操作，所以除法散列法是非常快的。\n\n由上面的公式可以看出，该方法的重点在于 p 的取值，如果 p 值选的不好，就可能会容易产生同义词。见下面这种情况。我们哈希表长度为 6，我们选择 6 为 p 值，则有可能产生这种情况，所有关键字都得到了 0 这个地址数。![image-20201117191635083](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201117191635083.4z4tf8bvv2g0.png)\n\n那我们在选用除法散列法时选取 p 值时应该遵循怎样的规则呢？\n\n- m 不应为 2 的幂，因为如果 m = 2^p ，则 f(k) 就是 k 的 p 个最低位数字。例 12 % 8 = 4 ，12 的二进制表示位 1100，后三位为 100。\n- 若散列表长为 m ,通常 p 为 小于或等于表长（最好接近 m）的最小质数或不包含小于 20 质因子的合数。\n\n> **合数：**合数是指在大于 1 的整数中除了能被 1 和本身整除外，还能被其他数（0 除外）整除的数。\n>\n> **质因子**：质因子（或质因数）在数论里是指能整除给定正整数的质数。\n\n![质因子](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/质因子.75q2ya0sdts0.png)\n\n这里的 2，3，5 为质因子\n\n还是上面的例子，我们根据规则选择 5 为 p 值，我们再来看。这时我们发现只有 6 和 36 冲突，相对来说就好了很多。\n\n![image-20201117192738889](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201117192738889.4zt1f4q8isc0.png)\n\n优点：计算效率高，灵活\n\n应用场景：不知道关键字分布情况\n\n#### 乘法散列法\n\n构造散列函数的乘法散列法主要包含两个步骤\n\n- 用关键字 k 乘上常数 A(0 < A < 1)，并提取 k A 的小数部分\n- 用 m 乘以这个值，再向下取整\n\n散列函数为\n\n​ **f (k) = ⌊ m(kA mod 1) ⌋**\n\n这里的 **kA mod 1** 的含义是取 keyA 的小数部分，即 **kA - ⌊kA⌋** 。\n\n优点：对 m 的选择不是特别关键，一般选择它为 2 的某个幂次（m = 2 ^ p ,p 为某个整数）\n\n应用场景：不知道关键字情况\n\n#### 平方取中法\n\n这个方法就比较简单了，假设关键字是 321，那么他的平方就是 103041，再抽取中间的 3 位就是 030 或 304 用作散列地址。再比如关键字是 1234 那么它的平方就是 1522756 ，抽取中间 3 位就是 227 用作散列地址.\n\n优点：灵活，适用范围广泛\n\n适用场景：不知道关键字分布，而位数又不是很大的情况。\n\n#### 随机数法\n\n故名思意，取关键字的随机函数值为它的散列地址。也就是 **f(key) = random(key)**。这里的 random 是 随机函数。\n\n优点：易实现\n\n适用场景：关键字的长度不等时\n\n上面我们的例子都是通过数字进行举例，那么如果是字符串可不可以作为键呢？当然也是可以的，各种各样的符号我们都可以转换成某种数字来对待，比如我们经常接触的 ASCII 码，所以是同样适用的。\n\n以上就是常用的散列函数构造方法，其实他们的中心思想是一致的，将关键字经过加工处理之后变成另外一个数字，而这个数字就是我们的存储位置，是不是有一种间谍传递情报的感觉。\n\n一个好的哈希函数可以帮助我们尽可能少的产生冲突，但是也不能完全避免产生冲突，那么遇到冲突时应该怎么做呢？下面给大家带来几种常用的处理散列冲突的方法。\n\n### 处理散列冲突的方法\n\n我们在使用 hash 函数之后发现关键字 key1 不等于 key2 ，但是 f(key1) = f(key2)，即有冲突，那么该怎么办呢？不急我们慢慢往下看。\n\n#### 开放地址法\n\n了解开放地址法之前我们先设想以下场景。\n\n> 袁记菜馆内，铃铃铃，铃铃铃 电话铃响了\n>\n> 大鹏：老袁，给我订个包间，我今天要去带几个客户去你那谈生意。\n>\n> 袁厨：大鹏啊，你常用的那个包间被人订走啦。\n>\n> 大鹏：老袁你这不仗义呀，咋没给我留住呀，那你给我找个**空房间**吧。\n>\n> 袁厨：好滴老哥\n\n哦，穿越回古代就没有电话啦，那看来穿越的时候得带着几个手机了。\n\n上面的场景其实就是一种处理冲突的方法-----开放地址法\n\n**开放地址法**就是一旦发生冲突，就去寻找下一个空的散列地址，只要列表足够大，空的散列地址总能找到，并将记录存入，为了使用开放寻址法插入一个元素，需要连续地检查散列表，或称为**探查**，我们常用的有**线性探测，二次探测，随机探测**。\n\n##### 线性探测法\n\n下面我们先来看一下线性探测，公式：\n\n> **f,(key) = ( f(key) + di ) MOD m（di = 1,2,3,4,5,6....m-1）**\n\n我们来看一个例子，我们的关键字集合为{12，67，56，16，25，37，22，29，15，47，48，21}，表长为 12，我们再用散列函数 **f(key) = key mod 12。**\n\n我们求出每个 key 的 f(key)见下表\n\n![image-20201118121740324](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201118121740324.26hu17vbf5fk.png)\n\n我们查看上表发现，前五位的 **f(key)** 都不相同，即没有冲突，可以直接存入，但是到了第六位 **f(37) = f(25) = 1**,那我们就需要利用上面的公式 **f(37) = f (f(37) + 1 ) mod 12 = 2**，这其实就是我们的订包间的做法。下面我们看一下将上面的所有数存入哈希表是什么情况吧。\n\n![image-20201118121801671](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201118121801671.6g0iqe60o9c0.png)\n\n我们把这种解决冲突的开放地址法称为**线性探测法**。下面我们通过视频来模拟一下线性探测法的存储过程。\n\n![线性探测法](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/线性探测法.36c7dqr0r120.gif)\n\n另外我们在解决冲突的时候，会遇到 48 和 37 虽然不是同义词，却争夺一个地址的情况，我们称其为**堆积**。因为堆积使得我们需要不断的处理冲突，插入和查找效率都会大大降低。\n\n通过上面的视频我们应该了解了线性探测的执行过程了，那么我们考虑一下这种情况，若是我们的最后一位不为 21，为 34 时会有什么事情发生呢？\n\n![image-20201118133459372](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201118133459372.2vdk7gxr7hg0.png)\n\n此时他第一次会落在下标为 10 的位置，那么如果继续使用线性探测的话，则需要通过不断取余后得到结果，数据量小还好，要是很大的话那也太慢了吧，但是明明他的前面就有一个空房间呀，如果向前移动只需移动一次即可。不要着急，前辈们已经帮我们想好了解决方法\n\n##### 二次探测法\n\n其实理解了我们的上个例子之后，这个一下就能整明白了，根本不用费脑子，这个方法就是更改了一下 di 的取值\n\n> **线性探测： f,(key) = ( f(key) + di ) MOD m（di = 1,2,3,4,5,6....m-1）**\n>\n> **二次探测：** **f,(key) = ( f(key) + di ) MOD m（di =1^2 , -1^2 , 2^2 , -2^2 .... q^2, -q^2, q<=m/2）**\n\n**注：这里的是 -1^2 为负值 而不是 （-1)^2**\n\n所以对于我们的 34 来说，当 di = -1 时，就可以找到空位置了。\n\n![image-20201118142851095](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201118142851095.5hdys12bsgg0.png)\n\n二次探测法的目的就是为了不让关键字聚集在某一块区域。另外还有一种有趣的方法，位移量采用随机函数计算得到，接着往下看吧.\n\n##### 随机探测法\n\n大家看到这是不又有新问题了，刚才我们在散列函数构造规则的第一条中说\n\n> （1）**必须是一致的**，假设你输入辣子鸡丁时得到的是**在看**，那么每次输入辣子鸡丁时，得到的也必须为**在看**。如果不是这样，散列表将毫无用处。\n\n咦？怎么又是**在看**哈哈，那么问题来了，我们使用随机数作为他的偏移量，那么我们查找的时候岂不是查不到了？因为我们 di 是随机生成的呀，这里的随机其实是伪随机数，伪随机数含义为，我们设置**随机种子**相同，则不断调用随机函数可以生成**不会重复的数列**，我们在查找时，**用同样的随机种子**，**它每次得到的数列是相同的**，那么相同的 di 就能得到**相同的散列地址**。\n\n> 随机种子（Random Seed）是计算机专业术语，一种以随机数作为对象的以真随机数（种子）为初始条件的随机数。一般计算机的随机数都是伪随机数，以一个真随机数（种子）作为初始条件，然后用一定的算法不停迭代产生随机数\n\n![image-20201118154853554](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201118154853554.36a1ec591620.png)\n\n![image-20201118205305792](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201118205305792.3czdgupb1540.png)\n\n通过上面的测试是不是一下就秒懂啦，为什么我们可以使用随机数作为它的偏移量，理解那句，相同的随机种子，他每次得到的数列是相同的。\n\n下面我们再来看一下其他的函数处理散列冲突的方法\n\n#### 再哈希法\n\n这个方法其实也特别简单，利用不同的哈希函数再求得一个哈希地址，直到不出现冲突为止。\n\n> **f,(key) = RH,( key ) (i = 1,2,3,4.....k)**\n\n这里的 RH,就是不同的散列函数，你可以把我们之前说过的那些散列函数都用上，每当发生冲突时就换一个散列函数，相信总有一个能够解决冲突的。这种方法能使关键字不产生聚集，但是代价就是增加了计算时间。是不是很简单啊。\n\n#### 链地址法\n\n下面我们再设想以下情景。\n\n> 袁记菜馆内，铃铃铃，铃铃铃电话铃又响了，那个大鹏又来订房间了。\n>\n> 大鹏：老袁啊，我一会去你那吃个饭，还是上回那个包间\n>\n> 袁厨：大鹏你下回能不能早点说啊，又没人订走了，这回是老王订的\n>\n> 大鹏：老王这个老东西啊，反正也是熟人，你再给我整个桌子，我拼在他后面吧\n\n不好意思啊各位同学，信鸽最近太贵了还没来得及买。上面的情景就是模拟我们的新的处理冲突的方法链地址法。\n\n上面我们都是遇到冲突之后，就换地方。那么我们有没有不换地方的办法呢？那就是我们现在说的链地址法。\n\n还记得我们说过得同义词吗？就是 key 不同 f(key) 相同的情况，我们将这些同义词存储在一个单链表中，这种表叫做同义词子表，散列表中只存储同义词子表的头指针。我们还是用刚才的例子，关键字集合为{12，67，56，16，25，37，22，29，15，47，48，21}，表长为 12，我们再用散列函数 **f(key) = key mod 12。**我们用了链地址法之后就再也不存在冲突了，无论有多少冲突，我们只需在同义词子表中添加结点即可。下面我们看下链地址法的存储情况。\n\n![image-20201118161354566](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201118161354566.139yir8z205s.png)\n\n链地址法虽然能够不产生冲突，但是也带来了查找时需要遍历单链表的性能消耗，有得必有失嘛。\n\n#### 公共溢出区法\n\n下面我们再来看一种新的方法，这回大鹏又要来吃饭了。\n\n> 袁记菜馆内.....\n>\n> 袁厨：呦，这是什么风把你给刮来了，咋没开你的大奔啊。\n>\n> 大鹏：哎呀妈呀，别那么多废话了，我快饿死了，你快给我找个位置，我要吃点饭。\n>\n> 袁厨：你来的，太不巧了，咱们的店已经满了，你先去旁边的小屋看会电视，等有空了我再叫你。小屋里面还有几个和你一样来晚的，你们一起看吧。\n>\n> 大鹏：电视？看电视？\n\n上面得情景就是模拟我们的公共溢出区法，这也是很好理解的，你不是冲突吗？那冲突的各位我先给你安排个地方呆着，这样你就有地方住了。我们为所有冲突的关键字建立了一个公共的溢出区来存放。\n\n![溢出区法](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/溢出区法.6oq4m66ei000.png)\n\n那么我们怎么进行查找呢？我们首先通过散列函数计算出散列地址后，先于基本表对比，如果不相等再到溢出表去顺序查找。这种解决冲突的方法，对于冲突很少的情况性能还是非常高的。\n\n### 散列表查找算法(线性探测法)\n\n下面我们来看一下散列表查找算法的实现\n\n首先需要定义散列列表的结构以及一些相关常数，其中 elem 代表散列表数据存储数组，count 代表的是当前插入元素个数，size 代表哈希表容量，NULLKEY 散列表初始值，然后我们如果查找成功就返回索引，如果不存在该元素就返回元素不存在。\n\n我们将哈希表初始化，为数组元素赋初值。\n\n![第一行](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/第一行.70gxkpul4fc0.png)\n\n插入操作的具体步骤：\n\n（1）通过哈希函数（除法散列法），将 key 转化为数组下标\n\n（2）如果该下标中没有元素，则插入，否则说明有冲突，则利用线性探测法处理冲突。详细步骤见注释\n\n![第二行](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/第二行.cph9jb8m24o.png)\n\n查找操作的具体步骤：\n\n（1）通过哈希函数（同插入时一样），将 key 转成数组下标\n\n（2）通过数组下标找到 key 值，如果 key 一致，则查找成功，否则利用线性探测法继续查找。\n\n![第三张](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/第三张.4iqbtyns3li0.png)\n\n下面我们来看一下完整代码\n\n![第四张](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/第四张.2uwq29s75o80.png)\n\n### 散列表性能分析\n\n如果没有冲突的话，散列查找是我们查找中效率最高的，时间复杂度为 O(1),但是没有冲突的情况是一种理想情况，那么散列查找的平均查找长度取决于哪些方面呢？\n\n**1.散列函数是否均匀**\n\n我们在上文说到，可以通过设计散列函数减少冲突，但是由于不同的散列函数对一组关键字产生冲突可能性是相同的，因此我们可以不考虑它对平均查找长度的影响。\n\n**2.处理冲突的方法**\n\n相同关键字，相同散列函数，不同处理冲突方式，会使平均查找长度不同，比如我们线性探测有时会堆积，则不如二次探测法好，因为链地址法处理冲突时不会产生任何堆积，因而具有最佳的平均查找性能\n\n**3.散列表的装填因子**\n\n本来想在上文中提到装填因子的，但是后来发现即使没有说明也不影响我们对哈希表的理解，下面我们来看一下装填因子的总结\n\n> 装填因子 α = 填入表中的记录数 / 散列表长度\n\n散列因子则代表着散列表的装满程度，表中记录越多，α 就越大，产生冲突的概率就越大。我们上面提到的例子中 表的长度为 12，填入记录数为 6，那么此时的 α = 6 / 12 = 0.5 所以说当我们的 α 比较大时再填入元素那么产生冲突的可能性就非常大了。所以说散列表的平均查找长度取决于装填因子，而不是取决于记录数。所以说我们需要做的就是选择一个合适的装填因子以便将平均查找长度限定在一个范围之内。\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/KMP.md",
    "content": "## KMP 算法（Knuth-Morris-Pratt）\n\n> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n我们刚才讲了 BM 算法，虽然不是特别容易理解，但是如果你用心看的话肯定可以看懂的，我们再来看一个新的算法，这个算法是考研时必考的算法。实际上 BM 和 KMP 算法的本质是一样的，你理解了 BM 再来理解 KMP 那就是分分钟的事啦。\n\n我们先来看一个实例\n\n![](https://img-blog.csdnimg.cn/20210319193924180.gif)\n\n为了让读者更容易理解，我们将指针移动改成了模式串移动，两者相对与主串的移动是一致的，重新比较时都是从指针位置继续比较。\n\n通过上面的实例是不是很快就能理解 KMP 算法的思想了，但是 KMP 的难点不是在这里，不过多思考，认真看理解起来也是很轻松的。\n\n在上面的例子中我们提到了一个名词，**最长公共前后缀**，这个是什么意思呢？下面我们通过一个较简单的例子进行描述。\n\n![KMP例子](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/KMP例子.1uirbimk5fcw.png)\n\n此时我们在红色阴影处匹配失败，绿色为匹配成功部分，则我们观察匹配成功的部分。\n\n我们来看一下匹配成功部分的所有前缀\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210401204019428.png)\n\n我们的最长公共前后缀如下图，则我们需要这样移动\n\n![原理](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/原理.bghc3ecm4z4.png)\n\n好啦，看完上面的图，KMP 的核心原理已经基本搞定了，但是我们现在的问题是，我们应该怎么才能知道他的最长公共前后缀的长度是多少呢？怎么知道移动多少位呢？\n\n刚才我们在 BM 中说到，我们移动位数跟主串无关，只跟模式串有关，跟我们的 bc,suffix,prefix 数组的值有关，我们通过这些数组就可以知道我们每次移动多少位啦，其实 KMP 也有一个数组，这个数组叫做 next 数组，那么这个 next 数组存的是什么呢？\n\nnext 数组存的咱们最长公共前后缀中，前缀的结尾字符下标。是不是感觉有点别扭，我们通过一个例子进行说明。\n\n![next数组](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/next数组.3nir7pgcs9c0.png)\n\n我们知道 next 数组之后，我们的 KMP 算法实现起来就很容易啦，另外我们看一下 next 数组到底是干什么用的。\n\n![KMP1](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/KMP1.j74ujxjuq1c.png)\n\n![kmp2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/kmp2.6jx846nmyd00.png)\n\n剩下的就不用说啦，完全一致啦，咱们将上面这个例子，翻译成和咱们开头对应的动画大家看一下。\n\n![请添加图片描述](https://img-blog.csdnimg.cn/20210319193924754.gif)\n\n下面我们看一下代码，标有详细注释，大家认真看呀。\n\n**注：很多教科书的 next 数组表示方式不一致，理解即可**\n\n```java\nclass Solution {\n    public int strStr(String haystack, String needle) {\n        //两种特殊情况\n        if (needle.length() == 0) {\n            return 0;\n        }\n        if (haystack.length() == 0) {\n            return -1;\n        }\n        // char 数组\n        char[] hasyarr = haystack.toCharArray();\n        char[] nearr = needle.toCharArray();\n        //长度\n        int halen = hasyarr.length;\n        int nelen = nearr.length;\n        //返回下标\n        return kmp(hasyarr,halen,nearr,nelen);\n\n    }\n    public int kmp (char[] hasyarr, int halen, char[] nearr, int nelen) {\n        //获取next 数组\n        int[] next = next(nearr,nelen);\n        int j = 0;\n        for (int i = 0; i < halen; ++i) {\n            //发现不匹配的字符，然后根据 next 数组移动指针，移动到最大公共前后缀的，\n            //前缀的后一位,和咱们移动模式串的含义相同\n            while (j > 0 && hasyarr[i] != nearr[j]) {\n                j = next[j - 1] + 1;\n                //超出长度时，可以直接返回不存在\n                if (nelen - j + i > halen) {\n                    return -1;\n                }\n            }\n            //如果相同就将指针同时后移一下，比较下个字符\n            if (hasyarr[i] == nearr[j]) {\n                ++j;\n            }\n            //遍历完整个模式串，返回模式串的起点下标\n            if (j == nelen) {\n                return i - nelen + 1;\n            }\n        }\n        return -1;\n    }\n    //这一块比较难懂，不想看的同学可以忽略，了解大致含义即可，或者自己调试一下，看看运行情况\n    //我会每一步都写上注释\n    public  int[] next (char[] needle,int len) {\n        //定义 next 数组\n        int[] next = new int[len];\n        // 初始化\n        next[0] = -1;\n        int k = -1;\n        for (int i = 1; i < len; ++i) {\n            //我们此时知道了 [0,i-1]的最长前后缀，但是k+1的指向的值和i不相同时，我们则需要回溯\n            //因为 next[k]就时用来记录子串的最长公共前后缀的尾坐标（即长度）\n            //就要找 k+1前一个元素在next数组里的值,即next[k+1]\n            while (k != -1 && needle[k + 1] != needle[i]) {\n                k = next[k];\n            }\n            // 相同情况，就是 k的下一位，和 i 相同时，此时我们已经知道 [0,i-1]的最长前后缀\n            //然后 k + 1 又和 i 相同，最长前后缀加1，即可\n            if (needle[k+1] == needle[i]) {\n                ++k;\n            }\n            next[i] = k;\n\n        }\n        return next;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def strStr(self, haystack: str, needle: str)->int:\n        # 两种特殊情况\n        if len(needle) == 0:\n            return 0\n        if len(haystack) == 0:\n            return -1\n        # 长度\n        halen = len(haystack)\n        nelen = len(needle)\n        # 返回下标\n        return self.kmp(haystack, halen, needle, nelen)\n\n    def kmp(self, hasyarr: str, halen: int, nearr: str, nelen: int)->int:\n        # 获取next 数组\n        next = self.next(nearr, nelen)\n        j = 0\n        for i in range(0, halen):\n            # 发现不匹配的字符，然后根据 next 数组移动指针，移动到最大公共前后缀的，\n            # 前缀的后一位,和咱们移动模式串的含义相同\n            while j > 0 and hasyarr[i] != nearr[j]:\n                j = next[j - 1] + 1\n                # 超出长度时，可以直接返回不存在\n                if nelen - j + i > halen:\n                    return -1\n            # 如果相同就将指针同时后移一下，比较下个字符\n            if hasyarr[i] == nearr[j]:\n                j += 1\n            # 遍历完整个模式串，返回模式串的起点下标\n            if j == nelen:\n                return i - nelen + 1\n        return -1\n\n    # 这一块比较难懂，不想看的同学可以忽略，了解大致含义即可，或者自己调试一下，看看运行情况\n    # 我会每一步都写上注释\n    def next(self, needle: str, len:int)->List[int]:\n        # 定义 next 数组\n        next = [0] * len\n        # 初始化\n        next[0] = -1\n        k = -1\n        for i in range(1, len):\n            # 我们此时知道了 [0,i-1]的最长前后缀，但是k+1的指向的值和i不相同时，我们则需要回溯\n            # 因为 next[k]就时用来记录子串的最长公共前后缀的尾坐标（即长度）\n            # 就要找 k+1前一个元素在next数组里的值,即next[k+1]\n            while k != -1 and needle[k + 1] != needle[i]:\n                k = next[k]\n            # 相同情况，就是 k的下一位，和 i 相同时，此时我们已经知道 [0,i-1]的最长前后缀\n            # 然后 k + 1 又和 i 相同，最长前后缀加1，即可\n            if needle[k + 1] == needle[i]:\n                k += 1\n            next[i] = k\n        return next\n```\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/read.md",
    "content": "**大家刚开始刷题时，会有不知道该从何刷起，也看不懂别人题解的情况**\n\n**不要着急，这是正常的。**\n\n**当你刷题一定数量之后，你就会有自己的刷题思维。**\n\n**知道这个题目属于何种类型，使用什么解题方法。**\n\n**刷题速度也会大幅提升。**\n\n**我现在想做的就是尽量把一些基础但很经典的问题细化，理清逻辑。**\n\n**为后面的师弟师妹提供一丢丢帮助**。\n\n**毕竟刚开始刷题时，不知道从哪开始和看不懂题解，是很打击自信心的，**\n\n**所以我就想着帮助大家尽快度过这段时期，让刷题初期的你对刷题没有那么排斥**\n\n**所以基地里的题解都尽量用动画模拟，加深大家对题目的理解。往下看吧**\n\n> **PS：基地的所有代码均在刷题网站 AC 大家可以放心食用**\n\n**如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 [tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg) ，备注 github + 题目 + 问题 向我反馈**\n\n**如果老哥觉得仓库很用心的话，麻烦老哥点个 star ，这也是我一直更新下去的动力！感谢支持。**\n\n**感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助，另外给刚开始刷题，但是不知道从哪里开始刷的同学，整理了一份[刷题大纲](https://mp.weixin.qq.com/s/fTMzLrv5Ou2Xf3_br80J0g)，可以先按这个顺序刷，刷完之后应该就能入门了。**\n\n**另外想要进阶的老哥可以看下我之前看过的这个谷歌大神的刷题笔记。**\n\n**刷题笔记**：链接：https://pan.baidu.com/s/1gNIhOv83ZMxDEEFXLWChuA 提取码：chef\n\n**希望手机阅读的同学可以来我的 [公众号：程序厨](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信图片_20210320152235.2c1f5hy6gmas.png)两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[刷题小队](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。**\n\n<div  align=\"center\">  <img src=\"https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信图片_20210320152235.2c1f5hy6gmas.png\" width = \"150px\" hight = \"150px\"/> </div>\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/关于栈和队列的那些事.md",
    "content": "# 希望这篇文章能合你的胃口\n\n> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n大家在学习数据结构的时候应该都学习过栈和队列，对他俩的原理应该很熟悉了，栈是先进后出，队列是后进后出。下面我们通过这篇文章来帮助小伙伴们回忆一下栈和队列的那些事。\n\n阅读完这篇文章你会有以下收获。\n\n了解栈和队列的意义\n\n了解栈和队列的实现方式\n\n了解循环队列\n\n学会中缀表达式转后缀表达式\n\n学会后缀表达式的运算\n\n## 这是栈\n\n### 栈模型\n\n**栈（stack）是限制插入和删除只能在一个位置上进行的表**，该位置是表的末端叫做栈的顶（top），对栈的基本操作有 push(进栈)和 pop(出栈),前者相当于插入，后者则是删除最后插入的元素。\n\n栈的另一个名字是 LIFO（先进后出）表。普通的清空栈的操作和判断是否空栈的测试都是栈的操作指令系统的一部分，我们对栈能做的基本上也就是 push 和 pop 操作。\n\n注：该图描述的模型只象征着 push 是输入操作，pop 和 top 是输出操作\n\n![栈和队列1](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/栈和队列1.1wjbrl9iudk0.png)\n\n下图表示进行若干操作后的一个抽象的栈。一般的模型是，存在某个元素位于栈顶，而该元素是唯一可见元素。\n\n![2222](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/2222.1ksit1l8tlr4.png)\n\n### 栈的实现\n\n因为栈是一个表，因此能够实现表的方法都可以实现栈，ArrayList 和 LinkedList 都可以支持栈操作。\n\n刷题时我们可以直接使用 Stack 类来进行创建一个栈。刷题时我们可以通过下列代码创建一个栈。下面两种方式哪种都可以使用。\n\n```\nDeque<TreeNode> stack = new LinkedList<TreeNode>();//类型为TreeNode\nStack<TreeNode> stack = new Stack<TreeNode>();\n```\n\n### 栈的应用\n\n栈在现实中应用场景很多，大家在刷题时就可以注意到，很多题目都可以用栈来解决的。下面我们来说一个比较常用的情景，数字表达式的求值。\n\n不知道大家是否还记得那句口令，先乘除，后加减，从左算到右，有括号的话就先算括号里面的。这是我们做小学数学所用到的。四则运算中括号也是其中的一部分，先乘除后加减使运算变的复杂，加上括号后甚之，那么我们有什么办法可以让其变的更好处理呢？波兰数学家**Jan Łukasiewicz**想到了一种不需要括号的后缀表达式，，我们也将它称之为逆波兰表示。不用数学家名字命名的原因有些尴尬，居然是因为他的名字太复杂了，所以用了国籍来表示而不是姓名。所以各位小伙伴以后给孩子起名字的时候不要太复杂啊。\n\n> 扬·武卡谢维奇（[波兰语](https://baike.baidu.com/item/波兰语)：_Jan Łukasiewicz_，1878 年 12 月 21 日[乌克兰](https://baike.baidu.com/item/乌克兰)利沃夫 - 1956 年 2 月 13 日爱尔兰都柏林），[波兰](https://baike.baidu.com/item/波兰)数学家，主要致力于[数理逻辑](https://baike.baidu.com/item/数理逻辑)的研究。著名的波兰表示法逆波兰表示法就是他的研究成果。\n\n#### 中缀表达式转为后缀表达式\n\n我们通过一个例子，来说明如何将中缀表达式转为后缀表达式。\n\n例\n\n中缀:9 + ( 3 - 1 ) \\* 3 + 10 / 2\n\n后缀:9 3 1 - 3 \\* + 10 2 / +\n\n规则\n\n1.从左到右遍历中缀表达式的每个数字和符号，若是数字就输出（直接成为后缀表达式的一部分，不进入栈）\n\n2.若是符合则判断其与栈顶符号的优先级，是右括号或低于栈顶元素，则栈顶元素依次出栈并输出，等出栈完毕，当前元素入栈。\n\n3.遵循以上两条直到输出后缀表达式为止。\n\n老样子大家直接看动图吧简单粗暴，清晰易懂\n\n![中缀转后缀](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/中缀转后缀.712hv6fxip40.gif)\n\n#### 后缀表达式计算结果\n\n中缀:9 + ( 3 - 1 ) \\* 3 + 10 / 2=20\n\n后缀:9 3 1 - 3 \\* + 10 2 / +\n\n后缀表达式的值也为 20，那么我们来了解一下计算机是如何将后缀表达式计算为 20 的。\n\n规则：\n\n1.从左到右遍历表达式的每个数字和符号，如果是数字就进栈\n\n2.如果是符号就将栈顶的两个数字出栈，进行运算，并将结果入栈，一直到获得最终结果。\n\n下面大家 继续看动图吧。\n\n![后缀运算](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/后缀运算.38havvkp8k40.gif)\n\n注：为了用动图把逻辑整的清晰明了，十几秒的动图，就要整半个多小时，改进好几遍。如果觉得图片对你有帮助的话就点个赞和在看吧。\n\n## 这是队列\n\n### 队列模型\n\n像栈一样，队列（queue）也是表。然而使用队列时插入在一端进行而删除在另一端进行，遵守先进先出的规则。所以队列的另一个名字是（FIFO）。\n\n队列的基本操作是入队（enqueue）:它是在表的末端(队尾(rear)插入一个元素。出队（dequeue）:出队他是删除在表的开头（队头(front)）的元素。\n\n注：下面模型只象征着输入输出操作\n\n![image-20201102213300674](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201102213300674.1yvt4eulwri8.png)\n\n具体模型\n\n![image-20201102214029660](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201102214029660.7fol7xl7uz40.png)\n\n### 队列的实现\n\n队列我们在树的层次遍历时经常使用，后面我们写到树的时候会给大家整理框架。队列同样也可以由数组和 LinkedList 实现，刷题时比较常用的方法是\n\n```\n  Queue<TreeNode> queue = new LinkedList<TreeNode>();\n```\n\n### 循环队列\n\n循环队列的出现就是为了解决队列的假溢出问题。何为假溢出呢？我们运用数组实现队列时，数组长度为 5，我们放入了[1,2,3,4,5],我们将 1，2 出队，此时如果继续加入 6 时，因为数组末尾元素已经被占用，再向后加则会溢出，但是我们的下标 0，和下标 1 还是空闲的。所以我们把这种现象叫做“假溢出”。\n\n例如，我们在学校里面排队洗澡一人一个格，当你来到澡堂发现前面还有两个格，但是后面已经满了，你是去前面洗，还是等后面格子的哥们洗完再洗？肯定是去前面的格子洗。除非澡堂的所有格子都满了。我们才会等。\n\n所以我们用来解决假溢出的方法就是后面满了，就再从头开始，也就是头尾相接的循环，我们把队列的这种头尾相接的顺序存储结构成为循环队列。\n\n![循环队列](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/循环队列.1841k3lsp7cw.gif)\n\n我们发现队列为空时 front == rear，队列满时也是 front == rear，那么问题来了，我们应该怎么区分满和空呢？\n\n我们可以通过以下两种方法进行区分，\n\n1.设置标记变量 flag;当 front==rear 时且 flag==0 时为空，当 front==rear 且 rear 为 1 时且 flag==1 时为满\n\n2.当队列为空时，front==rear,当队列满是我们保留一个元素空间，也就是说，队列满时，数组内还有一个空间。\n\n例：\n\n![image-20201102222857190](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201102222857190.4trq9b6gfjc0.png)\n\n![image-20201102222914762](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201102222914762.34b1o0cqwse0.png)\n\n然后我们再根据以下公式则能够判断队列满没满了。\n\n(rear+1)%queuesize==front\n\nqueuesize,代表队列的长度，上图为 5。我们来判断上面两张图是否满。（4+1）%5==0，（1+1）%5==3\n\n两种情况都是满的,over。\n\n注：为了用动图把逻辑整的清晰明了，十几秒的动图，就要整半个多小时，改进好几遍。如果觉得图片对你有帮助的话就点个赞和在看吧。\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/关于链表的那些事.md",
    "content": "# 链表详解\n\n> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n阅读完本文你会有以下收获\n\n1.知道什么是链表？\n\n2.了解链表的几种类型。\n\n3.了解链表如何构造。\n\n4.链表的存储方式\n\n5.如何遍历链表\n\n6.了解链表的操作。\n\n7.知道链表和数组的不同点\n\n8.掌握链表的经典题目。\n\n### 链表的定义：\n\n> 定义：链表是一种递归的数据结构，他或者为空（null），或者是指向一个结点（node）的引用，该结点含有一个泛型的元素和一个指向另一条链表的引用。\n\n我们来对其解读一下，链表是一种常见且基础的数据结构，是一种线性表，但是他不是按线性顺序存取数据，而是在每一个节点里存到下一个节点的地址。我们可以这样理解，链表是通过指针串联在一起的线性结构，每一个链表结点由两部分组成，数据域及指针域，链表的最后一个结点指向 null。也就是我们所说的空指针。\n\n### 链表的几种类型\n\n我们先来看一下链表的可视化表示方法，以便更好的对其理解。\n\n- 用长方形表示对象\n- 将实例变量的值写在长方形中；\n- 用指向被引用对象的箭头表示引用关系。\n\n#### 单链表\n\n一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。\n\n我们通过上面说到的可视化表示方法，构造单链表的可视化模型，如图所示。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210321125110539.png)\n\n#### 双向链表\n\n上面提到了单链表的节点只能指向节点的下一个节点。而双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接，所以双链表既能向前查询也可以向后查询。\n\n![双链表](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/双链表.3cw4hra1g3q0.png)\n\n####\n\n还有一个常用的链表则为循环单链表，则单链表尾部的指针指向头节点。例如在 leetcode61 旋转链表中，我们就是先将链表闭合成环，找到新的打开位置，并定义新的表头和表尾。\n\n### 构造链表\n\njava 是面向对象语言，实现链表很容易。我们首先用一个嵌套类来定义节点的抽象数据类型\n\n```java\nprivate class Node {\n  Item item;\n  Node next;\n}\n```\n\n现在我们需要构造一条含有 one,two,three 的链表，我们首先为每个元素创造一个节点\n\n```java\nNode first = new Node();\nNode second = new Node();\nNode third = new Node();\n```\n\n将每个节点的 item 域设为所需的值\n\n```java\nfirst.item = \"one\";\nsecond.item = \"two\";\nthird.item = \"three\";\n```\n\n然后我们设置 next 域来构造链表\n\n```java\nfirst.next = second;\nsecond.next = third;\n```\n\n注：此时 third 的 next 仍为 null，即被初始化的值。\n\n### 链表的存储方式\n\n我们知道了如何构造链表，我们再来说一下链表的存储方式。\n\n我们都知道数组在内存中是连续分布的，但是链表在内存不是连续分配的。链表是通过指针域的指针链接内存中的各个节点。\n\n所以链表在内存中是散乱分布在内存中的某地址上，分配机制取决于操作系统的内存管理。我们可以根据下图来进行理解。\n\n![image-20201101153659912](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201101153659912.9neaap4ogtc.png)\n\n### 遍历链表\n\n链表的遍历我们通常使用 while 循环（for 循环也可以但是代码不够简洁）下面我们来看一下链表的遍历代码\n\nfor:\n\n```java\nfor (Node x = first; x != null; x = x.next) {\n     //处理x.item\n}\n```\n\nwhile:\n\n```\nNode x = first;\nwhile (x!=null) {\n  //处理x.item\n   x = x.next;\n}\n```\n\n### 链表的几种操作\n\n#### 添加节点\n\n![image-20201101155937520](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201101155937520.my13cevp2cg.png)\n\n#### 删除节点\n\n删除 B 节点，如图所示\n\n![image-20201101155003257](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201101155003257.4onlntrwj2i0.png)\n\n我们只需将 A 节点的 next 指针指向 C 节点即可。\n\n有的同学可能会有这种疑问，B 节点这样不会留着内存里吗？java 含有自己的内存回收机制，不用自己手动释放内存了，但是 C++,则需要手动释放。\n\n我们通过上图的删除和插入都是 O(1)操作。\n\n链表和数组的比较\n\n|      | 插入/删除操作(时间复杂度) | 查询（时间复杂度） | 存储方式     |\n| ---- | ------------------------- | ------------------ | ------------ |\n| 数组 | O(n)                      | O(1)               | 内存连续分布 |\n| 链表 | O(1)                      | O(n)               | 内存散乱分布 |\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/冒泡排序.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n**写在前面**\n\n袁记菜馆内\n\n> 袁厨：小二，最近快要过年了，咱们店也要给大家发点年终奖啦，你去根据咱们的**红黑豆小本本**，看一下大家都该发多少的年终奖，然后根据金额从小到大排好，按顺序一个一个发钱，大家回去过个好年，你也老大不小了，回去取个媳妇。\n>\n> 小二：好滴掌柜的，我现在马上就去。\n\n上面说到的按照金额从大到小排好就是我们今天要讲的内容 --- 排序。\n\n排序是我们生活中经常会面对的问题，体育课的时候，老师会让我们从矮到高排列，考研录取时，成绩会按总分从高到底进行排序（考研的各位读者，你们必能收到心仪学校给你们寄来的大信封），我们网购时，有时会按销量从高到低，价格从低到高，将最符合咱们预期的商品列在前面。\n\n概念：将杂乱无章的数据元素，通过**一定的方法**（排序算法）按**关键字**（k）顺序排列的过程叫做排序。例如我们上面的销量和价格就是关键字\n\n**排序算法的稳定性**\n\n什么是排序算法的稳定性呢？\n\n因为我们待排序的记录序列中可能存在两个或两个以上的关键字相等的记录，**排序结果可能会存在不唯一的情况**，所以我们排序之后，如果相等元素之间**原有的先后顺序不变**。则称所用的排序方法是**稳定的**，反之则称之为**不稳定的**。见下图\n\n![微信截图_20210119163314](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210119163314.19vqze8e2g00.png)\n\n例如上图，我们的数组中有两个相同的元素 4， 我们分别用不同的排序算法对其排序，算法一排序之后，两个相同元素的**相对位置**没有发生改变，我们则称之为**稳定的排序算法**，算法二排序之后相对位置发生改变，则为**不稳定的排序算法**。\n\n那排序算法的稳定性又有什么用呢？\n\n在我们做题中大多只是将数组进行排序，只需考虑时间复杂度空间复杂度等指标，排序算法是否稳定，一般不进行考虑。但是在真正软件开发中排序算法的稳定性是一个特别重要的衡量指标。继续说我们刚才的例子。我们想要实现年终奖从少到多的排序，然后相同年终奖区间内的红豆数也按照从少到多进行排序。\n\n排序算法的稳定性在这里就显得至关重要。这是为什么呢？见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210119171706.xe5v3t5wjw0.png)\n\n第一次排序之后，所有的职工按照**红豆数**从少到多有序。\n\n第二次排序中，我们使用**稳定的排序算法**，所以经过第二次排序之后，年终奖相同的职工，仍然保持着红豆的有序（想对位置不变），红豆仍是从小到大排序。我们使用稳定的排序算法，只需要两次排序即可。\n\n稳定排序可以让第一个关键字排序的结果服务于第二个关键字排序中数值相等的那些数。\n\n上述情况如果我们利用不稳定的排序算法，实现这一效果是十分复杂的。\n\n**比较类和非比较类**\n\n我们根据元素是否依靠与其他元素的比较来决定元素间的相对次序。以此来区分比较类排序算法和非比较类排序算法。\n\n**内排序和外排序**\n\n内排序是在排序的整个过程中，待排序的所有记录全部被放置在内存中。外排序是由于排序的记录个数太多，不能同时放置在内存中，整个排序过程需要在内外存之间多次交换数据才能进行，常见的内部排序算法有：插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。\n\n对我们内排序来说，我们主要受三个方面影响，时间性能，辅助空间，算法的复杂性\n\n**时间性能**\n\n在我们的排序算法执行过程中，主要执行两种操作比较和交换，比较是排序算法最起码的操作，移动指记录从一个位置移动到另一个位置。所以我们一个高效的排序算法，应该尽可能少的比较和移动。\n\n**辅助空间**\n\n执行算法所需要的辅助空间的多少，也是来衡量排序算法性能的一个重要指标\n\n**算法的复杂度**\n\n这里的算法复杂度不是指算法的时间复杂度，而是指算法本身的复杂度，过于复杂的算法也会影响排序的性能。\n\n下面我们一起复习两种**简单排序算法**，**冒泡排序**和**简单选择排序**，看看有没有之前忽略的东西。\n\n### **冒泡排序**（Bubble Sort）\n\n估计我们在各个算法书上介绍排序时，第一个估计都是冒泡排序。主要是这个排序算法思路最简单，也最容易理解，（也可能是它的名字好听，哈哈），学过的老哥们也一起来复习一下吧，我们一起深挖一下冒泡排序。\n\n冒泡排序的基本思想是，**两两比较相邻记录的关键字**，如果是反序则交换，直到没有反序为止。冒泡一次冒泡会让至少一个元素移动到它应该在的位置，那么如果数组有 n 个元素，重复 n 次后则能完成排序。根据定义可知那么冒泡排序显然是一种比较类排序。\n\n**最简单的排序实现**\n\n我们来看一下这段代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n        int len = nums.length;\n        for (int i = 0; i < len; ++i) {\n            for (int j = i+1; j < len; ++j) {\n                if (nums[i] > nums[j]) {\n                    swap(nums,i,j);\n                }\n            }\n        }\n        return nums;\n\n    }\n    public void swap(int[] nums,int i,int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortArray(self, nums: List[int])->List[int]:\n        leng = len(nums)\n        for i in range(0, leng):\n            for j in range(i + 1, leng):\n                if nums[i] > nums[j]:\n                    self.swap(nums, i, j)\n        return nums\n\n    def swap(self, nums: List[int], i: int, j: int):\n        temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n```\n\n我们来思考一下上面的代码，每次让关键字 nums[i] 和 nums[j] 进行比较如果 nums[i] > nums[j] 时则进行交换，这样 nums[0] 在经过一次循环后一定为最小值。那么这段代码是冒泡排序吗？\n\n显然不是，我们冒泡排序的思想是两两比较**相邻记录**的关键字，注意里面有相邻记录，所以这段代码不是我们的冒泡排序，下面我们用动图来模拟一下冒泡排序的执行过程，看完之后一定可以写出正宗的冒泡排序。\n\n![](https://img-blog.csdnimg.cn/20210321130011175.gif)\n\n**题目代码**\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n        int len = nums.length;\n        for (int i = 0; i < len; ++i) {\n            for (int j = 0; j < len - i - 1; ++j) {\n                if (nums[j] > nums[j+1]) {\n                    swap(nums,j,j+1);\n                }\n            }\n        }\n        return nums;\n    }\n    public void swap(int[] nums,int i,int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\n上图中的代码则为正宗的冒泡排序代码，但是我们是不是发现了这个问题\n\n![微信截图_20210119221439](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210119221439.4selgahuepi0.png)\n\n我们此时数组已经完全有序了，可以直接返回，但是动图中并没有返回，而是继续执行，那我们有什么办法让其完全有序时，直接返回，不继续执行吗？\n\n我们设想一下，我们是通过 nums[j] 和 nums[j+1] 进行比较，如果大于则进行交换，那我们设想一下，如果一个完全有序的数组，我们进行冒泡排序，每次比较发现都不用进行交换。\n\n那么如果没有交换则说明当前完全有序。那我们可不可以通过一个标志位来进行判断是否发生了交换呢？当然是可以的\n\n我们来对冒泡排序进行改进\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n        int len = nums.length;\n        //标志位\n        boolean flag = true;\n        //注意看 for 循环条件\n        for (int i = 0; i < len && flag; ++i) {\n            //如果没发生交换，则依旧为false，下次就会跳出循环\n            flag = false;\n            for (int j = 0; j < len - i - 1; ++j) {\n                if (nums[j] > nums[j+1]) {\n                    swap(nums,j,j+1);\n                    //发生交换，则变为true,下次继续判断\n                    flag = true;\n                }\n            }\n        }\n        return nums;\n\n    }\n    public void swap(int[] nums,int i,int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortArray(self, nums: List[int])->List[int]:\n        leng = len(nums)\n        # 标志位\n        flag = True\n        for i in range(0, leng):\n            if not flag:\n                break\n            flag = False\n            for j in range(0, leng - i - 1):\n                if nums[j] > nums[j + 1]:\n                    self.swap(nums, j, j + 1)\n                    # 发生交换，则变为true，下次继续判断\n                    flag = True\n        return nums\n\n    def swap(self, nums: List[int], i: int, j: int):\n        temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n```\n\n这样我们就避免掉了已经有序的情况下无意义的循环判断。\n\n**冒泡排序时间复杂度分析**\n\n最好情况，就是要排序的表完全有序的情况下，根据改进后的代码，我们只需要一次遍历即可，\n\n只需 n -1 次比较，时间复杂度为 O(n)。最坏情况时，即待排序表逆序的情况，则需要比较(n-1) + (n-2) +.... + 2 + 1= n*(n-1)/2 ，并等量级的交换，则时间复杂度为 O(n^2)。*\n\n_平均情况下，需要 n_(n-1)/4 次交换操作，比较操作大于等于交换操作，而复杂度的上限是 O(n^2)，所以平均情况下的时间复杂度就是 O(n^2)。\n\n**冒泡排序空间复杂度分析**\n\n因为冒泡排序只是相邻元素之间的交换操作，只用到了常量级的额外空间，所以空间复杂度为 O(1)\n\n**冒泡排序稳定性分析**\n\n那么冒泡排序是稳定的吗？当然是稳定的，我们代码中，当 nums[j] > nums[j + 1] 时，才会进行交换，相等时不会交换，相等元素的相对位置没有改变，所以冒泡排序是稳定的。\n\n| 算法名称 | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 空间复杂度 | 是否稳定 |\n| -------- | -------------- | -------------- | -------------- | ---------- | -------- |\n| 冒泡排序 | O(n)           | O(n^2)         | O(n^2)         | O(1)       | 稳定     |\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/合成.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n之前我们说过了如何利用快速排序解决荷兰国旗问题，下面我们看下这两个题目\n\n**剑指 Offer 45. 把数组排成最小的数**，**leetcode 179 最大数**\n\n这两个问题根本上也是排序问题，下面我们一起来看一下题目描述\n\n输入一个非负整数数组，把数组里所有数字拼接起来排成一个数，打印能拼接出的所有数字中最小的一个。\n\n示例 1:\n\n> 输入: [10,2]\n> 输出: \"102\"\n\n示例 2:\n\n> 输入: [3,30,34,5,9]\n> 输出: \"3033459\"\n\n题目很容易理解，就是让我们找出拼接的所有数字中最小的一个，但是我们需要注意的是，因为输出结果较大，所以我们不能返回 int 应该将数字转换成字符串，所以这类问题还是隐形的大数问题。\n\n我们看到这个题目时，可能想到的是这种解题思路，我们首先求出数组中所有数字的全排列，然后将排列拼起来，最后再从中取出最小的值，但是我们共有 n 个数，则有 n ！个排列，显然数目是十分庞大的，那么我们有没有其他更高效的方法呢？\n\n大家先来思考一下这个问题。\n\n我们假设两个数字 m , n 可以拼接成 mn 和 nm 那么我们怎么返回最小的那个数字呢？\n\n我们需要比较 mn 和 nm ，假设 mn < nm 则此时我们求得的最小数字就是 mn\n\n> 注：mn 代表 m 和 n 进行拼接，例如 m = 10, n = 1，mn = 101\n\n当 mn < nm 时，得到最小数字 mn, 因为在最小数字 mn 中 ，m 排在 n 的前面，我们此时定义 m \"小于\" n。\n\n**注意：此时的 \"小于\" ，并不是数值的 < 。是我们自己定义，因为 m 在最小数字 mn 中位于 n 的前面，所以我们定义 m 小于 n。**\n\n下面我们通过一个例子来加深下理解。\n\n假设 ｍ = 10，n = 1 则有 mn = 101 和 nm = 110\n\n我们比较 101 和 110 ，发现 101 < 110 所以此时我们的最小数字为 101 ，又因为在最小数字中 10 (m) 排在 1(n) 的前面，我们根据定义则是 10 “小于” 1，反之亦然。\n\n这时我们自己定义了一种新的，比较两个数字大小的规则，但是我们怎么保证这种规则是有效的？\n\n怎么能确保通过这种规则，拼接数组中**所有数字**（我们之前仅仅是通过两个数字进行举例），得到的数就是最小的数字呢？\n\n下面我们先来证明下规则的有效性\n\n注：为了便于分辨我们用 A,B,C 表示元素， a,b,c 表示元素用十进制表示时的位数\n\n（1）自反性：AA = AA，所以 A 等于 A\n\n（2）对称性：如果 A \"小于\" B 则 AB < BA，所以 BA > AB 则 B \"大于\" A\n\n（3）传递性：传递性的证明稍微有点复杂，大家记得认真阅读。\n\n如果 A“小于” B，则 AB < BA, 假设 A 和 B 用十进制表示时分别有 a 位和 b 位\n\n则 AB = A _ 10 ^ b + B , BA = B _ 10 ^ a + A\n\n> 例 A = 10, a = 2 (两位数) B = 1, b = 1 (一位数)\n>\n> AB = A _ 10 ^ b + B = 10 _ 10 ^ 1 + 1 = 101\n>\n> BA = B _ 10 ^ a + A = 1 _ 10 ^ 2 + 10 = 110\n\nAB < BA 则 **A _ 10 ^ b + B < BA = B _ 10 ^ a + A** 整理得\n\nA / (10^a - 1) < B / (10 ^ b - 1)\n\n同理，如果 B “小于” C 则 BC < CB ,C 用十进制表示时有 c 位，和前面推导过程一样\n\nBC = B \\* 10 ^ c + C\n\nCB = C \\* 10 ^ b + B\n\nBC < CB 整理得 B / (10 ^ b - 1) < C / (10 ^ c - 1);\n\n我们通过 A / (10 ^ a - 1) < B / (10 ^ b - 1) ，B / (10 ^ b - 1) < C / (10 ^ c - 1);\n\n可以得到 A / (10^a - 1) < C / (10 ^ c - 1)\n\n则可以得到 AC < CA 即 A “小于” C\n\n传递性证得。\n\n我们通过上面的证明过程知道了我们定义的规则，满足自反性，对称性，传递性，则说明规则是有效的。\n\n接下来我们证明，利用这种规则得到的数字，的确是最小的。我们利用反证法来进行证明\n\n我们先来回顾一下我们之前定义的规则\n\n> 当 mn < nm 时，得到最小数字 mn, 因为在最小数字 mn 中 ，m 排在 n 的前面，\n>\n> 我们此时定义 m \"小于\" n。\n\n我们假设我们根据上诉规则得到的数字为 xxxxxxxx\n\n存在这么一对字符串 A B ,虽然 AB < BA， 按照规则 A 应该排在 B 的前面，但是在最后结果中 A 排在 B 的后面。则此时共有这么几种情况\n\n见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210306160015.5x1o7nyb6c40.png)\n\n其实我们可以归结为两大类， B 和 A 之间没有其他值， B 和 A 之间有其他值。\n\n我们先来看**没有其他值**的情况\n\n假设我们求得的最小值为 XXXXBA, 虽然 A \"小于\" B,但是在最后结果中 B 排在了 A 的前面，这和我们之前定义的规则是冲突的，大家思考一下这个值为最小值吗？\n\n假设 XXXXBA 为最小值，但是因为 A \"小于\" B ,则 AB < BA ,\n\n所以 XXXXAB 一定小于 XXXXBA 。\n\n和我们之前的假设矛盾。\n\n当然 BAXXXX 也一样。\n\n下面我们来看当 B 和 A 之间有其他值的情况\n\n即 BXXXXA\n\n我们可以将 XXXX 看成一个字符串 C，则为 BCA\n\n因为求得的最小值为 BCA ,\n\n在最小值 BCA 中 B 在 C 的前面，C 在 A 的前面，\n\n则 BC < CB, CA < AC，B \"小于 C\", C “小于” A\n\n根据我们之前证明的传递性\n\n则 B \"小于\" A\n\n但是我们假设是 A “小于” B ,与假设冲突，证得\n\n综上所述，得出假设不成立，从而得出结论：对于排成的最小数字，不存在满足下述关系的一对字符串：虽然 A \"小于\" B , 但是在最后结果中 B 排在了 A 的前面.\n\n好啦，我们证明我们定义的规则有效下面我们直接看代码吧。继续使用我们的三向切分来解决\n\nJava Code:\n\n```java\nclass Solution {\n    public String minNumber(int[] nums) {\n        String[] arr = new String[nums.length];\n        //解决大数问题，将数字转换为字符串\n        for (int i = 0 ; i < nums.length; ++i) {\n            arr[i] = String.valueOf(nums[i]);\n        }\n\n        quickSort(arr,0,arr.length-1);\n        StringBuffer str = new StringBuffer();\n\n        for (String x : arr) {\n            str.append(x);\n        }\n        return str.toString();\n    }\n    public void quickSort(String[] arr, int left, int right) {\n        if (left >= right) {\n            return;\n        }\n        int low = left;\n        int high = right;\n        int i = low+1;\n        String pivot = arr[low];\n\n        while (i <= high) {\n             //比较大小\n            if ((pivot+arr[i]).compareTo(arr[i]+pivot) > 0 ) {\n                 swap(arr,i++,low++);\n            } else if ((pivot+arr[i]).compareTo(arr[i]+pivot) < 0) {\n                 swap(arr,i,high--);\n            } else {\n                 i++;\n            }\n        }\n\n        quickSort(arr,left,low-1);\n        quickSort(arr,high+1,right);\n\n    }\n    public void swap(String[] arr, int i, int j) {\n        String temp = arr[i];\n        arr[i] = arr[j];\n        arr[j] = temp;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def minNumber(self, nums: List[int])->str:\n\n        arr = [''] * len(nums)\n        # 解决大数问题，将数字转换为字符串\n        for i in range(0, len(nums)):\n            arr[i] = str(nums[i])\n\n        self.quickSort(arr, 0, len(arr) - 1)\n        s = ''\n        for x in arr:\n            s += x\n        return s\n\n    def quickSort(self, arr: List[str], left: int, right: int):\n        if left >= right:\n            return\n        low = left\n        high = right\n        i = low + 1\n        pivot = arr[low]\n\n        while i <= high:\n            # 比较大小\n            if int(pivot + arr[i]) > int(arr[i] + pivot):\n                self.swap(arr, i, low)\n                i += 1\n                low += 1\n            elif int(pivot + arr[i]) < int(arr[i] + pivot):\n                self.swap(arr, i, high)\n                high -= 1\n            else:\n                i += 1\n\n        self.quickSort(arr, left, low - 1)\n        self.quickSort(arr, high + 1, right)\n\n    def swap(self, arr: List[str], i: int, j: int):\n        temp = arr[i]\n        arr[i] = arr[j]\n        arr[j] = temp\n```\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/基数排序.md",
    "content": ""
  },
  {
    "path": "animation-simulation/数据结构和算法/堆排序.md",
    "content": "### **堆排序**\n\n> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n> 刷题网站\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n说堆排序之前，我们先简单了解一些什么是堆？堆这种数据结构应用场景非常多，所以我们需要熟练掌握呀！\n\n那我们了解堆之前，先来简单了解下，什么是完全二叉树？\n\n我们来看下百度百科的定义，完全二叉树：叶子结点只能出现在最下层和次下层，且最下层的叶子结点集中在树的左部。\n\n哦！我们可以这样理解，除了最后一层，其他层的节点个数都是满的，而且最后一层的叶子节点必须靠左。\n\n下面我们来看一下这几个例子\n\n![微信图片_20210316124303](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信图片_20210316124303.1lo4nr3xhrwg.jpg)\n\n上面的几个例子中，（1）（4）为完全二叉树，（2）（3）不是完全二叉树，通过上面的几个例子，我们了解了什么是完全二叉树，\n\n那么堆到底是什么呢？\n\n下面我们来看一下二叉堆的要求\n\n（1）必须是完全二叉树\n\n（2）二叉堆中的每一个节点，都必须大于等于（或小于等于）其子树中每个节点的值。\n\n若是每个节点大于等于子树中的每个节点，我们称之为大顶堆，小于等于子树中的每个节点，我们则称之为小顶堆。见下图\n\n下面我们再来看一下二叉堆的具体例子。\n\n![堆](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210223221833.6slujxq1cb40.png)\n\n上图则为大顶堆和小顶堆，我们再来回顾一下堆的要求，看下是否符合\n\n（1）必须是完全二叉树\n\n（2）堆中的每一个节点，都必须大于等于（或小于等于）其子树中每个节点的值。\n\n好啦，到这里我们已经完全掌握二叉堆了，那么二叉堆又是怎么存储的呢？因为堆是完全二叉树，所以我们完全可以用数组存储。具体思想见下图，我们仅仅按照顺序将节点存入数组即可，我们通过小顶堆进行演示。\n\n注：我们是从下标 1 开始存储的，这样能省略一些计算，下文中我们将二叉堆简称为堆\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210223223621.3juf4t4hc9a0.png)\n\n我们来看一下为什么我们可以用数组来存储堆呢？\n\n我们首先看根节点，也就是值为 1 的节点，它在数组中的下标为 1 ,它的左子节点，也就是值为 4 的节点，此时索引为 2，右子节点也就是值为 2 的节点，它的索引为 3。\n\n我们发现其中的关系了吗？\n\n数组中，某节点（非叶子节点）的下标为 i , 那么其**左子节点下标为 2\\*i** （这里可以直接通过相乘得到左孩子，如果从 0 开始存，需要 2*i+1 才行）, 右子节点为 2*i+1**，**其父节点为 i/2 。既然我们完全可以根据索引找到某节点的 **左子节点** 和 **右子节点**，那么我们用数组存储是完全没有问题的。\n\n好啦，我们知道了什么是堆和如何用数组存储堆，那我们如何完成堆排序呢？\n\n堆排序其实主要有两个步骤\n\n- 建堆\n- 排序\n\n下面我们先来了解下建堆\n\n我们刚才说了用数组来存储大顶（小顶）堆，此时的元素已经满足某节点大于等于（或小于等于）子树节点，但是随机给我们一个数组，此时并不一定满足上诉要求，所以我们需要调整数组，使其满足大顶堆或小顶堆的要求。这个就是堆化，也可以称其为建堆。\n\n建堆我们这里提出两种方法，利用上浮操作，也就是不断插入元素进行建堆，另一种是利用下沉操作，遍历父节点，不断将其下沉，进行建堆，我们一起来看吧。\n\n我们先来说下第一种建堆方式\n\n**利用上浮操作建堆**\n\n说之前我们先来了解下，如何往已经建好的堆里，插入新的元素，我们直接看例子吧，一下就懂啦。\n\n![](https://img-blog.csdnimg.cn/20210317193005203.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70#pic_center)\n\n假设让我们插入新的元素 1 （绿色节点），我们发现此时 1 小于其父节点 的值 7 ，并不遵守小顶堆的规则，那我们则需要移动元素 1 。让 1 与 7 交换，（如果新插入元素大于父节点的值，则说明插入新节点后仍满足小顶堆规则，无需交换）。\n\n之前我们说过，我们可以用数组保存堆，并且可以通过 i/2 得到其父节点的值，那么此时我们就明白怎么做啦。\n\n![](https://img-blog.csdnimg.cn/20210317192914891.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70#pic_center)\n\n将插入节点与其父节点，交换。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210317192922435.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70#pic_center)\n\n交换之后，我们继续将新插入元素，也就是 1 与其父节点比较，如果大于其父节点，则无需交换，循环结束。若小于则需要继续交换，直到 1 到达适合他的地方。大家是不是已经直到该怎么做啦！下面我们直接看动图吧。\n\n![](https://img-blog.csdnimg.cn/20210317193205782.gif#pic_center)\n\n看完动图是不是就妥了，其实很简单，我们只需将新加入元素与其父节点比较，判断是否小于堆顶元素（小顶堆），如果小于则进行交换，（让更小的节点为父节点）直到符合堆的规则位置，大顶堆则相反。\n\n**我们发现，我们新插入的元素是不是一层层的上浮，直到找到属于自己的位置，我们将这个操作称之为上浮操作。**\n\n那我们知道了上浮，岂不是就可以实现建堆了？是的，我们则可以依次遍历数组，就好比不断往堆中插入新元素，直至遍历结束，这样我们就完成了建堆，这种方法当然是可以的。\n\n我们一起来看一下上浮操作代码。\n\nJava Code:\n\n```java\npublic void swim (int[] nums, int index) {\n    while (index > 1 && nums[index/2] > nums[index]) {\n        swap(index/2,index);//交换\n        index = index/2;\n    }\n}\n```\n\nPython Code:\n\n```python\ndef swim(nums: int, index: int):\n    while index > 1 and nums[int(index/2)] > nums[index]:\n        swap(int(index/2), index)# 交换\n        index = int(index/2)\n```\n\n既然利用上浮操作建堆已经搞懂啦，那么我们再来了解一下，利用下沉操作建堆吧，也很容易理解。\n\n给我们一个无序数组（不满足堆的要求），见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210309143155.2fhvnp8lqe4g.png)\n\n我们发现，7 位于堆顶，但是此时并不满足小顶堆的要求，我们需要把 7 放到属于它的位置，我们应该怎么做呢？\n\n废话不多说，我们先来看视频模拟，看完保准可以懂\n\n![](https://img-blog.csdnimg.cn/20210317193217911.gif#pic_center)\n\n看完视频是不是懂个大概了，但是不知道大家有没有注意到这个地方。为什么 7 第一次与其左孩子节点 2 交换，第二次与右孩子节点 3 交换。见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210309145953.1byz4zq0cx6o.png)\n\n其实很容易理解，我们需要与孩子节点中最小的那个交换，因为我们需要交换后，父节点小于两个孩子节点，如果我们第一步，7 与 5 进行交换的话，则同样不能满足小顶堆。\n\n那我们怎么判断节点找到属于它的位置了呢？主要有两个情况\n\n- 待下沉元素小于（大于）两个子节点，此时符合堆的规则，无序下沉，例如上图中的 6\n- 下沉为叶子节点，此时没有子节点，例如 7 下沉到最后变成了叶子节点。\n\n我们将上面的操作称之为下沉操作。\n\n这时我们又有疑问了，下沉操作我懂了，但是这跟建堆有个锤子关系啊！\n\n不要急，我们继续来看视频，这次我们通过下沉操作建个大顶堆。\n\n> **初始数组 [8,5,7,9,2,10,1,4,6,3]**\n\n![](https://img-blog.csdnimg.cn/20210317193229153.gif#pic_center)\n\n我们一起来拆解一下视频，我们只需要从最后一个非叶子节点开始，依次执行下沉操作。执行完毕后我们就能够完成堆化。是不是一下就懂了呀。\n\n好啦我们一起看哈下沉操作的代码吧。\n\nJava Code:\n\n```java\npublic void sink (int[] nums, int index,int len) {\n        while (true) {\n            //获取子节点\n            int j = 2 * index;\n            if (j < len-1 && nums[j] < nums[j+1]) {\n                j++;\n            }\n            //交换操作，父节点下沉，与最大的孩子节点交换\n            if (j < len && nums[index] < nums[j]) {\n                swap(nums,index,j);\n            } else {\n                break;\n            }\n            //继续下沉\n            index = j;\n        }\n    }\n```\n\nPython Code:\n\n```python\ndef sink(nums: list, index: int, len: int):\n    while True:\n        # 获取子节点\n        j = 2 * index\n        if j < len-1 and nums[j] < nums[j+1]:\n            j += 1\n        # 交换操作，父节点下沉，与最大的孩子节点交换\n        if j < len and nums[index] < nums[j]:\n            swap(nums, index, j)\n        else:\n            break\n        # 继续下沉\n        index = j\n```\n\n好啦，两种建堆方式我们都已经了解啦，那么我们如何进行排序呢？\n\n了解排序之前我们先来，看一下如何删除堆顶元素，我们需要保证的是，删除堆顶元素后，其他元素仍能满足堆的要求，我们思考一下如何实现呢？见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210309200153.3jx6dvweliq0.png)\n\n假设我们想要去除堆顶的 11 ，那我们则需要将其与堆的最后一个节点交换也就是 2 ，2 然后再执行下沉操作，执行完毕后仍能满足堆的要求，见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210309200301.5zqydpf44mo0.png)\n\n好啦，其实你已经学会如何排序啦！你不信？那我给你放视频\n\n![](https://img-blog.csdnimg.cn/20210317193246980.gif#pic_center)\n\n好啦，大家是不是已经搞懂啦，下面我们总结一下堆排序的具体执行过程\n\n1.建堆，通过下沉操作建堆效率更高，具体过程是，找到最后一个非叶子节点，然后从后往前遍历执行下沉操作。\n\n2.排序，将堆顶元素（代表最大元素）与最后一个元素交换，然后新的堆顶元素进行下沉操作，遍历执行上诉操作，则可以完成排序。\n\n好啦，下面我们一起看代码吧\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n\n        int len = nums.length;\n        int[] a = new int[len + 1];\n\n        for (int i = 0; i < nums.length; ++i) {\n            a[i+1] = nums[i];\n        }\n        //下沉建堆\n        for (int i = len/2; i >= 1; --i) {\n            sink(a,i,len);\n        }\n\n        int k = len;\n        //排序\n        while (k > 1) {\n            swap(a,1,k--);\n            sink(a,1,k);\n        }\n        for (int i = 1; i < len+1; ++i) {\n            nums[i-1] = a[i];\n        }\n        return nums;\n    }\n    public void sink (int[] nums, int k,int end) {\n        //下沉\n        while (2 * k <= end) {\n            int j = 2 * k;\n            //找出子节点中最大或最小的那个\n            if (j + 1 <= end && nums[j + 1] > nums[j]) {\n                j++;\n            }\n            if (nums[j] > nums[k]) {\n                swap(nums, j, k);\n            } else {\n                break;\n            }\n            k = j;\n        }\n    }\n    public void swap (int nums[], int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n\n}\n```\n\nPython Code:\n\n```python\ndef sortArray(nums: list)->list:\n    leng = len(nums)\n    a = [0] + nums\n    # 下沉建堆\n    for i in range(int(leng / 2), 0, -1):\n        sink(a, i, leng)\n\n    k = leng\n    # 排序\n    while k > 1:\n        swap(a, 1, k)\n        k -= 1\n        sink(a, 1, k)\n\n    for i in range(1, leng + 1):\n        nums[i - 1] = a[i]\n    return nums\n\ndef swap(nums: list, i: int, j: int):\n    temp = nums[i]\n    nums[i] = nums[j]\n    nums[j] = temp\n\ndef sink(nums: list, k: int, end: int):\n    while 2 * k <= end:\n        j = 2 * k\n        if j + 1 <= end and nums[j + 1] > nums[j]:\n            j += 1\n        if nums[j] > nums[k]:\n            swap(nums, j, k)\n        else:\n            break\n        k = j\n```\n\n好啦，堆排序我们就到这里啦，是不是搞定啦，总的来说堆排序比其他排序算法稍微难理解一些，重点就是建堆，而且应用比较广泛，大家记得打卡呀。\n\n好啦，我们再来分析一下堆排序的时间复杂度、空间复杂度以及稳定性。\n\n**堆排序时间复杂度分析**\n\n因为我们建堆的时间复杂度为 O(n），排序过程的时间复杂度为 O(nlogn),所以总的空间复杂度为 O(nlogn)\n\n**堆排序空间复杂度分析**\n\n这里需要注意，我们上面的描述过程中，为了更直观的描述，空出数组的第一位，这样我们就可以通过 i _ 2 和 i _ 2+1 来求得左孩子节点和右孩子节点 。我们也可以根据 i _ 2 + 1 和 i _ 2 + 2 来获取孩子节点，这样则不需要临时数组来处理原数组，将所有元素后移一位，所以堆排序的空间复杂度为 O(1),是原地排序算法。\n\n**堆排序稳定性分析**\n\n堆排序不是稳定的排序算法，在排序的过程，我们会将堆的最后一个节点跟堆顶节点交换，改变相同元素的原始相对位置。\n\n最后我们来比较一下我们快速排序和堆排序\n\n1.对于快速排序来说，数据是顺序访问的。而对于堆排序来说，数据是跳着访问的。这样对 CPU 缓存是不友好的\n\n2.相同的的数据，排序过程中，堆排序的数据交换次数要多于快速排序。\n\n所以上面两条也就说明了在实际开发中，堆排序的性能不如快速排序性能好。\n\n好啦，今天的内容就到这里啦，咱们下期见。\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/字符串匹配算法.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n> 为保证代码严谨性，文中所有代码均在 leetcode 刷题网站 AC ，大家可以放心食用。\n\n皇上生辰之际，举国同庆，袁记菜馆作为天下第一饭店，所以被选为这次庆典的菜品供应方，这次庆典对于袁记菜馆是一项前所未有的挑战，毕竟是第一次给皇上庆祝生辰，稍有不慎就是掉脑袋的大罪，整个袁记菜馆内都在紧张的布置着。此时突然有一个店小二慌慌张张跑到袁厨面前汇报，到底发生了什么事，让店小二如此慌张呢？\n\n袁记菜馆内\n\n店小二：不好了不好了，掌柜的，出大事了。\n\n袁厨：发生什么事了，慢慢说，如此慌张，成何体统。（开店开久了，架子出来了哈）\n\n店小二：皇上按照咱们菜单点了 666 道菜，但是咱们做西湖醋鱼的师傅请假回家结婚了，不知道皇上有没有点这道菜，如果点了这道菜，咱们做不出来，那咱们店可就完了啊。\n\n（袁厨听了之后，吓得一屁股坐地上了，缓了半天说道）\n\n袁厨：别说那么多了，快给我找找皇上点的菜里面，有没有这道菜！\n\n找了很久，并且核对了很多遍，最后确认皇上没有点这道菜。菜馆内的人都松了一口气\n\n通过上面的一个例子，让我们简单了解了字符串匹配。\n\n字符串匹配：设 S 和 T 是给定的两个串，在主串 S 中找到模式串 T 的过程称为字符串匹配，如果在主串 S 中找到 模式串 T ，则称匹配成功，函数返回 T 在 S 中首次出现的位置，否则匹配不成功，返回 -1。\n\n例：\n\n![字符串匹配](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/字符串匹配.3q9wqbh8ws40.png)\n\n在上图中，我们试图找到模式 T = baab,在主串 S = abcabaabcabac 中第一次出现的位置，即为红色阴影部分， T 第一次在 S 中出现的位置下标为 4 （ 字符串的首位下标是 0 ），所以返回 4。如果模式串 T 没有在主串 S 中出现，则返回 -1。\n\n解决上面问题的算法我们称之为字符串匹配算法，今天我们来介绍三种字符串匹配算法，大家记得打卡呀，说不准面试的时候就问到啦。\n\n## BF 算法（Brute Force）\n\n这个算法很容易理解，就是我们将模式串和主串进行比较，一致时则继续比较下一字符，直到比较完整个模式串。不一致时则将模式串后移一位，重新从模式串的首位开始对比，重复刚才的步骤下面我们看下这个方法的动图解析，看完肯定一下就能搞懂啦。\n\n视频详解\n\n**因为不可以放置视频，所以想看视频的同学，可以去看公众号原文，那里有视频**\n\n通过上面的代码是不是一下就将这个算法搞懂啦，下面我们用这个算法来解决下面这个经典题目吧。\n\n### leetcdoe 28. 实现 strStr()\n\n#### 题目描述\n\n给定一个 haystack 字符串和一个 needle 字符串，在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从 0 开始)。如果不存在，则返回 -1。\n\n示例 1:\n\n> 输入: haystack = \"hello\", needle = \"ll\"\n> 输出: 2\n\n示例 2:\n\n> 输入: haystack = \"aaaaa\", needle = \"bba\"\n> 输出: -1\n\n#### 题目解析\n\n其实这个题目很容易理解，但是我们需要注意的是一下几点，比如我们的模式串为 0 时，应该返回什么，我们的模式串长度大于主串长度时，应该返回什么，也是我们需要注意的地方。下面我们来看一下题目代码吧。\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int strStr(String haystack, String needle) {\n        int haylen = haystack.length();\n        int needlen = needle.length();\n        //特殊情况\n        if (haylen < needlen) {\n            return -1;\n        }\n        if (needlen == 0) {\n            return 0;\n        }\n        //主串\n        for (int i = 0; i < haylen - needlen + 1; ++i) {\n            int j;\n            //模式串\n            for (j = 0; j < needlen; j++) {\n                //不符合的情况，直接跳出，主串指针后移一位\n                if (haystack.charAt(i+j) != needle.charAt(j)) {\n                    break;\n                }\n            }\n            //匹配成功\n            if (j == needlen) {\n                return i;\n            }\n\n        }\n        return -1;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def strStr(self, haystack: str, needle: str)->int:\n        haylen = len(haystack)\n        needlen = len(needle)\n        # 特殊情况\n        if haylen < needlen:\n            return -1\n        if needlen == 0:\n            return 0\n        # 主串\n        for i in range(0, haylen - needlen + 1):\n            # 模式串\n            j = 0\n            while j < needlen:\n                if haystack[i + j] != needle[j]:\n                    break\n                j += 1\n            # 匹配成功\n            if j == needlen:\n                return i\n        return -1\n```\n\n我们看一下 BF 算法的另一种算法（显示回退），其实原理一样，就是对代码进行了一下修改，只要是看完咱们的动图，这个也能够一下就能看懂，大家可以结合下面代码中的注释和动图进行理解。\n\nJava Code:\n\n```java\nclass Solution {\n    public int strStr(String haystack, String needle) {\n        //i代表主串指针，j模式串\n        int i,j;\n        //主串长度和模式串长度\n        int halen = haystack.length();\n        int nelen = needle.length();\n        //循环条件，这里只有 i 增长\n        for (i = 0 , j = 0; i < halen && j < nelen; ++i) {\n            //相同时，则移动 j 指针\n            if (haystack.charAt(i) == needle.charAt(j)) {\n                ++j;\n            } else {\n                //不匹配时，将 j 重新指向模式串的头部，将 i 本次匹配的开始位置的下一字符\n                i -= j;\n                j = 0;\n            }\n        }\n        //查询成功时返回索引，查询失败时返回 -1；\n        int renum = j == nelen ? i - nelen : -1;\n        return renum;\n\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def strStr(self, haystack: str, needle: str)->int:\n        # i代表主串指针，j模式串\n        i = 0\n        j = 0\n        # 主串长度和模式串长度\n        halen = len(haystack)\n        nelen = len(needle)\n        # 循环条件，这里只有 i 增长\n        while i < halen and j < nelen:\n            # 相同时，则移动 j 指针\n            if haystack[i] == needle[j]:\n                j += 1\n            else:\n                # 不匹配时，将 j 重新只想模式串的头部，将 i 本次匹配的开始位置的下一字符\n                i -= j\n                j = 0\n            i += 1\n            # 查询成功时返回索引，查询失败时返回 -1\n            renum = i - nelen if j == nelen else -1\n        return renum\n```\n\n## BM 算法(Boyer-Moore)\n\n我们刚才说过了 BF 算法，但是 BF 算法是有缺陷的，比如我们下面这种情况\n\n![BF第一次](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/BF第一次.2qo0876qvs80.png)\n\n如上图所示，如果我们利用 BF 算法，遇到不匹配字符时，每次右移一位模式串，再重新从头进行匹配，我们观察一下，我们的模式串 abcdex 中每个字符都不一样，但是我们第一次进行字符串匹配时，abcde 都匹配成功，到 x 时失败，又因为模式串每位都不相同，所以我们不需要再每次右移一位，再重新比较，我们可以直接跳过某些步骤。如下图\n\n![BM2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/BM2.141fhslg6vek.png)\n\n我们可以跳过其中某些步骤，直接到下面这个步骤。那我们是依据什么原则呢？\n\n![BM3](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/BM3.7iamevkxf0g0.png)\n\n### 坏字符规则\n\n我们之前的 BF 算法是从前往后进行比较 ，BM 算法是从后往前进行比较，我们来看一下具体过程，我们还是利用上面的例子。\n\n![BM4](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/BM4.2mayfaccj3i0.png)\n\nBM 算法是从后往前进行比较，此时我们发现比较的第一个字符就不匹配，我们将**主串**这个字符称之为**坏字符**，也就是 f ,我们发现坏字符之后，模式串 T 中查找是否含有该字符（f），我们发现并不存在 f，此时我们只需将模式串右移到坏字符的后面一位即可。如下图\n\n![BM5](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/BM5.31j3sja7vsq0.png)\n\n那我们在模式串中找到坏字符该怎么办呢？\n\n![含有坏字符](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/含有坏字符.10z8yxka8z8g.png)\n\n此时我们的坏字符为 f ,我们在模式串中，查找发现含有坏字符 f,我们则需要移动模式串 T ,将模式串中的 f 和坏字符对齐。见下图。\n\n![坏字符移动](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/坏字符移动.kl5k3nnzkcg.png)\n\n然后我们继续从右往左进行比较，发现 d 为坏字符，则需要将模式串中的 d 和坏字符对齐。\n\n![换字符对其2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/换字符对其2.4xdb38am9e60.png)\n\n![坏字符原则](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/坏字符原则.781vhv3vm280.png)\n\n那么我们在来思考一下这种情况，那就是模式串中含有多个坏字符怎么办呢？\n\n![两个坏字符](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/两个坏字符.1a6hcs8ildkw.png)\n\n那么我们为什么要让**最靠右的对应元素与坏字符匹配**呢？如果上面的例子我们没有按照这条规则看下会产生什么问题。\n\n![坏字符匹配不按规则](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/坏字符匹配不按规则.1y45278xg1vk.png)\n\n如果没有按照我们上述规则，则会**漏掉我们的真正匹配**。我们的主串中是**含有 babac** 的，但是却**没有匹配成功**，所以应该遵守**最靠右的对应字符与坏字符相对**的规则。\n\n我们上面一共介绍了三种移动情况，分别是下方的模式串中没有发现与坏字符对应的字符，发现一个对应字符，发现两个。这三种情况我们分别移动不同的位数，那我们是根据依据什么来决定移动位数的呢？下面我们给图中的字符加上下标。见下图\n\n![坏字符移动规则](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/坏字符移动规则.48oh1msdypy0.png)\n\n下面我们来考虑一下这种情况。\n\n![换字符bug](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/换字符bug.24av6jslzh40.png)\n\n此时这种情况肯定是不行的，不往右移动，甚至还有可能左移，那么我们有没有什么办法解决这个问题呢？继续往下看吧。\n\n### 好后缀规则\n\n好后缀其实也很容易理解，我们之前说过 BM 算法是从右往左进行比较，下面我们来看下面这个例子。\n\n![好后缀1](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/好后缀1.4j88yw6hecu0.png)\n\n这里如果我们按照坏字符进行移动是不合理的，这时我们可以使用好后缀规则，那么什么是好后缀呢？\n\nBM 算法是从右往左进行比较，发现坏字符的时候此时 cac 已经匹配成功，在红色阴影处发现坏字符。此时已经匹配成功的 cac 则为我们的好后缀，此时我们拿它在模式串中查找，如果找到了另一个和好后缀相匹配的串，那我们就将另一个和**好后缀相匹配**的串 ，滑到和好后缀对齐的位置。\n\n是不是感觉有点拗口，没关系，我们看下图，红色代表坏字符，绿色代表好后缀\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/好后缀对其.5wf80nidao80.png)\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/好后缀中间.7b6m6ki25l00.png)\n\n上面那种情况搞懂了，但是我们思考一下下面这种情况\n\n![比较](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/比较.4m9ci1x1c1e0.png)\n\n上面我们说到了，如果在模式串的**头部**没有发现好后缀，发现好后缀的子串也可以。但是为什么要强调这个头部呢？\n\n我们下面来看一下这种情况\n\n![不完全重合](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/不完全重合.6oayqd0dre00.png)\n\n但是当我们在头部发现好后缀的子串时，是什么情况呢？\n\n![好后缀ok](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/好后缀ok.131zefgf1d1s.png)\n\n下面我们通过动图来看一下某一例子的具体的执行过程\n\n视频\n\n说到这里，坏字符和好后缀规则就算说完了，坏字符很容易理解，我们对好后缀总结一下\n\n1.如果模式串**含有好后缀**，无论是中间还是头部可以按照规则进行移动。如果好后缀在模式串中出现多次，则以**最右侧的好后缀**为基准。\n\n2.如果模式串**头部含有**好后缀子串则可以按照规则进行移动，中间部分含有好后缀子串则不可以。\n\n3.如果在模式串尾部就出现不匹配的情况，即不存在好后缀时，则根据坏字符进行移动，这里有的文章没有提到，是个需要特别注意的地方，我是在这个论文里找到答案的，感兴趣的同学可以看下。\n\n> Boyer R S，Moore J S. A fast string searching algorithm［J］. Communications of the ACM，1977，10： 762-772.\n\n之前我们刚开始说坏字符的时候，是不是有可能会出现负值的情况，即往左移动的情况，所以我们为了解决这个问题，我们可以分别计算好后缀和坏字符往后滑动的位数**（好后缀不为 0 的情况）**，然后取两个数中最大的，作为模式串往后滑动的位数。\n\n![五好后缀](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/五好后缀.6wvqxa4um040.png)\n\n这破图画起来是真费劲啊。下面我们来看一下算法代码，代码有点长，我都标上了注释也在网站上 AC 了，如果各位感兴趣可以看一下，不感兴趣理解坏字符和好后缀规则即可。可以直接跳到 KMP 部分\n\nJava Code:\n\n```java\nclass Solution {\n    public int strStr(String haystack, String needle) {\n        char[] hay = haystack.toCharArray();\n        char[] need = needle.toCharArray();\n        int haylen = haystack.length();\n        int needlen = need.length;\n        return bm(hay,haylen,need,needlen);\n    }\n    //用来求坏字符情况下移动位数\n    private static void badChar(char[] b, int m, int[] bc) {\n        //初始化\n        for (int i = 0; i < 256; ++i) {\n            bc[i] = -1;\n        }\n        //m 代表模式串的长度，如果有两个 a,则后面那个会覆盖前面那个\n        for (int i = 0; i < m; ++i) {\n            int ascii = (int)b[i];\n            bc[ascii] = i;//下标\n        }\n    }\n    //用来求好后缀条件下的移动位数\n    private static void goodSuffix (char[] b, int m, int[] suffix,boolean[] prefix) {\n        //初始化\n        for (int i = 0; i < m; ++i) {\n            suffix[i] = -1;\n            prefix[i] = false;\n        }\n        for (int i = 0; i < m - 1; ++i) {\n            int j = i;\n            int k = 0;\n            while (j >= 0 && b[j] == b[m-1-k]) {\n                --j;\n                ++k;\n                suffix[k] = j + 1;\n            }\n            if (j == -1) prefix[k] = true;\n        }\n    }\n    public static int bm (char[] a, int n, char[] b, int m) {\n\n        int[] bc = new int[256];//创建一个数组用来保存最右边字符的下标\n        badChar(b,m,bc);\n        //用来保存各种长度好后缀的最右位置的数组\n        int[] suffix_index = new int[m];\n        //判断是否是头部，如果是头部则true\n        boolean[] ispre = new boolean[m];\n        goodSuffix(b,m,suffix_index,ispre);\n        int i = 0;//第一个匹配字符\n        //注意结束条件\n        while (i <= n-m) {\n            int j;\n            //从后往前匹配，匹配失败，找到坏字符\n            for (j = m - 1; j >= 0; --j) {\n                if (a[i+j] != b[j]) break;\n            }\n            //模式串遍历完毕，匹配成功\n            if (j < 0) {\n                return i;\n            }\n            //下面为匹配失败时，如何处理\n            //求出坏字符规则下移动的位数，就是我们坏字符下标减最右边的下标\n            int x = j - bc[(int)a[i+j]];\n            int y = 0;\n            //好后缀情况，求出好后缀情况下的移动位数,如果不含有好后缀的话，则按照坏字符来\n            if (y < m-1 && m - 1 - j > 0) {\n                y = move(j, m, suffix_index,ispre);\n            }\n            //移动\n            i = i + Math.max(x,y);\n\n        }\n        return -1;\n    }\n    // j代表坏字符的下标\n    private static int move (int j, int m, int[] suffix_index, boolean[] ispre) {\n        //好后缀长度\n        int k = m - 1 - j;\n        //如果含有长度为 k 的好后缀，返回移动位数，\n        if (suffix_index[k] != -1) return j - suffix_index[k] + 1;\n        //找头部为好后缀子串的最大长度，从长度最大的子串开始\n        for (int r = j + 2; r <= m-1; ++r) {\n            //如果是头部\n            if (ispre[m-r] == true) {\n                return r;\n            }\n        }\n        //如果没有发现好后缀匹配的串，或者头部为好后缀子串，则移动到 m 位，也就是匹配串的长度\n        return m;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def strStr(self, haystack: str, needle: str)->int:\n        haylen = len(haystack)\n        needlen = len(needle)\n        return self.bm(haystack, haylen, needle, needlen)\n\n    # 用来求坏字符情况下移动位数\n    def badChar(self, b: str, m: int, bc: List[int]):\n        # 初始化\n        for i in range(0, 256):\n            bc[i] = -1\n        # m 代表模式串的长度，如果有两个 a，则后面那个会覆盖前面那个\n        for i in range(0, m,):\n            ascii = ord(b[i])\n            bc[ascii] = i# 下标\n\n    # 用来求好后缀条件下的移动位数\n    def goodSuffix(self, b: str, m: int, suffix: List[int], prefix: List[bool]):\n        # 初始化\n        for i in range(0, m):\n            suffix[i] = -1\n            prefix[i] = False\n        for i in range(0, m - 1):\n            j = i\n            k = 0\n            while j >= 0 and b[j] == b[m - 1 - k]:\n                j -= 1\n                k += 1\n                suffix[k] = j + 1\n            if j == -1:\n                prefix[k] = True\n\n    def bm(self, a: str, n: int, b: str, m: int)->int:\n        bc = [0] * 256# 创建一个数组用来保存最右边字符的下标\n        self.badChar(b, m, bc)\n        # 用来保存各种长度好后缀的最右位置的数组\n        suffix_index = [0] * m\n        # 判断是否是头部，如果是头部则True\n        ispre = [False] * m\n        self.goodSuffix(b, m, suffix_index, ispre)\n        i = 0# 第一个匹配字符\n        # 注意结束条件\n        while i <= n - m:\n            # 从后往前匹配，匹配失败，找到坏字符\n            j = m - 1\n            while j >= 0:\n                if a[i + j] != b[j]:\n                    break\n                j -= 1\n            # 模式串遍历完毕，匹配成功\n            if j < 0:\n                return i\n            # 下面为匹配失败时，如何处理\n            # 求出坏字符规则下移动的位数，就是我们坏字符下标减最右边的下标\n            x = j - bc[ord(a[i + j])]\n            y = 0\n            # 好后缀情况，求出好后缀情况下的移动位数,如果不含有好后缀的话，则按照坏字符来\n            if y < m - 1 and m - 1 - j > 0:\n                y = self.move(j, m, suffix_index, ispre)\n            # 移动\n            i += max(x, y)\n        return -1\n\n    # j代表坏字符的下标\n    def move(j: int, m: int, suffix_index: List[int], ispre: List[bool])->int:\n        # 好后缀长度\n        k = m - 1 - j\n        # 如果含有长度为 k 的好后缀，返回移动位数\n        if suffix_index[k] != -1:\n            return j - suffix_index[k] + 1\n        # 找头部为好后缀子串的最大长度，从长度最大的子串开始\n        for r in range(j + 2, m):\n            # //如果是头部\n            if ispre[m - r] == True:\n                return r\n        # 如果没有发现好后缀匹配的串，或者头部为好后缀子串，则移动到 m 位，也就是匹配串的长度\n        return m\n```\n\n我们来理解一下我们代码中用到的两个数组，因为两个规则的移动位数，只与模式串有关，与主串无关，所以我们可以提前求出每种情况的移动情况，保存到数组中。\n\n![头缀函数](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/头缀函数.145da63ig3s0.png)\n\n## KMP 算法（Knuth-Morris-Pratt）\n\n我们刚才讲了 BM 算法，虽然不是特别容易理解，但是如果你用心看的话肯定可以看懂的，我们再来看一个新的算法，这个算法是考研时必考的算法。实际上 BM 和 KMP 算法的本质是一样的，你理解了 BM 再来理解 KMP 那就是分分钟的事啦。\n\n我们先来看一个实例\n\n视频\n\n为了让读者更容易理解，我们将指针移动改成了模式串移动，两者相对与主串的移动是一致的，重新比较时都是从指针位置继续比较。\n\n通过上面的实例是不是很快就能理解 KMP 算法的思想了，但是 KMP 的难点不是在这里，不过多思考，认真看理解起来也是很轻松的。\n\n在上面的例子中我们提到了一个名词，**最长公共前后缀**，这个是什么意思呢？下面我们通过一个较简单的例子进行描述。\n\n![KMP例子](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/KMP例子.1uirbimk5fcw.png)\n\n此时我们在红色阴影处匹配失败，绿色为匹配成功部分，则我们观察匹配成功的部分。\n\n我们来看一下匹配成功部分的所有前缀\n\n![公共前后缀](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/公共前后缀.3wib411usww0.png)\n\n我们的最长公共前后缀如下图，则我们需要这样移动\n\n![原理](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/原理.bghc3ecm4z4.png)\n\n好啦，看完上面的图，KMP 的核心原理已经基本搞定了，但是我们现在的问题是，我们应该怎么才能知道他的最长公共前后缀的长度是多少呢？怎么知道移动多少位呢？\n\n刚才我们在 BM 中说到，我们移动位数跟主串无关，只跟模式串有关，跟我们的 bc,suffix,prefix 数组的值有关，我们通过这些数组就可以知道我们每次移动多少位啦，其实 KMP 也有一个数组，这个数组叫做 next 数组，那么这个 next 数组存的是什么呢？\n\nnext 数组存的咱们最长公共前后缀中，前缀的结尾字符下标。是不是感觉有点别扭，我们通过一个例子进行说明。\n\n![next数组](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/next数组.3nir7pgcs9c0.png)\n\n我们知道 next 数组之后，我们的 KMP 算法实现起来就很容易啦，另外我们看一下 next 数组到底是干什么用的。\n\n![KMP1](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/KMP1.j74ujxjuq1c.png)\n\n![kmp2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/kmp2.6jx846nmyd00.png)\n\n剩下的就不用说啦，完全一致啦，咱们将上面这个例子，翻译成和咱们开头对应的动画大家看一下。\n\n**因为不可以放置视频，所以想看视频的同学，可以去看公众号原文，那里有视频**\n\n下面我们看一下代码，标有详细注释，大家认真看呀。\n\n**注：很多教科书的 next 数组表示方式不一致，理解即可**\n\nJava Code:\n\n```java\nclass Solution {\n    public int strStr(String haystack, String needle) {\n        //两种特殊情况\n        if (needle.length() == 0) {\n            return 0;\n        }\n        if (haystack.length() == 0) {\n            return -1;\n        }\n        // char 数组\n        char[] hasyarr = haystack.toCharArray();\n        char[] nearr = needle.toCharArray();\n        //长度\n        int halen = hasyarr.length;\n        int nelen = nearr.length;\n        //返回下标\n        return kmp(hasyarr,halen,nearr,nelen);\n\n    }\n    public int kmp (char[] hasyarr, int halen, char[] nearr, int nelen) {\n        //获取next 数组\n        int[] next = next(nearr,nelen);\n        int j = 0;\n        for (int i = 0; i < halen; ++i) {\n            //发现不匹配的字符，然后根据 next 数组移动指针，移动到最大公共前后缀的，\n            //前缀的后一位,和咱们移动模式串的含义相同\n            while (j > 0 && hasyarr[i] != nearr[j]) {\n                j = next[j - 1] + 1;\n                //超出长度时，可以直接返回不存在\n                if (nelen - j + i > halen) {\n                    return -1;\n                }\n            }\n            //如果相同就将指针同时后移一下，比较下个字符\n            if (hasyarr[i] == nearr[j]) {\n                ++j;\n            }\n            //遍历完整个模式串，返回模式串的起点下标\n            if (j == nelen) {\n                return i - nelen + 1;\n            }\n        }\n        return -1;\n    }\n    //这一块比较难懂，不想看的同学可以忽略，了解大致含义即可，或者自己调试一下，看看运行情况\n    //我会每一步都写上注释\n    public  int[] next (char[] needle,int len) {\n        //定义 next 数组\n        int[] next = new int[len];\n        // 初始化\n        next[0] = -1;\n        int k = -1;\n        for (int i = 1; i < len; ++i) {\n            //我们此时知道了 [0,i-1]的最长前后缀，但是k+1的指向的值和i不相同时，我们则需要回溯\n            //因为 next[k]就时用来记录子串的最长公共前后缀的尾坐标（即长度）\n            //就要找 k+1前一个元素在next数组里的值,即next[k+1]\n            while (k != -1 && needle[k + 1] != needle[i]) {\n                k = next[k];\n            }\n            // 相同情况，就是 k的下一位，和 i 相同时，此时我们已经知道 [0,i-1]的最长前后缀\n            //然后 k - 1 又和 i 相同，最长前后缀加1，即可\n            if (needle[k+1] == needle[i]) {\n                ++k;\n            }\n            next[i] = k;\n\n        }\n        return next;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def strStr(self, haystack: str, needle: str)->int:\n        # 两种特殊情况\n        if len(needle) == 0:\n            return 0\n        if len(haystack) == 0:\n            return -1\n        # 长度\n        halen = len(haystack)\n        nelen = len(needle)\n        # 返回下标\n        return self.kmp(haystack, halen, needle, nelen)\n\n    def kmp(self, hasyarr: str, halen: int, nearr: str, nelen: int)->int:\n        # 获取next 数组\n        next = self.next(nearr, nelen)\n        j = 0\n        for i in range(0, halen):\n            # 发现不匹配的字符，然后根据 next 数组移动指针，移动到最大公共前后缀的，\n            # 前缀的后一位,和咱们移动模式串的含义相同\n            while j > 0 and hasyarr[i] != nearr[j]:\n                j = next[j - 1] + 1\n                # 超出长度时，可以直接返回不存在\n                if nelen - j + i > halen:\n                    return -1\n            # 如果相同就将指针同时后移一下，比较下个字符\n            if hasyarr[i] == nearr[j]:\n                j += 1\n            # 遍历完整个模式串，返回模式串的起点下标\n            if j == nelen:\n                return i - nelen + 1\n        return -1\n\n    # 这一块比较难懂，不想看的同学可以忽略，了解大致含义即可，或者自己调试一下，看看运行情况\n    # 我会每一步都写上注释\n    def next(self, needle: str, len:int)->List[int]:\n        # 定义 next 数组\n        next = [0] * len\n        # 初始化\n        next[0] = -1\n        k = -1\n        for i in range(1, len):\n            # 我们此时知道了 [0,i-1]的最长前后缀，但是k+1的指向的值和i不相同时，我们则需要回溯\n            # 因为 next[k]就时用来记录子串的最长公共前后缀的尾坐标（即长度）\n            # 就要找 k+1前一个元素在next数组里的值,即next[k+1]\n            while k != -1 and needle[k + 1] != needle[i]:\n                k = next[k]\n            # 相同情况，就是 k的下一位，和 i 相同时，此时我们已经知道 [0,i-1]的最长前后缀\n            # 然后 k - 1 又和 i 相同，最长前后缀加1，即可\n            if needle[k + 1] == needle[i]:\n                k += 1\n            next[i] = k\n        return next\n```\n\n这篇文章真的写了很久很久，觉得还不错的话，就麻烦您点个赞吧，大家也可以去我的公众号看我的所有文章，每个都有动图解析，公众号：[程序厨](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/qrcode_for_gh_1f36d2ef6df9_258.5lojyphpkso0.jpg)\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/希尔排序.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### **希尔排序 （Shell's Sort）**\n\n我们在之前说过直接插入排序在记录基本有序的时候和元素较少时效率是很高的，基本有序时，只需执行少量的插入操作，就可以完成整个记录的排序工作。当元素较少时，效率也很高，就比如我们经常用的 Arrays.sort (),当元素个数少于 47 时，使用的排序算法就是直接插入排序。那么直接希尔排序和直接插入排序有什么关系呢？\n\n希尔排序是**插入排序**的一种，又称“缩小增量排序”（Diminishing Increment Sort），是直接插入排序的高级变形，其思想简单点说就是有跨度的插入排序，这个跨度会逐渐变小，直到变为 1，变为 1 时记录也就基本有序，这时用到的也就是我们之前讲的直接插入排序了。\n\n基本有序：就是小的关键字基本在前面，大的关键字基本在后面，不大不小的基本在中间。见下图。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/微信截图_20210127164642.3glch9g6oey0.png)\n\n我们已经了解了希尔排序的基本思想，下面我们通过一个绘图来描述下其执行步骤。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/2021031719420587.b27cu8pv3eo.png)\n\n先逐步分组进行粗调，在进行直接插入排序的思想就是希尔排序。我们刚才的分组跨度（4，2，1）被称为希尔排序的增量，我们上面用到的是逐步折半的增量方法，这也是在发明希尔排序时提出的一种朴素方法，被称为希尔增量，\n\n下面我们用动图模拟下使用希尔增量的希尔排序的执行过程\n\n![希尔排序](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/希尔排序.4vxwr7bkbjw0.gif)\n\n大家可能看了视频模拟，也不是特别容易写出算法代码，不过你们看到代码肯定会很熟悉滴。\n\n**希尔排序代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n        int increment = nums.length;\n        //注意看结束条件\n        while (increment > 1) {\n            //这里可以自己设置\n            increment = increment / 2;\n            //根据增量分组\n            for (int i = 0; i < increment; ++i) {\n                //这快是不是有点面熟，回去看看咱们的插入排序\n                for (int j = i + increment; j < nums.length; j += increment) {\n                    int temp = nums[j];\n                    int k;\n                    for (k = j - increment; k >= 0; k -= increment) {\n                        if (temp < nums[k]) {\n                            nums[k+increment] = nums[k];\n                            continue;\n                        }\n                        break;\n                    }\n                    nums[k+increment] = temp;\n                }\n            }\n        }\n        return nums;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortArray(self, nums: List[int])->List[int]:\n        increment = len(nums)\n        # 注意看结束条件\n        while increment > 1:\n            # 这里可以自己设置\n            increment = int(increment / 2)\n            # 根据增量分组\n            for i in range(0, increment):\n                # 这块是不是有点面熟，回去看看咱们的插入排序\n                for j in range(i + increment, len(nums), increment):\n                    temp = nums[j]\n                    k = j - increment\n                    while k >= 0:\n                        if temp < nums[k]:\n                            nums[k + increment] = nums[k]\n                            k -= increment\n                            continue\n                        break\n                    nums[k + increment] = temp\n        return nums\n```\n\n我们刚才说，我们的增量可以自己设置的，我们上面的例子是用的希尔增量，下面我们看这个例子，看看使用希尔增量会出现什么问题。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/微信截图_20210127212901.62c3o3ss6pg0.png)\n\n我们发现无论是以 4 为增量，还是以 2 为增量，每组内部的元素没有任何交换。直到增量为 1 时，数组才会按照直接插入排序进行调整。所以这种情况希尔排序的效率是低于直接插入排序呢？\n\n我们的希尔增量因为每一轮之间是等比的，所以会有盲区，这里增量的选取就非常关键了。\n\n下面给大家介绍两个比较有代表性的 Sedgewick 增量和 Hibbard 增量\n\nSedgewick 增量序列如下：\n\n1，5，19，41，109.。。。\n\n通项公式 9*4^k - 9*2^\n\n利用此种增量方式的希尔排序，最坏时间复杂度是 O(n^(4/3))\n\nHibbard 增量序列如下：\n\n1，3，7，15......\n\n通项公式 2 ^ k-1\n\n利用此种增量方式的希尔排序，最坏时间复杂度为 O(n^(3/2))\n\n上面是两种比较具有代表性的增量方式，可究竟应该选取怎样的增量才是最好，目前还是一个数学难题。不过我们需要注意的一点，就是增量序列的最后一个增量值必须等于 1 才行。\n\n**希尔排序时间复杂度分析**\n\n希尔排序的时间复杂度跟增量序列的选择有关，范围为 O(n^(1.3-2)) 在此之前的排序算法时间复杂度基本都是 O(n^2),希尔排序是突破这个时间复杂度的第一批算法之一。\n\n**希尔排序空间复杂度分析**\n\n根据我们的视频可知希尔排序的空间复杂度为 O(1),\n\n**希尔排序的稳定性分析**\n\n我们见下图，一起来分析下希尔排序的稳定性。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/微信截图_20210128083925.5v2s0w4ummk0.png)\n\n通过上图，可知，如果我们选用 4 为跨度的话，交换后，两个相同元素 2 的相对位置会发生改变，所以希尔排序是一个不稳定的排序\n\n![](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/微信截图_20210128084911.6tmdmz51m2c0.png)\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/归并排序.md",
    "content": "### **归并排序**\n\n> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n归并排序是必须要掌握的排序算法，也算是面试高频考点，下面我们就一起来扒一扒归并排序吧，原理很简单，大家一下就能搞懂。\n\n袁记菜馆内\n\n第 23 届食神争霸赛开赛啦！\n\n袁厨想在自己排名前 4 的分店中，挑选一个最优秀的厨师来参加食神争霸赛，选拔规则如下。\n\n第一场 PK：每个分店选出两名厨师，首先进行店内 PK,选出店内里的胜者\n\n第二场 PK: 然后店内的优胜者代表分店挑战其他某一分店的胜者（半决赛）\n\n第三场 PK：最后剩下的两名胜者进行 PK,选出最后的胜者。\n\n示意图如下\n\n![武林大会](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/武林大会.531pwa8nrk00.png)\n\n上面的例子大家应该不会陌生吧，其实我们归并排序和食神选拔赛的流程是有些相似的，下面我们一起来看一下吧\n\n归并这个词语的含义就是合并，并入的意思，而在我们的数据结构中的定义是将**两个或两个以上的有序表和成一个新的有序表**。而我们这里说的归并排序就是使用归并的思想实现的排序方法。\n\n归并排序使用的就是分治思想。顾名思义就是分而治之，将一个大问题分解成若干个小的子问题来解决。小的子问题解决了，大问题也就解决了。分治后面会专门写一篇文章进行描述滴，这里先简单提一下。\n\n下面我们通过一个图片来描述一下归并排序的数据变换情况，见下图。\n\n![归并排序](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/微信截图_20210202212227.2yaiv41e5ok0.png)\n\n我们简单了解了归并排序的思想，从上面的描述中，我们可以知道算法的归并过程是比较难实现的，这也是这个算法的重点，看完我们这个视频就能懂个大概啦。\n\n![归并排序](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/归并排序.5xyk55s6xjc0.gif)\n\n视频中归并步骤大家有没有看懂呀，没看懂也不用着急，下面我们一起来拆解一下，归并共有三步走。\n\n第一步：创建一个额外大集合用于存储归并结果，长度则为那两个小集合的和，从视频中也可以看的出\n\n第二步：我们从左自右比较两个指针指向的值，将较小的那个存入大集合中，存入之后指针移动，并继续比较，直到某一小集合的元素全部都存到大集合中。见下图\n\n![合并](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/合并.2gev4sm7ifbw.png)\n\n第三步：当某一小集合元素全部放入大集合中，则需将另一小集合中剩余的所有元素存到大集合中，见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/微信截图_20210203150013.4zfufjynrq00.png)\n\n好啦，看完视频和图解是不是能够写出个大概啦，了解了算法原理之后代码写起来就很简单啦，\n\n下面我们看代码吧。\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n        mergeSort(nums,0,nums.length-1);\n        return nums;\n    }\n    public void mergeSort(int[] arr, int left, int right) {\n        if (left < right) {\n            int mid = left + ((right - left) >> 1);\n            mergeSort(arr,left,mid);\n            mergeSort(arr,mid+1,right);\n            merge(arr,left,mid,right);\n        }\n    }\n    //归并\n    public void merge(int[] arr,int left, int mid, int right) {\n        //第一步，定义一个新的临时数组\n        int[] temparr = new int[right -left + 1];\n        int temp1 = left, temp2 = mid + 1;\n        int index = 0;\n        //对应第二步，比较每个指针指向的值，小的存入大集合\n        while (temp1 <= mid && temp2 <= right) {\n            if (arr[temp1] <= arr[temp2]) {\n                temparr[index++] = arr[temp1++];\n            } else {\n                temparr[index++] = arr[temp2++];\n            }\n        }\n        //对应第三步，将某一小集合的剩余元素存到大集合中\n        if (temp1 <= mid) System.arraycopy(arr, temp1, temparr, index, mid - temp1 + 1);\n        if (temp2 <= right) System.arraycopy(arr, temp2, temparr, index, right -temp2 + 1);     //将大集合的元素复制回原数组\n        System.arraycopy(temparr,0,arr,0+left,right-left+1);\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortArray(self, nums: List[int])->List[int]:\n        self.mergeSort(nums, 0, len(nums) - 1)\n        return nums\n\n    def mergeSort(self, arr: List[int], left: int, right: int):\n        if left < right:\n            mid = left + ((right - left) >> 1)\n            self.mergeSort(arr, left, mid)\n            self.mergeSort(arr, mid + 1, right)\n            self.merge(arr, left, mid, right)\n\n    # 归并\n    def merge(self, arr: List[int], left: int, mid: int, right: int):\n        # 第一步，定义一个新的临时数组\n        temparr = [0] * (right - left + 1)\n        temp1 = left\n        temp2 = mid + 1\n        index = 0\n        # 对应第二步，比较每个指针指向的值，小的存入大集合\n        while temp1 <= mid and temp2 <= right:\n            if arr[temp1] <= arr[temp2]:\n                temparr[index] = arr[temp1]\n                index += 1\n                temp1 += 1\n            else:\n                temparr[index] = arr[temp2]\n                index += 1\n                temp2 += 1\n        # 对应第三步，将某一集合的剩余元素存到大集合中\n        if temp1 <= mid:\n            temparr[index: index + mid - temp1 + 1] = arr[temp1: temp1 + mid - temp1 + 1]\n        if temp2 <= right:\n            temparr[index: index + right - temp2 + 1] = arr[temp2: temp2 + right - temp2 + 1]\n\n        # 将大集合的元素复制回原数组\n        arr[left: left + right- left + 1] = temparr[0: right - left + 1]\n```\n\n**归并排序时间复杂度分析**\n\n我们一趟归并，需要将两个小集合的长度放到大集合中，则需要将待排序序列中的所有记录扫描一遍所以时间复杂度为 O(n)。归并排序把集合一层一层的折半分组，则由完全二叉树的深度可知，整个排序过程需要进行 logn（向上取整）次,则总的时间复杂度为 O(nlogn)。另外归并排序的执行效率与要排序的原始数组的有序程度无关，所以在最好，最坏，平均情况下时间复杂度均为 O(nlogn) 。虽然归并排序时间复杂度很稳定，但是他的应用范围却不如快速排序广泛，这是因为归并排序不是原地排序算法，空间复杂度不为 O(1)，那么他的空间复杂度为多少呢？\n\n**归并排序的空间复杂度分析**\n\n归并排序所创建的临时结合都会在方法结束时释放，单次归并排序的最大空间是 n ,所以归并排序的空间复杂度为 O(n).\n\n**归并排序的稳定性分析**\n\n归并排序的稳定性，要看我们的 merge 函数，我们代码中设置了 arr[temp1] <= arr[temp2] ，当两个元素相同时，先放入 arr[temp1] 的值到大集合中，所以两个相同元素的相对位置没有发生改变，所以归并排序是稳定的排序算法。\n\n| 算法名称 | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 空间复杂度 | 是否稳定 |\n| -------- | -------------- | -------------- | -------------- | ---------- | -------- |\n| 归并排序 | O(nlogn)       | O(nlogn)       | O(nlogn)       | O(n)       | 稳定     |\n\n等等还没完嘞，不要走。\n\n归并排序的递归实现是比较常见的，也是比较容易理解的，下面我们一起来扒一下归并排序的迭代写法。看看他是怎么实现的。\n\n我们通过一个视频来了解下迭代方法的思想，\n\n![归并排序迭代](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/归并排序迭代.4zx9uezcky80.gif)\n\n是不是通过视频了解个大概啦，下面我们来对视频进行解析。\n\n迭代实现的归并排序是将小集合合成大集合，小集合大小为 1,2,4,8,.....。依次迭代，见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/微信截图_20210203205336.4j443ciyj7u0.png)\n\n比如此时小集合大小为 1 。两个小集合分别为 [3],[1]。然后我们根据合并规则，见第一个视频，将[3],[1]合并到临时数组中，则小的先进，则实现了排序，然后再将临时数组的元素复制到原来数组中。则实现了一次合并。\n\n下面则继续合并[4],[6]。具体步骤一致。所有的小集合合并完成后，则小集合的大小变为 2，继续执行刚才步骤，见下图。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/微信截图_20210203210041.2t0e1gji8xy0.png)\n\n此时子集合的大小为 2 ，则为 [2,5],[1,3] 继续按照上面的规则合并到临时数组中完成排序。 这就是迭代法的具体执行过程，\n\n下面我们直接看代码吧。\n\n注：递归法和迭代法的 merge 函数代码一样。\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] sortArray (int[] nums) {\n        //代表子集合大小，1，2，4，8，16.....\n        int k = 1;\n        int len = nums.length;\n        while (k < len) {\n            mergePass(nums,k,len);\n            k *= 2;\n        }\n        return nums;\n\n    }\n    public void mergePass (int[] array, int k, int len) {\n\n         int i;\n         for (i = 0; i < len-2*k; i += 2*k) {\n             //归并\n             merge(array,i,i+k-1,i+2*k-1);\n         }\n         //归并最后两个序列\n         if (i + k < len) {\n             merge(array,i,i+k-1,len-1);\n         }\n\n    }\n     public void merge (int[] arr,int left, int mid, int right) {\n        //第一步，定义一个新的临时数组\n        int[] temparr = new int[right - left + 1];\n        int temp1 = left, temp2 = mid + 1;\n        int index = 0;\n        //对应第二步，比较每个指针指向的值，小的存入大集合\n        while (temp1 <= mid && temp2 <= right) {\n            if (arr[temp1] <= arr[temp2]) {\n                temparr[index++] = arr[temp1++];\n            } else {\n                temparr[index++] = arr[temp2++];\n            }\n        }\n        //对应第三步，将某一小集合的剩余元素存到大集合中\n        if (temp1 <= mid) System.arraycopy(arr, temp1, temparr, index, mid - temp1 + 1);\n        if (temp2 <= right) System.arraycopy(arr, temp2, temparr, index, right -temp2 + 1);\n        //将大集合的元素复制回原数组\n        System.arraycopy(temparr,0,arr,0+left,right-left+1);\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortArray(self, nums: List[int])->List[int]:\n        # 代表子集合大小，1，2，4，8，16.....\n        k = 1\n        leng = len(nums)\n        while k < leng:\n            self.mergePass(nums, k, leng)\n            k *= 2\n            print(nums)\n        return nums\n\n    def mergePass(self, array: List[int], k: int, leng: int):\n        i = 0\n        while i < leng - 2 * k:\n            # 归并\n            self.merge(array, i, i + k - 1, i + 2 * k - 1)\n            i += 2 * k\n\n        # 归并最后两个序列\n        if i + k < leng:\n            self.merge(array, i, i + k - 1, leng - 1)\n\n    # 归并\n    def merge(self, arr: List[int], left: int, mid: int, right: int):\n        # 第一步，定义一个新的临时数组\n        temparr = [0] * (right - left + 1)\n        temp1 = left\n        temp2 = mid + 1\n        index = 0\n        # 对应第二步，比较每个指针指向的值，小的存入大集合\n        while temp1 <= mid and temp2 <= right:\n            if arr[temp1] <= arr[temp2]:\n                temparr[index] = arr[temp1]\n                index += 1\n                temp1 += 1\n            else:\n                temparr[index] = arr[temp2]\n                index += 1\n                temp2 += 1\n        # 对应第三步，将某一集合的剩余元素存到大集合中\n        if temp1 <= mid:\n            temparr[index: index + mid - temp1 + 1] = arr[temp1: temp1 + mid - temp1 + 1]\n        if temp2 <= right:\n            temparr[index: index + right - temp2 + 1] = arr[temp2: temp2 + right - temp2 + 1]\n\n        # 将大集合的元素复制回原数组\n        arr[left: left + right- left + 1] = temparr[0: right - left + 1]\n```\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/快速排序.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### 快速排序\n\n今天我们来说一下快速排序，这个排序算法也是面试的高频考点，原理很简单，我们一起来扒一下他吧。\n\n我们先来说一下快速排序的基本思想。\n\n1.先从数组中找一个基准数\n\n2.让其他比它大的元素移动到数列一边，比他小的元素移动到数列另一边，从而把数组拆解成两个部分。\n\n3.再对左右区间重复第二步，直到各区间只有一个数。\n\n见下图\n\n![快速排序](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210214212239.5rco4z7idoc0.png)\n\n上图则为一次快排示意图，下面我们再利用递归，分别对左半边区间也就是 [3,1,2] 和右半区间 [7,6,5,8] 执行上诉过程，直至区间缩小为 1 也就是第三条，则此时所有的数据都有序。\n\n简单来说就是我们利用基准数通过一趟排序将待排记录分割成独立的两部分，其中一部分记录的关键字均比基准数小，另一部分记录的关键字均比基准数大，然后分别对这两部分记录继续进行排序，进而达到有序。\n\n我们现在应该了解了快速排序的思想，那么大家还记不记得我们之前说过的归并排序，他们两个用到的都是分治思想，那他们两个有什么不同呢？见下图\n\n注：快速排序我们以序列的第一个元素作为基准数\n\n![对比](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210218143843.7l004l5cya00.png)\n\n虽然归并排序和快速排序都用到了分治思想，但是归并排序是自下而上的，先处理子问题，然后再合并，将小集合合成大集合，最后实现排序。而快速排序是由上到下的，先分区，然后再处理子问题。归并排序虽然是稳定的、时间复杂度为 O(nlogn) 的排序算法，但是它是非原地排序算法。我们前面讲过，归并之所以是非原地排序算法，主要原因是合并函数无法在原地执行。快速排序通过设计巧妙的原地分区函数，可以实现原地排序，解决了归并排序占用太多内存的问题\n\n我们根据思想可知，排序算法的核心就是如何利用基准数将记录分区，这里我们主要介绍两种容易理解的方法，一种是挖坑填数，另一种是利用双指针思想进行元素交换。\n\n下面我们先来介绍下挖坑填数的分区方法\n\n基本思想是我们首先以序列的第一个元素为基准数，然后将该位置挖坑，下面判断 nums[high] 是否大于基准数，如果大于则左移 high 指针，直至找到一个小于基准数的元素，将其填入之前的坑中，则 high 位置会出现一个新的坑，此时移动 low 指针，找到大于基准数的元素，填入新的坑中。不断迭代直至完成分区。\n\n大家直接看我们的视频模拟吧，一目了然。\n\n注：为了便于理解所以采取了挖坑的形式展示\n\n![快速排序](https://img-blog.csdnimg.cn/20210317190017344.gif)\n\n是不是很容易就理解啦，下面我们直接看代码吧。\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n\n        quickSort(nums,0,nums.length-1);\n        return nums;\n\n    }\n    public void quickSort (int[] nums, int low, int high) {\n\n        if (low < high) {\n            int index = partition(nums,low,high);\n            quickSort(nums,low,index-1);\n            quickSort(nums,index+1,high);\n        }\n\n    }\n    public int partition (int[] nums, int low, int high) {\n\n            int pivot = nums[low];\n            while (low < high) {\n                //移动hight指针\n                while (low < high && nums[high] >= pivot) {\n                    high--;\n                }\n                //填坑\n                if (low < high) nums[low] = nums[high];\n                while (low < high && nums[low] <= pivot) {\n                    low++;\n                }\n                //填坑\n                if (low < high) nums[high] = nums[low];\n            }\n            //基准数放到合适的位置\n            nums[low] = pivot;\n            return low;\n    }\n}\n```\n\n下面我们来看一下交换思路，原理一致，实现也比较简单。\n\n见下图。\n\n![快速排序](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210218153208.5wn3lgpbljg0.png)\n\n其实这种方法，算是对上面方法的挖坑填坑步骤进行合并，low 指针找到大于 pivot 的元素，hight 指针找到小于 pivot 的元素，然后两个元素交换位置，最后再将基准数归位。两种方法都很容易理解和实现，即使是完全没有学习过快速排序的同学，理解了思想之后也能自己动手实现，下面我们继续用视频模拟下这种方法的执行过程吧。\n\n![](https://img-blog.csdnimg.cn/20210317190153677.gif#pic_center)\n\n两种方法都很容易实现，对新手非常友好，大家可以自己去 AC 一下啊。\n\n```java\nclass Solution {\n    public int[] sortArray (int[] nums) {\n\n        quickSort(nums,0,nums.length-1);\n        return nums;\n\n    }\n\n    public void quickSort (int[] nums, int low, int high) {\n\n        if (low < high) {\n            int index = partition(nums,low,high);\n            quickSort(nums,low,index-1);\n            quickSort(nums,index+1,high);\n        }\n\n    }\n\n    public int partition (int[] nums, int low, int high) {\n\n            int pivot = nums[low];\n            int start = low;\n\n            while (low < high) {\n                while (low < high && nums[high] >= pivot) high--;\n                while (low < high && nums[low] <= pivot) low++;\n                if (low >= high) break;\n                swap(nums, low, high);\n            }\n            //基准值归位\n            swap(nums,start,low);\n            return low;\n    }\n    public void swap (int[] nums, int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n     }\n}\n```\n\n**快速排序的时间复杂度分析**\n\n快排也是用递归来实现的。所以快速排序的时间性能取决于快速排序的递归树的深度。如果每次分区操作，都能正好把数组分成大小接近相等的两个小区间，那么此时的递归树是平衡的，性能也较好，递归树的深度也就和之前归并排序求解方法一致，然后我们每一次分区需要对数组扫描一遍，做 n 次比较，所以最优情况下，快排的时间复杂度是 O(nlogn)。\n\n但是大多数情况下我们不能划分的很均匀，比如数组为正序或者逆序时，即 [1,2,3,4] 或 [4,3,2,1] 时，此时为最坏情况，那么此时我们则需要递归调用 n-1 次，此时的时间复杂度则退化到了 O(n^2)。\n\n**快速排序的空间复杂度分析**\n\n快速排序主要时递归造成的栈空间的使用，最好情况时其空间复杂度为 O (logn),对应递归树的深度。最坏情况时则需要 n-1 次递归调用，此时空间复杂度为 O(n).\n\n**快速排序的稳定性分析**\n\n快速排序是一种不稳定的排序算法，因为其关键字的比较和交换是跳跃进行的，见下图。\n\n![稳定性](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210218165440.17ovoc8246gw.png)\n\n此时无论使用哪一种方法，第一次排序后，黄色的 1 都会跑到红色 1 的前面，所以快速排序是不稳定的排序算法。\n\n![性能分析](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210218170306.6oe0mbb5gr40.png)\n\n好啦，快速排序我们掌握的差不多了，下面我们一起来看看如何对其优化吧。\n\n**快速排序的迭代写法**\n\n该方法实现也是比较简单的，借助了栈来实现，很容易实现。主要利用了先进后出的特性，这里需要注意的就是入栈的顺序，这里算是一个小细节，需要注意一下。\n\n```java\nclass Solution {\n\n     public int[] sortArray(int[] nums) {\n        Stack<Integer> stack = new Stack<>();\n        stack.push(nums.length - 1);\n        stack.push(0);\n        while (!stack.isEmpty()) {\n            int low = stack.pop();\n            int high = stack.pop();\n\n            if (low < high) {\n                int index = partition(nums, low, high);\n                stack.push(index - 1);\n                stack.push(low);\n                stack.push(high);\n                stack.push(index + 1);\n            }\n        }\n        return nums;\n    }\n\n    public int partition (int[] nums, int low, int high) {\n\n            int pivot = nums[low];\n            int start = low;\n            while (low < high) {\n\n                while (low < high && nums[high] >= pivot) high--;\n                while (low < high && nums[low] <= pivot) low++;\n                if (low >= high) break;\n                swap(nums, low, high);\n            }\n            swap(nums,start,low);\n            return low;\n\n    }\n    public void swap (int[] nums, int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\n**快速排序优化**\n\n**三数取中法**\n\n我们在上面的例子中选取的都是 nums[low] 做为我们的基准值，那么当我们遇到特殊情况时呢？见下图\n\n![特殊举例](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210218172418.338n8fxuqka0.png)\n\n我们按上面的方法，选取第一个元素做为基准元素，那么我们执行一轮排序后，发现仅仅是交换了 2 和 7 的位置，这是因为 7 为序列的最大值。所以我们的 pivot 的选取尤为重要，选取时应尽量避免选取序列的最大或最小值做为基准值。则我们可以利用三数取中法来选取基准值。\n\n也就是选取三个元素中的中间值放到 nums[low] 的位置，做为基准值。这样就避免了使用最大值或最小值做为基准值。\n\n所以我们可以加上这几行代码实现三数取中法。\n\n```java\n     int mid = low + ((high-low) >> 1);\n     if (nums[low] > nums[high]) swap(nums,low,high);\n     if (nums[mid] > nums[high]) swap(nums,mid,high);\n     if (nums[mid] > nums[low]) swap(nums,mid,low);\n```\n\n其含义就是让我们将中间元素放到 nums[low] 位置做为基准值，最大值放到 nums[high],最小值放到 nums[mid],即 [4,2,3] 经过上面代码处理后，则变成了 [3,2,4].此时我们选取 3 做为基准值，这样也就避免掉了选取最大或最小值做为基准值的情况。\n\n**三数取中法**\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n        quickSort(nums,0,nums.length-1);\n        return nums;\n    }\n    public void quickSort (int[] nums, int low, int high) {\n        if (low < high) {\n            int index = partition(nums,low,high);\n            quickSort(nums,low,index-1);\n            quickSort(nums,index+1,high);\n        }\n    }\n\n    public int partition (int[] nums, int low, int high) {\n            //三数取中，大家也可以使用其他方法\n            int mid = low + ((high-low) >> 1);\n            if (nums[low] > nums[high]) swap(nums,low,high);\n            if (nums[mid] > nums[high]) swap(nums,mid,high);\n            if (nums[mid] > nums[low]) swap(nums,mid,low);\n            //下面和之前一样，仅仅是多了上面几行代码\n            int pivot = nums[low];\n            int start = low;\n            while (low < high) {\n                while (low < high && nums[high] >= pivot) high--;\n                while (low < high && nums[low] <= pivot) low++;\n                if (low >= high) break;\n                swap(nums, low, high);\n            }\n            swap(nums,start,low);\n            return low;\n    }\n    public void swap (int[] nums, int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\n**和插入排序搭配使用**\n\n我们之前说过，插入排序在元素个数较少时效率是最高的，所以当元素数量较少时，快速排序反而不如插入排序好用，所以我们可以设定一个阈值，当元素个数大于阈值时使用快速排序，小于等于该阈值时则使用插入排序。我们设定阈值为 7 。\n\n**三数取中+插入排序**\n\n```java\nclass Solution {\n    private static final int INSERTION_SORT_MAX_LENGTH = 7;\n\n    public int[] sortArray(int[] nums) {\n        quickSort(nums,0,nums.length-1);\n        return nums;\n    }\n\n    public void quickSort (int[] nums, int low, int high) {\n\n            if (high - low <= INSERTION_SORT_MAX_LENGTH) {\n                insertSort(nums,low,high);\n                return;\n            }\n            int index = partition(nums,low,high);\n            quickSort(nums,low,index-1);\n            quickSort(nums,index+1,high);\n    }\n\n    public int partition (int[] nums, int low, int high) {\n            //三数取中，大家也可以使用其他方法\n            int mid = low + ((high-low) >> 1);\n            if (nums[low] > nums[high]) swap(nums,low,high);\n            if (nums[mid] > nums[high]) swap(nums,mid,high);\n            if (nums[mid] > nums[low]) swap(nums,mid,low);\n            int pivot = nums[low];\n            int start = low;\n            while (low < high) {\n                while (low < high && nums[high] >= pivot) high--;\n                while (low < high && nums[low] <= pivot) low++;\n                if (low >= high) break;\n                swap(nums, low, high);\n            }\n            swap(nums,start,low);\n            return low;\n    }\n\n    public void insertSort (int[] nums, int low, int high) {\n\n        for (int i = low+1; i <= high; ++i) {\n            int temp = nums[i];\n            int j;\n            for (j = i-1; j >= 0; --j) {\n                if (temp < nums[j]) {\n                    nums[j+1] = nums[j];\n                    continue;\n                }\n                break;\n            }\n            nums[j+1] = temp;\n        }\n    }\n    public void swap (int[] nums, int i, int j) {\n\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\n我们继续看下面这种情况\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210220134731.3i6nhxhivo80.png)\n\n我们对其执行一次排序后，则会变成上面这种情况，然后我们继续对蓝色基准值的左区间和右区间执行相同操作。也就是 [2,3,6,3,1,6] 和 [8,6] 。我们注意观察一次排序的结果数组中是含有很多重复元素的。\n\n那么我们为什么不能将相同元素放到一起呢？这样就能大大减小递归调用时的区间大小，见下图。\n\n![三向切分](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210220134928.1sx24i3fkc8w.png)\n\n这样就减小了我们的区间大小，将数组切分成了三部分，小于基准数的左区间，大于基准数的右区间，等于基准数的中间区间。\n\n下面我们来看一下如何达到上面的情况，我们先来进行视频模拟，模拟之后应该就能明白啦。\n\n![三向切分](https://img-blog.csdnimg.cn/20210317190456320.gif#pic_center)\n\n我们来剖析一下视频，其实原理很简单，我们利用探路指针也就是 i，遇到比 pivot 大的元素，则和 right 指针进行交换，此时 right 指向的元素肯定比 pivot 大，则 right--，但是，此时我们的 nums[i] 指向的元素并不知道情况，所以我们的 i 指针不动，如果此时 nums[i] < pivot 则与 left 指针交换，注意此时我们的 left 指向的值肯定是 等于 povit 的，所以交换后我们要 left++,i++, nums[i] == pivot 时，仅需要 i++ 即可，继续判断下一个元素。 我们也可以借助这个思想来解决经典的荷兰国旗问题。\n\n好啦，我们下面直接看代码吧。\n\n**三数取中+三向切分+插入排序**\n\nJava Code:\n\n```java\nclass Solution {\n    private static final int INSERTION_SORT_MAX_LENGTH = 7;\n    public int[] sortArray(int[] nums) {\n        quickSort(nums,0,nums.length-1);\n        return nums;\n\n    }\n    public void quickSort(int nums[], int low, int high) {\n        //插入排序\n        if (high - low <= INSERTION_SORT_MAX_LENGTH) {\n            insertSort(nums,low,high);\n            return;\n        }\n        //三数取中\n        int mid = low + ((high-low) >> 1);\n        if (nums[low] > nums[high]) swap(nums,low,high);\n        if (nums[mid] > nums[high]) swap(nums,mid,high);\n        if (nums[mid] > nums[low]) swap(nums,mid,low);\n        //三向切分\n        int left = low,  i = low + 1, right = high;\n        int pvoit = nums[low];\n        while (i <= right) {\n            if (pvoit < nums[i]) {\n                swap(nums,i,right);\n                right--;\n            } else if (pvoit == nums[i]) {\n                i++;\n            } else {\n                swap(nums,left,i);\n                left++;\n                i++;\n            }\n        }\n        quickSort(nums,low,left-1);\n        quickSort(nums,right+1,high);\n    }\n     public void insertSort (int[] nums, int low, int high) {\n\n        for (int i = low+1; i <= high; ++i) {\n            int temp = nums[i];\n            int j;\n            for (j = i-1; j >= 0; --j) {\n                if (temp < nums[j]) {\n                    nums[j+1] = nums[j];\n                    continue;\n                }\n                break;\n            }\n            nums[j+1] = temp;\n        }\n    }\n    public  void swap (int[] nums, int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    INSERTION_SORT_MAX_LENGTH = 7\n    def sortArray(self, nums: List[int])->List[int]:\n        self.quickSort(nums, 0, len(nums) - 1)\n        return nums\n\n    def quickSort(self, nums: List[int], low: int, high: int):\n        # 插入排序\n        if high - low <= self.INSERTION_SORT_MAX_LENGTH:\n            self.insertSort(nums, low, high)\n            return\n        # 三数取中\n        mid = low + ((high - low) >> 1)\n        if nums[low] > nums[high]:\n            self.swap(nums, low, high)\n        if nums[mid] > nums[high]:\n            self.swap(nums, mid, high)\n        if nums[mid] > nums[low]:\n            self. swap(nums, mid, low)\n        # 三向切分\n        left = low\n        i = low + 1\n        right = high\n        pivot = nums[low]\n        while i <= right:\n            if pivot < nums[i]:\n                self.swap(nums, i, right)\n                right -= 1\n            elif pivot == nums[i]:\n                i += 1\n            else:\n                self.swap(nums, left, i)\n                left += 1\n                i += 1\n        self.quickSort(nums, low, left - 1)\n        self.quickSort(nums, right + 1, high)\n\n    def insertSort(self, nums: List[int], low: int, high: int):\n        for i in range(low + 1, high + 1):\n            temp = nums[i]\n            j = i - 1\n            while j >= 0:\n                if temp < nums[j]:\n                    nums[j + 1] = nums[j]\n                    j -= 1\n                    continue\n                break\n            nums[j + 1] = temp\n\n    def swap(self, nums: List[int], i: int, j: int):\n        temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n```\n\n好啦，一些常用的优化方法都整理出来啦，还有一些其他的优化算法九数取中，优化递归操作等就不在这里进行描述啦，感兴趣的可以自己看一下。好啦，这期的文章就到这里啦，我们下期见，拜了个拜。\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/桶排序.md",
    "content": "我们之前介绍了计数排序，大家应该已经完全搞定了吧，\n\n趁着今天没啥大事咱们再来搞点其他的，如果忘记计数排序的彦祖可以先去复习一下\n\n大家还记不记得咱们上回说的，我们的计数排序在数字范围较小，且全为整数时才可以使用，\n\n但是当我们遇到 double 类型的数怎么办呢？\n\n例如每日温度，26.2, 21.2, 19.8 等\n\n所以我们今天来学一个新的线性排序算法，大家继续往下看吧\n\n假设我们需要对本周（假设今天周日）的温度进行排序，温度分别为\n\n16.3，20.1，17.2，16.8，15.3，19.3，18.5\n\n我们需要对本周温度进行排序，我们当然可以使用我们之前说过的快速排序和归并排序等，\n\n不过这里我们介绍一种我们之前没有说过的排序算法，**桶排序**。\n\n![图片来自百度百科](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210331130134.3wsjrib22ue0.png)\n\n桶，盛水或其他东西的器具。。。,哎嘛跑偏了，那么我们今天说的桶排序的桶，又是什么概念呢？\n\n我们这里的每个桶代表一个区间范围，我们将要排序的数据分到几个有序的桶里，\n\n然后再对桶内元素单独进行排序。排序之后，再把每个桶的桶内元素按照顺序依次取出，\n\n最后得到的序列就是有序的啦。\n\n见下图，我们将元素放到对应的桶中\n\n![微信截图_20210331140519](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210331140519.6f19qdtu04w0.png)\n\n上图表示每个元素对应的桶，然后我们将其放入对应的桶中。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210331140752.18z2563p02o0.png)\n\n将桶内元素排序\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210331140921.274drwaxkv9c.png)\n\n看到这里是不是就悟啦，下一步，我们再将桶内的每个元素取出，就能够得到有序序列啦。\n\n即\n\n15.3，16.3，16.8，17.2，18.5，19.3，20.1\n\n我们这里桶的数量根据元素的个数来设定，几个元素几个桶。\n\n例如上图中，我们共有 7 个元素，则设置 7 个桶\n\n然后我们每个桶的区间这样定义（当然也可以使用其他方法），因为我们桶的数量等于元素数量，所以我们每个区间这样设定\n\n除了最后一个桶只包含最大值，其余桶的区间为\n\n区间大小 = （最大值-最小值）/ （桶的数量-1）\n\n例如：(20.1-15.3) / (7-1) = 0.8\n\n那我们又有问题啦，我们将元素放到合适的桶之后，那么桶内元素是怎样进行保存的？并且桶内元素是如何进行排序的。\n\n这里我们借用算法导论的方法。我们桶内元素使用链表进行保存，和我们之前说过的哈希表处理冲突时使用链地址法类似。\n\n对桶内元素进行排序时，我们使用插入排序。（很多文章都写错了这里）\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210331162409.6gz2xev8lgg0.png)\n\n那么桶排序是完全适用于任何情况的吗？当然不是，我们思考下这种情况\n\n![微信截图_20210331173942](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210331173942.5w8mixnni5k0.png)\n\n此时我们元素值差距太大时，则会产生这种情况，此时效率几乎退化成了链表的插入排序，所以我们的桶排序也不是完全使用于所有情况的。所以遇到不同情况时选择合适的排序算法才是最重要的。\n\n**桶排序的时间复杂度分析**\n\n桶排序可以在线性时间内完成排序，即使桶内排序使用插入排序，具体证明过程可以看下算法导论 p202\n\n**桶排序的空间复杂度分析**\n\n桶排序使用了辅助空间所以空间复杂度为 O（n）\n\n**桶排序的稳定性分析**\n\n桶内使用的是链表的插入排序，所以桶排序是稳定的排序算法\n\n好啦，是不是一下就搞懂了，今天的文章就到这里啦，大家如果觉得还不错的话就叫我一声彦祖吧。\n\n因为这个算法面试时不会让我们手写，所以我们了解下具体含义即可，有需要代码的可以私信我，我发给你。\n\n感谢各位老哥支持，我会继续加油的。\n\n不过我们还有一个问题，那就是我们遍历到一个新元素时，如何知道他应该在哪个桶呢 ？\n\n我们这样求得。\n\n```java\n(int) ((array[i] - min)/w  * (bucketNum-1));\n// w = max - min\n```\n\n好啦，大致含义我们就搞定啦，另外桶排序的原理很容易理解，但是代码不是特别容易实现，大家可以选择性阅读。\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/直接插入排序.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### **直接插入排序（Straight Insertion Sort）**\n\n袁记菜馆内\n\n袁厨：好嘞，我们打烊啦，一起来玩会扑克牌吧。\n\n小二：好啊，掌柜的，咱们玩会斗地主吧。\n\n相信大家应该都玩过扑克牌吧，我们平常摸牌时，是不是一边摸牌，一边理牌，摸到新牌时，会将其插到合适的位置。这其实就是我们的插入排序思想。\n\n直接插入排序：将一个记录插入到已经排好序的有序表中，从而得到一个新的有序表。通俗理解，我们首先将序列分成两个区间，有序区间和无序区间，我们每次在无序区间内取一个值，在已排序区间中找到合适的插入位置将其插入，并保证已排序区间一直有序。下面我们看一下动图吧。\n\n注：为了更清晰表达算法思想，则采用了挖掉待排序元素的形式展示，后面也会采取此形式表达。\n\n![直接插入排序](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/直接插入排序.2marc4epuzy0.gif)\n\n**直接插入排序代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n        //注意 i 的初始值为 1，也就是第二个元素开始\n        for (int i = 1; i < nums.length; ++i) {\n            //待排序的值\n            int temp = nums[i];\n            //需要注意\n            int j;\n            for (j = i-1; j >= 0; --j) {\n                //找到合适位置\n                if (temp < nums[j]) {\n                    nums[j+1] = nums[j];\n                    continue;\n                }\n                //跳出循环\n                break;\n            }\n            //插入到合适位置，这也就是我们没有在 for 循环内定义变量的原因\n            nums[j+1] = temp;\n        }\n        return nums;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortArray(self, nums: List[int])->List[int]:\n        # 注意 i 的初始值为 1 ，也就是第二个元素开始\n        for i in range(1, len(nums)):\n            # 待排序的值\n            temp = nums[i]\n            # 需要注意\n            j = i - 1\n            while j >= 0:\n                # 找到合适位置\n                if temp < nums[j]:\n                    nums[j + 1] = nums[j]\n                    j -= 1\n                    continue\n                # 跳出循环\n                break\n            # 插入到合适位置，这也就是我们没有在循环内定义变量的原因\n            nums[j + 1] = temp\n        return nums\n```\n\n**直接插入排序时间复杂度分析**\n\n最好情况时，也就是有序的时候，我们不需要移动元素，每次只需要比较一次即可找到插入位置，那么最好情况时的时间复杂度为 O(n)。\n\n最坏情况时，即待排序表是逆序的情况，则此时需要比较 2+3+....+n = (n+2)_(n-1)/2,移动次数也达到了最大值，3 +4+5+....n+1 = (n+4)_(n-1)/2,时间复杂度为 O(n^2).\n\n我们每次插入一个数据的时间复杂度为 O(n)，那么循环执行 n 次插入操作，平均时间复杂度为 O(n^2)。\n\n**直接插入排序空间复杂度分析**\n\n根据动画可知，插入排序不需要额外的存储空间，所以其空间复杂度为 O(1)\n\n**直接插入排序稳定性分析**\n\n我们根据代码可知，我们只会移动比 temp 值大的元素，所以我们排序后可以保证相同元素的相对位置不变。所以直接插入排序为稳定性排序算法。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/bedphoto2@master/20210122/微信截图_20210128084750.6911k6mnrac0.png)\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/简单选择排序.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### **简单选择排序**\n\n我们的冒泡排序不断进行交换，通过交换完成最终的排序，我们的简单选择排序的思想也很容易理解，主要思路就是我们每一趟在 n-i+1 个记录中选取关键字最小的记录作为有序序列的第 i 个记录。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210120150816.4za4u7331sw0.png)\n\n例如上图，绿色代表已经排序的元素，红色代表未排序的元素。我们当前指针指向 4 ，则我们遍历红色元素，从中找到最小值，然后与 4 交换。我们发现选择排序执行完一次循环也至少可以将 1 个元素归位。\n\n下面我们来看一下代码的执行过程，看过之后肯定能写出代码的。\n\n注：我们为了更容易理解，min 值保存的是值，而不是索引，实际代码中保存的是索引\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/2021032113041462.gif)\n\n**简单选择排序代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n\n        int len = nums.length;\n        int min = 0;\n        for (int i = 0; i < len; ++i) {\n            min = i;\n            //遍历到最小值\n            for (int j = i + 1; j < len; ++j) {\n                if (nums[min] > nums[j]) min = j;\n            }\n            if (min != i) swap(nums,i,min);\n        }\n        return nums;\n    }\n    public void swap (int[] nums, int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortArray(self, nums: List[int])->List[int]:\n        leng = len(nums)\n        min = 0\n        for i in range(0, leng):\n            min = i\n            # 遍历到最小值\n            for j in range(i + 1, leng):\n                if nums[min] > nums[j]:\n                    min = j\n            if min != i:\n                self.swap(nums, i, min)\n        return nums\n\n    def swap(self, nums: List[int], i: int, j: int):\n        temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n```\n\n**简单选择排序时间复杂度分析**\n\n从简单选择排序的过程来看，他最大的特点就是交换移动数据次数相当少，这样也就节省了排序时间，简单选择和冒泡排序不一样，我们发现无论最好情况和最坏情况，元素间的比较次数是一样的，第 i 次排序，需要 n - i 次比较,n 代表数组长度，则一共需要比较(n-1) + (n-2) +.... + 2 + 1= n\\*(n-1)/2 次，对于交换而言，最好情况交换 0 次，最坏情况(逆序时)交换 n - 1 次。那么简单选择排序时间复杂度也为 O(n^2) 但是其交换次数远小于冒泡排序，所以其效率是好于冒泡排序的。\n\n**简单选择排序空间复杂度分析**\n\n由我们动图可知，我们的简单选择排序只用到了常量级的额外空间，所以空间复杂度为 O(1)。\n\n**简单选择排序稳定性分析**\n\n我们思考一下，我们的简单选择排序是稳定的吗？显然不是稳定的，因为我们需要在指针后面找到最小的值，与指针指向的值交换，见下图。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210120153751.7dygihb0ko80.png)\n\n此时我们需要从后面元素中找到最小的元素与指针指向元素交换，也就是元素 2 。但是我们交换后发现，两个相等元素 3 的相对位置发生了改变，所以简单选择排序是不稳定的排序算法。\n\n| 算法名称     | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 空间复杂度 | 是否稳定 |\n| ------------ | -------------- | -------------- | -------------- | ---------- | -------- |\n| 简单选择排序 | O(n^2)         | O(n^2)         | O(n^2)         | O(1)       | 不稳定   |\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/翻转对.md",
    "content": "#### [leetcode 493 翻转对](https://leetcode-cn.com/problems/reverse-pairs/)\n\n> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n**题目描述**\n\n给定一个数组 nums ，如果 i < j 且 nums[i] > 2\\*nums[j] 我们就将 (i, j) 称作一个重要翻转对。\n\n你需要返回给定数组中的重要翻转对的数量。\n\n示例 1:\n\n> 输入: [1,3,2,3,1]\n> 输出: 2\n\n示例 2:\n\n> 输入: [2,4,3,5,1]\n> 输出: 3\n\n**题目解析**\n\n我们理解了逆序对的含义之后，题目理解起来完全没有压力的，这个题目第一想法可能就是用暴力法解决，但是会超时，所以我们有没有办法利用归并排序来完成呢？\n\n我们继续回顾一下归并排序的归并过程，两个小集合是有序的，然后我们需要将小集合归并到大集合中，则我们完全可以在归并之前，先统计一下翻转对的个数，然后再进行归并，则最后排序完成之后自然也就得出了翻转对的个数。具体过程见下图。\n\n![翻转对](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210214121010.50g9z0xgda80.png)\n\n此时我们发现 6 > 2 \\* 2，所以此时是符合情况的，因为小数组是单调递增的，所以 6 后面的元素都符合条件，所以我们 count += mid - temp1 + 1；则我们需要移动紫色指针，判断后面是否还存在符合条件的情况。\n\n![翻转对](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210214121711.77crljdzra00.png)\n\n我们此时发现 6 = 3 \\* 2，不符合情况，因为小数组都是完全有序的，所以我们可以移动红色指针，看下后面的数有没有符合条件的情况。这样我们就可以得到翻转对的数目啦。下面我们直接看动图加深下印象吧！\n\n![](https://img-blog.csdnimg.cn/20210317192545806.gif#pic_center)\n\n是不是很容易理解啊，那我们直接看代码吧，仅仅是在归并排序的基础上加了几行代码。\n\nJava Code:\n\n```java\nclass Solution {\n    private int count;\n\n    public int reversePairs(int[] nums) {\n        count = 0;\n        merge(nums, 0, nums.length - 1);\n        return count;\n    }\n\n    public void merge(int[] nums, int left, int right) {\n\n        if (left < right) {\n            int mid = left + ((right - left) >> 1);\n            merge(nums, left, mid);\n            merge(nums, mid + 1, right);\n            mergeSort(nums, left, mid, right);\n        }\n\n    }\n\n    public void mergeSort(int[] nums, int left, int mid, int right) {\n\n        int[] temparr = new int[right - left + 1];\n        int temp1 = left, temp2 = mid + 1, index = 0;\n        //计算翻转对\n        while (temp1 <= mid && temp2 <= right) {\n            //这里需要防止溢出\n            if (nums[temp1] > 2 * (long) nums[temp2]) {\n                count += mid - temp1 + 1;\n                temp2++;\n            } else {\n                temp1++;\n            }\n        }\n        //记得归位，我们还要继续使用\n        temp1 = left;\n        temp2 = mid + 1;\n        //归并排序\n        while (temp1 <= mid && temp2 <= right) {\n\n            if (nums[temp1] <= nums[temp2]) {\n                temparr[index++] = nums[temp1++];\n            } else {\n                temparr[index++] = nums[temp2++];\n            }\n        }\n        //照旧\n        if (temp1 <= mid) System.arraycopy(nums, temp1, temparr, index, mid - temp1 + 1);\n        if (temp2 <= right) System.arraycopy(nums, temp2, temparr, index, right - temp2 + 1);\n        System.arraycopy(temparr, 0, nums, left, right - left + 1);\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    count = 0\n    def reversePairs(self, nums: List[int])->int:\n        self.count = 0\n        self.merge(nums, 0, len(nums) - 1)\n        return self.count\n\n    def merge(self, nums: List[int], left: int, right: int):\n        if left < right:\n            mid = left + ((right - left) >> 1)\n            self.merge(nums, left, mid)\n            self.merge(nums, mid + 1, right)\n            self.mergeSort(nums, left, mid, right)\n\n    def mergeSort(self, nums: List[int], left: int, mid: int, right: int):\n\n        temparr = [0] * (right - left + 1)\n        temp1 = left\n        temp2 = mid + 1\n        index = 0\n        while temp1 <= mid and temp2 <= right:\n            # 这里需要防止溢出\n            if nums[temp1] > 2 * nums[temp2]:\n                self.count += mid - temp1 + 1\n                temp2 += 1\n            else:\n                temp1 += 1\n\n        # 记得归位，我们还要继续使用\n        temp1 = left\n        temp2 = mid + 1\n        # 归并排序\n        while temp1 <= mid and temp2 <= right:\n            if nums[temp1] <= nums[temp2]:\n                temparr[index] = nums[temp1]\n                index += 1\n                temp1 += 1\n            else:\n                temparr[index] = nums[temp2]\n                index += 1\n                temp2 += 1\n        # 照旧\n        if temp1 <= mid:\n             temparr[index: index + mid - temp1 + 1] = nums[temp1: temp1 + mid - temp1 + 1]\n        if temp2 <= right:\n            temparr[index: index + right - temp2 + 1] = nums[temp2: temp2 + right - temp2 + 1]\n        nums[left: left + right- left + 1] = temparr[0: right - left + 1]\n```\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/荷兰国旗.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n今天我们一起来看一下可以用快速排序秒杀的经典题，或许这些题目大家已经做过，不过可以再来一起复习一遍，加深印象。\n\n阅读这篇文章之前，大家要先去看一下我之前写过的快速排序，这样才不会对这篇文章一知半解。好啦，我们一起开整吧。\n\n在上篇文章中，我们提到了，快速排序的优化，利用三向切分来优化数组中存在大量重复元素的情况，到啦这里各位应该猜到我想写什么了吧，\n\n我们今天先来说一下那个非常经典的荷兰国旗问题。\n\n> 题目来源：https://www.jianshu.com/p/356604b8903f\n\n问题描述：\n\n荷兰国旗是由红白蓝 3 种颜色的条纹拼接而成，如下图所示：\n\n![荷兰国旗](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210305145819.4jrud8f8xny0.png)\n\n假设这样的条纹有多条，且各种颜色的数量不一，并且随机组成了一个新的图形，新的图形可能如下图所示，但是绝非只有这一种情况：\n\n![荷兰国旗问题](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/7789414-8baf85cac6228621.62ygbgv09ek0.png)\n\n需求是：把这些条纹按照颜色排好，红色的在上半部分，白色的在中间部分，蓝色的在下半部分，我们把这类问题称作荷兰国旗问题。\n\n我们把荷兰国旗问题用数组的形式表达一下是这样的：\n\n给定一个整数数组，给定一个值 K，这个值在原数组中一定存在，要求把数组中小于 K 的元素放到数组的左边，大于 K 的元素放到数组的右边，等于 K 的元素放到数组的中间，最终返回一个整数数组，其中只有两个值，分别是等于 K 的数组部分的左右两个下标值。\n\n例如，给定数组：[2, 3, 1, 9, 7, 6, 1, 4, 5]，给定一个值 4，那么经过处理原数组可能得一种情况是：[2, 3, 1, 1, 4, 9, 7, 6, 5]，需要注意的是，小于 4 的部分不需要有序，大于 4 的部分也不需要有序，返回等于 4 部分的左右两个下标，即[4, 4]\n\n这不就是我们之前说过的三向切分吗？一模一样！\n\n那么 leetcode 有没有相似问题呢？我们一起来看下面这道题\n\n**leetcode 75 颜色分类**\n\n题目描述：\n\n给定一个包含红色、白色和蓝色，一共 n 个元素的数组，原地对它们进行排序，使得相同颜色的元素相邻，并按照红色、白色、蓝色顺序排列。\n\n此题中，我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。\n\n示例 1：\n\n> 输入：nums = [2,0,2,1,1,0]\n> 输出：[0,0,1,1,2,2]\n\n示例 2：\n\n> 输入：nums = [2,0,1]\n> 输出：[0,1,2]\n\n示例 3：\n\n> 输入：nums = [0]\n> 输出：[0]\n\n示例 4：\n\n> 输入：nums = [1]\n> 输出：[1]\n\n**做题思路**\n\n这个题目我们使用 Arrays.sort() 解决，哈哈，但是那样太无聊啦，题目含义就是让我们将所有的 0 放在前面，2 放在后面，1 放在中间，是不是和我们上面说的荷兰国旗问题一样。我们仅仅将 1 做为 pivot 值。\n\n下面我们直接看代码吧，和三向切分基本一致。\n\nJava Code：\n\n```java\nclass Solution {\n    public void sortColors(int[] nums) {\n        int len = nums.length;\n        int left = 0;\n        //这里和三向切分不完全一致\n        int i = left;\n        int right = len-1;\n\n        while (i <= right) {\n             if (nums[i] == 2) {\n                 swap(nums,i,right--);\n             } else if (nums[i] == 0) {\n                 swap(nums,i++,left++);\n             } else {\n                 i++;\n             }\n        }\n    }\n    public void swap (int[] nums, int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\nC++ Code：\n\n```c++\nclass Solution {\npublic:\n    void sortColors(vector<int>& nums) {\n        int len = nums.size();\n        int left = 0;\n        //这里和三向切分不完全一致\n        int i = left;\n        int right = len-1;\n\n        while (i <= right) {\n            if (nums[i] == 2) {\n                swap(nums,i,right--);\n            } else if (nums[i] == 0) {\n                swap(nums,i++,left++);\n            } else {\n                i++;\n            }\n        }\n    }\n\n    void swap (vector<int>& nums, int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n};\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortColors(self, nums: List[int]):\n        leng = len(nums)\n        left = 0\n        # 这里和三向切分不完全一致\n        i = left\n        right = leng - 1\n        while i <= right:\n            if nums[i] == 2:\n                self.swap(nums, i, right)\n                right -= 1\n            elif nums[i] == 0:\n                self.swap(nums, i, left)\n                i += 1\n                left += 1\n            else:\n                i += 1\n\n    def swap(self, nums: List[int], i: int, j: int):\n        temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n```\n\n另外我们看这段代码，有什么问题呢？那就是我们即使完全符合时，仍会交换元素，这样会大大降低我们的效率。\n\n例如：[0,0,0,1,1,1,2,2,2]\n\n此时我们完全符合情况，不需要交换元素，但是按照我们上面的代码，0,2 的每个元素会和自己进行交换，所以这里我们可以根据 i 和 left 的值是否相等来决定是否需要交换，大家可以自己写一下。\n\n下面我们看一下另外一种写法\n\n这个题目的关键点就是，当我们 nums[i] 和 nums[right] 交换后，我们的 nums[right] 此时指向的元素是符合要求的，但是我们 nums[i] 指向的元素不一定符合要求，所以我们需要继续判断。\n\n![细节地方](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210305153911.28capmzljy80.png)\n\n我们 2 和 0 交换后，此时 i 指向 0 ，0 应放在头部，所以不符合情况，所以 0 和 1 仍需要交换。下面我们来看一下动画来加深理解吧。\n\n![](https://img-blog.csdnimg.cn/20210318093047325.gif#pic_center)\n\n另一种代码表示\n\nJava Code：\n\n```java\nclass Solution {\n    public void sortColors(int[] nums) {\n\n        int left = 0;\n        int len = nums.length;\n        int right = len - 1;\n        for (int i = 0; i <= right; ++i) {\n            if (nums[i] == 0) {\n                swap(nums,i,left);\n                left++;\n            }\n            if (nums[i] == 2) {\n                swap(nums,i,right);\n                right--;\n                //如果不等于 1 则需要继续判断，所以不移动 i 指针，i--\n                if (nums[i] != 1) {\n                    i--;\n                }\n            }\n        }\n\n    }\n    public void swap (int[] nums,int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\nC++ Code：\n\n```c++\nclass Solution {\npublic:\n    void sortColors(vector<int>& nums) {\n        int left = 0;\n        int len = nums.size();\n        int right = len - 1;\n        for (int i = 0; i <= right; ++i) {\n            if (nums[i] == 0) {\n                swap(nums,i,left);\n                left++;\n            }\n            if (nums[i] == 2) {\n                swap(nums,i,right);\n                right--;\n                //如果不等于 1 则需要继续判断，所以不移动 i 指针，i--\n                if (nums[i] != 1) {\n                    i--;\n                }\n            }\n        }\n\n    }\n    void swap (vector<int>& nums, int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n};\n```\n\n```python\nfrom typing import List\nclass Solution:\n    def sortColors(self, nums: List[int]):\n        left = 0\n        leng = len(nums)\n        right = leng - 1\n        for i in range(0, right + 1):\n            if nums[i] == 0:\n                self.swap(nums, i, left)\n                left += 1\n            if nums[i] == 2:\n                swap(nums, i, right)\n                right -= 1\n                # 如果不等于 1 则需要继续判断，所以不移动 i 指针，i--\n                if nums[i] != 1:\n                    i -= 1\n\n    def swap(nums: List[int], i: int, j: int):\n        temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n```\n\n好啦，这个问题到这就结束啦，是不是很简单啊，我们明天见！\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/计数排序.md",
    "content": "# 计数排序\n\n> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n今天我们就一起来看看线性排序里的计数排序到底是怎么回事吧。\n\n我们将镜头切到袁记菜馆\n\n因为今年袁记菜馆的效益不错，所以袁厨就想给员工发些小福利，让小二根据员工工龄进行排序，但是菜馆共有 100000 名员工，菜馆开业 10 年，员工工龄从 0 - 10 不等。看来这真是一个艰巨的任务啊。\n\n当然我们可以借助之前说过的 归并排序 和 快速排序解决，但是我们有没有其他更好的方法呢？\n\n了解排序算法的老哥可能已经猜到今天写什么啦。是滴，我们今天来写写用空间换时间的线性排序。\n\n说之前我们先来回顾一下之前的排序算法，最好的时间复杂度为 O(nlogn) ,且都基于元素之间的比较来进行排序，\n\n我们来说一下非基于元素比较的排序算法，且时间复杂度为 O（n），时间复杂度是线性的，所以我们称其为线性排序算法。\n\n其优势在于在对一定范围内的整数排序时，它的复杂度为 Ο(n+k)，此时的 k 则代表整数的范围。快于任何一种比较类排序算法，不过也是需要牺牲一些空间来换取时间。\n\n下面我们先来看看什么是计数排序，这个计数的含义是什么？\n\n我们假设某一分店共有 10 名员工，\n\n工龄分别为 1，2，3，5，0，2，2，4，5，9\n\n那么我们将其存在一个长度为 10 的数组里，，但是我们注意，我们数组此时存的并不是元素值，而是元素的个数。见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210327184632.ho3d12nf3q8.png)\n\n注：此时我们这里统计次数的数组根据最大值来决定，上面的例子中最大值为 9 ，则长度为 9 + 1 = 10。暂且先这样理解，后面会对其优化 。\n\n我们继续以上图的例子来说明，在该数组中，**索引代表的为元素值**（也就是上面例子中的工龄），数组的值代表的则是元素个数（也就是不同工龄出现的次数）。\n\n即工龄为 0 的员工有 1 个， 工龄为 1 的员工有 1 个，工龄为 2 的员工有 3 个 。。。\n\n然后我们根据出现次数将其依次取出看看是什么效果。\n\n0，1，2，2，2，3，4，5，5，9\n\n我们发现此时元素则变成了有序的，但是这并不是排序，只是简单的按照统计数组的下标，输出了元素值，并没有真正的给原始数组进行排序。\n\n这样操作之后我们不知道工龄属于哪个员工。\n\n见下图\n\n![微信截图_20210327202256](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210327202256.7g3nka7n0p40.png)\n\n虽然喵哥和杰哥工龄相同，如果我们按照上面的操作输出之后，我们不能知道工龄为 4 的两个员工，哪个是喵哥哪个是杰哥。\n\n所以我们需要借助其他方法来对元素进行排序。\n\n大家还记不记得我们之前说过的前缀和，下面我们通过上面统计次数的数组求出其前缀和数组。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210328131226.3x42hsrnna80.png)\n\n因为我们是通过统计次数的数组得到了前缀和数组，那么我们来分析一下 presum 数组里面值的含义。\n\n例如我们的 presum[2] = 5 ,代表的则是原数组小于等于 2 的值共有 5 个。presum[4] = 7 代表小于等于 4 的元素共有 7 个。\n\n是不是感觉计数排序的含义要慢慢显现出来啦。\n\n其实到这里我们已经可以理解的差不多了，还差最后一步，\n\n此时我们要从后往前遍历原始数组，然后将遍历到的元素放到临时数组的合适位置，并修改 presum 数组的值，遍历结束后则达到了排序的目的。\n\n![微信截图_20210328132549](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210328132549.4w6kovlhtsa0.png)\n\n我们从后往前遍历，nums[9] = 9,则我们拿该值去 presum 数组中查找，发现 presum[nums[9]] = presum[9] = 10 ，大家还记得我们 presum 数组里面每个值得含义不，我们此时 presum[9] = 10,则代表在数组中，小于等于的数共有 10 个，则我们要将他排在临时数组的第 10 个位置，也就是 temp[9] = 9。\n\n我们还需要干什么呢？我们想一下，我们已经把 9 放入到 temp 数组里了，已经对其排好序了，那么我们的 presum 数组则不应该再统计他了，则将相应的位置减 1 即可，也就是 presum[9] = 10 - 1 = 9;\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210328133100.5h5w473oi2s0.png)\n\n下面我们继续遍历 5 ，然后同样执行上诉步骤\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210328133401.23fulpjowbnk.png)\n\n我们继续查询 presum 数组，发现 presum[5] = 9,则说明小于等于 5 的数共有 9 个，我们将其放入到 temp 数组的第 9 个位置，也就是\n\ntemp[8] = 5。然后再将 presum[5] 减 1 。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210328133726.3s8wgrlcpzm0.png)\n\n是不是到这里就理解了计数排序的大致思路啦。\n\n这个排序的过程像不像查字典呢？通过查询 presum 数组，得出自己应该排在临时数组的第几位。然后再修改下字典，直到遍历结束。\n\n那么我们先来用动画模拟一下我们这个 bug 版的计数排序，加深理解。\n\n注：我们得到 presum 数组的过程在动画中省略。直接模拟排序过程。\n\n![计数排序](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/计数排序.6y4quuwtxgw0.gif)\n\n但是到现在就完了吗？显然没有，我们思考下这个情况。\n\n假如我们的数字为 90，93，94，91，92 如果我们根据上面方法设置 presum 数组的长度，那我们则需要设置数组长度为 95（因为最大值是 94），这样显然是不合理的，会浪费掉很多空间。\n\n还有就是当我们需要对负数进行排序时同样会出现问题，因为我们求次数的时候是根据 nums[index] 的值来填充 presum 数组的，所以当 nums[index] 为负数时，填充 presum 数组时则会报错。而且此时通过最大值来定义数组长度也不合理。\n\n所以我们需要采取别的方法来定义数组长度。\n\n下面我们来说一下偏移量的概念。\n\n例如 90，93，94，91，92，我们 可以通过 max ，min 的值来设置数组长度即 94 - 90 + 1 = 5 。偏移量则为 min 值，也就是 90。\n\n见下图。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210328153724.61tvwfwjmmo0.png)\n\n这样我们填充 presum 数组时就不会出现浪费空间的情况了，负数？出现负数的情况当然也可以。继续看\n\n例如：-1，-3，0，2，1\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210328154337.qtnhiuaixzk.png)\n\n一样可以，哦了，到这里我们就搞定了计数排序，下面我们来看一哈代码吧。\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] sortArray(int[] nums) {\n\n        int len = nums.length;\n        if (nums.length < 1) {\n             return nums;\n        }\n        //求出最大最小值\n        int max = nums[0];\n        int min = nums[0];\n        for (int x : nums) {\n            if (max < x)  max = x;\n            if (min > x)  min = x;\n        }\n        //设置 presum 数组长度,然后求出我们的前缀和数组，\n        //这里我们可以把求次数数组和前缀和数组用一个数组处理\n        int[] presum = new int[max-min+1];\n        for (int x : nums) {\n            presum[x-min]++;\n        }\n        for (int i = 1; i < presum.length; ++i) {\n            presum[i] = presum[i-1]+presum[i];\n        }\n        //临时数组\n        int[] temp = new int[len];\n        //遍历数组，开始排序,注意偏移量\n        for (int i = len-1; i >= 0; --i) {\n            //查找 presum 字典，然后将其放到临时数组，注意偏移度\n            int index = presum[nums[i]-min]-1;\n            temp[index] = nums[i];\n            //相应位置减一\n            presum[nums[i]-min]--;\n        }\n        //copy回原数组\n        System.arraycopy(temp,0,nums,0,len);\n        return nums;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortArray(self,nums: List[int])->List[int]:\n        leng = len(nums)\n        if leng < 1:\n            return nums\n        # 求出最大最小值\n        max = nums[0]\n        min = nums[0]\n        for x in nums:\n            if max < x:\n                max = x\n            if min > x:\n                min = x\n        # 设置 presum 数组长度,然后求出我们的前缀和数组，\n        # 这里我们可以把求次数数组和前缀和数组用一个数组处理\n        presum = [0] * (max - min + 1)\n        for x in nums:\n            presum[x - min] += 1\n        for i in range(1, len(presum)):\n            presum[i] = presum[i - 1] + presum[i]\n        # 临时数组\n        temp = [0] * leng\n        # 遍历数组，开始排序，注意偏移量\n        for i in range(leng - 1, -1, -1):\n            # 查找 presum 字典，然后将其放到临时数组，注意偏移度\n            index = presum[nums[i] - min] - 1\n            temp[index] = nums[i]\n            # 相应位置减一\n            presum[nums[i] - min] -= 1\n        # copy回原数组\n        nums = temp\n        return nums\n```\n\n好啦，这个排序算法我们已经搞定了，下面我们来扒一扒它。\n\n**计数排序时间复杂度分析**\n\n我们的总体运算量为 n+n+k+n ，总体运算是 3n + k 所以时间复杂度为 O(N+K)；\n\n**计数排序空间复杂度分析**\n\n我们用到了辅助数组，空间复杂度为 O（n）\n\n**计数排序稳定性分析**\n\n稳定性在我们最后存入临时数组时有体现，我们当时让其放入临时数组的合适位置，并减一，所以某元素前面的相同元素，在临时数组，仍然在其前面。所以计数排序是稳定的排序算法。\n\n虽然计数排序效率不错但是用到的并不多。\n\n- 这是因为其当数组元素的范围太大时，并不适合计数排序，不仅浪费时间，效率还会大大降低。\n\n- 当待排序的元素非整数时,也不适用,大家思考一下这是为什么呢?\n\n好啦,今天的文章就到这啦,我们下期再见,拜了个拜.\n"
  },
  {
    "path": "animation-simulation/数据结构和算法/逆序对问题.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [剑指 offer 51 数组中的逆序对](https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof)\n\n逆序对：在数组中的两个数字，如果前面一个数字大于后面的数字，则这两个数字组成一个逆序对，见下图。\n\n![逆序对](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/逆序对.2p9sfhlbkaw0.png)\n\n是不是很容易理解，因为数组是无序的，当较大的数，出现在较小数前面的时候，它俩则可以组成逆序对。因为数组的（有序度+逆序度）= n (n-1) / 2，逆序对个数 = 数组的逆序度，有序对个数 = 数组的有序度，所以我们知道有序对个数的话，也能得到逆序对的个数。另外我们如何通过归并排序来计算逆序对个数呢？\n\n关键点在我们的**归并过程中**，我们先来看下归并过程中是怎么计算逆序对个数的。见下图\n\n![逆序对举例](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210212200744.1upng86ndbr4.png)\n\n我们来拆解下上图，我们此时 temp1 指向元素为 6，temp2 指向元素为 2, nums[temp1] > temp[temp2]，则此时我们需要将 temp2 指向的元素存入临时数组中，又因为每个小集合中的元素都是有序的，所以 temp1 后面的元素也一定大于 2，那么我们就可以根据 temp1 的索引得出逆序对中包含 2 的逆序对个数，则是 mid - temp + 1。\n\n好啦这个题目你已经会做啦，下面我们一起来做下吧。\n\n**题目描述**\n\n在数组中的两个数字，如果前面一个数字大于后面的数字，则这两个数字组成一个逆序对。输入一个数组，求出这个数组中的逆序对的总数。\n\n**示例 1:**\n\n> 输入: [7,5,6,4]\n> 输出: 5\n\n**题目解析**\n\n各位如果忘记归并排序的话，可以再看一下咱们之前的文章回顾一下 [归并排序详解](https://mp.weixin.qq.com/s/YK43J73UNFRjX4r0vh13ZA)，这个题目我们仅仅在归并排序的基础上加了一行代码。那就是在归并过程时，nums[temp2] < nums[temp1] 时统计个数。下面我们直接看代码吧。\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    //全局变量\n    private int count;\n    public int reversePairs(int[] nums) {\n         count = 0;\n         merge(nums,0,nums.length-1);\n         return count;\n    }\n\n    public void merge (int[] nums, int left, int right) {\n\n        if (left < right) {\n            int mid = left + ((right - left) >> 1);\n            merge(nums,left,mid);\n            merge(nums,mid+1,right);\n            mergeSort(nums,left,mid,right);\n        }\n\n    }\n\n    public void mergeSort(int[] nums, int left, int mid, int right) {\n\n         int[] temparr = new int[right-left+1];\n         int index = 0;\n         int temp1 = left, temp2 = mid+1;\n\n         while (temp1 <= mid && temp2 <= right) {\n\n             if (nums[temp1] <= nums[temp2]) {\n                 temparr[index++] = nums[temp1++];\n             } else {\n                 //增加的一行代码，用来统计逆序对个数\n                 count += (mid - temp1 + 1);\n                 temparr[index++] = nums[temp2++];\n             }\n         }\n\n         if (temp1 <= mid) System.arraycopy(nums,temp1,temparr,index,mid-temp1+1);\n         if (temp2 <= right) System.arraycopy(nums,temp2,temparr,index,right-temp2+1);\n         System.arraycopy(temparr,0,nums,left,right-left+1);\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    count = 0\n    def reversePairs(self, nums: List[int])->int:\n        self.count = 0\n        self.mergeSort(nums, 0, len(nums) - 1)\n        return self.count\n\n    def mergeSort(self, arr: List[int], left: int, right: int):\n        if left < right:\n            mid = left + ((right - left) >> 1)\n            self.mergeSort(arr, left, mid)\n            self.mergeSort(arr, mid + 1, right)\n            self.merge(arr, left, mid, right)\n\n    # 归并\n    def merge(self, arr: List[int], left: int, mid: int, right: int):\n        # 第一步，定义一个新的临时数组\n        temparr = [0] * (right - left + 1)\n        temp1 = left\n        temp2 = mid + 1\n        index = 0\n        # 对应第二步，比较每个指针指向的值，小的存入大集合\n        while temp1 <= mid and temp2 <= right:\n            if arr[temp1] <= arr[temp2]:\n                temparr[index] = arr[temp1]\n                index += 1\n                temp1 += 1\n            else:\n                self.count += (mid - temp1 + 1)\n                temparr[index] = arr[temp2]\n                index += 1\n                temp2 += 1\n        # 对应第三步，将某一集合的剩余元素存到大集合中\n        if temp1 <= mid:\n            temparr[index: index + mid - temp1 + 1] = arr[temp1: temp1 + mid - temp1 + 1]\n        if temp2 <= right:\n            temparr[index: index + right - temp2 + 1] = arr[temp2: temp2 + right - temp2 + 1]\n\n        # 将大集合的元素复制回原数组\n        arr[left: left + right- left + 1] = temparr[0: right - left + 1]\n```\n\n好啦，这个题目我们就解决啦，哦对，大家也可以顺手去解决下这个题目。leetcode 912 排序数组，这个题目大家可以用来练手，因为有些排序算法是面试高频考点，所以大家可以防止遗忘，多用这个题目进行练习，防止手生。下面则是我写文章时代码的提交情况，冒泡排序怎么优化都会超时，其他排序算法倒是都可以通过。\n\n![排序](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/排序.1unok1gcygtc.png)\n\n好啦，下面我们继续做一个题目吧，也完全可以用归并排序解决，稍微加了一丢丢代码，但是也是很好理解的。感谢支持\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode1052爱生气的书店老板.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [1052. 爱生气的书店老板](https://leetcode-cn.com/problems/grumpy-bookstore-owner/)\n\n**题目描述**\n\n今天，书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客（customers[i]）会进入书店，所有这些顾客都会在那一分钟结束后离开。\n\n在某些时候，书店老板会生气。 如果书店老板在第 i 分钟生气，那么 grumpy[i] = 1，否则 grumpy[i] = 0。 当书店老板生气时，那一分钟的顾客就会不满意，不生气则他们是满意的。\n\n书店老板知道一个秘密技巧，能抑制自己的情绪，可以让自己连续 X 分钟不生气，但却只能使用一次。\n\n请你返回这一天营业下来，最多有多少客户能够感到满意的数量。\n\n示例：\n\n> 输入：customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], X = 3\n> 输出：16\n\n解释：\n书店老板在最后 3 分钟保持冷静。\n感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.\n\n该题目思想就是，我们将 customer 数组的值分为三部分， leftsum, winsum, rightsum。我们题目的返回值则是三部分的最大和。\n\n注意这里的最大和，我们是怎么计算的。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210223083057.1vns7wrs2z0.png)\n\nwinsum 是窗口内的所有值，不管 grumpy[i] 的值是 0 还是 1,窗口的大小，就对应 K 的值，也就是老板的技能发动时间，该时间段内，老板不会生气，所以为所有的值。\n\nleftsum 是窗口左边区间的值，此时我们不能为所有值，只能是 grumpy[i] == 0 时才可以加入，因为此时不是技能发动期，老板只有在 grumpy[i] == 0 时，才不会生气。\n\nrightsum 是窗口右区间的值，和左区间加和方式一样。那么我们易懂一下窗口，我们的 win 值和 leftsum 值，rightsum 值是怎么变化的呢？\n\n见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/微信截图_20210223084549.5ht4nytfe1o0.png)\n\n我们此时移动了窗口，\n\n则左半区间范围扩大，但是 leftsum 的值没有变，这时因为新加入的值，所对应的 grumpy[i] == 1，所以其值不会发生改变，因为我们只统计 grumpy[i] == 0 的值，\n\n右半区间范围减少，rightsum 值也减少，因为右半区间减小的值，其对应的 grumpy[i] == 0，所以 rightsum -= grumpy[i]。\n\nwinsum 也会发生变化， winsum 需要加上新加入窗口的值，减去刚离开窗口的值, 也就是 customer[left-1]，left 代表窗口左边缘。\n\n好啦，知道怎么做了，我们直接开整吧。\n\nJava Code:\n\n```java\nclass Solution {\n    public int maxSatisfied(int[] customers, int[] grumpy, int X) {\n\n        int winsum = 0;\n        int rightsum = 0;\n        int len = customers.length;\n        //右区间的值\n        for (int i = X; i < len; ++i) {\n            if (grumpy[i] == 0) {\n                rightsum += customers[i];\n            }\n        }\n        //窗口的值\n        for (int i = 0; i < X; ++i) {\n              winsum += customers[i];\n        }\n        int leftsum = 0;\n        //窗口左边缘\n        int left = 1;\n        //窗口右边缘\n        int right = X;\n        int maxcustomer = winsum + leftsum + rightsum;\n        while (right < customers.length) {\n            //重新计算左区间的值，也可以用 customer 值和 grumpy 值相乘获得\n            if (grumpy[left-1] == 0) {\n                leftsum += customers[left-1];\n            }\n            //重新计算右区间值\n            if (grumpy[right] == 0) {\n                rightsum -= customers[right];\n            }\n            //窗口值\n            winsum = winsum - customers[left-1] + customers[right];\n            //保留最大值\n            maxcustomer = Math.max(maxcustomer,winsum+leftsum+rightsum);\n            //移动窗口\n            left++;\n            right++;\n        }\n        return maxcustomer;\n    }\n}\n```\n\nPython3 Code:\n\n```py\nclass Solution:\n    def maxSatisfied(self, customers: List[int], grumpy: List[int], X: int) -> int:\n        t = ans = sum(customers[:X]) + sum(map(lambda x: customers[X+x[0]] if x[1] == 0 else 0, enumerate(grumpy[X:])))\n        for j in range(X, len(customers)):\n            t += customers[j] * grumpy[j] - customers[j-X] * grumpy[j-X]\n            ans = max(ans, t)\n        return ans\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func maxSatisfied(_ customers: [Int], _ grumpy: [Int], _ minutes: Int) -> Int {\n        let len = customers.count\n        var winSum = 0, rightSum = 0, leftSum = 0\n        // 右区间的值\n        for i in minutes..<len {\n            if grumpy[i] == 0 {\n                rightSum += customers[i]\n            }\n        }\n        // 窗口的值\n        for i in 0..<minutes {\n            winSum += customers[i]\n        }\n        var maxCustomer = winSum + leftSum + rightSum\n        // 窗口左边缘\n        var left = 1, right = minutes\n        while right < len {\n            // 重新计算左区间的值，也可以用 customer 值和 grumpy 值相乘获得\n            if grumpy[left - 1] == 0 {\n                leftSum += customers[left - 1]\n            }\n            // 重新计算右区间值\n            if grumpy[right] == 0 {\n                rightSum -= customers[right]\n            }\n            // 窗口值\n            winSum = winSum - customers[left - 1] + customers[right]\n            maxCustomer = max(maxCustomer, winSum + leftSum + rightSum) // 保留最大值\n            // 移动窗口\n            left += 1\n            right += 1\n        }\n\n        return maxCustomer\n    }\n}\n```\n\nC++ Code\n\n```C++\nclass Solution\n{\npublic:\n    int maxSatisfied(vector<int> &customers, vector<int> &grumpy, int minutes)\n    {\n        for_each(grumpy.begin(), grumpy.end(), [](auto &g){ g = !g; });\n        vector<int> osum(customers.size(), 0);\n\n        //先初始化第一个元素\n        osum[0] = customers[0] * grumpy[0];\n        //计算前缀和, osum是origin sum\n        for (int i = 1; i < osum.size(); i++)\n        {\n            osum[i] = osum[i - 1] + customers[i] * grumpy[i];\n        }\n\n        //计算连续minutes的和\n        vector<int> msum(customers.size() - minutes + 1, 0);\n        for (int i = 0; i < minutes; i++)\n        {\n            msum[0] += customers[i];\n        }\n        for (int i = 1; i < msum.size(); i++)\n        {\n            msum[i] = msum[i - 1] - customers[i - 1] + customers[i + minutes - 1];\n        }\n\n        //分成三段计算\n        int result = 0;\n        for (int i = 0; i < msum.size(); i++)\n        {\n            //左                                         中         右\n            //注意左的边界条件, 可以使用边界测试\n            int sum = ((i - 1 >= 0) ? osum[i - 1] : 0) + msum[i] + osum[osum.size() - 1] - osum[i + minutes - 1];\n            if (sum > result)\n                result = sum;\n        }\n\n        return result;\n    }\n};\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode1438绝对值不超过限制的最长子数组.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [1438. 绝对差不超过限制的最长连续子数组](https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/)\n\n给你一个整数数组 nums ，和一个表示限制的整数 limit，请你返回最长连续子数组的长度，该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。\n\n如果不存在满足条件的子数组，则返回 0 。\n\n示例\n\n> 输入：nums = [10,1,2,4,7,2], limit = 5\n> 输出：4\n> 解释：满足题意的最长子数组是 [2,4,7,2]，其最大绝对差 |2-7| = 5 <= 5 。\n\n**提示：**\n\n- 1 <= nums.length <= 10^5\n\n- 1 <= nums[i] <= 10^9\n- 0 <= limit <= 10^9\n\n**题目解析**\n\n我们结合题目，示例，提示来看，这个题目也可以使用滑动窗口的思想来解决。我们需要判断某个子数组是否满足最大绝对差不超过限制值。\n\n那么我们应该怎么解决呢？\n\n我们想一下，窗口内的最大绝对差，如果我们知道窗口的最大值和最小值，最大值减去最小值就能得到最大绝对差。\n\n所以我们这个问题就变成了获取滑动窗口内的最大值和最小值问题，哦？滑动窗口的最大值，是不是很熟悉，大家可以先看一下[滑动窗口的最大值](https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/solution/yi-shi-pin-sheng-qian-yan-shuang-duan-du-mbga/)这个题目，那我们完全可以借助刚才题目的思想来解决这个题目。啪的一下我就搞懂了。\n\n滑动窗口的最大值，我们当时借助了双端队列，来维护一个单调递减的双端队列，进而得到滑动窗口的最大值\n\n那么我们同样可以借助双端队列，来维护一个单调递增的双端队列，来获取滑动窗口的最小值。既然知道了最大值和最小值，我们就可以判断当前窗口是否符合要求，如果符合要求则扩大窗口，不符合要求则缩小窗口，循环结束返回最大的窗口值即可。\n\n下面我们来看一下我们的动画模拟，一下就能看懂！\n\n<img src=\"https://img-blog.csdnimg.cn/20210320092423565.gif\" style=\"zoom:150%;\" />\n\n其实，我们只要把握两个重点即可，我们的 maxdeque 维护的是一个单调递减的双端队列，头部为当前窗口的最大值， mindeque 维护的是一个单调递增的双端队列，头部为窗口的最小值，即可。好啦我们一起看代码吧。\n\nJava Code:\n\n```java\nclass Solution {\n    public int longestSubarray(int[] nums, int limit) {\n\n        Deque<Integer> maxdeque = new LinkedList<>();\n        Deque<Integer> mindeque = new LinkedList<>();\n        int len = nums.length;\n        int right = 0, left = 0, maxwin = 0;\n\n        while (right < len) {\n             while (!maxdeque.isEmpty() && maxdeque.peekLast() < nums[right]) {\n                  maxdeque.removeLast();\n             }\n             while (!mindeque.isEmpty() && mindeque.peekLast() > nums[right]) {\n                  mindeque.removeLast();\n             }\n             //需要更多视频解算法，可以来我的公众号：程序厨\n             maxdeque.addLast(nums[right]);\n             mindeque.addLast(nums[right]);\n             while (maxdeque.peekFirst() - mindeque.peekFirst() > limit) {\n                if (maxdeque.peekFirst() == nums[left]) maxdeque.removeFirst();\n                if (mindeque.peekFirst() == nums[left]) mindeque.removeFirst();\n                 left++;\n             }\n             //保留最大窗口\n             maxwin = Math.max(maxwin,right-left+1);\n             right++;\n        }\n        return maxwin;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nimport collections\nclass Solution:\n    def longestSubarray(self, nums: List[int], limit: int)->int:\n        maxdeque = collections.deque()\n        mindeque = collections.deque()\n        leng = len(nums)\n        right = 0\n        left = 0\n        maxwin = 0\n        while right < leng:\n            while len(maxdeque) != 0 and maxdeque[-1] < nums[right]:\n                maxdeque.pop()\n            while len(mindeque) != 0 and mindeque[-1] > nums[right]:\n                mindeque.pop()\n\n            maxdeque.append(nums[right])\n            mindeque.append(nums[right])\n            while (maxdeque[0] - mindeque[0]) > limit:\n                if maxdeque[0] == nums[left]:\n                    maxdeque.popleft()\n                if mindeque[0] == nums[left]:\n                    mindeque.popleft()\n                left += 1\n            # 保留最大窗口\n            maxwin = max(maxwin, right - left + 1)\n            right += 1\n        return maxwin\n```\n\nSwift Code\n\nSwift：数组模拟，超时（58 / 61 个通过测试用例）\n\n```swift\nclass Solution {\n    func longestSubarray(_ nums: [Int], _ limit: Int) -> Int {\n        var maxQueue:[Int] = []\n        var minQueue:[Int] = []\n        let len = nums.count\n        var right = 0, left = 0, maxWin = 0\n        while right < len {\n            while !maxQueue.isEmpty && (maxQueue.last! < nums[right]) {\n                maxQueue.removeLast()\n            }\n            while !minQueue.isEmpty && (minQueue.last! > nums[right]) {\n                minQueue.removeLast()\n            }\n            maxQueue.append(nums[right])\n            minQueue.append(nums[right])\n            while (maxQueue.first! - minQueue.first!) > limit {\n                if maxQueue.first! == nums[left] {\n                    maxQueue.removeFirst()\n                }\n                if minQueue.first! == nums[left] {\n                    minQueue.removeFirst()\n                }\n                left += 1\n            }\n            maxWin = max(maxWin, right - left + 1)\n            right += 1\n        }\n        return maxWin\n    }\n}\n```\n\nSwift：使用双端队列（击败了 100.00%）\n\n```swift\nclass Solution {\n    func longestSubarray(_ nums: [Int], _ limit: Int) -> Int {\n        var maxQueue = Deque<Int>.init()\n        var minQueue = Deque<Int>.init()\n        let len = nums.count\n        var right = 0, left = 0, maxWin = 0\n        while right < len {\n            while !maxQueue.isEmpty && (maxQueue.peekBack()! < nums[right]) {\n                maxQueue.dequeueBack()\n            }\n            while !minQueue.isEmpty && (minQueue.peekBack()! > nums[right]) {\n                minQueue.dequeueBack()\n            }\n            maxQueue.enqueue(nums[right])\n            minQueue.enqueue(nums[right])\n            while (maxQueue.peekFront()! - minQueue.peekFront()!) > limit {\n                if maxQueue.peekFront()! == nums[left] {\n                    maxQueue.dequeue()\n                }\n                if minQueue.peekFront()! == nums[left] {\n                    minQueue.dequeue()\n                }\n                left += 1\n            }\n            maxWin = max(maxWin, right - left + 1)\n            right += 1\n        }\n        return maxWin\n    }\n\n    // 双端队列数据结构\n    public struct Deque<T> {\n        private var array: [T?]\n        private var head: Int\n        private var capacity: Int\n        private let originalCapacity: Int\n\n        public init(_ capacity: Int = 10) {\n            self.capacity = max(capacity, 1)\n            originalCapacity = self.capacity\n            array = [T?](repeating: nil, count: capacity)\n            head = capacity\n        }\n\n        public var isEmpty: Bool {\n            return count == 0\n        }\n\n        public var count: Int {\n            return array.count - head\n        }\n\n        public mutating func enqueue(_ element: T) {\n            array.append(element)\n        }\n\n        public mutating func enqueueFront(_ element: T) {\n            if head == 0 {\n            capacity *= 2\n            let emptySpace = [T?](repeating: nil, count: capacity)\n            array.insert(contentsOf: emptySpace, at: 0)\n            head = capacity\n            }\n\n            head -= 1\n            array[head] = element\n        }\n\n        public mutating func dequeue() -> T? {\n            guard head < array.count, let element = array[head] else { return nil }\n\n            array[head] = nil\n            head += 1\n\n            if capacity >= originalCapacity && head >= capacity*2 {\n            let amountToRemove = capacity + capacity/2\n            array.removeFirst(amountToRemove)\n            head -= amountToRemove\n            capacity /= 2\n            }\n            return element\n        }\n\n        public mutating func dequeueBack() -> T? {\n            if isEmpty {\n            return nil\n            } else {\n            return array.removeLast()\n            }\n        }\n\n        public func peekFront() -> T? {\n            if isEmpty {\n            return nil\n            } else {\n            return array[head]\n            }\n        }\n\n        public func peekBack() -> T? {\n            if isEmpty {\n            return nil\n            } else {\n            return array.last!\n            }\n        }\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc longestSubarray(nums []int, limit int) int {\n    maxdeq := []int{} // 递减队列\n    mindeq := []int{} // 递增队列\n\n    length := len(nums)\n    left, right, maxwin := 0, 0, 0\n    for right < length {\n        for len(maxdeq) != 0 && maxdeq[len(maxdeq) - 1] < nums[right] {\n            maxdeq = maxdeq[: len(maxdeq) - 1]\n        }\n        maxdeq = append(maxdeq, nums[right])\n\n        for len(mindeq) != 0 && mindeq[len(mindeq) - 1] > nums[right] {\n            mindeq = mindeq[: len(mindeq) - 1]\n        }\n        mindeq = append(mindeq, nums[right])\n\n        for maxdeq[0] - mindeq[0] > limit {\n            if maxdeq[0] == nums[left] {\n                maxdeq = maxdeq[1:]\n            }\n            if mindeq[0] == nums[left] {\n                mindeq = mindeq[1:]\n            }\n            left++\n        }\n        maxwin = max(maxwin, right - left + 1)\n        right++\n    }\n    return maxwin\n}\n\nfunc max(a, b int) int {\n    if a > b {\n        return a\n    }\n    return b\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode1两数之和.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [1. 两数之和](https://leetcode-cn.com/problems/two-sum/)\n\n**题目描述：**\n\n> 给定一个整数数组 nums 和一个目标值 target，请你在该数组中找出和为目标值的那 两个 整数，并返回他们的数组下标。\n>\n> 你可以假设每种输入只会对应一个答案。但是，数组中同一个元素不能使用两遍。\n\n**示例:**\n\n> 给定 nums = [2, 7, 11, 15], target = 9\n>\n> 因为 nums[0] + nums[1] = 2 + 7 = 9\n> 所以返回 [0, 1]\n\n题目很容易理解，即让查看数组中有没有两个数的和为目标数，如果有的话则返回两数下标，我们为大家提供两种解法双指针（暴力）法，和哈希表法\n\n**双指针（暴力）法**\n\n**解析**\n\n双指针（L,R）法的思路很简单，L 指针用来指向第一个值，R 指针用来从第 L 指针的后面查找数组中是否含有和 L 指针指向值和为目标值的数。见下图\n\n![图示](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信图片_20210104150003.3unncifeoe80.jpg)\n\n例：绿指针指向的值为 3，蓝指针需要在绿指针的后面遍历查找是否含有 target - 3 = 2 的元素，若含有返回即可。\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] twoSum(int[] nums, int target) {\n        if(nums.length < 2){\n            return new int[0];\n        }\n        int[] rearr = new int[2];\n        //查询元素\n        for(int i = 0; i < nums.length; i++){\n            for(int j = i+1; j < nums.length; j++ ){\n                //发现符合条件情况\n                if(nums[i] + nums[j] ==target){\n                    rearr[0] = i;\n                    rearr[1] = j;\n                }\n            }\n        }\n        return rearr;\n    }\n}\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def twoSum(nums: List[int], target: int)->List[int]:\n        if len(nums) < 2:\n            return [0]\n        rearr = [0] * 2\n        # 查询元素\n        for i in range(0, len(nums)):\n            for j in range(i + 1, len(nums)):\n                # 发现符合条件情况\n                if nums[i] + nums[j] == target:\n                    rearr[0] = i\n                    rearr[1] = j\n        return rearr\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func twoSum(_ nums: [Int], _ target: Int) -> [Int] {\n        let count = nums.count\n        if count < 2 {\n            return [0]\n        }\n\n        var rearr: [Int] = []\n        // 查询元素\n        for i in 0..<count {\n            for j in i+1..<count {\n                // 发现符合条件情况\n                if nums[i] + nums[j] == target {\n                    rearr.append(i)\n                    rearr.append(j)\n                }\n            }\n        }\n        return rearr\n    }\n}\n```\n\n**哈希表**\n\n**解析**\n\n哈希表的做法很容易理解，我们只需通过一次循环即可，假如我们的 target 值为 9，当前指针指向的值为 2 ，我们只需从哈希表中查找是否含有 7，因为 9 - 2 =7 。如果含有 7 我们直接返回即可，如果不含有则将当前的 2 存入哈希表中，指针移动，指向下一元素。注： key 为元素值，value 为元素索引。\n\n**动图解析：**\n\n![两数之和](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/两数之和.7228lcxkqpw0.gif)\n\n是不是很容易理解，下面我们来看一下题目代码。\n\n**题目代码：**\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] twoSum(int[] nums, int target) {\n        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();\n        for(int i = 0; i < nums.length; i++){\n            //如果存在则返回\n            if(map.containsKey(target-nums[i])){\n                return new int[]{map.get(target-nums[i]),i};\n            }\n            //不存在则存入\n            map.put(nums[i],i);\n\n        }\n        return new int[0];\n\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    vector<int> twoSum(vector<int>& nums, int target) {\n        unordered_map<int, int> m;\n        for (int i = 0; i < nums.size(); ++i) {\n            int t = target - nums[i];\n            if (m.count(t)) return { m[t], i };\n            m[nums[i]] = i;\n        }\n        return {};\n    }\n};\n```\n\nJS Code:\n\n```js\nconst twoSum = function (nums, target) {\n  const map = new Map();\n  for (let i = 0; i < nums.length; i++) {\n    const diff = target - nums[i];\n    if (map.has(diff)) {\n      return [map.get(diff), i];\n    }\n    map.set(nums[i], i);\n  }\n};\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def twoSum(self, nums: List[int], target: int)->List[int]:\n        m = {}\n        for i in range(0, len(nums)):\n            # 如果存在则返回\n            if (target - nums[i]) in m.keys():\n                return [m[target - nums[i]], i]\n            # 不存在则存入\n            m[nums[i]] = i\n        return [0]\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func twoSum(_ nums: [Int], _ target: Int) -> [Int] {\n        var m:[Int:Int] = [:]\n        for i in 0..<nums.count {\n            let n = nums[i]\n            if let k = m[target - n] { // 如果存在则返回\n                return [k, i]\n            }\n            m[n] = i // 不存在则存入\n        }\n        return [0]\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc twoSum(nums []int, target int) []int {\n    m := make(map[int]int)\n    for i, num := range nums {\n        if v, ok := m[target - num]; ok {\n            return []int{v, i}\n        }\n        m[num] = i\n    }\n    return []int{}\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode219数组中重复元素2.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### [219 数组中重复元素 2](https://leetcode-cn.com/problems/contains-duplicate-ii/)\n\n**题目描述**\n\n给定一个整数数组和一个整数 k，判断数组中是否存在两个不同的索引 i 和 j，使得 nums [i] = nums [j]，并且 i 和 j 的差的 绝对值 至多为 k。\n\n示例 1:\n\n> 输入: nums = [1,2,3,1], k = 3\n> 输出: true\n\n示例 2:\n\n> 输入: nums = [1,0,1,1], k = 1\n> 输出: true\n\n示例 3:\n\n> 输入: nums = [1,2,3,1,2,3], k = 2\n> 输出: false\n\n**Hashmap**\n\n这个题目和我们上面那个数组中的重复数字几乎相同，只不过是增加了一个判断相隔是否小于 K 位的条件，我们先用 HashMap 来做一哈，和刚才思路一致，我们直接看代码就能整懂\n\nJava Code:\n\n```java\nclass Solution {\n    public boolean containsNearbyDuplicate(int[] nums, int k) {\n        //特殊情况\n        if (nums.length == 0) {\n            return false;\n        }\n        // hashmap\n        HashMap<Integer,Integer> map = new HashMap<>();\n        for (int i = 0; i < nums.length; i++) {\n            // 如果含有\n            if (map.containsKey(nums[i])) {\n                //判断是否小于K，如果小于等于则直接返回\n                int abs = Math.abs(i - map.get(nums[i]));\n                if (abs <= k)  return true;//小于等于则返回\n            }\n            //更新索引，此时有两种情况，不存在，或者存在时，将后出现的索引保存\n            map.put(nums[i],i);\n        }\n        return false;\n    }\n}\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def containsNearbyDuplicate(self, nums: List[int], k: int)->bool:\n        # 特殊情况\n        if len(nums) == 0:\n            return False\n        # 字典\n        m = {}\n        for i in range(0, len(nums)):\n            # 如果含有\n            if nums[i] in m.keys():\n                # 判断是否小于K，如果小于等于则直接返回\n                a = abs(i - m[nums[i]])\n                if a <= k:\n                    return True# 小于等于则返回\n            # 更新索引，此时有两种情况，不存在，或者存在时，将后出现的索引保存\n            m[nums[i]] = i\n        return False\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    bool containsNearbyDuplicate(vector<int>& nums, int k) {\n        unordered_map <int, int> m;\n        for(int i = 0; i < nums.size(); ++i){\n            if(m.count(nums[i]) && i - m[nums[i]] <= k) return true;\n            m[nums[i]] = i;\n        }\n        return false;\n    }\n};\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func containsNearbyDuplicate(_ nums: [Int], _ k: Int) -> Bool {\n        if nums.count == 0 {\n            return false\n        }\n        var dict:[Int:Int] = [:]\n        for i in 0..<nums.count {\n            // 如果含有\n            if let v = dict[nums[i]] {\n                // 判断是否小于K，如果小于等于则直接返回\n                let abs = abs(i - v)\n                if abs <= k {\n                    return true\n                }\n            }\n            // 更新索引，此时有两种情况，不存在，或者存在时，将后出现的索引保存\n            dict[nums[i]] = i\n        }\n        return false\n    }\n}\n```\n\n**HashSet**\n\n**解析**\n\n这个方法算是属于固定滑动窗口。我们需要维护一个长度为 K 的滑动窗口，如果窗口内含有该值，则直接返回 true，尾部进入新元素时，则将头部的元素去掉。继续查看是否含有该元素。下面我们来看视频解析吧，保证以下就能搞懂了。\n\n![leetcode219数组中重复元素2](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/leetcode219数组中重复元素2.6m947ehfpb40.gif)\n\n**题目代码**\n\nJava Code\n\n```java\nclass Solution {\n    public boolean containsNearbyDuplicate(int[] nums, int k) {\n        //特殊情况\n        if (nums.length == 0) {\n            return false;\n        }\n        // set\n        HashSet<Integer> set = new HashSet<>();\n        for (int i = 0; i < nums.length; ++i) {\n            //含有该元素，返回true\n            if (set.contains(nums[i])) {\n                return true;\n            }\n            // 添加新元素\n            set.add(nums[i]);\n            //维护窗口长度\n            if (set.size() > k) {\n                set.remove(nums[i-k]);\n            }\n        }\n        return false;\n    }\n}\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def containsNearbyDuplicate(self, nums: List[int], k: int)->bool:\n        # 特殊情况\n        if len(nums) == 0:\n            return False\n        # 集合\n        s = set()\n        for i in range(0, len(nums)):\n            # 如果含有，返回True\n            if nums[i] in s:\n                return True\n            # 添加新元素\n            s.add(nums[i])\n            # 维护窗口长度\n            if len(s) > k:\n                s.remove(nums[i - k])\n        return False\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    bool containsNearbyDuplicate(vector<int>& nums, int k) {\n        multiset <int> S;\n        for(int i = 0; i < nums.size(); ++i){\n            if(S.count(nums[i])) return true;\n            S.insert(nums[i]);\n            if(S.size() > k) S.erase(nums[i - k]);\n        }\n        return false;\n    }\n};\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func containsNearbyDuplicate(_ nums: [Int], _ k: Int) -> Bool {\n        if nums.count == 0 {\n            return false\n        }\n        var set:Set<Int> = []\n        for i in 0..<nums.count {\n            // 含有该元素，返回true\n            if set.contains(nums[i]) {\n                return true\n            }\n            // 添加新元素\n            set.insert(nums[i])\n            if set.count > k {\n                set.remove(nums[i - k])\n            }\n        }\n        return false\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc containsNearbyDuplicate(nums []int, k int) bool {\n    length := len(nums)\n    if length == 0 {\n        return false\n    }\n    m := map[int]int{}\n    for i := 0; i < length; i++ {\n        if v, ok := m[nums[i]]; ok {\n            if i - v <= k {\n                return true\n            }\n        }\n        m[nums[i]] = i\n    }\n    return false\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode27移除元素.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [27. 移除元素](https://leetcode-cn.com/problems/remove-element/)\n\n**题目描述**\n\n> 给你一个数组 nums 和一个值 val，你需要 原地 移除所有数值等于 val 的元素，并返回移除后数组的新长度。\n>\n> 不要使用额外的数组空间，你必须仅使用 O(1) 额外空间并 原地 修改输入数组。\n>\n> 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。\n\n**示例 1:**\n\n> 给定 nums = [3,2,2,3], val = 3,\n>\n> 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。\n>\n> 你不需要考虑数组中超出新长度后面的元素。\n\n**示例 2:**\n\n> 给定 nums = [0,1,2,2,3,0,4,2], val = 2,\n>\n> 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。\n>\n> 注意这五个元素可为任意顺序。\n>\n> 你不需要考虑数组中超出新长度后面的元素。\n\n**暴力法**\n\n**解析**\n\n该题目也算是简单题目，适合新手来做，然后大家也不要看不起暴力解法，我们可以先写出暴力解法，然后再思考其他方法，这对于我们的编码能力有很大的帮助。我们来解析一下这个题目的做题思路，他的含义就是让我们删除掉数组中的元素，然后将数组后面的元素跟上来。最后返回删除掉元素的数组长度即可。比如数组长度为 10，里面有 2 个目标值，我们最后返回的长度为 8，但是返回的 8 个元素，需要排在数组的最前面。那么暴力解法的话则就需要两个 for 循环，一个用来找到删除，另一个用来更新数组。\n\n![移除数组元素暴力法](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/移除数组元素.lhuefelqd5o.png)\n\n总体思路就是这样的，后面的会不断往前覆盖。暴力解法也是不超时的，实现也不算太简单主要需要注意两个地方。\n\n（1）需要先定义变量 len 获取数组长度，因为后面我们的返回的数组长度是改变的，所以不可以用 nums.length 作为上界\n\n（2）我们每找到一个需要删除的值的时候，需要 i--，防止出现多个需要删除的值在一起的情况，然后漏删。\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public int removeElement(int[] nums, int val) {\n        //获取数组长度\n        int len = nums.length;\n        if (len == 0) {\n            return 0;\n        }\n        int i = 0;\n        for (i = 0; i < len; ++i) {\n            //发现符合条件的情况\n            if (nums[i] == val) {\n                //前移一位\n                for (int j = i; j < len-1; ++j) {\n                    nums[j] = nums[j+1];\n                }\n                i--;\n                len--;\n            }\n        }\n        return i;\n    }\n}\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def removeElement(self, nums: List[int], val: int)->int:\n        # 获取数组长度\n        leng = len(nums)\n        if 0 == leng:\n            return 0\n        i = 0\n        while i < leng:\n            # 发现符合条件的情况\n            if nums[i] == val:\n                # 前移一位\n                for j in range(i, leng - 1):\n                    nums[j] = nums[j + 1]\n                i -= 1\n                leng -= 1\n            i += 1\n        return i\n```\n\n**双指针**\n\n快慢指针的做法比较有趣，只需要一个 for 循环即可解决，时间复杂度为 O(n) ,总体思路就是有两个指针，前面一个后面一个，前面的用于搜索需要删除的值，当遇到需要删除的值时，前指针直接跳过，后面的指针不动，当遇到正常值时，两个指针都进行移动，并修改慢指针的值。最后只需输出慢指针的索引即可。\n\n**动图解析:**\n\n![](https://img-blog.csdnimg.cn/20210317194638700.gif#pic_center)\n\n**题目代码：**\n\nJava Code:\n\n```java\nclass Solution {\n    public int removeElement(int[] nums, int val) {\n          int len = nums.length;\n          if (len == 0) {\n              return 0;\n          }\n          int i = 0;\n          for (int j = 0; j < len; ++j) {\n                //如果等于目标值，则删除\n                if (nums[j] == val) {\n                    continue;\n                }\n                // 不等于目标值时，则赋值给nums[i],i++\n                nums[i++] = nums[j];\n          }\n          return i;\n    }\n}\n```\n\nPython3 Code:\n\n```python\nclass Solution:\n    def removeElement(self, nums: List[int], val: int) -> int:\n        i = 0\n        for j in range(len(nums)):\n            if nums[j] != val:\n                nums[i] = nums[j]\n                i += 1\n        return i\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int removeElement(vector<int>& nums, int val) {\n        int n = nums.size();\n      \tif(!n) return 0;\n      \tint i = 0;\n      \tfor(int j = 0; j < n; ++j){\n          if(nums[j] == val) continue;\n          nums[i++] = nums[j];\n        }\n      \treturn i;\n    }\n};\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func removeElement(_ nums: inout [Int], _ val: Int) -> Int {\n        if nums.count == 0 {\n            return 0\n        }\n        var i = 0\n        for j in 0..<nums.count {\n            if nums[j] != val {\n                nums[i] = nums[j]\n                i += 1\n            }\n\n        }\n        return i\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc removeElement(nums []int, val int) int {\n    length := len(nums)\n    if length == 0 {\n        return 0\n    }\n    i := 0\n    for j := 0; j < length; j++ {\n        if nums[j] == val {\n            continue\n        }\n        nums[i] = nums[j]\n        i++\n    }\n    return i\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode41缺失的第一个正数.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [41. 缺失的第一个正数](https://leetcode-cn.com/problems/first-missing-positive/)\n\n给你一个未排序的整数数组，请你找出其中没有出现的最小的正整数。\n\n示例 1:\n\n> 输入: [1,2,0]\n> 输出: 3\n\n示例 2:\n\n> 输入: [3,4,-1,1]\n> 输出: 2\n\n示例 3:\n\n> 输入: [7,8,9,11,12]\n> 输出: 1\n\n### 重复遍历\n\n让我们找出缺失的最小正整数，而且这是一个未排序的数组，我们可以利用暴力求解，挨个遍历发现那个不存在直接返回即可。我们这里使用两种方法解决这个问题，大家也可以提出自己的做法。\n\n我们既然是返回缺失的正整数，那么我们则可以将这个数组中的所有正整数保存到相应的位置，见下图。\n\n![微信截图_20210109135536](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210109135536.41h4amio2me0.png)\n\n上图中，我们遍历一遍原数组，将正整数保存到新数组中，然后遍历新数组，第一次发现 newnum[i] != i 时，则说明该值是缺失的，返回即可，例如我上图中的第一个示例中的 2，如果遍历完新数组，发现说所有值都对应，说明缺失的是 新数组的长度对应的那个数，比如第二个示例中 ，新数组的长度为 5，此时缺失的为 5，返回长度即可，很容易理解。\n\n注：我们发现我们新的数组长度比原数组大 1，是因为我们遍历新数组从 1，开始遍历。\n\n动图解析\n\n![缺失的第一个正数](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/缺失的第一个正数.1it1cow5aa8w.gif)\n\nJava Code:\n\n```java\nclass Solution {\n    public int firstMissingPositive(int[] nums) {\n        if (nums.length == 0) {\n            return 1;\n        }\n        //因为是返回第一个正整数，不包括 0，所以需要长度加1，细节1\n        int[] res = new int[nums.length + 1];\n        //将数组元素添加到辅助数组中\n        for (int x : nums) {\n            if (x > 0 && x < res.length) {\n               res[x] = x;\n            }\n        }\n        //遍历查找,发现不一样时直接返回\n        for (int i = 1; i < res.length; i++) {\n            if (res[i] != i) {\n                return i;\n            }\n        }\n        //缺少最后一个，例如 1，2，3此时缺少 4 ，细节2\n        return res.length;\n    }\n}\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def firstMissingPositive(self, nums: List[int])->int:\n        if len(nums) == 0:\n            return 1\n        # 因为是返回第一个正整数，不包括 0，所以需要长度加1，细节1\n        res = [0] * (len(nums) + 1)\n        # 将数组元素添加到辅助数组中\n        for x in nums:\n            if x > 0 and x < len(res):\n                res[x] = x\n        # 遍历查找,发现不一样时直接返回\n        for i in range(1, len(res)):\n            if res[i] != i:\n                return i\n        # 缺少最后一个，例如 1，2，3此时缺少 4 ，细节2\n        return len(res)\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func firstMissingPositive(_ nums: [Int]) -> Int {\n        if nums.count == 0 {\n            return 1\n        }\n        // 因为是返回第一个正整数，不包括 0，所以需要长度加1，细节1\n        var res:[Int] = Array.init(repeating: 0, count: nums.count + 1)\n        // 将数组元素添加到辅助数组中\n        for x in nums {\n            if x > 0 && x < res.count {\n                res[x] = x\n            }\n        }\n        // 遍历查找,发现不一样时直接返回\n        for i in 1..<res.count {\n            if res[i] != i {\n                return i\n            }\n        }\n        // 缺少最后一个，例如 1，2，3此时缺少 4 ，细节2\n        return res.count\n    }\n}\n```\n\n我们通过上面的例子了解这个解题思想，我们有没有办法不使用辅助数组完成呢？我们可以使用原地置换，直接在 nums 数组内，将值换到对应的索引处，与上个方法思路一致，只不过没有使用辅助数组，理解起来也稍微难理解一些。\n\n下面我们看一下原地置换的一些情况。\n\n注：红色代表待置换，绿色代表置换完毕\n\n![置换1](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/置换1.4j4pcz56ml40.png)\n\n![置换2](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/置换2.5rawbyws7h40.png)\n\n动图解析：\n\n![原地置换](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/原地置换.52wi0yoiu3o0.gif)\n\n题目代码：\n\nJava Code:\n\n```java\nclass Solution {\n    public int firstMissingPositive(int[] nums) {\n        int len = nums.length;\n        if (len == 0) {\n            return 1;\n        }\n        for (int i = 0; i < len; ++i) {\n            //需要考虑指针移动情况，大于0，小于len+1，不等与i+1，两个交换的数相等时，防止死循环\n            while (nums[i] > 0 && nums[i] < len + 1 && nums[i] != i+1 && nums[i] != nums[nums[i]-1]) {\n                swap(nums,i,nums[i] - 1);\n            }\n        }\n        //遍历寻找缺失的正整数\n        for (int i = 0; i < len; ++i) {\n            if(nums[i] != i+1) {\n                return i+1;\n            }\n        }\n        return len + 1;\n    }\n    //交换\n   public void swap(int[] nums, int i, int j) {\n        if (i != j) {\n            nums[i] ^= nums[j];\n            nums[j] ^= nums[i];\n            nums[i] ^= nums[j];\n        }\n    }\n}\n```\n\nPython3 Code:\n\n```python\nclass Solution:\n    def firstMissingPositive(self, nums: List[int]) -> int:\n        n = len(nums)\n\n        def swap(nums, a, b):\n            temp = nums[a]\n            nums[a] = nums[b]\n            nums[b] = temp\n        i = 0\n        while i < n:\n            num = nums[i]\n            # 已经就位\n            if num <= 0 or num >= n or num == i + 1 or nums[num - 1] == num:\n                i += 1\n            # 可以交换\n            else:\n                swap(nums, i, num - 1)\n        for i in range(n):\n            if nums[i] != i + 1:\n                return i + 1\n        return n + 1\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func firstMissingPositive(_ nums: [Int]) -> Int {\n        var nums = nums\n        let len = nums.count\n        if len == 0 {\n            return 1\n        }\n        // 遍历数组\n        for i in 0..<len {\n            // 需要考虑指针移动情况，大于0，小于len+1，不等与i+1，\n            // 两个交换的数相等时，防止死循环\n            while nums[i] > 0\n                  && nums[i] < len + 1\n                  && nums[i] != i + 1\n                  && nums[i] != nums[nums[i] - 1]\n            {\n                //nums.swapAt(i, (nums[i] - 1)) // 系统方法\n                self.swap(&nums, i, (nums[i] - 1)) // 自定义方法\n            }\n        }\n        // 遍历寻找缺失的正整数\n        for i in 0..<len {\n            if nums[i] != i + 1 {\n                return i + 1\n            }\n        }\n\n        return len + 1\n    }\n    func swap(_ nums: inout [Int], _ i: Int, _ j: Int) {\n        let temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n    }\n}\n```\n\nC++ Code\n\n```C++\nclass Solution\n{\npublic:\n    int firstMissingPositive(vector<int> &nums)\n    {\n        int size = nums.size();\n        //判断范围是否符合要求\n        auto inRange = [](auto s, auto e)\n        {\n            return [s, e](auto &n)\n            {\n                return e >= n && n >= s;\n            };\n        };\n        auto cusInRange = inRange(1, size);\n        //增加数组长度, 便于计算, 不需要再转换\n        nums.push_back(0);\n\n        for (int i = 0; i < size; i++)\n        {\n            //将不在正确位置的元素放到正确位置上\n            while (cusInRange(nums[i]) && nums[i] != i && nums[nums[i]] != nums[i])\n            {\n                swap(nums[i], nums[nums[i]]);\n            }\n        }\n\n        //找出缺失的元素\n        for (int i = 1; i <= size; i++)\n        {\n            if (nums[i] != i)\n                return i;\n        }\n        return size + 1;\n    }\n};\n```\n\nGo Code:\n\n```go\nfunc firstMissingPositive(nums []int) int {\n    length := len(nums)\n    if length == 0 { return 1 }\n    for i := 0; i < length; i++ {\n        // 将不在正确位置的元素放在正确的位置上。\n        for nums[i] > 0 && nums[i] < length + 1 && nums[i] != i + 1 && nums[i] != nums[nums[i] - 1] {\n            j := nums[i] - 1\n            nums[i], nums[j] = nums[j], nums[i]\n        }\n    }\n\t// 第一个不在正确位置上的元素就是结果。\n    for i := 0; i < length; i++ {\n        if nums[i] != i + 1 {\n            return i + 1\n        }\n    }\n    return length + 1\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode485最大连续1的个数.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [485. 最大连续 1 的个数](https://leetcode-cn.com/problems/max-consecutive-ones/)\n\n给定一个二进制数组， 计算其中最大连续 1 的个数。\n\n示例 1:\n\n> 输入: [1,1,0,1,1,1]\n> 输出: 3\n> 解释: 开头的两位和最后的三位都是连续 1，所以最大连续 1 的个数是 3.\n\n我的这个方法比较奇怪，但是效率还可以，战胜了 100% , 尽量减少了 Math.max()的使用，我们来看一下具体思路，利用 right 指针进行探路，如果遇到 1 则继续走，遇到零时则停下，求当前 1 的个数。\n\n这时我们可以通过 right - left 得到 1 的 个数，因为此时我们的 right 指针指在 0 处，所以不需要和之前一样通过 right - left + 1 获得窗口长度。\n\n然后我们再使用 while 循环，遍历完为 0 的情况，跳到下一段为 1 的情况，然后移动 left 指针。 left = right，站在同一起点，继续执行上诉过程。\n\n下面我们通过一个视频模拟代码执行步骤大家一下就能搞懂了。\n\n![leetcode485最长连续1的个数](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/leetcode485最长连续1的个数.7avzcthkit80.gif)\n\n下面我们直接看代码吧\n\nJava Code:\n\n```java\nclass Solution {\n    public int findMaxConsecutiveOnes(int[] nums) {\n\n        int len = nums.length;\n        int left = 0, right = 0;\n        int maxcount = 0;\n\n        while (right < len) {\n            if (nums[right] == 1) {\n                right++;\n                continue;\n            }\n            //保存最大值\n            maxcount = Math.max(maxcount, right - left);\n            //跳过 0 的情况\n            while (right < len && nums[right] == 0) {\n                right++;\n            }\n            //同一起点继续遍历\n            left = right;\n        }\n        return Math.max(maxcount, right-left);\n\n    }\n}\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def findMaxConsecutiveOnes(self, nums: List[int])->int:\n        leng = len(nums)\n        left = 0\n        right = 0\n        maxcount = 0\n        while right < leng:\n            if nums[right] == 1:\n                right += 1\n                continue\n            # 保存最大值\n            maxcount = max(maxcount, right - left)\n            # 跳过 0 的情况\n            while right < leng and nums[right] == 0:\n                right += 1\n            # 同一起点继续遍历\n            left = right\n        return max(maxcount, right - left)\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func findMaxConsecutiveOnes(_ nums: [Int]) -> Int {\n\n        var left = 0, right = 0, res = 0\n        let len = nums.count\n        while right < len {\n            if nums[right] == 1 {\n                right += 1\n                continue\n            }\n            // 保存最大值\n            res = max(res, right - left)\n            // 跳过 0 的情况\n            while right < len && nums[right] == 0 {\n                right += 1\n            }\n            // 同一起点继续遍历\n            left = right\n        }\n        return max(res, right - left)\n    }\n}\n```\n\n刚才的效率虽然相对高一些，但是代码不够优美，欢迎各位改进，下面我们说一下另外一种情况，一个特别容易理解的方法。\n\n我们通过计数器计数 连续 1 的个数，当 nums[i] == 1 时，count++，nums[i] 为 0 时，则先保存最大 count，再将 count 清零，因为我们需要的是连续的 1 的个数，所以需要清零。\n\n好啦，下面我们直接看代码吧。\n\nJava Code:\n\n```java\nclass Solution {\n    public int findMaxConsecutiveOnes(int[] nums) {\n\n        int count = 0;\n        int maxcount = 0;\n\n        for (int i = 0; i < nums.length; ++i) {\n            if (nums[i] == 1) {\n                count++;\n            //这里可以改成 while\n            } else {\n                 maxcount = Math.max(maxcount,count);\n                 count = 0;\n            }\n        }\n        return Math.max(count,maxcount);\n\n    }\n}\n```\n\nPython3 Code:\n\n```py\nclass Solution:\n    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:\n        ans = i = t = 0\n        for j in range(len(nums)):\n            if nums[j] == 1:\n                t += 1\n                ans = max(ans, t)\n            else:\n                i = j + 1\n                t = 0\n        return ans\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func findMaxConsecutiveOnes(_ nums: [Int]) -> Int {\n        let len = nums.count\n        var maxCount = 0, count = 0\n        for i in 0..<len {\n            if nums[i] == 1 {\n                count += 1\n            } else { // 这里可以改成 while\n                maxCount = max(maxCount, count)\n                count = 0\n            }\n        }\n        return max(maxCount, count)\n    }\n}\n```\n\nC++ Code\n\n```C++\nclass Solution\n{\npublic:\n    int findMaxConsecutiveOnes(vector<int> &nums)\n    {\n        int s = 0;\n        int e = 0;\n        int result = 0;\n        int size = nums.size();\n\n        while (s < size && e < size)\n        {\n            while (s < size && nums[s++] == 1)\n            {\n                e = s;\n                while (e < size && nums[e] == 1)\n                {\n                    e++;\n                };\n                //注意需要加1, 可以使用极限条件测试\n                int r = e - s + 1;\n                if (r > result)\n                    result = r;\n                s = e;\n            }\n        }\n\n        return result;\n    }\n};\n```\n\nGo Code:\n\n```go\nfunc findMaxConsecutiveOnes(nums []int) int {\n    cnt, maxCnt := 0, 0\n    for i := 0; i < len(nums); i++ {\n        if nums[i] == 1 {\n            cnt++\n        } else {\n            maxCnt = max(maxCnt, cnt)\n            cnt = 0\n        }\n    }\n    return max(maxCnt, cnt)\n}\n\nfunc max(a, b int) int {\n    if a > b {\n        return a\n    }\n    return b\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode54螺旋矩阵.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [54. 螺旋矩阵](https://leetcode-cn.com/problems/spiral-matrix/)\n\n题目描述\n\n_给定一个包含 m_ x n 个元素的矩阵（m 行, n 列），请按照顺时针螺旋顺序，返回矩阵中的所有元素。\n\n示例一\n\n> 输入：matrix = [[1,2,3],[4,5,6],[7,8,9]]\n> 输出：[1,2,3,6,9,8,7,4,5]\n\n示例二\n\n> 输入：matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]\n> 输出：[1,2,3,4,8,12,11,10,9,5,6,7]\n\n这个题目很细非常细，思路很容易想到，但是要是完全实现也不是特别容易，我们一起分析下这个题目，我们可以这样理解，我们像剥洋葱似的一步步的剥掉外皮，直到遍历结束，见下图。\n\n![](https://img-blog.csdnimg.cn/img_convert/cfa0192601dcc185e77125adc35e1cc5.png)\\*\n\n题目很容易理解，但是要想完全执行出来，也是不容易的，因为这里面的细节太多了，我们需要认真仔细的考虑边界。\n\n我们也要考虑重复遍历的情况即什么时候跳出循环。刚才我们通过箭头知道了我们元素的遍历顺序，这个题目也就完成了一大半了，下面我们来讨论一下什么时候跳出循环，见下图。\n\n注：这里需要注意的是，框框代表的是每个边界。\n\n![](https://img-blog.csdnimg.cn/20210318095839543.gif)\n\n题目代码：\n\nJava Code:\n\n```java\nclass Solution {\n    public List<Integer> spiralOrder(int[][] matrix) {\n\n        List<Integer> arr = new ArrayList<>();\n        int left = 0, right = matrix[0].length-1;\n        int top = 0, down = matrix.length-1;\n\n        while (true) {\n             for (int i = left; i <= right; ++i) {\n                 arr.add(matrix[top][i]);\n             }\n             top++;\n             if (top > down) break;\n             for (int i = top; i <= down; ++i) {\n                 arr.add(matrix[i][right]);\n             }\n             right--;\n             if (left > right) break;\n             for (int i = right; i >= left; --i) {\n                 arr.add(matrix[down][i]);\n             }\n             down--;\n             if (top > down) break;\n             for (int i = down; i >= top; --i) {\n                 arr.add(matrix[i][left]);\n             }\n             left++;\n             if (left > right) break;\n\n        }\n        return arr;\n    }\n}\n\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    vector<int> spiralOrder(vector<vector<int>>& matrix) {\n        vector <int> arr;\n        int left = 0, right = matrix[0].size()-1;\n        int top = 0, down = matrix.size()-1;\n        while (true) {\n             for (int i = left; i <= right; ++i) {\n                 arr.emplace_back(matrix[top][i]);\n             }\n             top++;\n             if (top > down) break;\n             for (int i = top; i <= down; ++i) {\n                 arr.emplace_back(matrix[i][right]);\n             }\n             right--;\n             if (left > right) break;\n             for (int i = right; i >= left; --i) {\n                 arr.emplace_back(matrix[down][i]);\n             }\n             down--;\n             if (top > down) break;\n             for (int i = down; i >= top; --i) {\n                 arr.emplace_back(matrix[i][left]);\n             }\n             left++;\n             if (left > right) break;\n        }\n        return arr;\n    }\n};\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def spiralOrder(self, matrix: List[List[int]])->List[int]:\n        arr = []\n        left = 0\n        right = len(matrix[0]) - 1\n        top = 0\n        down = len(matrix) - 1\n        while True:\n            for i in range(left, right + 1):\n                arr.append(matrix[top][i])\n            top += 1\n            if top > down:\n                break\n            for i in range(top, down + 1):\n                arr.append(matrix[i][right])\n            right -= 1\n            if left > right:\n                break\n            for i in range(right, left - 1, -1):\n                arr.append(matrix[down][i])\n            down -= 1\n            if top > down:\n                break\n            for i in range(down, top - 1, -1):\n                arr.append(matrix[i][left])\n            left += 1\n            if left > right:\n                break\n        return arr\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func spiralOrder(_ matrix: [[Int]]) -> [Int] {\n        var arr:[Int] = []\n        var left = 0, right = matrix[0].count - 1\n        var top = 0, down = matrix.count - 1\n\n        while (true) {\n            for i in left...right {\n                arr.append(matrix[top][i])\n            }\n            top += 1\n            if top > down { break }\n            for i in top...down {\n                arr.append(matrix[i][right])\n            }\n            right -= 1\n            if left > right { break}\n            for i in stride(from: right, through: left, by: -1) {\n                arr.append(matrix[down][i])\n            }\n            down -= 1\n            if top > down { break}\n            for i in stride(from: down, through: top, by: -1) {\n                arr.append(matrix[i][left])\n            }\n            left += 1\n            if left > right { break}\n        }\n\n        return arr\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc spiralOrder(matrix [][]int) []int {\n    res := []int{}\n    left, right := 0, len(matrix[0]) - 1\n    top, down   := 0, len(matrix) - 1\n\n    for {\n        for i := left; i <= right; i++ {\n            res = append(res, matrix[top][i])\n        }\n        top++\n        if top > down { break }\n\n        for i := top; i <= down; i++ {\n            res = append(res, matrix[i][right])\n        }\n        right--\n        if left > right { break }\n\n        for i := right; i >= left; i-- {\n            res = append(res, matrix[down][i])\n        }\n        down--\n        if top > down { break }\n\n        for i := down; i >= top; i-- {\n            res = append(res, matrix[i][left])\n        }\n        left++\n        if left > right { break }\n    }\n    return res\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode560和为K的子数组.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### [leetcode560. 和为 K 的子数组](https://leetcode-cn.com/problems/subarray-sum-equals-k/)\n\n**题目描述**\n\n> 给定一个整数数组和一个整数 k，你需要找到该数组中和为 k 的连续的子数组的个数。\n\n**示例 1 :**\n\n> 输入:nums = [1,1,1], k = 2\n> 输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。\n\n**暴力法**\n\n**解析**\n\n这个题目的题意很容易理解，就是让我们返回和为 k 的子数组的个数，所以我们直接利用双重循环解决该题，这个是很容易想到的。我们直接看代码吧。\n\nJava Code:\n\n```java\nclass Solution {\n    public int subarraySum(int[] nums, int k) {\n         int len = nums.length;\n         int sum = 0;\n         int count = 0;\n         for (int i = 0; i < len; ++i) {\n             for (int j = i; j < len; ++j) {\n                 sum += nums[j];\n                 if (sum == k) {\n                     count++;\n                 }\n             }\n             sum = 0;\n         }\n         return count;\n    }\n}\n```\n\nPython3 版本的代码会超时\n\nSwift 版本的代码会超时\n\n下面我们我们使用前缀和的方法来解决这个题目，那么我们先来了解一下前缀和是什么东西。其实这个思想我们很早就接触过了。见下图\n\n![](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截图_20210113193831.4wk2b9zc8vm0.png)\n\n我们通过上图发现，我们的 presum 数组中保存的是 nums 元素的和，presum[1] = presum[0] + nums[0];\n\npresum [2] = presum[1] + nums[1],presum[3] = presum[2] + nums[2] ... 所以我们通过前缀和数组可以轻松得到每个区间的和，\n\n例如我们需要获取 nums[2] 到 nums[4] 这个区间的和，我们则完全根据 presum 数组得到，是不是有点和我们之前说的字符串匹配算法中 BM,KMP 中的 next 数组和 suffix 数组作用类似。\n\n那么我们怎么根据 presum 数组获取 nums[2] 到 nums[4] 区间的和呢？见下图\n\n![前缀和](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/前缀和.77twdj3gpkg0.png)\n\n所以我们 nums[2] 到 nums[4] 区间的和则可以由 presum[5] - presum[2] 得到。\n\n也就是前 5 项的和减去前 2 项的和，得到第 3 项到第 5 项的和。那么我们可以遍历 presum 就能得到和为 K 的子数组的个数啦。\n\n直接上代码。\n\nJava Code:\n\n```java\nclass Solution {\n    public int subarraySum(int[] nums, int k) {\n        //前缀和数组\n        int[] presum = new int[nums.length+1];\n        for (int i = 0; i < nums.length; i++) {\n            //这里需要注意，我们的前缀和是presum[1]开始填充的\n            presum[i+1] = nums[i] + presum[i];\n        }\n        //统计个数\n        int count = 0;\n        for (int i = 0; i < nums.length; ++i) {\n            for (int j = i; j < nums.length; ++j) {\n                //注意偏移，因为我们的nums[2]到nums[4]等于presum[5]-presum[2]\n                //所以这样就可以得到nums[i,j]区间内的和\n                if (presum[j+1] - presum[i] == k) {\n                    count++;\n                }\n            }\n        }\n        return count;\n    }\n}\n```\n\nPython3 版本的代码也会超时\n\n我们通过上面的例子我们简单了解了前缀和思想，那么我们如果继续将其优化呢？\n\n**前缀和 + HashMap**\n\n**解析**\n\n其实我们在之前的两数之和中已经用到了这个方法，我们一起来回顾两数之和 HashMap 的代码.\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] twoSum(int[] nums, int target) {\n\n        HashMap<Integer,Integer> map  = new HashMap<>();\n        //一次遍历\n        for (int i = 0; i < nums.length; ++i) {\n            //存在时，我们用数组得值为 key，索引为 value\n            if (map.containsKey(target - nums[i])){\n               return new int[]{i,map.get(target-nums[i])};\n            }\n            //存入值\n            map.put(nums[i],i);\n        }\n        //返回\n        return new int[]{};\n    }\n}\n```\n\n上面代码中，我们将数组的值和索引存入 map 中，当我们遍历到某一值 x 时，判断 map 中是否含有 target - x，即可。其实我们现在这个题目和两数之和原理是一致的，只不过我们是将**所有的前缀和**该**前缀和出现的次数**存到了 map 里。下面我们来看一下代码的执行过程。\n\n**动图解析**\n\n![](https://img-blog.csdnimg.cn/2021031809231883.gif#pic_center)\n\n**题目代码**\n\n```java\nclass Solution {\n    public int subarraySum(int[] nums, int k) {\n        if (nums.length == 0) {\n            return 0;\n        }\n        HashMap<Integer,Integer> map = new HashMap<>();\n        //细节，这里需要预存前缀和为 0 的情况，会漏掉前几位就满足的情况\n        //例如输入[1,1,0]，k = 2 如果没有这行代码，则会返回0,漏掉了1+1=2，和1+1+0=2的情况\n        //输入：[3,1,1,0] k = 2时则不会漏掉\n        //因为presum[3] - presum[0]表示前面 3 位的和，所以需要map.put(0,1),垫下底\n        map.put(0, 1);\n        int count = 0;\n        int presum = 0;\n        for (int x : nums) {\n            presum += x;\n            //当前前缀和已知，判断是否含有 presum - k的前缀和，那么我们就知道某一区间的和为 k 了。\n            if (map.containsKey(presum - k)) {\n                count += map.get(presum - k);//获取presum-k前缀和出现次数\n            }\n            //更新\n            map.put(presum,map.getOrDefault(presum,0) + 1);\n        }\n        return count;\n    }\n}\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func subarraySum(_ nums: [Int], _ k: Int) -> Int {\n        if nums.count == 0 {\n            return 0\n        }\n        var map: [Int: Int] = [:]\n        map[0] = 1 // 需要添加入一个元素垫底，已支持前几位就满足的情况\n        var presum = 0, count = 0\n\n        for x in nums {\n            presum += x\n            //当前前缀和已知，判断是否含有 presum - k的前缀和，那么我们就知道某一区间的和为 k 了。\n            if let v = map[presum - k] {\n                count += v //获取presum-k前缀和出现次数\n            }\n            map[presum] = (map[presum] ?? 0) + 1\n        }\n        return count\n    }\n}\n```\n\nC++ Code\n\n```C++\nclass Solution\n{\npublic:\n    int subarraySum(vector<int> &nums, int k)\n    {\n        unordered_map<int, int> smp;\n        int sum = 0;\n        //初始化\"最外面\"的0\n        smp[0] = 1;\n        int result = 0;\n        for(int i = 0; i < nums.size(); i++)\n        {\n            sum += nums[i];\n            auto mp = smp.find(sum - k);\n            if (mp != smp.end())\n            {\n                //map里面存的一定是在前面的元素\n                //可以尝试将map的value换为数组\n                result += mp->second;\n            }\n            smp[sum]++;\n        }\n\n        return result;\n    }\n};\n```\n\nGo Code:\n\n```go\nfunc subarraySum(nums []int, k int) int {\n    m := map[int]int{}\n    m[0] = 1\n    sum := 0\n    cnt := 0\n    for _, num := range nums {\n        sum += num\n        if v, ok := m[sum - k]; ok {\n            cnt += v\n        }\n        m[sum]++\n    }\n    return cnt\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode59螺旋矩阵2.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### [59.螺旋矩阵 II](https://leetcode-cn.com/problems/spiral-matrix-ii)\n\n给你一个正整数 `n` ，生成一个包含 `1` 到 `n2` 所有元素，且元素按顺时针顺序螺旋排列的 `n x n` 正方形矩阵 `matrix` 。\n\n**示例 1：**\n\n> 输入：n = 3\n> 输出：[[1,2,3],[8,9,4],[7,6,5]]\n\n**示例 2：**\n\n> 输入：n = 1\n> 输出：[[1]]\n\n其实我们只要做过了螺旋矩阵 第一题，这个题目我们完全可以一下搞定，几乎没有进行更改，我们先来看下 **leetcode 54** 题的解析。\n\n### leetcode 54 螺旋矩阵\n\n题目描述\n\n_给定一个包含 m_ x n 个元素的矩阵（m 行, n 列），请按照顺时针螺旋顺序，返回矩阵中的所有元素。\n\n示例一\n\n> 输入：matrix = [[1,2,3],[4,5,6],[7,8,9]]\n> 输出：[1,2,3,6,9,8,7,4,5]\n\n示例二\n\n> 输入：matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]\n> 输出：[1,2,3,4,8,12,11,10,9,5,6,7]\n\n这个题目很细非常细，思路很容易想到，但是要是完全实现也不是特别容易，我们一起分析下这个题目，我们可以这样理解，我们像剥洋葱似的一步步的剥掉外皮，直到遍历结束，见下图。\n\n_![螺旋矩阵](https://pic.leetcode-cn.com/1615813563-uUiWlF-file_1615813563382)_\n\n题目很容易理解，但是要想完全执行出来，也是不容易的，因为这里面的细节太多了，我们需要认真仔细的考虑边界。\n\n我们也要考虑重复遍历的情况即什么时候跳出循环。刚才我们通过箭头知道了我们元素的遍历顺序，这个题目也就完成了一大半了，下面我们来讨论一下什么时候跳出循环，见下图。\n\n注：这里需要注意的是，框框代表的是每个边界。\n\n![](https://img-blog.csdnimg.cn/20210318095839543.gif)\n\n题目代码：\n\nJava Code:\n\n```java\nclass Solution {\n    public List<Integer> spiralOrder(int[][] matrix) {\n\n        List<Integer> arr = new ArrayList<>();\n        int left = 0, right = matrix[0].length-1;\n        int top = 0, down = matrix.length-1;\n\n        while (true) {\n             for (int i = left; i <= right; ++i) {\n                 arr.add(matrix[top][i]);\n             }\n             top++;\n             if (top > down) break;\n             for (int i = top; i <= down; ++i) {\n                 arr.add(matrix[i][right]);\n             }\n             right--;\n             if (left > right) break;\n             for (int i = right; i >= left; --i) {\n                 arr.add(matrix[down][i]);\n             }\n             down--;\n             if (top > down) break;\n             for (int i = down; i >= top; --i) {\n                 arr.add(matrix[i][left]);\n             }\n             left++;\n             if (left > right) break;\n\n        }\n        return arr;\n    }\n}\n\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def spiralOrder(self, matrix: List[List[int]])->List[int]:\n        arr = []\n        left = 0\n        right = len(matrix[0]) - 1\n        top = 0\n        down = len(matrix) - 1\n        while True:\n            for i in range(left, right + 1):\n                arr.append(matrix[top][i])\n            top += 1\n            if top > down:\n                break\n            for i in range(top, down + 1):\n                arr.append(matrix[i][right])\n            right -= 1\n            if left > right:\n                break\n            for i in range(right, left - 1, -1):\n                arr.append(matrix[down][i])\n            down -= 1\n            if top > down:\n                break\n            for i in range(down, top - 1, -1):\n                arr.append(matrix[i][left])\n            left += 1\n            if left > right:\n                break\n        return arr\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    vector<int> spiralOrder(vector<vector<int>>& matrix) {\n        vector <int> arr;\n        int left = 0, right = matrix[0].size()-1;\n        int top = 0, down = matrix.size()-1;\n        while (true) {\n             for (int i = left; i <= right; ++i) {\n                 arr.emplace_back(matrix[top][i]);\n             }\n             top++;\n             if (top > down) break;\n             for (int i = top; i <= down; ++i) {\n                 arr.emplace_back(matrix[i][right]);\n             }\n             right--;\n             if (left > right) break;\n             for (int i = right; i >= left; --i) {\n                 arr.emplace_back(matrix[down][i]);\n             }\n             down--;\n             if (top > down) break;\n             for (int i = down; i >= top; --i) {\n                 arr.emplace_back(matrix[i][left]);\n             }\n             left++;\n             if (left > right) break;\n        }\n        return arr;\n    }\n};\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func spiralOrder(_ matrix: [[Int]]) -> [Int] {\n        var arr:[Int] = []\n        var left = 0, right = matrix[0].count - 1\n        var top = 0, down = matrix.count - 1\n\n        while (true) {\n            for i in left...right {\n                arr.append(matrix[top][i])\n            }\n            top += 1\n            if top > down { break }\n            for i in top...down {\n                arr.append(matrix[i][right])\n            }\n            right -= 1\n            if left > right { break}\n            for i in stride(from: right, through: left, by: -1) {\n                arr.append(matrix[down][i])\n            }\n            down -= 1\n            if top > down { break}\n            for i in stride(from: down, through: top, by: -1) {\n                arr.append(matrix[i][left])\n            }\n            left += 1\n            if left > right { break}\n        }\n\n        return arr\n    }\n}\n```\n\n我们仅仅是将 54 反过来了，往螺旋矩阵里面插值，下面我们直接看代码吧,大家可以也可以对其改进，大家可以思考一下，如果修改能够让代码更简洁！\n\nJava Code:\n\n```java\nclass Solution {\n    public int[][] generateMatrix(int n) {\n\n        int[][] arr = new int[n][n];\n        int left = 0;\n        int right = n-1;\n        int top = 0;\n        int buttom = n-1;\n        int num = 1;\n        int numsize = n*n;\n        while (true) {\n            for (int i = left; i <= right; ++i) {\n                arr[top][i] = num++;\n            }\n            top++;\n            if (num > numsize) break;\n            for (int i = top; i <= buttom; ++i) {\n                arr[i][right] = num++;\n\n            }\n            right--;\n            if (num > numsize) break;\n            for (int i = right; i >= left; --i) {\n                arr[buttom][i] = num++;\n            }\n            buttom--;\n            if (num > numsize) break;\n            for (int i = buttom; i >= top; --i) {\n                arr[i][left] = num++;\n            }\n            left++;\n            if (num > numsize) break;\n\n        }\n        return arr;\n    }\n}\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nimport numpy as np\nclass Solution:\n    def generateMatrix(self, n: int)->List[List[int]]:\n        arr = np.array([[0] * n] * n)\n        left = 0\n        right = n - 1\n        top = 0\n        buttom = n - 1\n        num = 1\n        numsize = n * n\n        while True:\n            for i in range(left, right + 1):\n                arr[top][i] = num\n                num += 1\n            top += 1\n            if num > numsize:\n                break\n            for i in range(top, buttom + 1):\n                arr[i][right] = num\n                num += 1\n            right -= 1\n            if num > numsize:\n                break\n            for i in range(right, left - 1, -1):\n                arr[buttom][i] = num\n                num += 1\n            buttom -= 1\n            if num > numsize:\n                break\n            for i in range(buttom, top - 1, -1):\n                arr[i][left] = num\n                num += 1\n            left += 1\n            if num > numsize:\n                break\n        return arr.tolist()\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    vector<vector<int>> generateMatrix(int n) {\n        vector <vector <int>> arr(n, vector <int>(n));\n        int left = 0, right = n-1, top = 0, buttom = n - 1, num = 1, numsize = n * n;\n        while (true) {\n            for (int i = left; i <= right; ++i) {\n                arr[top][i] = num++;\n            }\n            top++;\n            if (num > numsize) break;\n            for (int i = top; i <= buttom; ++i) {\n                arr[i][right] = num++;\n            }\n            right--;\n            if (num > numsize) break;\n            for (int i = right; i >= left; --i) {\n                arr[buttom][i] = num++;\n            }\n            buttom--;\n            if (num > numsize) break;\n            for (int i = buttom; i >= top; --i) {\n                arr[i][left] = num++;\n            }\n            left++;\n            if (num > numsize) break;\n\n        }\n        return arr;\n    }\n};\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func generateMatrix(_ n: Int) -> [[Int]] {\n        var arr:[[Int]] = Array.init(repeating: Array.init(repeating: 0, count: n), count: n)\n        var left = 0, right = n - 1\n        var top = 0, bottom = n - 1\n        var num = 1, numSize = n * n\n\n        while true {\n            for i in left...right {\n                arr[top][i] = num\n                num += 1\n            }\n            top += 1\n            if num > numSize { break}\n            for i in top...bottom {\n                arr[i][right] = num\n                num += 1\n            }\n            right -= 1\n            if num > numSize { break}\n            for i in stride(from: right, through: left, by: -1) {\n                arr[bottom][i] = num\n                num += 1\n            }\n            bottom -= 1\n            if num > numSize { break}\n            for i in stride(from: bottom, through: top, by: -1) {\n                arr[i][left] = num\n                num += 1\n            }\n            left += 1\n            if num > numSize { break}\n        }\n\n        return arr\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc generateMatrix(n int) [][]int {\n    res := make([][]int, n)\n    for i := 0; i < n; i++ {\n        res[i] = make([]int, n)\n    }\n    left, right := 0, n - 1\n    top, buttom := 0, n - 1\n    size, num := n * n, 1\n    for {\n        for i := left; i <= right; i++ {\n            res[top][i] = num\n            num++\n        }\n        top++\n        if num > size { break }\n\n        for i := top; i <= buttom; i++ {\n            res[i][right] = num\n            num++\n        }\n        right--\n        if num > size { break }\n\n        for i := right; i >= left; i-- {\n            res[buttom][i] = num\n            num++\n        }\n        buttom--\n        if num > size { break }\n\n        for i := buttom; i >= top; i-- {\n            res[i][left] = num\n            num++\n        }\n        left++\n        if num > size { break }\n    }\n    return res\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode66加一.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [66. 加一](https://leetcode-cn.com/problems/plus-one/)\n\n**题目描述**\n\n> 给定一个由 整数 组成的 非空 数组所表示的非负整数，在该数的基础上加一。\n>\n> 最高位数字存放在数组的首位， 数组中每个元素只存储单个数字。\n>\n> 你可以假设除了整数 0 之外，这个整数不会以零开头。\n\n**示例 1：**\n\n> 输入：digits = [1,2,3]\n> 输出：[1,2,4]\n> 解释：输入数组表示数字 123。\n\n**示例 2：**\n\n> 输入：digits = [4,3,2,1]\n> 输出：[4,3,2,2]\n> 解释：输入数组表示数字 4321。\n\n**示例 3：**\n\n输入：digits = [0]\n输出：[1]\n\n**数组遍历**\n\n**题目解析**\n\n我们思考一下，加一的情况一共有几种情况呢？是不是有以下三种情况\n\n![加一](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/加一.3lp9zidw61s0.png)\n\n则我们根据什么来判断属于第几种情况呢？\n\n我们可以根据当前位 余 10 来判断，这样我们就可以区分属于第几种情况了，大家直接看代码吧，很容易理解的。\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] plusOne(int[] digits) {\n        //获取长度\n        int len = digits.length;\n        for (int i = len-1; i >= 0; i--) {\n            digits[i] = (digits[i] + 1) % 10;\n            //第一种和第二种情况，如果此时某一位不为 0 ，则直接返回即可。\n            if (digits[i] != 0) {\n                return digits;\n            }\n\n        }\n        //第三种情况，因为数组初始化每一位都为0，我们只需将首位设为1即可\n        int[] arr = new int[len+1];\n        arr[0] = 1;\n        return arr;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def plusOne(self, digits: List[int])->List[int]:\n        # 获取长度\n        leng = len(digits)\n        for i in range(leng - 1, -1, -1):\n            digits[i] = (digits[i] + 1) % 10\n            # 第一种和第二种情况，如果此时某一位不为 0 ，则直接返回即可。\n            if digits[i] != 0:\n                return digits\n        # 第三种情况，因为数组初始化每一位都为0，我们只需将首位设为1即可\n        arr = [0] * (leng + 1)\n        arr[0] = 1\n        return arr\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    vector<int> plusOne(vector<int>& digits) {\n        for(int i = digits.size() - 1; i >= 0; --i){\n            digits[i] = (digits[i] + 1)%10;\n            if(digits[i]) return digits;\n        }\n        for(int & x: digits) x = 0;\n        digits.emplace_back(1);\n        reverse(digits.begin(), digits.end());\n        return digits;\n    }\n};\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func plusOne(_ digits: [Int]) -> [Int] {\n        let count = digits.count\n        var digits = digits\n        for i in stride(from: count - 1, through: 0, by: -1) {\n            digits[i] = (digits[i] + 1) % 10\n            if digits[i] != 0 {\n                return digits\n            }\n        }\n        var arr: [Int] = Array.init(repeating: 0, count: count + 1)\n        arr[0] = 1\n        return arr\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc plusOne(digits []int) []int {\n    l := len(digits)\n    for i := l - 1; i >= 0; i-- {\n        digits[i] = (digits[i] + 1) % 10\n        if digits[i] != 0 {\n            return digits\n        }\n    }\n    digits = append([]int{1}, digits...)\n    return digits\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/leetcode75颜色分类.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### [75 颜色分类](https://leetcode-cn.com/problems/sort-colors/)\n\n题目描述：\n\n给定一个包含红色、白色和蓝色，一共 n 个元素的数组，原地对它们进行排序，使得相同颜色的元素相邻，并按照红色、白色、蓝色顺序排列。\n\n此题中，我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。\n\n示例 1：\n\n> 输入：nums = [2,0,2,1,1,0]\n> 输出：[0,0,1,1,2,2]\n\n示例 2：\n\n> 输入：nums = [2,0,1]\n> 输出：[0,1,2]\n\n示例 3：\n\n> 输入：nums = [0]\n> 输出：[0]\n\n示例 4：\n\n> 输入：nums = [1]\n> 输出：[1]\n\n**做题思路**\n\n这个题目我们使用 Arrays.sort() 解决，哈哈，但是那样太无聊啦，题目含义就是让我们将所有的 0 放在前面，2 放在后面，1 放在中间，是不是和我们上面说的荷兰国旗问题一样。我们仅仅将 1 做为 pivot 值。\n\n下面我们直接看代码吧，和三向切分基本一致。\n\nJava Code:\n\n```java\nclass Solution {\n    public void sortColors(int[] nums) {\n        int len = nums.length;\n        int left = 0;\n        //这里和三向切分不完全一致\n        int i = left;\n        int right = len-1;\n\n        while (i <= right) {\n             if (nums[i] == 2) {\n                 swap(nums,i,right--);\n             } else if (nums[i] == 0) {\n                 swap(nums,i++,left++);\n             } else {\n                 i++;\n             }\n        }\n    }\n    public void swap (int[] nums, int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortColors(self, nums: List[int]):\n        leng = len(nums)\n        left = 0\n        # 这里和三向切分不完全一致\n        i = left\n        right = leng - 1\n        while i <= right:\n            if nums[i] == 2:\n                self.swap(nums, i, right)\n                right -= 1\n            elif nums[i] == 0:\n                self.swap(nums, i, left)\n                i += 1\n                left += 1\n            else:\n                i += 1\n        return nums\n\n    def swap(self, nums: List[int], i: int, j: int):\n        temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    void sortColors(vector<int>& nums) {\n        int len = nums.size(), left = 0;\n        int i = left, right = len-1;\n        while (i <= right) {\n             if (nums[i] == 2) {\n                 swap(nums[i],nums[right--]);\n             } else if (nums[i] == 0) {\n                 swap(nums[i++],nums[left++]);\n             } else {\n                 i++;\n             }\n        }\n    }\n};\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func sortColors(_ nums: inout [Int]) {\n\n        let count = nums.count\n        var left = 0, i = left, right = count - 1\n        while i <= right {\n            if nums[i] == 2 {\n                //nums.swapAt(i, right) 直接调用系统方法\n                self.swap(&nums, i, right) // 保持风格统一走自定义交换\n                right -= 1\n            } else if nums[i] == 0 {\n                //nums.swapAt(i, left) 直接调用系统方法\n                self.swap(&nums, i, left) // 保持风格统一走自定义交换\n                i += 1\n                left += 1\n            } else {\n                i += 1\n            }\n        }\n    }\n\n    func swap(_ nums: inout [Int], _ i: Int, _ j: Int) {\n        let temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc sortColors(nums []int)  {\n    length := len(nums)\n    left, right := 0, length - 1\n    i := left\n    for i <= right {\n        if nums[i] == 2 {\n            // 把2换到最后\n            nums[i], nums[right] = nums[right], nums[i]\n            right--\n        } else if nums[i] == 0 {\n            // 把0换到最前面\n            nums[i], nums[left] = nums[left], nums[i]\n            i++\n            left++\n        } else {\n            i++\n        }\n    }\n}\n```\n\n另外我们看这段代码，有什么问题呢？那就是我们即使完全符合时，仍会交换元素，这样会大大降低我们的效率。\n\n例如：[0,0,0,1,1,1,2,2,2]\n\n此时我们完全符合情况，不需要交换元素，但是按照我们上面的代码，0,2 的每个元素会和自己进行交换，所以这里我们可以根据 i 和 left 的值是否相等来决定是否需要交换，大家可以自己写一下。\n\n下面我们看一下另外一种写法\n\n这个题目的关键点就是，当我们 nums[i] 和 nums[right] 交换后，我们的 nums[right] 此时指向的元素是符合要求的，但是我们 nums[i] 指向的元素不一定符合要求，所以我们需要继续判断。\n\n![细节地方](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210305153911.28capmzljy80.png)\n\n我们 2 和 0 交换后，此时 i 指向 0 ，0 应放在头部，所以不符合情况，所以 0 和 1 仍需要交换。下面我们来看一下动画来加深理解吧。\n\n![](https://img-blog.csdnimg.cn/20210318093047325.gif#pic_center)\n\n另一种代码表示\n\nJava Code:\n\n```java\nclass Solution {\n    public void sortColors(int[] nums) {\n\n        int left = 0;\n        int len = nums.length;\n        int right = len - 1;\n        for (int i = 0; i <= right; ++i) {\n            if (nums[i] == 0) {\n                swap(nums,i,left);\n                left++;\n            }\n            if (nums[i] == 2) {\n                swap(nums,i,right);\n                right--;\n                //如果不等于 1 则需要继续判断，所以不移动 i 指针，i--\n                if (nums[i] != 1) {\n                    i--;\n                }\n            }\n        }\n\n    }\n    public void swap (int[] nums,int i, int j) {\n        int temp = nums[i];\n        nums[i] = nums[j];\n        nums[j] = temp;\n    }\n}\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def sortColors(self, nums: List[int]):\n        left = 0\n        leng = len(nums)\n        right = leng - 1\n        i = 0\n        while i <= right:\n            if nums[i] == 0:\n                self.swap(nums, i, left)\n                left += 1\n            if nums[i] == 2:\n                self.swap(nums, i, right)\n                right -= 1\n                # 如果不等于 1 则需要继续判断，所以不移动 i 指针，i--\n                if nums[i] != 1:\n                    i -= 1\n            i += 1\n        return nums\n\n    def swap(self, nums: List[int], i: int, j: int):\n        temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    void sortColors(vector<int>& nums) {\n        int left = 0, len = nums.size();\n        int right = len - 1;\n        for (int i = 0; i <= right; ++i) {\n            if (nums[i] == 0) {\n                swap(nums[i],nums[left++]);\n            }\n            if (nums[i] == 2) {\n                swap(nums[i],nums[right--]);\n                if (nums[i] != 1) {\n                        i--;\n                    }\n                }\n            }\n        }\n};\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func sortColors(_ nums: inout [Int]) {\n\n        let count = nums.count\n        var left = 0, i = left, right = count - 1\n        while i <= right {\n            if nums[i] == 0 {\n                //nums.swapAt(i, left) 直接调用系统方法\n                self.swap(&nums, i, left) // 保持风格统一走自定义交换\n                left += 1\n            }\n            if nums[i] == 2 {\n                //nums.swapAt(i, right) 直接调用系统方法\n                self.swap(&nums, i, right) // 保持风格统一走自定义交换\n                right -= 1\n                //如果不等于 1 则需要继续判断，所以不移动 i 指针，i--\n                if nums[i] != 1 {\n                    i -= 1\n                }\n            }\n            i += 1\n        }\n    }\n\n    func swap(_ nums: inout [Int], _ i: Int, _ j: Int) {\n        let temp = nums[i]\n        nums[i] = nums[j]\n        nums[j] = temp\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc sortColors(nums []int)  {\n    length := len(nums)\n    left, right := 0, length - 1\n    for i := 0; i <= right; i++ {\n        if nums[i] == 0 {\n            // 为0时，和头交换\n            nums[i], nums[left] = nums[left], nums[i]\n            left++\n        } else if nums[i] == 2 {\n            // 为2时，和尾交换\n            nums[i], nums[right] = nums[right], nums[i]\n            right--\n            // 不为1时，需要把i减回去\n            if nums[i] != 1 {\n                i--\n            }\n        }\n    }\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/剑指offer3数组中重复的数.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [剑指 Offer 03. 数组中重复的数字](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/)\n\n**题目描述**\n\n找出数组中重复的数字。\n\n在一个长度为 n 的数组 nums 里的所有数字都在 0 ～ n-1 的范围内。数组中某些数字是重复的，但不知道有几个数字重复了，也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。\n\n示例 1：\n\n输入：\n[2, 3, 1, 0, 2, 5, 3]\n输出：2 或 3\n\n#### **HashSet**\n\n**解析**\n\n这种题目或许一下就让人想到 HashSet，题目描述很清楚就是让我们找到数组中重复的元素，那我们第一下想到的就是 HashSet，我们遍历数组，如果发现 set 含有该元素则返回，不含有则存入哈希表，题目代码也很简单\n\n**题目代码**\n\n```java\nclass Solution {\n    public int findRepeatNumber(int[] nums) {\n        // HashSet\n        HashSet<Integer> set = new HashSet<Integer>();\n        for (int x : nums) {\n            //发现某元素存在，返回\n            if (set.contains(x)) {\n                return x;\n            }\n            //存入哈希表\n            set.add(x);\n        }\n        return -1;\n    }\n}\n```\n\nPython Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def findRepeatNumber(self, nums: List[int])->int:\n        s = set()\n        for x in nums:\n            # 如果发现某元素存在，则返回\n            if x in s:\n                return x\n            # 存入集合\n            s.add(x)\n        return -1\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func findRepeatNumber(_ nums: [Int]) -> Int {\n        var set: Set<Int> = []\n        for n in nums {\n            if set.contains(n) { // 如果发现某元素存在，则返回\n                return n\n            }\n            set.insert(n) // 存入集合\n        }\n\n        return -1\n    }\n}\n```\n\n#### **原地置换**\n\n**解析**\n\n这一种方法也是我们经常用到的，主要用于重复出现的数，缺失的数等题目中，下面我们看一下这个原地置换法，原地置换的大体思路就是将我们**指针对应**的元素放到属于他的位置（索引对应的地方）。我们可以这样理解，每个人都有自己的位置，我们需要和别人调换回到属于自己的位置，调换之后，如果发现我们的位置上有人了，则返回。大致意思了解了，下面看代码的执行过程。通过视频一下就可以搞懂啦。\n\n![剑指offer3数组中重复的数](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/剑指offer3数组中重复的数.2p6cd5os0em0.gif)\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public int findRepeatNumber(int[] nums) {\n        if (nums.length == 0) {\n            return -1;\n        }\n        for (int i = 0; i < nums.length; ++i) {\n            while (nums[i] != i) {\n                //发现重复元素\n                if (nums[i] == nums[nums[i]]) {\n                    return nums[i];\n                }\n                //置换，将指针下的元素换到属于他的索引处\n                int temp = nums[i];\n                nums[i] = nums[temp];\n                nums[temp] = temp;\n            }\n        }\n        return -1;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int findRepeatNumber(vector<int>& nums) {\n\tif(nums.empty()) return 0;\n      \tint n = nums.size();\n      \tfor(int i = 0; i < n; ++i){\n          while(nums[i] != i){\n            if(nums[i] == nums[nums[i]]) return nums[i];\n            swap(nums[i], nums[nums[i]]);\n          }\n        }\n      return -1;\n    }\n};\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nclass Solution:\n    def findRepeatNumber(self, nums: List[int])->int:\n        if len(nums) == 0:\n            return -1\n        for i in range(0, len(nums)):\n            while nums[i] != i:\n                # 发现重复元素\n                if nums[i] == nums[nums[i]]:\n                    return nums[i]\n                # 置换，将指针下的元素换到属于它的索引处\n                temp = nums[i]\n                nums[i] = nums[temp]\n                nums[temp] = temp\n        return -1\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func findRepeatNumber(_ nums: [Int]) -> Int {\n        if nums.isEmpty {\n            return -1\n        }\n        var nums = nums;\n        for i in 0..<nums.count {\n            while nums[i] != i {\n                if nums[i] == nums[nums[i]] {\n                    return nums[i]\n                }\n                nums.swapAt(i, nums[i])\n            }\n        }\n\n        return -1\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc findRepeatNumber(nums []int) int {\n    l := len(nums)\n    if l == 0 {\n        return -1\n    }\n    for i := 0; i < l; i++ {\n        // 将nums[i]换到i的位置。\n        for nums[i] != i {\n            if nums[i] == nums[nums[i]] {\n                return nums[i]\n            }\n            nums[i], nums[nums[i]] = nums[nums[i]], nums[i]\n        }\n    }\n    return -1\n}\n```\n"
  },
  {
    "path": "animation-simulation/数组篇/长度最小的子数组.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [209. 长度最小的子数组](https://leetcode-cn.com/problems/minimum-size-subarray-sum/)\n\n我们下面再看一种新类型的双指针，也就是我们大家熟知的滑动窗口。这也是我们做题时经常用到的，下面我们来看一下题目吧！\n\n#### 题目描述\n\n> 给定一个含有 n 个正整数的数组和一个正整数 s ，找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组，并返回其长度。如果不存在符合条件的子数组，返回 0。\n\n示例：\n\n> 输入：s = 7, nums = [2,3,1,2,4,3]\n> 输出：2\n> 解释：子数组 [4,3] 是该条件下的长度最小的子数组。\n\n#### 题目解析\n\n滑动窗口：**就是通过不断调节子数组的起始位置和终止位置，进而得到我们想要的结果**，滑动窗口也是双指针的一种。\n\n下面我们来看一下这道题目的做题思路，其实原理也很简单，我们创建两个指针，一个指针负责在前面探路，并不断累加遍历过的元素的值，当和大于等于我们的目标值时，后指针开始进行移动，判断去除当前值时，是否仍能满足我们的要求，直到不满足时后指针 停止，前面指针继续移动，直到遍历结束。是不是很简单呀。前指针和后指针之间的元素个数就是我们的滑动窗口的窗口大小。见下图\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210321131617533.png)\n\n好啦，该题的解题思路我们已经了解啦，下面我们看一下，代码的运行过程吧。\n\n![](https://img-blog.csdnimg.cn/2021032111513777.gif)\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int minSubArrayLen(int s, int[] nums) {\n\n        int len = nums.length;\n        int windowlen = Integer.MAX_VALUE;\n        int i = 0;\n        int sum = 0;\n        for (int j = 0; j < len; ++j) {\n            sum += nums[j];\n            while (sum >= s) {\n                windowlen = Math.min (windowlen, j - i + 1);\n                sum -= nums[i];\n                i++;\n            }\n        }\n        return windowlen == Integer.MAX_VALUE ? 0 : windowlen;\n\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int minSubArrayLen(int t, vector<int>& nums) {\n\t\tint n = nums.size();\n      \tint i = 0, sum = 0, winlen = INT_MAX;\n      \tfor(int j = 0; j < n; ++j){\n          sum += nums[j];\n          while(sum >= t){\n            winlen = min(winlen, j - i + 1);\n            sum -= nums[i++];\n          }\n        }\n      return winlen == INT_MAX? 0: winlen;\n    }\n};\n```\n\nPython3 Code:\n\n```python\nfrom typing import List\nimport sys\nclass Solution:\n    def minSubArrayLen(self, s: int, nums: List[int])->int:\n        leng = len(nums)\n        windowlen = sys.maxsize\n        i = 0\n        sum = 0\n        for j in range(0, leng):\n            sum += nums[j]\n            while sum >= s:\n                windowlen = min(windowlen, j - i + 1)\n                sum -= nums[i]\n                i += 1\n\n        if windowlen == sys.maxsize:\n            return 0\n        else:\n            return windowlen\n```\n\nSwift Code\n\n```swift\nclass Solution {\n    func minSubArrayLen(_ target: Int, _ nums: [Int]) -> Int {\n\n        var sum = 0, windowlen = Int.max, i = 0\n        for j in 0..<nums.count {\n            sum += nums[j]\n            while sum >= target {\n                windowlen = min(windowlen, j - i + 1)\n                sum -= nums[i]\n                i += 1\n            }\n        }\n        return windowlen == Int.max ? 0 : windowlen\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc minSubArrayLen(target int, nums []int) int {\n    length := len(nums)\n    winLen := length + 1\n    i := 0\n    sum := 0\n    for j := 0; j < length; j++ {\n        sum += nums[j]\n        for sum >= target {\n            winLen = min(winLen, j - i + 1)\n            sum -= nums[i]\n            i++\n        }\n    }\n    if winLen == length + 1 {\n        return 0\n    }\n    return winLen\n}\n\nfunc min(a, b int) int {\n    if a < b {\n        return a\n    }\n    return b\n}\n```\n"
  },
  {
    "path": "animation-simulation/栈和队列/225.用队列实现栈.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [225. 用队列实现栈](https://leetcode-cn.com/problems/implement-stack-using-queues/)\n\n我们昨天实现了如何用两个栈实现队列，原理很简单，今天我们来实现一下如何用队列实现栈。\n\n其实原理也很简单，我们利用队列先进先出的特点，每次队列模拟入栈时，我们先将队列之前入队的元素都出列，仅保留最后一个进队的元素。\n\n然后再重新入队，这样就实现了颠倒队列中的元素。比如我们首先入队 1，然后再入队 2，我们需要将元素 1 出队，然后再重新入队，则实现了队列内元素序列变成了 2,1。\n\n废话不多说，我们继续看动图\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/2021032113283042.gif)\n\n下面我们来看一下题目代码，也是很容易理解。\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass MyStack {\n    //初始化队列\n    Queue<Integer> queue;\n    public MyStack() {\n         queue = new LinkedList<>();\n    }\n\n    //模拟入栈操作\n    public void push(int x) {\n        queue.offer(x);\n        //将之前的全部都出队，然后再入队\n        for(int i = 1;i<queue.size();i++){\n            queue.offer(queue.poll());\n        }\n\n    }\n   //模拟出栈\n    public int pop() {\n        return queue.poll();\n\n    }\n\n    //返回栈顶元素\n    public int top() {\n        return queue.peek();\n\n    }\n   //判断是否为空\n    public boolean empty() {\n        return queue.isEmpty();\n\n    }\n}\n\n```\n\nJS Code:\n\n```javascript\nvar MyStack = function () {\n  this.queue = [];\n};\n\nMyStack.prototype.push = function (x) {\n  this.queue.push(x);\n  if (this.queue.length > 1) {\n    let i = this.queue.length - 1;\n    while (i) {\n      this.queue.push(this.queue.shift());\n      i--;\n    }\n  }\n};\n\nMyStack.prototype.pop = function () {\n  return this.queue.shift();\n};\n\nMyStack.prototype.top = function () {\n  return this.empty() ? null : this.queue[0];\n};\n\nMyStack.prototype.empty = function () {\n  return !this.queue.length;\n};\n```\n\nC++ Code:\n\n```cpp\nclass MyStack {\n    queue <int> q;\npublic:\n    void push(int x) {\n        q.push(x);\n        for(int i = 1;i < q.size();i++){\n            int val = q.front();\n            q.push(val);\n            q.pop();\n        }\n    }\n\n    int pop() {\n        int val = q.front();\n        q.pop();\n        return val;\n    }\n    int top() {\n        return q.front();\n    }\n    bool empty() {\n        return q.empty();\n    }\n};\n```\n"
  },
  {
    "path": "animation-simulation/栈和队列/leetcode1047 删除字符串中的所有相邻重复项.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [1047. 删除字符串中的所有相邻重复项](https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/)\n\n今天给大家带来一个栈的经典题目，删除字符串中的相邻重复项，下面我们先来看一下题目描述。\n\n给出由小写字母组成的字符串 S，重复项操作会选择**两个相邻且相同**的字母，并删除他们。\n\n在 S 上反复执行重复项删除操作，直到无法继续删除。在完成所有重复项删除操作后返回最终字符串。答案保证唯一\n\n示例 1：\n\n> 输入：“abbaca”\n> 输出：”ca“\n\n​ 我们在之前的文章中介绍过删除重复项的思想，当时我们介绍的重复项可能是两个或更多，今天的题目更加简单是两字母相邻且相同。这个题目我们可以使用双指针思想解决，用来判断两个字符是否相同，但是我们这个板块的主题是栈和队列，那么我们就详细介绍一下如何用栈解答这个题目。\n\n## 解题思路：\n\n我们将字符入栈，然后新的字符入栈之前先于栈顶元素对比，判断是否和栈顶元素一致，如果一致则栈顶元素出栈，指针移到下一位，则就实现了去除重复元素。如果和栈顶元素不同或栈为空则将当前元素入栈。直至字符串遍历结束，另外我们需要注意的是栈是先进后出，最后我们元素出栈的时候，我们需要对字符串反转一下才为我们的答案。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210320141506967.gif)\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public String removeDuplicates(String S) {\n         Stack<Character> stack = new Stack<>();\n         char[] s = S.toCharArray();//先将字符串变成字符数组\n         //特殊情况\n         if (S.length() == 0 || S.length() == 1) {\n             return S;\n         }\n         //遍历数组\n         for (int i= 0; i<S.length(); i++) {\n         //为空或者和栈顶元素不同时入栈\n             if(stack.isEmpty() || s[i] != stack.peek()) {\n                 stack.push(s[i]);\n             }\n         //相同出栈\n             else {\n                 stack.pop();\n             }\n         }\n\n         StringBuilder str = new StringBuilder();\n         //字符出栈\n         while (!stack.isEmpty()) {\n             str.append(stack.pop());\n         }\n         //翻转字符并返回\n         return str.reverse().toString();\n\n    }\n}\n```\n\n当然这个题目也可以用 set 来做，大家可以随意发挥\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\nstring removeDuplicates(string S) {\n         string str;\n         if (S.empty() || S.size() == 1) {\n             return S;\n         }\n         for (int i = 0; i<S.size(); i++) {\n             if(str.empty() || S[i] != str.back()) {\n                 str.push_back(S[i]);\n             }\n             else {\n                 str.pop_back();\n             }\n         }\n         return str;\n    }\n};\n```\n"
  },
  {
    "path": "animation-simulation/栈和队列/leetcode20有效的括号.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [20. 有效的括号](https://leetcode-cn.com/problems/valid-parentheses/)\n\n今天我们开始了一个新的模块，栈和队列，另外昨天肝了一篇栈和队列的文章，大家可以先去了解以下，今天我们先来一道经典简单题热热身。大家一定要记得打卡，这个题目是真不错。\n\n> 文章里的所有题目都是经过认真挑选的并且所有代码都经过测试大家可以放心食用。\n\n题目描述\n\n给定一个只包括 '('，')'，'{'，'}'，'['，']' 的字符串，判断字符串是否有效。\n\n有效字符串需满足：\n\n左括号必须用相同类型的右括号闭合。\n左括号必须以正确的顺序闭合。\n注意空字符串可被认为是有效字符串。\n\n示例 1:\n\n> 输入: \"()\"\n> 输出: true\n\n示例 2:\n\n> 输入: \"(]\"\n> 输出: false\n\n示例 3:\n\n> 输入: \"()]\"\n> 输出: false\n\n示例 4：\n\n> 输入:\"()[\"\n>\n> 输出:false\n\n我这里用了两种方法进行解决，第一种是利用 ArrayList，第二种是利用栈，今天主要讲 一下用栈的方法。思路很简单，我们遇到左括号就将其入栈，遇到右括号就和栈顶元素进行比较，如果是对应的则 pop 栈顶元素，不对应直接返回 false 即可。另外我们还需要考虑的就是示例 3 和示例 4 这两种情况，需要我们好好思考一下。\n\n下面我们直接上动图。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210320141414239.gif)\n\n题目代码：\n\n```java\nclass Solution {\n    public boolean isValid(String s) {\n       Stack<Character> stack = new Stack<Character>();\n        //循环遍历字符串\n        for(char ch : s.toCharArray()){\n            //入栈的三种情况\n            if (ch == '(' || ch == '[' || ch == '{') {\n                stack.push(ch);\n            }\n            //右括号对比，其中注意为空的情况\n            if (ch == ')') {\n                if( stack.isEmpty() || stack.pop() != '(') {\n                    return false;\n                }\n            }\n            if (ch == ']') {\n                 if (stack.isEmpty() || stack.pop() != '[') {\n                    return false;\n                 }\n            }\n             if (ch == '}') {\n                 if (stack.isEmpty() || stack.pop() != '{') {\n                    return false;\n                }\n            }\n        }\n        //遍历结束的情况\n        if (!stack.isEmpty()) {\n            return false;\n        }\n        return true;\n    }\n}\n```\n\n另外我们看下另一种方法,这个方法很有趣，，我们遇到 ‘ [ ’ 时，则入栈 ' ] ' ，这样当我们遇到 ‘]’ 时只需判断栈顶元素是否和其一致即可，一致则出，继续遍历下一个，否则返回 false 。\n\n这个方法有些巧妙，大家第一次看时可能不是那么容易理解，所以大家可以自己打一下，动脑子想一下代码逻辑。\n\n```java\nclass Solution {\n    public boolean isValid(String s) {\n        LinkedList<Character> stack = new LinkedList<>();\n        //遍历字符串\n        for (char ch : s.toCharArray()) {\n            //遍历到左括号时右括号入栈，右括号来对比时，只需要对比是否相同。\n            if (ch == '[') stack.push(']');\n            else if (ch == '(') stack.push(')');\n            else if (ch == '{') stack.push('}');\n            //当ch为右括号时，如果为空，或不对应，则返回false;\n            else if (stack.isEmpty() || ch != stack.pop()) return false;\n        }\n        return stack.isEmpty();//最后判断是否为空。\n    }\n}\n```\n"
  },
  {
    "path": "animation-simulation/栈和队列/leetcode402移掉K位数字.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [402. 移掉 K 位数字](https://leetcode-cn.com/problems/remove-k-digits/)\n\n今天给大家带来一个栈的中等题目，移掉 K 位数字，题目很简单，但是很有趣。另外明天继续给大家带来一道栈和队列题目（困难），那么咱们的栈和队列模块就结束啦，下周开始整字符串的题目啦！\n\n### 题目描述\n\n给定一个以字符串表示的非负整数 num，移除这个数中的 k 位数字，使得剩下的数字最小。\n\n注意:\n\n> num 的长度小于 10002 且 ≥ k。\n> num 不会包含任何前导零。\n\n示例 1 :\n\n> 输入: num = \"1432219\", k = 3\n> 输出: \"1219\"\n> 解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。\n\n示例 2 :\n\n> 输入: num = \"10200\", k = 1\n> 输出: \"200\"\n> 解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。\n\n示例 3 :\n\n> 输入: num = \"10\", k = 2\n> 输出: \"0\"\n> 解释: 从原数字移除所有的数字，剩余为空就是 0\n\n题目很容易理解，而且也很容易实现，因为在示例中几乎把所有特殊情况都进行了举例，我们直接代码实现就好啦。\n\n### 栈（贪心）\n\n下面我们来看一下用栈的解题思路，因为我们需要删除掉 K 位数字得到最小值，那么我们需要注意的是，删除的数字应该尽量在高位，则当前位小于前一位时，对前一位出栈，当前位入栈。大家思考一下思路是不是这样呢？\n\n另外我们需要注意的是，仅删除 K 位数字，得到最小值，比如 54321，我们删除 3 位，得到 21。但是刚才我们说当前位小于前一位时，则前一位出栈，当前位入栈，所以我们需要加上删除 K 位的规则。\n\n废话不多说我们直接上动图，把该题吃透！\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210320141440557.gif)\n\nPPT 中的文字\n\n> 这里需要注意的是，我们不需要将 0 入栈，因为 0 如果处于栈底，没有比它更小的值所以它不会被移除，我们只有在最后才有机会处理它。因为我们的 010 = 10 ，首位 0 是需要在最后去掉的。所以我们这里可以直接不让其入栈，continue 掉这次循环，也不改变 K 值，这样我们最后出栈处理时就不用考虑啦。这样逻辑就比官方题解好理解一些，也简洁一些。\n\n> 这里需要注意的是，我们的 K 值还为 2，我们目前仅删除 2 位数字，但是我们需要删除 4 位，但是后面的几位都是当前位大于前一位。所以我们需要在遍历结束后再移除后面最大的两位数字\n\n```java\nclass Solution {\n    public String removeKdigits(String num, int k) {\n        //特殊情况全部删除\n        if (num.length() == k) {\n            return \"0\";\n        }\n        char[] s = num.toCharArray();\n        Stack<Character> stack = new Stack<>();\n        //遍历数组\n        for (Character i : s) {\n          //移除元素的情况，k--\n            while (!stack.isEmpty() && i < stack.peek() && k > 0) {\n                   stack.pop();\n                   k--;\n            }\n            //栈为空，且当前位为0时，我们不需要将其入栈\n            if (stack.isEmpty() && i == '0') {\n                continue;\n            }\n            stack.push(i);\n        }\n        while (k > 0) {\n            stack.pop();\n            k--;\n        }\n         if (stack.isEmpty()) {\n             return \"0\";\n         }\n         //反转并返回字符串\n         StringBuilder str = new StringBuilder();\n         while (!stack.isEmpty()) {\n             str.append(stack.pop());\n         }\n         return str.reverse().toString();\n    }\n}\n```\n\n这个题目也是很不错的，题目是精心挑选的，然后动图里面的例子也是精心构思过的。所以大家记得打卡呀！\n"
  },
  {
    "path": "animation-simulation/栈和队列/剑指Offer09用两个栈实现队列.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [剑指 Offer 09. 用两个栈实现队列](https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/)\n\n今天给大家带来一个有意思的题目，思路很 easy，但是刚刷题的小伙伴，示例理解起来可能会有点费劲，花里胡哨一大堆是啥意思啊。在之前的文章《不知道这篇文章合不合你的胃口》中写了栈是先进后出，队列是先进先出。本题让我们用两个先进后出的栈，完成一个先进先出的队列。我们应该怎么实现呢？\n\n废话不多说，大家看图\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210320141325908.gif)\n\n这就是具体思路，然后我们来看一下题目示例及官方提供的函数都是什么意思。\n\n```java\nclass CQueue {\n    //创建队列\n    public CQueue() {\n\n    }\n    //入队\n    public void appendTail(int value) {\n\n    }\n    //出队\n    public int deleteHead() {\n\n    }\n}\n```\n\n示例 1：\n\n```java\n输入：\n[\"CQueue\",\"appendTail\",\"deleteHead\",\"deleteHead\"]\n[[],[3],[],[]]\n输出：[null,null,3,-1]\n```\n\n示例 2：\n\n```java\n输入：\n[\"CQueue\",\"deleteHead\",\"appendTail\",\"appendTail\",\"deleteHead\",\"deleteHead\"]\n[[],[],[5],[2],[],[]]\n输出：[null,-1,null,null,5,2]\n```\n\n其实也很容易理解，输入有两行第一行，为执行的函数，Cqueue 代表创建队列(代表我们初始化两个栈)，appendTail 代表入队操作（代表 stackA 入栈），deleteHead 代表出队操作（代表我们 stackB 出栈）\n\n第二行输入代表值，分别给每个函数传入的参数，我们发现只有 appendTail 函数下有对应值，因为只有该函数传入参数。\n\n大家可以点击该链接[剑指 Offer 09. 用两个栈实现队列](https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/)去实现一下，下面我们看代码。\n\nJava Code:\n\n```java\nclass CQueue {\n     //初始化两个栈\n    Stack<Integer> stack1,stack2;\n    public CQueue() {\n        stack1 = new Stack<>();\n        stack2 = new Stack<>();\n\n    }\n    //入队，我们往第一个栈压入值\n    public void appendTail (int value) {\n        stack1.push(value);\n    }\n    //出队\n    public int deleteHead() {\n        //大家可以自己思考一下为什么if条件为stack2.isEmpty(),细节所在\n        if (stack2.isEmpty()) {\n           //如果此时A栈没有值，则直接-1，我们可以看示例\n           if (stack1.isEmpty()) {\n               return -1;\n           }\n           //将A栈的值，压入B栈中\n           while (!stack1.isEmpty()) {\n               stack2.push(stack1.pop());\n           }\n        }\n        return stack2.pop();\n    }\n}\n```\n\nJS Code:\n\n```javascript\nvar CQueue = function () {\n  this.stack1 = [];\n  this.stack2 = [];\n};\n\nCQueue.prototype.appendTail = function (value) {\n  this.stack1.push(value);\n};\n\nCQueue.prototype.deleteHead = function () {\n  if (!this.stack2.length) {\n    while (this.stack1.length) {\n      this.stack2.push(this.stack1.pop());\n    }\n  }\n  return this.stack2.pop() || -1;\n};\n```\n"
  },
  {
    "path": "animation-simulation/求和问题/三数之和.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [15. 三数之和](https://leetcode-cn.com/problems/3sum/)\n\n## 题目描述：\n\n> 给你一个包含 n 个整数的数组 nums，判断 nums 中是否存在三个元素 a，b，c ，使得 a + b + c = 0 ？请你找出所有满足条件且不重复的三元组。\n>\n> 注意：答案中不可以包含重复的三元组。\n\n示例：\n\n> 给定数组 nums = [-1, 0, 1, 2, -1, -4]，\n>\n> 满足要求的三元组集合为：\n> [\n> [-1, 0, 1],\n> [-1, -1, 2]\n> ]\n\n这个题目算是对刚才题目的升级，刚才题目我们是只需返回一个例子即可，但是这个题目是让我们返回所有情况，这个题目我们需要返回三个数相加为 0 的所有情况，但是我们需要去掉重复的三元组(算是该题的核心)，所以这个题目还是挺有趣的，大家记得打卡呀。\n\n### 哈希表：\n\n#### 解析\n\n我们这个题目的哈希表解法是很容易理解的，我们首先将数组排序，排序之后我们将排序过的元素存入哈希表中，我们首先通过两层遍历，确定好前两位数字，那么我们只需要哈希表是否存在符合情况的第三位数字即可，跟暴力解法的思路类似，很容易理解，\n\n但是这里我们需要注意的情况就是，例如我们的例子为[-2 , 1 , 1],如果我们完全按照以上思路来的话，则会出现两个解，[-2 , 1 , 1]和[1 , 1, -2]。具体原因，确定 -2，1 之后发现 1 在哈希表中，存入。确定 1 ，1 之后发现 -2 在哈希表中，存入。\n\n所以我们需要加入一个约束避免这种情况，那就是我们第三个数的索引大于第二个数时才存入。\n\n![640](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/640.9tp5a5guhr0.png)\n\n上面这种情况时是不可以存入的，因为我们虽然在哈希表中找到了符合要求的值，但是 -2 的索引为 0 小于 2 所以不可以存入。\n\n#### 题目代码\n\n```java\nclass Solution {\n    public List<List<Integer>> threeSum (int[] nums) {\n       if (nums.length < 3) {\n           return new ArrayList<>();\n       }\n       //排序\n       Arrays.sort(nums);\n       HashMap<Integer,Integer> map = new HashMap<>();\n       List<List<Integer>> resultarr = new ArrayList<>();\n       //存入哈希表\n       for (int i = 0; i < nums.length; i++) {\n           map.put(nums[i],i);\n       }\n       Integer t;\n       int target = 0;\n       for (int i = 0; i < nums.length; ++i) {\n            target = -nums[i];\n            //去重\n            if (i > 0 && nums[i] == nums[i-1]) {\n                continue;\n            }\n            for (int j = i + 1; j < nums.length; ++j) {\n                if (j > i+1 && nums[j] == nums[j-1]) {\n                    continue;\n                }\n                if ((t = map.get(target - nums[j])) != null) {\n                    //符合要求的情况,存入\n                    if (t > j) {\n                       resultarr.add(new ArrayList<>\n                       (Arrays.asList(nums[i], nums[j], nums[t])));\n\n                    }\n                    else {\n                        break;\n                    }\n                }\n            }\n       }\n       return resultarr;\n    }\n}\n```\n\n### 多指针\n\n#### 解析：\n\n如果我们将上个题目得指针解法称做是双指针的话，那么这个题目用到的方法就是三指针，因为我们是三数之和嘛，一个指针对应一个数，下面我们看一下具体思路，其实原理很简单，我们先将数组排序，直接 Arrays.sort() 解决，排序之后处理起来就很容易了。\n\n下面我们来看下三个指针的初始位置。\n\n![三数之和起始](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/三数之和起始.44vete07oy80.png)\n\n初始情况见上图，我们看当前情况，三数之和为 -3 ，很显然不是 0 ，那么我们应该怎么做呢？\n\n我们设想一下，我们当前的三数之和为 -3 < 0 那么我们如果移动橙色指针的话则会让我们的三数之和变的更小，因为我们的数组是有序的，所以我们移动橙色指针（蓝色不动）时和会变小，如果移动蓝色指针（橙色不动）的话，三数之和则会变大，所以这种情况则需要向右移动我们的蓝色指针，\n\n找到三数之和等于 0 的情况进行保存，如果三数之和大于 0 的话，则需要移动橙色指针，途中有三数之和为 0 的情况则保存。直至蓝橙两指针相遇跳出该次循环，然后我们的绿指针右移一步，继续执行上诉步骤。\n\n但是这里我们需要注意的一个细节就是，我们需要去除相同三元组的情况，我们看下面的例子。\n\n![三数之和举例](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/三数之和举例.69b6nvlu4zc0.png)\n\n这里我们发现 0 - 1 + 1 = 0，当前情况是符合的，所以我们需要存入该三元组，存入后，蓝色指针向后移动一步，橙色指针向前移动一步，我们发现仍为 0 -1 + 1 = 0 仍然符合，但是如果继续存入该三元组的话则不符合题意，所以我们需要去重。\n\n这里可以借助 HashSet 但是效率太差，不推荐。\n\n这里我们可以使用 while 循环将蓝色指针移动到不和刚才相同的位置，也就是直接移动到元素 0 上，橙色指针同样也是。则是下面这种情况，\n\n这样我们就实现了去重，然后继续判断当前三数之和是否为 0 。\n\n![三数之和例子](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/三数之和例子.6c8xobhrieg0.png)\n\n**动图解析：**\n\n![三数之和](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/三数之和.5akhtx5y0g00.gif)\n\n#### 题目代码：\n\n```java\nclass Solution {\n    public List<List<Integer>> threeSum(int[] nums) {\n        List<List<Integer>> arr = new ArrayList<List<Integer>>();\n        if(nums.length < 3){\n            return arr;\n        }\n        //排序\n        Arrays.sort(nums);\n        if(nums[0] > 0){\n            return arr;\n        }\n        for(int i = 0; i < nums.length-2; i++){\n            int target = 0 - nums[i];\n            //去重\n            if(i > 0 && nums[i] == nums[i-1]){\n                continue;\n            }\n            int l = i+1;\n            int r = nums.length - 1;\n            while(l < r){\n                if(nums[l] + nums[r] == target){\n                    //存入符合要求的值\n                    arr.add(new ArrayList<>(Arrays.asList(nums[i], nums[l], nums[r])));\n                    //这里需要注意顺序\n                    while(l < r && nums[l] == nums[l+1]) l++;\n                    while(l < r && nums[r] == nums[r-1]) r--;\n                    l++;\n                    r--;\n                }\n                else if(nums[l] + nums[r] > target){\n                    r--;\n                }\n                else{\n                    l++;\n                }\n            }\n        }\n        return arr;\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc threeSum(nums []int) [][]int {\n    res := [][]int{}\n    length := len(nums)\n    if length < 3 {\n        return res\n    }\n\n    sort.Ints(nums)\n    for i := 0; i < length - 2; i++ {\n        // 去重\n        if i != 0 && nums[i] == nums[i - 1] {\n            continue\n        }\n        l, r := i + 1, length - 1\n        for l < r {\n            /*\n            // 下面两个for循环的去重也可以用下面的代码替换\n            if l != i + 1 && nums[l] == nums[l - 1] {\n                l++\n                continue\n            }\n            */\n            if nums[i] + nums[l] + nums[r] == 0 {\n                res = append(res, []int{nums[i], nums[l], nums[r]})\n                for l < r && nums[l] == nums[l + 1] {\n                    l++\n                }\n                for l < r && nums[r] == nums[r - 1] {\n                    r--\n                }\n                l++\n                r--\n            } else if nums[i] + nums[l] + nums[r] < 0 {\n                l++\n            } else {\n                r--\n            }\n\n        }\n    }\n    return res\n}\n```\n"
  },
  {
    "path": "animation-simulation/求和问题/两数之和.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [1. 两数之和](https://leetcode-cn.com/problems/two-sum/)\n\n## 题目描述：\n\n> 给定一个整数数组 nums 和一个目标值 target，请你在该数组中找出和为目标值的那 两个 整数，并返回他们的数组下标。\n>\n> 你可以假设每种输入只会对应一个答案。但是，数组中同一个元素不能使用两遍。\n\n示例:\n\n> 给定 nums = [2, 7, 11, 15], target = 9\n>\n> 因为 nums[0] + nums[1] = 2 + 7 = 9\n> 所以返回 [0, 1]\n\n题目很容易理解，即让查看数组中有没有两个数的和为目标数，如果有的话则返回两数下标，我们为大家提供两种解法双指针（暴力）法，和哈希表法\n\n### 哈希表\n\n#### 解析\n\n哈希表的做法很容易理解，我们只需通过一次循环即可，假如我们的 target 值为 9，当前指针指向的值为 2 ，我们只需从哈希表中查找是否含有 7，因为 9 - 2 =7 。如果含有 7 我们直接返回即可，如果不含有则将当前的 2 存入哈希表中，指针移动，指向下一元素。注： key 为元素值，value 为元素索引。\n\n动图解析：\n\n![两数之和](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/两数之和.7228lcxkqpw0.gif)\n\n是不是很容易理解，下面我们来看一下题目代码。\n\n#### 题目代码：\n\n```java\nclass Solution {\n    public int[] twoSum(int[] nums, int target) {\n        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();\n        for(int i = 0; i < nums.length; i++){\n            //如果存在则返回\n            if(map.containsKey(target-nums[i])){\n                return new int[]{map.get(target-nums[i]),i};\n            }\n            //不存在则存入\n            map.put(nums[i],i);\n\n        }\n        return new int[0];\n\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc twoSum(nums []int, target int) []int {\n    m := make(map[int]int)\n    for i, num := range nums {\n        if v, ok := m[target - num]; ok {\n            return []int{v, i}\n        }\n        m[num] = i\n    }\n    return []int{}\n}\n```\n\n### 双指针（暴力）法\n\n#### 解析\n\n双指针（L,R）法的思路很简单，L 指针用来指向第一个值，R 指针用来从第 L 指针的后面查找数组中是否含有和 L 指针指向值和为目标值的数。见下图\n\n![](https://img-blog.csdnimg.cn/20210319151826855.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70)\n\n例：绿指针指向的值为 3，蓝指针需要在绿指针的后面遍历查找是否含有 target - 3 = 2 的元素，若含有返回即可。\n\n#### 题目代码\n\n```java\nclass Solution {\n    public int[] twoSum (int[] nums, int target) {\n        if(nums.length < 2){\n            return new int[0];\n        }\n        int[] rearr = new int[2];\n        //查询元素\n        for (int i = 0; i < nums.length; i++) {\n            for (int j = i+1; j < nums.length; j++ ) {\n                if (nums[i] + nums[j] == target) {\n                    rearr[0] = i;\n                    rearr[1] = j;\n                }\n            }\n        }\n        return rearr;\n    }\n}\n```\n"
  },
  {
    "path": "animation-simulation/求和问题/四数之和.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [18. 四数之和](https://leetcode-cn.com/problems/4sum/)\n\n## 题目描述\n\n> 给定一个包含 n 个整数的数组 nums 和一个目标值 target，判断 nums 中是否存在四个元素 a，b，c 和 d ，使得 a + b + c + d 的值与 target 相等？找出所有满足条件且不重复的四元组。\n>\n> 注意：\n>\n> 答案中不可以包含重复的四元组。\n\n示例：\n\n> 给定数组 nums = [1, 0, -1, 0, -2, 2]，和 target = 0。\n>\n> 满足要求的四元组集合为：\n> [\n> [-1, 0, 0, 1],\n> [-2, -1, 1, 2],\n> [-2, 0, 0, 2]\n> ]\n\n我们已经完成了两数之和和三数之和，这个题目应该就手到擒来了，因为我们已经知道这类题目的解题框架，两数之和呢，我们就先固定第一个数 ，然后移动指针去找第二个符合的，三数之和，固定一个数，双指针去找符合情况的其他两位数，那么我们四数之和，也可以先固定两个数，然后利用双指针去找另外两位数。所以我们来搞定他吧。\n\n### 多指针：\n\n#### 解析：\n\n三数之和是，我们首先确定一个数，然后利用双指针去找另外的两个数，我们在这个题目里面的解题思路是需要首先确定两个数然后利用双指针去找另外两个数，和三数之和思路基本一致很容易理解。我们具体思路可以参考下图。\n\n这里需要注意的是，我们的 target 不再和三数之和一样为 0 ，target 是不固定的，所以解题思路不可以完全照搬上面的题目。另外这里也需要考虑去重的情况，思路和上题一致。\n\n![四数之和起始](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/四数之和起始.2mi8qclt1h40.png)\n\n上图则为我们查找到一个符合条件的四元组的情况，查找成功之后，下一步移动蓝色指针，重新定义绿蓝指针，继续执行上面步骤。\n\n![四数之和例子](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/四数之和例子.3xy3mil2rp40.png)\n\n动图解析：\n\n![四数之和](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/四数之和.337d5ffc7040.gif)\n\n#### 题目代码：\n\n```java\nclass Solution {\n    public List<List<Integer>> fourSum(int[] nums, int target) {\n           if(nums.length < 4){\n               return new ArrayList<>();\n           }\n           Arrays.sort(nums);\n           List<List<Integer>> arr = new ArrayList<>();\n           for (int i = 0; i < nums.length-3; ++i) {\n               if (i > 0 && nums[i] == nums[i-1]) {\n                   continue;\n               }\n               for (int j = i+1; j < nums.length-2; j++) {\n\n                   if (j > i+1 && nums[j] == nums[j-1]) {\n                       continue;\n                   }\n                   int l = j+1;\n                   int r = nums.length-1;\n                   while (l < r) {\n                       int sum = nums[i] + nums[j] + nums[l] + nums[r];\n                       if (sum == target) {\n                           //存入\n                           arr.add(new ArrayList<>\n                           (Arrays.asList(nums[i], nums[j], nums[l], nums[r])));\n                           //去重\n                           while (l < r && nums[l] == nums[l+1]) {\n                               l++;\n                           }\n                           while (l < r && nums[r] == nums[r-1]) {\n                               r--;\n                           }\n                           l++;\n                           r--;\n                       }\n                       else if (sum > target) {\n                             r--;\n                       }\n                       else {\n                             l++;\n                       }\n                   }\n               }\n           }\n           return arr;\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc fourSum(nums []int, target int) [][]int {\n    res := [][]int{}\n    length := len(nums)\n    if length < 4 {\n        return res\n    }\n\n    sort.Ints(nums)\n    for i := 0; i < length - 3; i++ {\n        // 去重\n        if i != 0 && nums[i] == nums[i - 1] {\n            continue\n        }\n        for j := i + 1; j < length - 2; j++ {\n            // 去重\n            if j != i + 1 && nums[j] == nums[j - 1] {\n                continue\n            }\n            l, r := j + 1, length - 1\n            for l < r {\n                /*\n                // 下面两个for循环的去重也可以用下面的代码替换\n                if l != i + 1 && nums[l] == nums[l - 1] {\n                    l++\n                    continue\n                }\n                */\n                sum := nums[i] + nums[j] + nums[l] + nums[r]\n                if sum == target {\n                    res = append(res, []int{nums[i], nums[j], nums[l], nums[r]})\n                    for l < r && nums[l] == nums[l + 1] {\n                        l++\n                    }\n                    for l < r && nums[r] == nums[r - 1] {\n                        r--\n                    }\n                    l++\n                    r--\n                } else if sum < target {\n                    l++\n                } else {\n                    r--\n                }\n\n            }\n        }\n    }\n    return res\n}\n```\n"
  },
  {
    "path": "animation-simulation/求次数问题/只出现一次的数.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [136. 只出现一次的数字](https://leetcode-cn.com/problems/single-number/)\n\n> 给定一个非空整数数组，除了某个元素只出现一次以外，其余每个元素均出现两次。找出那个只出现了一次的元素。\n\n示例 1:\n\n> 输入: [2,2,1]\n> 输出: 1\n\n示例 2:\n\n> 输入: [4,1,2,1,2]\n> 输出: 4\n\n这个题目非常容易理解，就是让我们找出那个只出现一次的数字，那么下面我们来看一下这几种解题方法吧~\n\n### HashMap\n\n#### 解析\n\n用 HashMap 的这个方法是很容易实现的，题目要求不是让我们求次数嘛，那我们直接遍历数组将每个数字和其出现的次数存到哈希表里就可以了，然后我们再从哈希表里找出出现一次的那个数返回即可。\n\n![哈希表解法](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/哈希表解法.1kefww8xsig0.png)\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int singleNumber(int[] nums) {\n        //特殊情况\n        if (nums.length == 1) {\n            return nums[0];\n        }\n        //HashMap\n        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();\n        //将其存入哈希表中，含义为，若该元素不存在则存入表中，并计数为1，若已经存在获取次数并加1\n        for (int x : nums) {\n            map.put(x , map.getOrDefault(x,0) + 1);\n        }\n        //遍历出出现次数为1的情况\n        for (int y : map.keySet()) {\n            if(map.get(y) == 1){\n                return y;\n            }\n        }\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int singleNumber(vector<int>& nums) {\n        //特殊情况\n        if (nums.size() == 1) {\n            return nums[0];\n        }\n        //HashMap\n        unordered_map<int,int> map;\n        //将其存入哈希表中，含义为，若该元素不存在则存入表中，并计数为1，若已经存在获取次数并加1\n        for (int x : nums) {\n            if (map.find(x) == map.end()) {\n                map.insert({x, 0});\n            }\n            map[x] += 1;\n        }\n        //遍历出出现次数为1的情况\n        for (int y : nums) {\n            if(map.at(y) == 1){\n                return y;\n            }\n        }\n        return -1;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar singleNumber = function (nums) {\n  //特殊情况\n  if (nums.length === 1) {\n    return nums[0];\n  }\n  //HashMap\n  map = {};\n  //将其存入哈希表中，含义为，若该元素不存在则存入表中，并计数为1，若已经存在获取次数并加1\n  for (let x of nums) {\n    if (!(x in map)) {\n      map[x] = 0;\n    }\n    map[x] += 1;\n  }\n  //遍历出出现次数为1的情况\n  for (let key in map) {\n    if (map[key] === 1) {\n      return key;\n    }\n  }\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def singleNumber(self, nums: List[int]) -> int:\n        # 特殊情况\n        if len(nums) == 1:\n            return nums[0]\n        # HashMap\n        map_ = {}\n        # 将其存入哈希表中，含义为，若该元素不存在则存入表中，并计数为1，若已经存在获取次数并加1\n        for x in nums:\n            map_.setdefault(x, 0)\n            map_[x] += 1\n        # 遍历出出现次数为1的情况\n        for y, count in map_.items():\n            if count == 1:\n                return y\n```\n\nGo Code:\n\n```go\nfunc singleNumber(nums []int) int {\n    if len(nums) == 1 {\n        return nums[0]\n    }\n    m := map[int]int{}\n    for _, x := range nums {\n        m[x]++\n    }\n    for k, v := range m {\n        if v == 1 {\n            return k\n        }\n    }\n    return 0\n}\n```\n\n### 排序搜索法\n\n#### 解析\n\n这个方法也是特别容易想到的，我们首先对数组进行排序，然后遍历数组，因为数组中其他数字都出现两次，只有目标值出现一次，所以则让我们的指针每次跳两步，当发现当前值和前一位不一样的情况时，返回前一位即可，当然我们需要考虑这种情况，当我们的目标值出现在数组最后一位的情况，所以当数组遍历结束后没有返回值，则我们需要返回数组最后一位，下面我们看一下动图解析。\n\n![排序](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/排序.6sp72k3iaqw0.gif)\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int singleNumber(int[] nums) {\n        if (nums.length == 1){\n            return nums[0];\n        }\n        //排序\n        Arrays.sort(nums);\n        for (int i = 1; i < nums.length-1; i+=2){\n            if (nums[i] != nums[i-1]){\n                return nums[i-1];\n            }\n        }\n        return nums[nums.length-1];\n\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int singleNumber(vector<int>& nums) {\n        //排序\n        sort(nums.begin(), nums.end());\n        for (int i = 1; i < nums.size() - 1; i += 2){\n            if (nums[i] != nums[i - 1]){\n                return nums[i - 1];\n            }\n        }\n        return nums[nums.size() - 1];\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar singleNumber = function (nums) {\n  // 排序\n  nums.sort();\n  for (let i = 1; i < nums.length - 1; i += 2) {\n    if (nums[i] != nums[i - 1]) {\n      return nums[i - 1];\n    }\n  }\n  return nums[nums.length - 1];\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def singleNumber(self, nums: List[int]) -> int:\n        # 排序\n        nums.sort()\n        for i in range(1, len(nums), 2):\n            if nums[i] != nums[i - 1]:\n                return nums[i - 1]\n        return nums[len(nums) - 1]\n```\n\nGo Code:\n\n```go\nfunc singleNumber(nums []int) int {\n    if len(nums) == 1 {\n        return nums[0]\n    }\n    sort.Ints(nums)\n    for i := 1; i < len(nums) - 1; i+=2 {\n        if nums[i] == nums[i - 1] {\n            continue\n        } else {\n            return nums[i - 1]\n        }\n    }\n    return nums[len(nums) - 1]\n}\n```\n\n### HashSet\n\n#### 解析\n\n这个方法也是比较容易实现的，我们利用 HashSet 来完成。HashSet 在我们刷题时出现频率是特别高的，它是基于 HashMap 来实现的，是一个不允许有重复元素的集合。那么在这个题解中，它起到什么作用呢？解题思路如下，我们依次遍历元素并与 HashSet 内的元素进行比较，如果 HashSet 内没有该元素（说明该元素第一次出现）则存入，若是 HashSet 已经存在该元素(第二次出现)，则将其从 HashSet 中去除，并继续遍历下一个元素。最后 HashSet 内剩下的则为我们的目标数。思路和我们之前说过的括号匹配问题类似，我们一起来看一下动图解析吧。\n\n![HashSet](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/HashSet.4b6dcxwj07c0.gif)\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int singleNumber(int[] nums) {\n         HashSet<Integer> set = new HashSet<>();\n         //循环遍历\n         for (int x : nums){\n             //已经存在，则去除\n             if(set.contains(x)){\n                 set.remove(x);\n             //否则存入\n             } else {\n                 set.add(x);\n             }\n         }\n         //返回仅剩的一个元素\n         return set.iterator().next();\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int singleNumber(vector<int>& nums) {\n        unordered_set<int> set;\n        //循环遍历\n        for (int x : nums) {\n            //已经存在，则去除\n            if (set.find(x) == set.end()) {\n                set.insert(x);\n            //否则存入\n            } else {\n                set.erase(x);\n            }\n        }\n        //返回仅剩的一个元素\n        return *set.begin();\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar singleNumber = function (nums) {\n  let set = new Set();\n  //循环遍历\n  for (let x of nums) {\n    //已经存在，则去除\n    if (set.has(x)) {\n      set.delete(x);\n      //否则存入\n    } else {\n      set.add(x);\n    }\n  }\n  return set.values().next().value;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def singleNumber(self, nums: List[int]) -> int:\n        set_ = set()\n        # 循环遍历\n        for x in nums:\n            # 已经存在，则去除\n            if x in set_:\n                set_.remove(x)\n            # 否则存入\n            else:\n                set_.add(x)\n        # 返回仅剩的一个元素\n        return set_.pop()\n```\n\n### 栈\n\n#### 解析\n\n该方法也很容易想到，我们首先将其排序，然后遍历数组，如果栈为空则将当前元素压入栈，如果栈不为空，若当前元素和栈顶元素相同则出栈，继续遍历下一元素，如果当前元素和栈顶元素不同的话，则说明栈顶元素是只出现一次的元素，我们将其返回即可。这个题目也可以使用队列做，思路一致，我们就不在这里说明啦。下面我们看下动图解析。\n\n![栈](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/栈.6mzstgebww00.gif)\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int singleNumber(int[] nums) {\n        Arrays.sort(nums);\n        Stack<Integer> stack = new Stack<>();\n        for (int x : nums){\n           if (stack.isEmpty()) {\n               stack.push(x);\n               continue;\n           }\n           //不同时直接跳出\n           if (stack.peek() != x) {\n               break;\n           }\n           //相同时出栈\n           stack.pop();\n        }\n        return stack.peek();\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int singleNumber(vector<int>& nums) {\n        sort(nums.begin(), nums.end());\n        stack<int> stack;\n        for (int x: nums) {\n            if (stack.empty()) {\n                stack.push(x);\n                continue;\n            }\n            //不同时直接跳出\n            if (stack.top() != x) {\n                break;\n            }\n            //相同时出栈\n            stack.pop();\n        }\n        return stack.top();\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar singleNumber = function (nums) {\n  nums.sort();\n  let stack = [];\n  for (let x of nums) {\n    if (!stack.length) {\n      stack.push(x);\n      continue;\n    }\n    //不同时直接跳出\n    if (stack[0] !== x) {\n      break;\n    }\n    //相同时出栈\n    stack.pop();\n  }\n  return stack[0];\n};\n```\n\nPython Code:\n\n```python\nfrom collections import deque\n\nclass Solution:\n    def singleNumber(self, nums: List[int]) -> int:\n        if len(nums) == 1:\n            return nums[0]\n        nums.sort()\n        stack = deque()\n        for x in nums:\n            if not stack:\n                stack.append(x)\n                continue\n            # 不同时直接跳出\n            if stack[-1] != x:\n                break\n            # 相同时出栈\n            stack.pop()\n        return stack[-1]\n```\n\n### 求和法\n\n#### 解析\n\n这个方法也比较简单，也是借助咱们的 HashSet ，具体思路如下，我们通过 HashSet 保存数组内的元素，然后进行求和（setsum），那么得到的这个和则为去除掉重复元素的和，我们也可以得到所有元素和（numsum）。因为我们其他元素都出现两次，仅有一个元素出现一次，那我们通过 setsum \\* 2 - numsum 得到的元素则为出现一次的数。\n\n![求和解法](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/求和解法.2tds49a3vzq0.png)\n\n上面我们的 SetSum \\* 2 - NumSum = z 也就是我们所求的值， 是不是感觉很简单呀。\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int singleNumber(int[] nums) {\n       HashSet<Integer> set = new HashSet<>();\n       int setsum = 0;\n       int numsum = 0;\n       for (int x : nums) {\n           //所有元素的和\n           numsum += x;\n           if (!set.contains(x)) {\n               //HashSet内元素的和\n               setsum += x;\n           }\n           set.add(x);\n       }\n       //返回值\n       return setsum * 2 - numsum;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int singleNumber(vector<int>& nums) {\n        unordered_set<int> set;\n        int setsum = 0;\n        int numsum = 0;\n        for (int x : nums) {\n           //所有元素的和\n           numsum += x;\n           if (set.find(x) == set.end()) {\n               //HashSet内元素的和\n               setsum += x;\n           }\n           set.insert(x);\n        }\n        //返回值\n        return setsum * 2 - numsum;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar singleNumber = function (nums) {\n  let set = new Set();\n  let setsum = 0;\n  let numsum = 0;\n  for (let x of nums) {\n    //所有元素的和\n    numsum += x;\n    if (!set.has(x)) {\n      setsum += x;\n    }\n    //HashSet内元素的和\n    set.add(x);\n  }\n  //返回值\n  return 2 * setsum - numsum;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def singleNumber(self, nums: List[int]) -> int:\n        return 2 * sum(set(nums)) - sum(nums)\n```\n\n### 位运算\n\n#### 解析\n\n这个方法主要是借助咱们的位运算符 ^ 按位异或，我们先来了解一下这个位运算符。\n\n> 按位异或（XOR）运算符“^”是双目运算符。 其功能是参与运算的两数各对应的二进位相异或，当两对应的二进位相异时，结果为 1。相同时为 0。\n\n> 任何数和 0 异或，仍为本身：a⊕0 = a\n> 任何数和本身异或，为 0：a⊕a = 0\n> 异或运算满足交换律和结合律：a⊕b⊕a = (a⊕a)⊕b = 0⊕b = b\n\n例：\n\n![异或运算](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/异或运算.1myeo11xgqo0.png)\n\n我们通过上面的例子了解了异或运算，对应位相异时得 1，相同时得 0，那么某个数跟本身异或时，因为对应位都相同所以结果为 0 ， 然后异或又满足交换律和结合律。则\n\n![image-20201129120648802](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/image-20201129120648802.2ajgng6zzd7o.png)\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int singleNumber(int[] nums) {\n        int num = 0;\n        //异或\n        for (int x : nums) {\n              num ^= x;\n        }\n        return num;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int singleNumber(vector<int>& nums) {\n        int num = 0;\n        //异或\n        for (vector<int>::iterator x = nums.begin(); x != nums.end(); x++) {\n            num ^= *x;\n        }\n        return num;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar singleNumber = function (nums) {\n  //异或\n  return nums.reduce((num, x) => (num ^= x));\n};\n```\n\nPython Code:\n\n```python\nfrom functools import reduce\n\nclass Solution:\n    def singleNumber(self, nums: List[int]) -> int:\n        # 异或\n        return reduce(lambda num, x: num ^ x, nums, 0)\n```\n\nGo Code:\n\n```go\nfunc singleNumber(nums []int) int {\n    res := 0\n    for _, x := range nums {\n        res ^= x\n    }\n    return res\n\n}\n```\n\n本题一共介绍了 6 种解题方法，肯定还有别的方法，欢迎大家讨论。大家可以在做题的时候一题多解。这样能大大提高自己解题能力。下面我们来看一下这些方法如何应用到其他题目上。\n"
  },
  {
    "path": "animation-simulation/求次数问题/只出现一次的数2.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [137. 只出现一次的数字 II](https://leetcode-cn.com/problems/single-number-ii/)\n\n> 给定一个非空整数数组，除了某个元素只出现一次以外，其余每个元素均出现了三次。找出那个只出现了一次的元素。\n\n示例 1:\n\n> 输入: [2,2,3,2]\n> 输出: 3\n\n示例 2:\n\n> 输入: [0,1,0,1,0,1,99]\n> 输出: 99\n\n题目很容易理解，刚才的题目是其他元素出现两次，目标元素出现一次，该题是其他元素出现三次，目标元素出现一次，所以我们完全可以借助上题的一些做法解决该题。\n\n### 求和法\n\n#### 解析\n\n我们在上题中介绍了求和法的解题步骤，现在该题中其他元素都出现三次，我们的目标元素出现一次，所以我们利用求和法也是完全 OK 的。下面我们来看具体步骤吧。\n\n1.通过遍历数组获取所有元素的和以及 HashSet 内元素的和。\n\n2.（SumSet \\* 3 - SumNum）/ 2 即可，除以 2 是因为我们减去之后得到的是 2 倍的目标元素。\n\n注：这个题目中需要注意溢出的情况 。\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int singleNumber(int[] nums) {\n        HashSet<Integer> set = new HashSet<>();\n        long setsum = 0;\n        long numsum = 0;\n        for (int x : nums) {\n            //所有元素的和\n            numsum += x;\n            if (!set.contains(x)) {\n                //HashSet元素和\n            \tsetsum += x;\n            }\n            set.add(x);\n        }\n        //返回只出现一次的数\n        return (int)((3 * setsum - numsum) / 2);\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int singleNumber(vector<int>& nums) {\n        unordered_set<int> set;\n        long setsum = 0;\n        long numsum = 0;\n        for (int x : nums) {\n           //所有元素的和\n           numsum += x;\n           if (set.find(x) == set.end()) {\n               //HashSet内元素的和\n               setsum += x;\n           }\n           set.insert(x);\n        }\n        //返回值\n        return (3 * setsum - numsum) / 2;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar singleNumber = function (nums) {\n  let set = new Set();\n  let setsum = 0;\n  let numsum = 0;\n  for (let x of nums) {\n    //所有元素的和\n    numsum += x;\n    if (!set.has(x)) {\n      setsum += x;\n    }\n    //HashSet内元素的和\n    set.add(x);\n  }\n  //返回值\n  return (3 * setsum - numsum) / 2;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def singleNumber(self, nums: List[int]) -> int:\n        return (3 * sum(set(nums)) - sum(nums)) // 2\n```\n\n这个题目用 HashMap 和排序查找肯定也是可以的，大家可以自己写一下，另外我们在第一题中有个利用异或求解的方法，但是这个题目是出现三次，我们则不能利用直接异或来求解，那还有其他方法吗？\n\n### 位运算\n\n#### 解析\n\n这个方法主要做法是将我们的数的二进制位每一位相加，然后对其每一位的和与 3 取余 ，我们看下面的例子。\n\n![只出现一次的数字2](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/只出现一次的数字2.5p4wxbiegxc0.png)\n\n那么我们为什么要这样做呢？大家想一下，如果其他数都出现 3 次，只有目标数出现 1 次，那么每一位的 1 的个数无非有这两种情况，为 3 的倍数（全为出现三次的数）或 3 的倍数 +1（包含出现一次的数）。这个 3 的倍数 +1 的情况也就是我们的目标数的那一位。\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int singleNumber(int[] nums) {\n      int res = 0;\n      for(int i = 0; i < 32; i++){\n          int count = 0;\n          for (int num: nums) {\n              //检查第 i 位是否为 1\n              if ((num >> i & 1) == 1) {\n                  count++;\n              }\n          }\n          if (count % 3 != 0) {\n              // 将第 i 位设为 1\n              res = res | 1 << i;\n          }\n      }\n      return res;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    int singleNumber(vector<int>& nums) {\n      int res = 0;\n      for(int i = 0; i < 32; i++){\n          int count = 0;\n          for (int num: nums) {\n              //检查第 i 位是否为 1\n              if ((num >> i & 1) == 1) {\n                  count++;\n              }\n          }\n          if (count % 3 != 0) {\n              // 将第 i 位设为 1\n              res = res | 1 << i;\n          }\n      }\n      return res;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar singleNumber = function (nums) {\n  let res = 0;\n  for (let i = 0; i < 32; i++) {\n    let count = 0;\n    for (let num of nums) {\n      //检查第 i 位是否为 1\n      if (((num >> i) & 1) == 1) {\n        count++;\n      }\n    }\n    if (count % 3 != 0) {\n      // 将第 i 位设为 1\n      res = res | (1 << i);\n    }\n  }\n  return res;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def singleNumber(self, nums: List[int]) -> int:\n        res = 0\n        for i in range(32):\n            count = 0\n            for num in nums:\n                # 检查第 i 位是否为 1\n                if (num >> i & 1) == 1:\n                    count += 1\n            if count % 3 != 0:\n                # 将第 i 位设为 1\n                res = res | 1 << i\n        # 这里的做法稍有不同，见下方解释\n        if (res >> 31 & 1) == 1:\n            res = ~(res ^ 4294967295)\n        return res\n```\n\nGo Code:\n\n```go\nfunc singleNumber(nums []int) int {\n    res := 0\n    // Go语言中，int占32位以上\n    for i := 0; i < 64; i++ {\n        cnt := 0\n        for j := 0; j < len(nums); j++ {\n            if (nums[j] >> i & 1) == 1 {\n                cnt++\n            }\n        }\n        if cnt % 3 != 0{\n            res = res | 1 << i\n        }\n    }\n    return res\n}\n```\n\n我们来解析一下我们的代码：\n\n> **<<** 左移运算符：运算数的各二进位全部左移若干位，由 **<<** 右边的数字指定了移动的位数，高位丢弃，低位补 0。\n>\n> **>>** 右移运算符：**>> ** 左边的运算数的各二进位全部右移若干位，**>>** 右边的数字指定了移动的位数。\n\n另外我们的代码中还包含了 a & 1 和 a | 1 这有什么作用呢？继续看下图。\n\n> **&** 按位与运算符：参与运算的两个值，如果两个相应位都为 1,则该位的结果为 1，否则为 0。\n\n![只出现一次的数位运算且](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/只出现一次的数位运算且.vq3lcgv0rbk.png)\n\n因为我们 a & 1 中 1 只有最后一位为 1，其余位皆为 0 ，所以我们发现 **a & 1 的作用就是判断 a 的最后一位是否为 1** ，如果 a 的最后一位为 1 ，a & 1 = 1，否则为 0 。所以我们还可以通过这个公式来判断 a 的奇偶性。\n\n> **|** 按位或运算符：只要对应的二个二进位有一个为 1 时，结果位就为 1。\n\n![或运算](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/或运算.6orep3gsrxc0.png)\n\n这个公式的作用就是**将我们移位后的 res 的最后一位 0 变为 1**。这个 1 也就代表着我们只出现一次元素的某一位。\n\n> 贡献者[@jaredliw](https://github.com/jaredliw)注：\n>\n> 这里我想解释一下 python 里的这两行：\n>\n> ```python\n> if (res >> 31 & 1) == 1:\n>     res = ~(res ^ 4294967295)\n> ```\n>\n> int 的的符号是由最高位（这题用的是 32 位）的值决定，1 就是负数，0 就是正数。由于 python 的 int 类型理论上是无限大的，这题里的 res 都会被认定为是正数。举个例子，32 位的 -4 是这样的：\n>\n> > 11111111111111111111111111111100 （最高位是 1 ）= -4\n>\n> python 里的则是这样的：\n>\n> > ...000000000000 11111111111111111111111111111100 （前面跟着无限个 0，最高位是 0 ）= 4294967292\n>\n> 怎么办呢？\n>\n> 我们可以先将 res 的后 32 位取反（与 4294967295 异或，4294967295 的二进制是 32 个 1），得到：\n>\n> > ...000000000000 00000000000000000000000000000011（最高位是 0）= 3\n>\n> 之后再用波浪号按位取反，得到：\n>\n> > ...111111111111 11111111111111111111111111111100 （前面跟着无限个 1，最高位是 1）= -4\n>\n> 大家可以自行验证看看：`(res >> n & 1) == 1` ，n 随便填个大于 31 的数字，之前是 false，之后就变成 true （代表第 33 位，第 34 位，……都转成 1 了）。\n>\n> 虽然说这种方法有一种脱裤子放屁的感觉 ，而且`res -= 2 ** 32` 也能办到，但由于涉及到 int 存储的问题（我省略了许多，大家自行度娘哈），我觉得还是有必要知道的。\n"
  },
  {
    "path": "animation-simulation/求次数问题/只出现一次的数3.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [260. 只出现一次的数字 III](https://leetcode-cn.com/problems/single-number-iii/)\n\n> 给定一个整数数组 nums，其中恰好有两个元素只出现一次，其余所有元素均出现两次。 找出只出现一次的那两个元素。\n\n示例 :\n\n> 输入: [1,2,1,3,2,5]\n> 输出: [3,5]\n\n这个也很容易理解，算是对第一题的升级，第一题有 1 个出现一次的数，其余出现两次，这个题目中有 2 个出现一次的数，其余数字出现两次。那么这个题目我们怎么做呢？我们看一下能不能利用第一题中的做法解决。\n\n### HashSet\n\n#### 解析\n\n这个做法和我们第一题的做法一致，只要理解了第一题的做法，这个很容易就能写出来，有一点不同的是，第一题的 HashSet 里面最后保留了一个元素，该题保留两个元素。\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] singleNumber(int[] nums) {\n        HashSet<Integer> set = new HashSet<Integer>();\n        for (int x : nums) {\n            //存在的则移除\n            if (set.contains(x)) {\n                set.remove(x);\n            //不存在则存入\n            } else {\n                set.add(x);\n            }\n        }\n        //存到数组里，然后返回\n        int[] arr = new int[2];\n        int i = 0;\n        for (int y : set) {\n           arr[i++] = y;\n        }\n        return arr;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    vector<int> singleNumber(vector<int>& nums) {\n        unordered_set<int> set;\n        for (int x : nums) {\n            //存在的则移除\n            if (set.find(x) == set.end()) {\n                set.insert(x);\n            //不存在则存入\n            } else {\n                set.erase(x);\n            }\n        }\n        //存到数组里，然后返回\n        vector<int> arr;\n        for (int y : set) {\n            arr.push_back(y);\n        }\n        return arr;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar singleNumber = function (nums) {\n  let set = new Set();\n  for (let x of nums) {\n    //存在的则移除\n    if (set.has(x)) {\n      set.delete(x);\n      //不存在则存入\n    } else {\n      set.add(x);\n    }\n  }\n  //存到数组里，然后返回\n  let arr = [];\n  for (let y of set) {\n    arr.push(y);\n  }\n  return arr;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def singleNumber(self, nums: List[int]) -> List[int]:\n        set_ = set()\n        for x in nums:\n            # 存在的则移除\n            if x in set_:\n                set_.remove(x)\n            # 不存在则存入\n            else:\n                set_.add(x)\n        # 存到数组里，然后返回\n        arr = []\n        for y in set_:\n            arr.append(y)\n        return arr\n```\n\n### 位运算\n\n#### 解析\n\n第一题中，我们可以通过异或运算直接求出目标数，但是我们第二题中不能直接用异或，是因为其他数字都出现三次，目标数出现一次。在这个题目中其他数字出现两次，目标数出现一次，但是这次的目标数为两个，我们直接异或运算的话，得到的数则为两个目标数的异或值，那么我们应该怎么做呢？\n\n我们试想一下，如果我们先将元素分成两组，然后每组包含一个目标值，那么异或之后，每组得到一个目标值，那么我们不就将两个目标值求出了吗？\n\n> 例： **a, b, a, b, c, d, e, f, e, f **\n>\n> 分组后：\n>\n> ​ A 组：a, a, b, b, c 异或得到 c\n>\n> ​ B 组：e, e, f, f, d 异或得到 d\n\n原理懂了，那么我们应该依据什么规则对其进行分类呢？\n\nc，d 两个不同的数，那么二进制上必定有一位是不同的，那么我们就可以根据这一位（分组位）来将 c，d 分到两个组中，数组中的其他元素，要么在 A 组中，要么在 B 组中。\n\n我们应该怎么得到分组位呢？\n\n我们让 c，d 异或即可，异或运算就是对应位不同时得 1，异或之后值为 1 的其中一位则为我们分组。\n\n例 001 ⊕ 100 = 101，我们可以用最右边的 1 或最左边的 1 做为分组位，数组元素中，若我们将最右边的 1 作为我们的分组位，最后一位为 0 的则进入 A 组，为 1 的进入 B 组。\n\n那么我们应该怎么借助分组位进行分组呢？\n\n我们处理 c , d 的异或值，可以仅保留异或值的分组位，其余位变为 0 ，例如 101 变成 001 或 100。\n\n为什么要这么做呢？在第二题提到，我们可以根据 a & 1 来判断 a 的最后一位为 0 还是为 1，所以我们将 101 变成 001 之后，然后数组内的元素 x & 001 即可对 x 进行分组 。同样也可以 x & 100 进行分组。\n\n那么我们如何才能仅保留分组位，其余位变为 0 呢？例 101 变为 001。\n\n我们可以利用 x & (-x) 来保留最右边的 1。\n\n![分组位](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/分组位.25gbi25kv7c0.png)\n\n#### 题目代码：\n\nJava Code:\n\n```java\nclass Solution {\n    public int[] singleNumber(int[] nums) {\n        //小心溢出\n        long temp = 0;\n        //求出异或值\n        for (int x : nums) {\n            temp ^= x;\n        }\n        System.out.println(temp);\n        System.out.println(-temp);\n        //保留最右边的一个 1\n        long group = temp & (-temp);\n        int[] arr = new int[2];\n        for (int y : nums) {\n            //分组位为 0 的组，组内异或\n            if ((group & y) == 0) {\n                arr[0] ^= y;\n            //分组位为 1 的组，组内异或\n            } else {\n                arr[1] ^= y;\n            }\n        }\n        return arr;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    vector<int> singleNumber(vector<int>& nums) {\n        //小心 -temp 溢出\n        long temp = 0;\n        //求出异或值\n        for (int x : nums) {\n            temp ^= x;\n        }\n        //保留最右边的一个 1\n        int group = temp & (-temp);\n        vector<int> arr(2, 0);\n        for (int y : nums) {\n            //分组位为 0 的组，组内异或\n            if ((group & y) == 0) {\n                arr[0] ^= y;\n            //分组位为 1 的组，组内异或\n            } else {\n                arr[1] ^= y;\n            }\n        }\n        return arr;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar singleNumber = function (nums) {\n  let temp = 0;\n  //求出异或值\n  for (let x of nums) {\n    temp ^= x;\n  }\n  //保留最右边的一个 1\n  let group = temp & -temp;\n  let arr = [0, 0];\n  for (let y of nums) {\n    //分组位为 0 的组，组内异或\n    if ((group & y) == 0) {\n      arr[0] ^= y;\n      //分组位为 1 的组，组内异或\n    } else {\n      arr[1] ^= y;\n    }\n  }\n  return arr;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def singleNumber(self, nums: List[int]) -> List[int]:\n        temp = 0\n        # 求出异或值\n        for x in nums:\n            temp ^= x\n        # 保留最右边的一个 1\n        group = temp & (-temp)\n        arr = [0, 0]\n        for y in nums:\n            # 分组位为 0 的组，组内异或\n            if (group & y) == 0:\n                arr[0] ^= y\n            # 分组位为 1 的组，组内异或\n            else:\n                arr[1] ^= y\n        return arr\n```\n\nGo Code:\n\n```go\nfunc singleNumber(nums []int) []int {\n    temp := 0\n    for _, x := range nums {\n        temp ^= x\n    }\n    // 保留最后那个1，为了区分两个数\n    group := temp & (^temp + 1)\n\n    res := make([]int, 2)\n    for _, x := range nums {\n        if group & x == 0{\n            res[0] ^= x\n        } else {\n            res[1] ^= x\n        }\n    }\n    return res\n}\n```\n"
  },
  {
    "path": "animation-simulation/滑动窗口/空.md",
    "content": ""
  },
  {
    "path": "animation-simulation/缓存淘汰算法/LFU.md",
    "content": ""
  },
  {
    "path": "animation-simulation/缓存淘汰算法/LRU.md",
    "content": ""
  },
  {
    "path": "animation-simulation/设计/LRU.md",
    "content": "说起缓存我们都不陌生\n\n浏览器缓存，数据库缓存等。\n\n说起**缓存淘汰策略**我们也很熟悉。\n\n例如先进先出策略 FIFO（First In，First Out）,最少使用策略 LFU（Least Frequently Used）,最近最少使用策略 LRU（Least Recently Used）\n\n看到这里大家是不是想到我们今天要说什么啦。\n\n我们就来说一下其中的一个缓存淘汰策略，**最近最少使用策略 LRU**\n\nLRU 的含义很容易理解，我们可以这样思考，最近使用过的我们则认为其是有用的，很久没用过的则认为是无用的，因为我们的内存有限，当我们内存满的时候，肯定是先清除那些很久没用过的数据。\n\n其实我们生活中有很多相似的例子，大家也都经历过。\n\n下面我们将镜头切换到袁记菜馆。\n\n袁厨最近新买了一些菜谱，打算在书房里好好闭关钻研一波。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210412213211.1d8uyzj1qysg.png)\n\n这下是不是理解个大概啦。\n\n其实生活中的我们也是这样的，买的一本新书则随手放到了最上面，从书堆中抽出一本，使用过后也放到了最上面，清理书时先清理很久没使用过的，对自己不太重要的（也就是最下面的图书）。\n\n到这里是不是就理解啦，其实生活中还有类似的例子，比如安卓手机的后台运行，整理衣柜的衣物等。\n\n好啦我们理解了 LRU 的含义。\n\n那么我们可以使用什么数据结构实现呢？\n\n数组？\n\n数组当然可以，但是我们使用数组需要\n"
  },
  {
    "path": "animation-simulation/贪心/空.md",
    "content": ""
  },
  {
    "path": "animation-simulation/递归/空.md",
    "content": ""
  },
  {
    "path": "animation-simulation/链表篇/234. 回文链表.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [234. 回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/)\n\n请判断一个链表是否为回文链表。\n\n示例 1:\n\n```\n输入: 1->2\n输出: false\n```\n\n示例 2:\n\n```java\n输入: 1->2->2->1\n输出: true\n```\n\n题目解析：\n\n题目理解起来很简单，判断是否为回文，如果单纯判断一个字符串或者数组是不是回文很容易。因为数组查询元素的时间复杂度为 O(1)，但是链表的查询时间复杂度为 O(n)，而且题目中的链表为单链表，指针只能后移不能前移。所以我们判断起来会比较困难。\n\n巧用数组法：\n\n我们首先将链表的所有元素都保存在数组中，然后再利用双指针遍历数组，进而来判断是否为回文。这个方法很容易理解，而且代码实现也比较简单。\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public boolean isPalindrome(ListNode head) {\n        //这里需要用动态数组，因为我们不知道链表的长度\n        List<Integer> arr = new ArrayList<Integer>();\n        ListNode copynode = head;\n        //将链表的值复制到数组中\n        while (copynode != null) {\n            arr.add(copynode.val);\n            copynode = copynode.next;\n        }\n        //双指针遍历数组\n        int back = 0;\n        int pro = arr.size() - 1;\n        while (back < pro) {\n            //判断两个指针的值是否相等\n            if (!arr.get(pro).equals(arr.get(back))) {\n                return false;\n            }\n            //移动指针\n            back++;\n            pro--;\n        }\n        return true;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    bool isPalindrome(ListNode* head) {\n        //这里需要用动态数组，因为我们不知道链表的长度\n        vector<int> arr;\n        ListNode* copynode = head;\n        //将链表的值复制到数组中\n        while (copynode) {\n            arr.push_back(copynode->val);\n            copynode = copynode->next;\n        }\n        //双指针遍历数组\n        int back = 0;\n        int pro = arr.size() - 1;\n        while (back < pro) {\n            //判断两个指针的值是否相等\n            if (arr[back] != arr[pro]) {\n                return false;\n            }\n            //移动指针\n            back++;\n            pro--;\n        }\n        return true;\n    }\n};\n```\n\nJS Code:\n\n```js\nvar isPalindrome = function (head) {\n  let arr = [];\n  let copynode = head;\n  //将链表的值复制到数组中\n  while (copynode) {\n    arr.push(copynode.val);\n    copynode = copynode.next;\n  }\n  //双指针遍历数组\n  let back = 0;\n  let pro = arr.length - 1;\n  while (back < pro) {\n    //判断两个指针的值是否相等\n    if (arr[back] !== arr[pro]) {\n      return false;\n    }\n    //移动指针\n    back += 1;\n    pro -= 1;\n  }\n  return true;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def isPalindrome(self, head: ListNode) -> bool:\n        arr = []\n        copynode = head\n        # 将链表的值复制到数组中\n        while copynode is not None:\n            arr.append(copynode.val)\n            copynode = copynode.next\n        # 双指针遍历数组\n        back = 0\n        pro = len(arr) - 1\n        while back < pro:\n        \t# 判断两个指针的值是否相等\n            if arr[back] != arr[pro]:\n                return False\n            # 移动指针\n            back += 1\n            pro -= 1\n        return True\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func isPalindrome(_ head: ListNode?) -> Bool {\n        // 这里需要用动态数组，因为我们不知道链表的长度\n        var arr:[Int?] = []\n        var copynode = head\n        // 将链表的值复制到数组中\n        while copynode != nil {\n            arr.append(copynode?.val)\n            copynode = copynode?.next\n        }\n        // 双指针遍历数组\n        var back = 0, pro = arr.count - 1\n        while back < pro {\n            // 判断两个指针的值是否相等\n            if arr[pro] != arr[back] {\n                return false\n            }\n            // 移动指针\n            back += 1\n            pro -= 1\n        }\n        return true\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc isPalindrome(head *ListNode) bool {\n    // 将节点中的值按顺序放在arr中。\n    arr := []int{}\n    node := head\n    for node != nil {\n        arr = append(arr, node.Val)\n        node = node.Next\n    }\n    // 双指针判断是否为回文\n    l, r := 0, len(arr) - 1\n    for l < r {\n        if arr[l] != arr[r] {\n            return false\n        }\n        l++\n        r--\n    }\n    return true\n}\n```\n\n这个方法可以直接通过，但是这个方法需要辅助数组，那我们还有其他更好的方法吗？\n\n**双指针翻转链表法**\n\n在上个题目中我们知道了如何找到链表的中间节点，那我们可以在找到中间节点之后，对后半部分进行翻转，翻转之后，重新遍历前半部分和后半部分进行判断是否为回文。\n\n动图解析：\n\n![翻转链表部分](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/翻转链表部分.1v2ncl72ligw.gif)\n\n#### **题目代码**\n\nJava Code：\n\n```java\nclass Solution {\n    public boolean isPalindrome(ListNode head) {\n         if (head==null || head.next==null) {\n             return true;\n         }\n         //找到中间节点，也就是翻转的头节点,这个在昨天的题目中讲到\n         //但是今天和昨天有一些不一样的地方就是，如果有两个中间节点返回第一个，昨天的题目是第二个\n         ListNode midnode = searchmidnode(head);\n         //原地翻转链表，需要两个辅助指针。这个也是面试题目，大家可以做一下\n         //这里我们用的是midnode.next需要注意，因为我们找到的是中点，但是我们翻转的是后半部分\n         ListNode backhalf = reverse(midnode.next);\n         //遍历两部分链表，判断值是否相等\n         ListNode p1 = head;\n         ListNode p2 = backhalf;\n         while (p2 != null) {\n            if (p1.val != p2.val) {\n                //若要还原，记得这里也要reverse\n                midnode.next = reverse(backhalf);\n                return false;\n            }\n            p1 = p1.next;\n            p2 = p2.next;\n        }\n        //还原链表并返回结果，这一步是需要注意的，我们不可以破坏初始结构，我们只是判断是否为回文，\n        //当然如果没有这一步也是可以AC，但是面试的时候题目要求可能会有这一条。\n        midnode.next = reverse(backhalf);\n        return true;\n    }\n    //找到中点\n    public ListNode searchmidnode (ListNode head) {\n          ListNode fast = head;\n          ListNode slow = head;\n          while (fast.next != null && fast.next.next != null) {\n              fast = fast.next.next;\n              slow = slow.next;\n          }\n          return slow;\n    }\n    //翻转链表\n    public ListNode reverse (ListNode slow) {\n          ListNode low  = null;\n          ListNode temp = null;\n          while (slow != null) {\n              temp = slow.next;\n              slow.next = low;\n              low = slow;\n              slow = temp;\n          }\n          return low;\n    }\n}\n```\n\nC++ Code：\n\n```cpp\nclass Solution {\npublic:\n    bool isPalindrome(ListNode* head) {\n        if (head == nullptr || head->next == nullptr) {\n              return true;\n         }\n         //找到中间节点，也就是翻转的头节点，这个在昨天的题目中讲到\n         //但是今天和昨天有一些不一样的地方就是，如果有两个中间节点返回第一个，昨天的题目是第二个\n         ListNode * midnode = searchmidnode(head);\n         //原地翻转链表，需要两个辅助指针。这个也是面试题目，大家可以做一下\n         //这里我们用的是midnode->next需要注意，因为我们找到的是中点，但是我们翻转的是后半部分\n         ListNode * backhalf = reverse(midnode->next);\n         //遍历两部分链表，判断值是否相等\n         ListNode * p1 = head;\n         ListNode * p2 = backhalf;\n         while (p2 != nullptr) {\n            if (p1->val != p2->val) {\n                //若要还原，记得这里也要reverse\n                midnode->next = reverse(backhalf);\n                return false;\n            }\n            p1 = p1->next;\n            p2 = p2->next;\n        }\n        //还原链表并返回结果，这一步是需要注意的，我们不可以破坏初始结构，我们只是判断是否为回文，\n        //当然如果没有这一步也是可以AC，但是面试的时候题目要求可能会有这一条。\n        midnode->next = reverse(backhalf);\n        return true;\n    }\n    //找到中间的部分\n    ListNode * searchmidnode (ListNode * head) {\n          ListNode * fast = head;\n          ListNode * slow = head;\n          while (fast->next != nullptr && fast->next->next != nullptr) {\n              fast = fast->next->next;\n              slow = slow->next;\n          }\n          return slow;\n    }\n    //翻转链表\n    ListNode * reverse (ListNode * slow) {\n          ListNode * low  = nullptr;\n          ListNode * temp = nullptr;\n          while (slow != nullptr) {\n              temp = slow->next;\n              slow->next = low;\n              low = slow;\n              slow = temp;\n          }\n          return low;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar isPalindrome = function (head) {\n  if (head === null || head.next === null) {\n    return true;\n  }\n  //找到中间节点，也就是翻转的头节点，这个在昨天的题目中讲到\n  //但是今天和昨天有一些不一样的地方就是，如果有两个中间节点返回第一个，昨天的题目是第二个\n  let midnode = searchmidnode(head);\n  //原地翻转链表，需要两个辅助指针。这个也是面试题目，大家可以做一下\n  //这里我们用的是midnode.next需要注意，因为我们找到的是中点，但是我们翻转的是后半部分\n  let backhalf = reverse(midnode.next);\n  //遍历两部分链表，判断值是否相等\n  let p1 = head;\n  let p2 = backhalf;\n  while (p2 != null) {\n    if (p1.val != p2.val) {\n      //若要还原，记得这里也要reverse\n      midnode.next = reverse(backhalf);\n      return false;\n    }\n    p1 = p1.next;\n    p2 = p2.next;\n  }\n  //还原链表并返回结果，这一步是需要注意的，我们不可以破坏初始结构，我们只是判断是否为回文，\n  //当然如果没有这一步也是可以AC，但是面试的时候题目要求可能会有这一条。\n  midnode.next = reverse(backhalf);\n  return true;\n};\n\n//找到中点\nvar searchmidnode = function (head) {\n  let fast = head;\n  let slow = head;\n  while (fast.next != null && fast.next.next != null) {\n    fast = fast.next.next;\n    slow = slow.next;\n  }\n  return slow;\n};\n\n//翻转链表\nvar reverse = function (slow) {\n  let low = null;\n  let temp = null;\n  while (slow != null) {\n    temp = slow.next;\n    slow.next = low;\n    low = slow;\n    slow = temp;\n  }\n  return low;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def isPalindrome(self, head: ListNode) -> bool:\n        if head is None or head.next is None:\n            return True\n        # 找到中间节点，也就是翻转的头节点，这个在昨天的题目中讲到\n        # 但是今天和昨天有一些不一样的地方就是，如果有两个中间节点返回第一个，昨天的题目是第二个\n        midnode = self.searchmidnode(head)\n        # 原地翻转链表，需要两个辅助指针。这个也是面试题目，大家可以做一下\n        # 这里我们用的是midnode.next需要注意，因为我们找到的是中点，但是我们翻转的是后半部分\n        backhalf = self.reverse(midnode.next)\n        # 遍历两部分链表，判断值是否相等\n        p1 = head\n        p2 = backhalf\n        while p2 is not None:\n            if p1.val != p2.val:\n                # 若要还原，记得这里也要reverse\n                midnode.next = self.reverse(backhalf)\n                return False\n            p1 = p1.next\n            p2 = p2.next\n        # 还原链表并返回结果，这一步是需要注意的，我们不可以破坏初始结构，我们只是判断是否为回文，\n        # 当然如果没有这一步也是可以AC，但是面试的时候题目要求可能会有这一条。\n        midnode.next = self.reverse(backhalf)\n        return True\n\n    # 找到中点\n    def searchmidnode(self, head):\n        fast = head\n        slow = head\n        while fast.next is not None and fast.next.next is not None:\n            fast = fast.next.next\n            slow = slow.next\n        return slow\n\n    # 翻转链表\n    def reverse(self, slow):\n        low = None\n        temp = None\n        while slow is not None:\n            temp = slow.next\n            slow.next = low\n            low = slow\n            slow = temp\n        return low\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func isPalindrome(_ head: ListNode?) -> Bool {\n        if head == nil || head?.next == nil {\n            return true\n        }\n        //找到中间节点，也就是翻转的头节点,这个在昨天的题目中讲到\n        //但是今天和昨天有一些不一样的地方就是，如果有两个中间节点返回第一个，昨天的题目是第二个\n        var midnode = searchmidnode(head)\n        //原地翻转链表，需要两个辅助指针。这个也是面试题目，大家可以做一下\n        //这里我们用的是midnode.next需要注意，因为我们找到的是中点，但是我们翻转的是后半部分\n        var backhalf = reverse(midnode?.next);\n        //遍历两部分链表，判断值是否相等\n        var p1 = head\n        var p2 = backhalf\n        while p2 != nil {\n            if p1?.val != p2?.val {\n                midnode?.next = reverse(backhalf)\n                return false\n            }\n            p1 = p1?.next\n            p2 = p2?.next\n        }\n        //还原链表并返回结果，这一步是需要注意的，我们不可以破坏初始结构，我们只是判断是否为回文，\n        //当然如果没有这一步也是可以AC，但是面试的时候题目要求可能会有这一条。\n        midnode?.next = reverse(backhalf)\n        return true\n    }\n    //找到中点\n    func searchmidnode(_ head: ListNode?) -> ListNode? {\n        var fast = head, slow = head\n        while fast?.next != nil && fast?.next?.next != nil {\n            fast = fast?.next?.next\n            slow = slow?.next\n        }\n        return slow\n    }\n    //翻转链表\n    func reverse(_ slow: ListNode?) -> ListNode? {\n        var slow = slow\n        var low: ListNode?\n        var temp: ListNode?\n        while slow != nil {\n            temp = slow?.next\n            slow?.next = low\n            low = slow\n            slow = temp\n        }\n        return low\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc isPalindrome(head *ListNode) bool {\n    if head == nil || head.Next == nil {\n        return true\n    }\n\n    midNode := searchMidNode(head)\n    backHalf := reverse(midNode.Next)\n\n    // 判断左右两边是否一样（回文）\n    p1, p2 := head, backHalf\n    for p2 != nil {\n        if p1.Val != p2.Val {\n            midNode.Next = reverse(backHalf)\n            return false\n        }\n        p1 = p1.Next\n        p2 = p2.Next\n    }\n    // 不破坏原来的数据\n    midNode.Next = reverse(backHalf)\n    return true\n}\n\n// searchMidNode 求中间的节点\nfunc searchMidNode(head *ListNode) *ListNode {\n    fast, slow := head, head\n    for fast.Next != nil && fast.Next.Next != nil {\n        fast = fast.Next.Next\n        slow = slow.Next\n    }\n    return slow\n}\n\n// reverse 反转链表\nfunc reverse(node *ListNode) *ListNode {\n    var pre *ListNode\n    for node != nil {\n        nxt := node.Next\n        node.Next = pre\n        pre = node\n        node = nxt\n    }\n    return pre\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/leetcode141环形链表.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### [141. 环形链表](https://leetcode-cn.com/problems/linked-list-cycle/)\n\n下面我们再来了解一种双指针，我们称之为快慢指针，顾名思义一个指针速度快，一个指针速度慢。\n\n#### 题目描述\n\n> 给定一个链表，判断链表中是否有环。pos 代表环的入口，若为-1，则代表无环。\n>\n> 如果链表中存在环，则返回 true 。否则，返回 false 。\n\n示例 1：\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210321131949755.png)\n\n> 输入：head = [3,2,0,-4], pos = 1\n> 输出：true\n> 解释：链表中有一个环，其尾部连接到第二个节点。\n\n#### 题目解析\n\n题目很容易理解，让我们判断链表中是否有环，我们只需通过我们的快慢指针即可，我们试想一下，如果链表中有环的话，一个速度快的指针和一个速度慢的指针在环中运动的话，若干圈后快指针肯定可以追上慢指针的。这是一定的。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210321132015849.png)\n\n好啦，做题思路已经有了，让我们一起看一下代码的执行过程吧。\n\n**动画模拟**\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210321115836276.gif)\n\n**题目代码**\n\nJava Code:\n\n```java\npublic class Solution {\n    public boolean hasCycle(ListNode head) {\n         ListNode fast = head;\n         ListNode low = head;\n         while (fast != null && fast.next != null) {\n             fast = fast.next.next;\n             low = low.next;\n             if (fast == low) {\n                 return true;\n             }\n         }\n         return false;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    bool hasCycle(ListNode *head) {\n         ListNode * fast = head;\n         ListNode * slow = head;\n         while (fast != nullptr && fast->next != nullptr) {\n             fast = fast->next->next;\n             slow = slow->next;\n             if (fast == slow) {\n                 return true;\n             }\n         }\n         return false;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar hasCycle = function (head) {\n  let fast = head;\n  let slow = head;\n  while (fast && fast.next) {\n    fast = fast.next.next;\n    slow = slow.next;\n    if (fast === slow) {\n      return true;\n    }\n  }\n  return false;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def hasCycle(self, head: ListNode) -> bool:\n        fast = head\n        slow = head\n        while fast and fast.next:\n            fast = fast.next.next\n            slow = slow.next\n            if fast == slow:\n                return True\n        return False\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func hasCycle(_ head: ListNode?) -> Bool {\n        var fast = head, slow = head\n        while fast != nil && fast?.next != nil {\n            fast = fast?.next?.next\n            slow = slow?.next\n            if fast === slow {\n                return true\n            }\n        }\n        return false\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc hasCycle(head *ListNode) bool {\n    if head == nil { return false }\n    s, f := head, head\n    for f != nil && f.Next != nil {\n        s = s.Next\n        f = f.Next.Next\n        if s == f {\n            return true\n        }\n    }\n    return false\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/leetcode142环形链表2.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [142. 环形链表 II](https://leetcode-cn.com/problems/linked-list-cycle-ii/)\n\n题目描述：\n\n今天给大家介绍比较有特点的题目，也是一个特别经典的题目，判断链表中有没有环，并返回环的入口。\n\n我们可以先做一下这个题目，就是如何判断链表中是否有环呢？下图则为链表存在环的情况。\n\n![image-20201027175552961](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027175552961.63sz69rbes00.png)\n\n判断链表是否有环是很简单的一个问题，我们只需利用之前的快慢指针即可，大家想一下，指针只要进入环内就只能在环中循环，那么我们可以利用快慢指针，虽然慢指针的速度小于快指针但是，总会进入环中，当两个指针都处于环中时，因为移动速度不同，两个指针必会相遇。\n\n我们可以这样假设，两个孩子在操场顺时针跑步，一个跑的快，一个跑的慢，跑的快的那个孩子总会追上跑的慢的孩子。\n\n代码请参考[【动画模拟】leetcode 141 环形链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode141%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.md)。\n\n判断链表是不是含有环很简单，但是我们想找到环的入口可能就没有那么容易了。（入口则为下图绿色节点）\n\n然后我们返回的则为绿色节点的索引，则返回 2。\n\n![image-20201027180921770](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027180921770.21fh8pt3cuv4.png)\n\n### HashSet\n\n我们可以利用 HashSet 来做，之前的文章说过 HashSet 是一个不允许有重复元素的集合。所以我们通过 HashSet 来保存链表节点，对链表进行遍历，如果链表不存在环则每个节点都会被存入环中，但是当链表中存在环时，则会发重复存储链表节点的情况，所以当我们发现 HashSet 中含有某节点时说明该节点为环的入口，返回即可。\n\n下图中，存储顺序为 0，1，2，3，4，5，6，**2 **因为 2 已经存在，则返回。\n\n![image-20201027182649669](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027182649669.2g8gq4ik6xs0.png)\n\nJava Code:\n\n```java\npublic class Solution {\n    public ListNode detectCycle(ListNode head) {\n         if (head == null) {\n             return head;\n         }\n         if (head.next == null) {\n             return head.next;\n         }\n         //创建新的HashSet，用于保存节点\n         HashSet<ListNode> hash = new HashSet<ListNode>();\n         //遍历链表\n         while (head != null) {\n             //判断哈希表中是否含有某节点，没有则保存，含有则返回该节点\n             if (hash.contains(head)) {\n                 return head;\n             }\n             //不含有，则进行保存，并移动指针\n             hash.add(head);\n             head = head.next;\n         }\n        return head;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode *detectCycle(ListNode *head) {\n        if (head == nullptr) return head;\n        if (head->next == nullptr) return head->next;\n        //创建新的HashSet，用于保存节点\n        set<ListNode *> hash;\n        //遍历链表\n        while (head != nullptr) {\n            //判断哈希表中是否含有某节点，没有则保存，含有则返回该节点\n            if (hash.count(head)) {\n                return head;\n            }\n            //不含有，则进行保存，并移动指针\n            hash.insert(head);\n            head = head->next;\n        }\n        return head;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar detectCycle = function (head) {\n  if (head === null) return head;\n  if (head.next === null) return head.next;\n  //创建新的HashSet，用于保存节点\n  let hash = new Set();\n  //遍历链表\n  while (head !== null) {\n    //判断哈希表中是否含有某节点，没有则保存，含有则返回该节点\n    if (hash.has(head)) {\n      return head;\n    }\n    //不含有，则进行保存，并移动指针\n    hash.add(head);\n    head = head.next;\n  }\n  return head;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def detectCycle(self, head: ListNode) -> ListNode:\n        if head is None:\n            return head\n        if head.next is None:\n            return head.next\n        # 创建新的HashSet，用于保存节点\n        hash = set()\n        while head is not None:\n            # 判断哈希表中是否含有某节点，没有则保存，含有则返回该节点\n            if head in hash:\n                return head\n            # 不含有，则进行保存，并移动指针\n            hash.add(head)\n            head = head.next\n        return head\n```\n\n### 快慢指针\n\n这个方法是比较巧妙的方法，但是不容易想到，也不太容易理解，利用快慢指针判断是否有环很容易，但是判断环的入口就没有那么容易，之前说过快慢指针肯定会在环内相遇，见下图。\n\n![image-20201027184755943](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201027184755943.3edot8s2xi60.png)\n\n上图黄色节点为快慢指针相遇的节点，此时\n\n快指针走的距离：**a+(b+c)n+b**，n 代表圈数。\n\n很容易理解 b+c 为环的长度，a 为直线距离，b 为绕了 n 圈之后又走了一段距离才相遇，所以相遇时走的总路程为 a+(b+c)n+b，合并同类项得 a+(n+1)b+nc。\n\n慢指针走的距离：**a+(b+c)m+b**，m 代表圈数。\n\n然后我们设快指针得速度是慢指针的 2 倍，含义为相同时间内，快指针走过的距离是慢指针的 2 倍。\n\n**a+(n+1)b+nc=2[a+(m+1)b+mc]**整理得**a+b=(n-2m)(b+c)，**那么我们可以从这个等式上面发现什么呢？\n\n**b+c**为一圈的长度。也就是说 a+b 等于 n-2m 个环的长度。为了便于理解我们看一种特殊情况，当 n-2m 等于 1，那么 a+b=b+c 整理得，a=c。此时我们只需重新释放两个指针，一个从 head 释放，一个从相遇点释放，速度相同，因为 a=c 所以他俩必会在环入口处相遇，则求得入口节点索引。\n\n算法流程：\n\n1.设置快慢指针，快指针速度为慢指针的 2 倍。\n\n2.找出相遇点。\n\n3.在 head 处和相遇点同时释放相同速度且速度为 1 的指针，两指针必会在环入口处相遇。\n\n![环形链表2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/环形链表2.elwu1pw2lw0.gif)\n\n**题目代码**\n\nJava Code:\n\n```java\npublic class Solution {\n    public ListNode detectCycle(ListNode head) {\n       //快慢指针\n        ListNode fast = head;\n        ListNode slow = head;\n        //设置循环条件\n        while (fast != null && fast.next != null) {\n            fast = fast.next.next;\n            slow = slow.next;\n            //相遇\n            if (fast == slow) {\n                //设置一个新的指针，从头节点出发，慢指针速度为1，所以可以使用慢指针从相遇点出发\n                ListNode newptr = head;\n                while (newptr != slow) {\n                    slow = slow.next;\n                    newptr = newptr.next;\n                }\n                //在环入口相遇\n                return slow;\n            }\n        }\n        return null;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode *detectCycle(ListNode *head) {\n        //快慢指针\n        ListNode * fast = head;\n        ListNode * slow = head;\n        //设置循环条件\n        while (fast != nullptr && fast->next != nullptr) {\n            fast = fast->next->next;\n            slow = slow->next;\n            //相遇\n            if (fast == slow) {\n                //设置一个新的指针，从头节点出发，慢指针速度为1，所以可以使用慢指针从相遇点出发\n                ListNode * newnode = head;\n                while (newnode != slow) {\n                    slow = slow->next;\n                    newnode = newnode->next;\n                }\n                //在环入口相遇\n                return slow;\n            }\n        }\n        return nullptr;\n    }\n};\n```\n\nJS Code:\n\n```js\nvar detectCycle = function (head) {\n  //快慢指针\n  let fast = head;\n  let slow = head;\n  //设置循环条件\n  while (fast && fast.next) {\n    fast = fast.next.next;\n    slow = slow.next;\n    //相遇\n    if (fast == slow) {\n      let newptr = head;\n      //设置一个新的指针，从头节点出发，慢指针速度为1，所以可以使用慢指针从相遇点出发\n      while (newptr != slow) {\n        slow = slow.next;\n        newptr = newptr.next;\n      }\n      //在环入口相遇\n      return slow;\n    }\n  }\n  return null;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def detectCycle(self, head: ListNode) -> ListNode:\n        # 快慢指针\n        fast = head\n        slow = head\n        # 设置循环条件\n        while fast is not None and fast.next is not None:\n            fast = fast.next.next\n            slow = slow.next\n            # 相遇\n            if fast is slow:\n                # 设置一个新的指针，从头节点出发，慢指针速度为1，所以可以使用慢指针从相遇点出发\n                newptr = head\n                while newptr is not slow:\n                    slow = slow.next\n                    newptr = newptr.next\n                # 在环入口相遇\n                return slow\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func detectCycle(_ head: ListNode?) -> ListNode? {\n        // 快慢指针\n        var fast = head, slow = head\n        while fast != nil && fast?.next != nil {\n            fast = fast?.next?.next\n            slow = slow?.next\n            // 相遇\n            if fast === slow {\n                // 设置一个新的指针，从头节点出发，慢指针速度为1，所以可以使用慢指针从相遇点出发\n                // 此处也可以不创新结点，直接将 fast = head\n                var newNode = head\n                while newNode !== slow {\n                    slow = slow?.next\n                    newNode = newNode?.next\n                }\n                return slow\n            }\n        }\n        return nil\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc detectCycle(head *ListNode) *ListNode {\n    if head == nil { return nil }\n    s, f := head, head\n    for f != nil && f.Next != nil {\n        s = s.Next\n        f = f.Next.Next\n        // 快慢指针相遇\n        if f == s {\n            // 快指针从头开始一步一步走，也可以用一个新的指针\n            f = head\n            for f != s {\n                f = f.Next\n                s = s.Next\n            }\n            return f\n        }\n    }\n    return nil\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/leetcode147对链表进行插入排序.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n有的老哥说链表的排序搞不明白，让我写一下，一不小心给忘记了，今天咱们就安排上。没有学过数据结构的同学可以先看下这个文章：\n\n[【绘图解析】链表详解](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%85%B3%E4%BA%8E%E9%93%BE%E8%A1%A8%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B.md)\n\n另外大家如果忘记了[【动画模拟】插入排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E7%9B%B4%E6%8E%A5%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F.md)和[【动画模拟】归并排序](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F.md)思想的话，可以先复习一下，不然这两道题目会看得云里雾里。\n\n#### [147. 对链表进行插入排序](https://leetcode-cn.com/problems/insertion-sort-list/)\n\n**示例 1：**\n\n> 输入: 4->2->1->3\n> 输出: 1->2->3->4\n\n**示例 2：**\n\n> 输入: -1->5->3->4->0\n> 输出: -1->0->3->4->5\n\n下面咱们先来看个动画回忆一下插入排序的具体执行步骤，然后我们再来说下面的题目。\n\n![直接插入排序](https://cdn.jsdelivr.net/gh/tan45du/test1@master/20210122/直接插入排序.2marc4epuzy0.gif)\n\n我们的指针在数组时，可以随意的前后移动，将指针指向值和新元素的值比较后，将新元素插入到合适的位置。\n\n我们知道链表查询元素的时间复杂度为 O(n)，我们只能够通过遍历链表查询元素。\n\n那么我们怎么才能将新元素放到合适的位置呢？见下图。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210325113449.75knzw7zmyg0.png)\n\n此时我们不能通过移动绿色指针来寻找 5 的合适位置，那么我们应该怎么做呢？见下图。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210325131349.14mi2ap89uxs.png)\n\n当我们发现绿色指针的值大于新元素时（7 > 5），我们则可以定义一个新指针，让其从哑节点开始遍历，直到找到新元素（5）的位置，（4 和 7 之间），然后再将新元素插入即可。\n\n我们是通过下面这行代码来找到插入位置的, temphead 则代表我们的紫色指针。\n\n```java\n while (temphead.next.val <= pre.val) {\n      temphead = temphead.next;\n }\n```\n\n下面我们再来看动画模拟具体过程。\n\n**注：为了更好的表达算法思想，让过程更流畅，特省略了指针的移动细节，直接插入到合适位置，后面会详细说明插入操作的具体步骤。**\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/链表的插入排序.4hnc4shp5le0.gif)\n\n我们通过上面的动画知道了大致过程，那么我们的是如何将新元素插入到指定位置的呢？\n\n见下图。\n\n![插入排序](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210325132359.1hc2axzks3k0.png)\n\n我们想要将 3 插入到 2 和 4 的中间，此时我们三个指针分别指向 2，4，3。\n\n我们共分 4 步，来完成这个操作，见下图。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/44444444.29mvcvs4yrms.png)\n\n我们来把上图中的最后结果来捋一下看看最后结果是不是完成了插入操作。\n\n![](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/微信截图_20210325134022.w5nnr1spyps.png)\n\n这样我们就完成了链表的插入排序。下面我们来看下题目代码吧。\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public ListNode insertionSortList(ListNode head) {\n        if (head == null && head.next == null) {\n            return head;\n        }\n        //哑节点\n        ListNode dummyNode = new ListNode(-1);\n        dummyNode.next = head;\n        //pre负责指向新元素，last 负责指向新元素的前一元素\n        //判断是否需要执行插入操作\n        ListNode pre = head.next;\n        ListNode last = head;\n        while (pre != null) {\n            //不需要插入到合适位置，则继续往下移动\n            if (last.val <= pre.val) {\n                pre = pre.next;\n                last = last.next;\n                continue;\n            }\n            //开始出发，查找新元素的合适位置\n            ListNode temphead = dummyNode;\n            while (temphead.next.val <= pre.val) {\n                temphead = temphead.next;\n            }\n            //此时我们已经找到了合适位置，我们需要进行插入，大家可以画一画\n            last.next = pre.next;\n            pre.next = temphead.next;\n            temphead.next = pre;\n            //继续往下移动\n            pre = last.next;\n        }\n        return dummyNode.next;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode* insertionSortList(ListNode* head) {\n        if (head == nullptr && head->next == nullptr) {\n            return head;\n        }\n        //哑节点\n        ListNode * dummyNode = new ListNode(-1);\n        dummyNode->next = head;\n        //pre负责指向新元素，last 负责指向新元素的前一元素\n        //判断是否需要执行插入操作\n        ListNode * pre = head->next;\n        ListNode * last = head;\n        while (pre != nullptr) {\n            //不需要插入到合适位置，则继续往下移动\n            if (last->val <= pre->val) {\n                pre = pre->next;\n                last = last->next;\n                continue;\n            }\n            //开始出发，查找新元素的合适位置\n            ListNode * temphead = dummyNode;\n            while (temphead->next->val <= pre->val) {\n                temphead = temphead->next;\n            }\n            //此时我们已经找到了合适位置，我们需要进行插入，大家可以画一画\n            last->next = pre->next;\n            pre->next = temphead->next;\n            temphead->next = pre;\n            //继续往下移动\n            pre = last->next;\n        }\n        return dummyNode->next;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar insertionSortList = function (head) {\n  if (head === null || head.next === null) return head;\n  //哑节点\n  let dummyNode = new ListNode(-1, head);\n  let pre = head.next;\n  //pre负责指向新元素，last 负责指向新元素的前一元素\n  //判断是否需要执行插入操作\n  let last = head;\n  while (pre) {\n    //不需要插入到合适位置，则继续往下移动\n    if (last.val <= pre.val) {\n      last = last.next;\n      pre = pre.next;\n      continue;\n    }\n    //开始出发，查找新元素的合适位置\n    let tempHead = dummyNode;\n    while (tempHead.next.val <= pre.val) {\n      tempHead = tempHead.next;\n    }\n    //此时我们已经找到了合适位置，我们需要进行插入，大家可以画一画\n    last.next = pre.next;\n    pre.next = tempHead.next;\n    tempHead.next = pre;\n    //继续往下移动\n    pre = last.next;\n  }\n  return dummyNode.next;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def insertionSortList(self, head: ListNode) -> ListNode:\n        if head is None or head.next is None:\n            return head\n        # 哑节点\n        dummyNode = ListNode(-1, head)\n        # pre负责指向新元素，last 负责指向新元素的前一元素\n        # 判断是否需要执行插入操作\n        pre = head.next\n        last = head\n        while pre is not None:\n            # 不需要插入到合适位置，则继续往下移动\n            if last.val <= pre.val:\n                pre = pre.next\n                last = last.next\n                continue\n            # 开始出发，查找新元素的合适位置\n            temphead = dummyNode\n            while temphead.next.val <= pre.val:\n                temphead = temphead.next\n            # 此时我们已经找到了合适位置，我们需要进行插入，大家可以画一画\n            last.next = pre.next\n            pre.next = temphead.next\n            temphead.next = pre\n            # 继续往下移动\n            pre = last.next\n        return dummyNode.next\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func insertionSortList(_ head: ListNode?) -> ListNode? {\n        if head == nil && head?.next == nil {\n            return head\n        }\n        //哑节点\n        var dummyNode = ListNode(-1)\n        dummyNode.next = head\n        //pre负责指向新元素，last 负责指向新元素的前一元素\n        //判断是否需要执行插入操作\n        var pre = head?.next\n        var last = head\n        while pre != nil {\n            //不需要插入到合适位置，则继续往下移动\n            if last!.val <= pre!.val {\n                pre = pre?.next\n                last = last?.next\n                continue\n            }\n            //开始出发，查找新元素的合适位置\n            var temphead = dummyNode\n            while temphead.next!.val <= pre!.val {\n                temphead = temphead.next!\n            }\n            //此时我们已经找到了合适位置，我们需要进行插入，大家可以画一画\n            last?.next = pre?.next\n            pre?.next = temphead.next\n            temphead.next = pre\n            //继续往下移动\n            pre = last?.next\n        }\n        return dummyNode.next\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc insertionSortList(head *ListNode) *ListNode {\n    if head == nil || head.Next == nil { return head }\n    root := &ListNode{\n        Next: head,\n    }\n    cur, nxt := head, head.Next\n    for nxt != nil {\n        // 有序的不需要换位置\n        if cur.Val <= nxt.Val {\n            cur = cur.Next\n            nxt = nxt.Next\n            continue\n        }\n        temp := root\n        for temp.Next.Val <= nxt.Val {\n            temp = temp.Next\n        }\n        // 此时找到合适的位置\n        cur.Next = nxt.Next\n        nxt.Next = temp.Next\n        temp.Next = nxt\n        // 继续向下\n        nxt = cur.Next\n    }\n    return root.Next\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/leetcode206反转链表.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n今天咱们说一道非常简单但是很经典的面试题，思路很容易，但是里面细节挺多，所以我们还是需要注意。\n\n我们先来看一下题目描述。\n\n#### [206. 反转链表](https://leetcode-cn.com/problems/reverse-linked-list/)\n\n反转一个单链表。\n\n**示例:**\n\n> 输入: 1->2->3->4->5->NULL\n> 输出: 5->4->3->2->1->NULL\n\n该题目我们刚开始刷题的同学可能会想到先保存到数组中，然后从后往前遍历数组，重新组成链表，这样做是可以 AC 的，但是我们机试时往往不允许我们修改节点的值，仅仅是修改节点的指向。所以我们应该用什么方法来解决呢？\n\n我们先来看动图，看看我们能不能理解。然后再对动图进行解析。\n\n![](https://img-blog.csdnimg.cn/20210323191331552.gif)\n\n原理很容易理解，我们首先将 low 指针指向空节点， pro 节点指向 head 节点，\n\n然后我们定义一个临时节点 temp 指向 pro 节点，\n\n此时我们就记住了 pro 节点的位置，然后 pro = pro.next，这样我们三个指针指向三个不同的节点。\n\n则我们将 temp 指针指向 low 节点，此时则完成了反转。\n\n反转之后我们继续反转下一节点，则 low = temp 即可。然后重复执行上诉操作直至最后，这样则完成了反转链表。\n\n我们下面看代码吧。\n\n我会对每个关键点进行注释，大家可以参考动图理解。\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public ListNode reverseList(ListNode head) {\n        //特殊情况\n        if (head == null || head.next == null) {\n            return head;\n        }\n        ListNode low = null;\n        ListNode pro = head;\n        while (pro != null) {\n            //代表橙色指针\n            ListNode temp = pro;\n            //移动绿色指针\n            pro = pro.next;\n            //反转节点\n            temp.next = low;\n            //移动黄色指针\n            low = temp;\n        }\n        return low;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode* reverseList(ListNode* head) {\n        //特殊情况\n        if (head == nullptr || head->next == nullptr) {\n            return head;\n        }\n        ListNode * low = nullptr;\n        ListNode * pro = head;\n        while (pro != nullptr) {\n            //代表橙色指针\n            ListNode * temp = pro;\n            //移动绿色指针\n            pro = pro->next;\n            //反转节点\n            temp->next = low;\n            //移动黄色指针\n            low = temp;\n        }\n        return low;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar reverseList = function (head) {\n  //特殊情况\n  if (!head || !head.next) {\n    return head;\n  }\n  let low = null;\n  let pro = head;\n  while (pro) {\n    //代表橙色指针\n    let temp = pro;\n    //移动绿色指针\n    pro = pro.next;\n    //反转节点\n    temp.next = low;\n    //移动黄色指针\n    low = temp;\n  }\n  return low;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def reverseList(self, head: ListNode) -> ListNode:\n    \t# 特殊情况\n        if head is None or head.next is None:\n            return head\n        low = None\n        pro = head\n        while pro is not None:\n\t        # 代表橙色指针\n            temp = pro\n            # 移动绿色指针\n            pro = pro.next\n            # 反转节点\n            temp.next = low\n            # 移动黄色指针\n            low = temp\n        return low\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func reverseList(_ head: ListNode?) -> ListNode? {\n        // 边界条件\n        if head == nil || head?.next == nil {\n            return head\n        }\n        var pro = head\n        var low: ListNode?\n        while pro != nil {\n            // 代表橙色指针\n            var temp = pro\n            // 移动绿色指针\n            pro = pro?.next\n            // 反转节点\n            temp?.next = low\n            // 移动黄色指针\n            low = temp\n        }\n        return low\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc reverseList(head *ListNode) *ListNode {\n    if head == nil || head.Next == nil { return head }\n    cur := head\n    var pre *ListNode\n    for cur != nil {\n        nxt := cur.Next\n        cur.Next = pre\n        pre = cur\n        cur = nxt\n        if nxt == nil {\n            return pre\n        }\n        nxt = nxt.Next\n    }\n    return pre\n}\n```\n\n上面的迭代写法是不是搞懂啦，现在还有一种递归写法，不是特别容易理解，刚开始刷题的同学，可以只看迭代解法。\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public ListNode reverseList(ListNode head) {\n        //结束条件\n        if (head == null || head.next == null) {\n            return head;\n        }\n        //保存最后一个节点\n        ListNode pro = reverseList(head.next);\n        //将节点进行反转。我们可以这样理解 4.next.next = 4\n        //4.next = 5\n        //则 5.next = 4 则实现了反转\n        head.next.next = head;\n        //防止循环\n        head.next = null;\n        return pro;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode * reverseList(ListNode * head) {\n        //结束条件\n        if (head == nullptr || head->next == nullptr) {\n            return head;\n        }\n        //保存最后一个节点\n        ListNode * pro = reverseList(head->next);\n        //将节点进行反转。我们可以这样理解 4->next->next = 4\n        //4->next = 5\n        //则 5->next = 4 则实现了反转\n        head->next->next = head;\n        //防止循环\n        head->next = nullptr;\n        return pro;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar reverseList = function (head) {\n  //结束条件\n  if (!head || !head.next) {\n    return head;\n  }\n  //保存最后一个节点\n  let pro = reverseList(head.next);\n  //将节点进行反转。我们可以这样理解 4.next.next = 4\n  //4.next = 5\n  //则 5.next = 4 则实现了反转\n  head.next.next = head;\n  //防止循环\n  head.next = null;\n  return pro;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def reverseList(self, head: ListNode) -> ListNode:\n    \t# 结束条件\n        if head is None or head.next is None:\n            return head\n        # 保存最后一个节点\n        pro = self.reverseList(head.next)\n        # 将节点进行反转。我们可以这样理解 4->next->next = 4\n        # 4->next = 5\n        # 则 5->next = 4 则实现了反转\n        head.next.next = head\n        # 防止循环\n        head.next = None\n        return pro\n```\n\nSwift Code:\n\n```swift\nclass Solution {\n    func reverseList(_ head: ListNode?) -> ListNode? {\n        // 结束条件\n        if head == nil || head?.next == nil {\n            return head\n        }\n        var pro = reverseList(head?.next)\n        // 将节点进行反转\n        head?.next?.next = head\n        // 防止循环\n        head?.next = nil\n        return pro\n    }\n}\n```\n\n<br/>\n\n> 贡献者[@jaredliw](https://github.com/jaredliw)注：\n>\n> 这里提供一个比较直观的递归写法供大家参考。由于代码比较直白，其它语言的我就不写啦。\n>\n> ```python\n> class Solution:\n>        def reverseList(self, head: ListNode, prev_nd: ListNode = None) -> ListNode:\n>            # 结束条件\n>            if head is None:\n>                return prev_nd\n>            # 记录下一个节点并反转\n>            next_nd = head.next\n>            head.next = prev_nd\n>            # 给定下一组该反转的节点\n>            return self.reverseList(next_nd, head)\n> ```\n"
  },
  {
    "path": "animation-simulation/链表篇/leetcode328奇偶链表.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n### [328. 奇偶链表](https://leetcode-cn.com/problems/odd-even-linked-list/)\n\n下面我们再来看一种双指针，我称之为交替领先双指针，起名鬼才哈哈。下面我们一起来看看吧。\n\n#### 题目描述\n\n> 给定一个单链表，把所有的奇数节点和偶数节点分别排在一起。请注意，这里的奇数节点和偶数节点指的是节点编号的奇偶性，而不是节点的值的奇偶性。\n>\n> 请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1)，时间复杂度应为 O(nodes)，nodes 为节点总数。\n\n示例 1:\n\n> 输入: 1->2->3->4->5->NULL\n> 输出: 1->3->5->2->4->NULL\n\n示例 2:\n\n> 输入: 2->1->3->5->6->4->7->NULL\n> 输出: 2->3->6->7->1->5->4->NULL\n\n#### 题目解析\n\n题目也很容易理解就是让我们将原来奇数位的结点放一起，偶数位的结点放一起。这里需要注意，题目和结点值无关，是奇数位和偶数位结点。\n\n我们可以先将奇数位和在一起，再将偶数位和在一起，最后再将两个链表合并很简单，我们直接看动画模拟吧。\n\n#### **动画模拟**\n\n![](https://img-blog.csdnimg.cn/20210321120150255.gif)\n\n#### 题目代码\n\nJava Code:\n\n```java\nclass Solution {\n    public ListNode oddEvenList(ListNode head) {\n         if (head == null || head.next == null) {\n             return head;\n         }\n         ListNode odd = head;\n         ListNode even = head.next;\n         ListNode evenHead = even;\n\n         while (odd.next != null && even.next != null) {\n             //将偶数位合在一起，奇数位合在一起\n             odd.next = even.next;\n             odd = odd.next;\n             even.next = odd.next;\n             even = even.next;\n         }\n         //链接\n         odd.next = evenHead;\n         return head;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode* oddEvenList(ListNode* head) {\n         if (head == nullptr || head->next == nullptr) {\n             return head;\n         }\n         ListNode * odd = head;\n         ListNode * even = head->next;\n         ListNode * evenHead = even;\n\n         while (odd->next != nullptr && even->next != nullptr) {\n             //将偶数位合在一起，奇数位合在一起\n             odd->next = even->next;\n             odd = odd->next;\n             even->next = odd->next;\n             even = even->next;\n         }\n         //链接\n         odd->next = evenHead;\n         return head;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar oddEvenList = function (head) {\n  if (!head || !head.next) return head;\n  let odd = head,\n    even = head.next,\n    evenHead = even;\n  while (odd.next && even.next) {\n    //将偶数位合在一起，奇数位合在一起\n    odd.next = even.next;\n    odd = odd.next;\n    even.next = odd.next;\n    even = even.next;\n  }\n  //链接\n  odd.next = evenHead;\n  return head;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def oddEvenList(self, head: ListNode) -> ListNode:\n        if head is None or head.next is None:\n            return head\n        odd = head\n        even = head.next\n        evenHead = even\n        while odd.next is not None and even.next is not None:\n            # 将偶数位合在一起，奇数位合在一起\n            odd.next = even.next\n            odd = odd.next\n            even.next = odd.next\n            even = even.next\n        # 链接\n        odd.next = evenHead\n        return head\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func oddEvenList(_ head: ListNode?) -> ListNode? {\n        if head == nil || head?.next == nil {\n            return head\n        }\n        var odd = head\n        var even = head?.next\n        var evenHead = even\n        while odd?.next != nil && even?.next != nil {\n            //将偶数位合在一起，奇数位合在一起\n            odd?.next = even?.next\n            odd = odd?.next\n            even?.next = odd?.next\n            even = even?.next\n        }\n        //链接\n        odd?.next = evenHead\n        return head\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc oddEvenList(head *ListNode) *ListNode {\n    if head == nil || head.Next == nil {\n        return head\n    }\n    odd, even := head, head.Next\n    evenHead := even\n    for odd.Next != nil && even.Next != nil {\n        odd.Next = even.Next\n        odd = odd.Next\n        even.Next = odd.Next\n        even = even.Next\n    }\n    odd.Next = evenHead\n    return head\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/leetcode82删除排序链表中的重复元素II.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [82. 删除排序链表中的重复元素 II](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/)\n\n**题目描述**\n\n给定一个排序链表，删除所有含有重复数字的节点，只保留原始链表中没有重复出现的数字。\n\n示例 1:\n\n```java\n输入: 1->2->3->3->4->4->5\n输出: 1->2->5\n```\n\n示例 2:\n\n```java\n输入: 1->1->1->2->3\n输出: 2->3\n```\n\n> 注意这里会将重复的值全部删除，1，1，2，3 最后只会保留 2，3。\n\n这道题目还是很简单的，更多的是考察大家的代码完整性，删除节点也是题库中的一类题目，我们可以可以通过这个题目举一反三。去完成其他删除阶段的题目。\n\n链表的题目建议大家能有指针实现还是尽量用指针实现，很多链表题目都可以利用辅助空间实现，我们也可以用，学会了那种方法的同时应该再想一下可不可以利用指针来完成。下面我们来思考一下这个题目如何用指针实现吧！\n\n做题思路：\n\n这个题目也是利用我们的双指针思想，一个走在前面，一个在后面紧跟，前面的指针就好比是侦察兵，当发现重复节点时，后面指针停止移动，侦察兵继续移动，直到移动完重复节点，然后将该节点赋值给后节点。思路是不是很简单啊，那么我们来看一下动图模拟吧。\n\n注：这里为了表达更直观，所以仅显示了该链表中存在的节点。\n\n![删除重复节点2](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/删除重复节点2.3btmii5cgxa0.gif)\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public ListNode deleteDuplicates(ListNode head) {\n        //侦察兵指针\n        ListNode pre = head;\n        //创建哑节点，接上head\n        ListNode dummy = new ListNode(-1);\n        dummy.next = head;\n        //跟随的指针\n        ListNode low = dummy;\n        while(pre != null && pre.next != null) {\n            if (pre.val == pre.next.val) {\n                //移动侦察兵指针直到找到与上一个不相同的元素\n                while (pre != null && pre.next != null && pre.val == pre.next.val) {\n                    pre = pre.next;\n                }\n                //while循环后，pre停留在最后一个重复的节点上\n                pre = pre.next;\n                //连上新节点\n                low.next = pre;\n             }\n             else{\n                 pre = pre.next;\n                 low = low.next;\n             }\n        }\n     return dummy.next;//注意，这里传回的不是head，而是虚拟节点的下一个节点，head有可能已经换了\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode* deleteDuplicates(ListNode* head) {\n        //侦察兵指针\n        ListNode * pre = head;\n        //创建哑节点，接上head\n        ListNode * dummy = new ListNode(-1);\n        dummy->next = head;\n        //跟随的指针\n        ListNode * low = dummy;\n        while(pre != nullptr && pre->next != nullptr) {\n            if (pre->val == pre->next->val) {\n                //移动侦察兵指针直到找到与上一个不相同的元素\n                while (pre != nullptr && pre->next != nullptr && pre->val == pre->next->val) {\n                    pre = pre->next;\n                }\n                //while循环后，pre停留在最后一个重复的节点上\n                pre = pre->next;\n                //连上新节点\n                low->next = pre;\n             }\n             else{\n                 pre = pre->next;\n                 low = low->next;\n             }\n        }\n     return dummy->next;//注意，这里传回的不是head，而是虚拟节点的下一个节点，head有可能已经换了\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar deleteDuplicates = function (head) {\n  //侦察兵指针\n  let pre = head;\n  //创建虚拟头节点，接上head\n  let dummy = new ListNode(-1);\n  dummy.next = pre;\n  //跟随的指针\n  let low = dummy;\n  while (pre != null && pre.next != null) {\n    if (pre.val == pre.next.val) {\n      //移动侦察兵指针直到找到与上一个不相同的元素\n      while (pre != null && pre.next != null && pre.val === pre.next.val) {\n        pre = pre.next;\n      }\n      //while循环后，pre停留在最后一个重复的节点上\n      pre = pre.next;\n      //连上新节点\n      low.next = pre;\n    } else {\n      pre = pre.next;\n      low = low.next;\n    }\n  }\n  return dummy.next; //注意，这里传回的不是head，而是虚拟节点的下一个节点，head有可能已经换了\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def deleteDuplicates(self, head: ListNode) -> ListNode:\n        # 侦察兵指针\n        pre = head\n        # 创建虚拟头节点，接上head\n        dummy = ListNode(-1, head)\n        # 跟随的指针\n        low = dummy\n        while pre is not None and pre.next is not None:\n            if pre.val == pre.next.val:\n                # 移动侦察兵指针直到找到与上一个不相同的元素\n                while pre is not None and pre.next is not None and pre.val == pre.next.val:\n                    pre = pre.next\n                # while循环后，pre停留在最后一个重复的节点上\n                pre = pre.next\n                # 连上新节点\n                low.next = pre\n            else:\n                pre = pre.next\n                low = low.next\n        return dummy.next  # 注意，这里传回的不是head，而是虚拟节点的下一个节点，head有可能已经换了\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func deleteDuplicates(_ head: ListNode?) -> ListNode? {\n        // 侦察兵指针\n        var pre = head\n        // 创建哑节点，接上head\n        var dummy = ListNode(-1)\n        dummy.next = head\n        // 跟随的指针\n        var low:ListNode? = dummy\n        while pre != nil && pre?.next != nil {\n            if pre?.val == pre?.next?.val {\n                // 移动侦察兵指针直到找到与上一个不相同的元素\n                while pre != nil && pre?.next != nil && pre?.val == pre?.next?.val {\n                    pre = pre?.next\n                }\n                // while循环后，pre停留在最后一个重复的节点上\n                pre = pre?.next\n                // 连上新节点\n                low?.next = pre\n            } else {\n                pre = pre?.next\n                low = low?.next\n            }\n        }\n        return dummy.next // 注意，这里传回的不是head，而是虚拟节点的下一个节点，head有可能已经换了\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc deleteDuplicates(head *ListNode) *ListNode {\n\t// 新建一个头结点，他的下一个节点才是开始\n    root := &ListNode{\n        Next: head,\n    }\n    pre, cur := root, head\n    for cur != nil && cur.Next != nil {\n        if cur.Val == cur.Next.Val {\n            // 相等的话，cur就一直向后移动\n            for cur != nil && cur.Next != nil && cur.Val == cur.Next.Val {\n                cur = cur.Next\n            }\n            // 循环后移动到了最后一个相同的节点。\n            cur = cur.Next\n            pre.Next = cur\n        } else {\n            cur = cur.Next\n            pre = pre.Next\n        }\n    }\n    return root.Next\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/leetcode86分隔链表.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [86. 分隔链表](https://leetcode-cn.com/problems/partition-list/)\n\n给你一个链表的头节点 head 和一个特定值 x ，请你对链表进行分隔，使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。\n\n你应当 保留 两个分区中每个节点的初始相对位置。\n\n![](https://img-blog.csdnimg.cn/20210319190335143.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzODg1OTI0,size_16,color_FFFFFF,t_70)\n\n示例 1：\n\n输入：head = [1,4,3,2,5,2], x = 3\n输出：[1,2,2,4,3,5]\n示例 2：\n\n输入：head = [2,1], x = 2\n输出：[1,2]\n\n来源：力扣（LeetCode）\n\n这个题目我的做题思路是这样的，我们先创建一个侦察兵，侦察兵负责比较链表值和 x 值，如果 >= 的话则接在 big 链表上，小于则接到 small 链表上，最后一个细节就是我们的 big 链表尾部要加上 null，不然会形成环。这是这个题目的一个小细节，很重要。\n\n中心思想就是，将链表先分后合。\n\n下面我们来看模拟视频吧。希望能给各位带来一丢丢帮助。\n\n![](https://img-blog.csdnimg.cn/20210319190417499.gif)\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public ListNode partition(ListNode head, int x) {\n        ListNode pro = head;\n        ListNode big = new ListNode(-1);\n        ListNode small = new ListNode(-1);\n        ListNode headbig = big;\n        ListNode headsmall = small;\n        //分\n        while (pro != null) {\n            //大于时，放到 big 链表上\n            if (pro.val >= x) {\n                big.next = pro;\n                big = big.next;\n            //小于时，放到 small 链表上\n            }else {\n                small.next = pro;\n                small = small.next;\n            }\n            pro = pro.next;\n        }\n        //细节\n        big.next = null;\n        //合\n        small.next = headbig.next;\n        return headsmall.next;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode* partition(ListNode* head, int x) {\n        ListNode * pro = head;\n        ListNode * big = new ListNode(-1);\n        ListNode * small = new ListNode(-1);\n        ListNode * headbig = big;\n        ListNode * headsmall = small;\n        //分\n        while (pro != nullptr) {\n            //大于时，放到 big 链表上\n            if (pro->val >= x) {\n                big->next = pro;\n                big = big->next;\n            //小于时，放到 small 链表上\n            }else {\n                small->next = pro;\n                small = small->next;\n            }\n            pro = pro->next;\n        }\n        //细节\n        big->next = nullptr;\n        //合\n        small->next = headbig->next;\n        return headsmall->next;\n    }\n};\n```\n\nJS Code:\n\n```js\nvar partition = function (head, x) {\n  let pro = head;\n  let big = new ListNode(-1);\n  let small = new ListNode(-1);\n  let headbig = big;\n  let headsmall = small;\n  //分\n  while (pro) {\n    //大于时，放到 big 链表上\n    if (pro.val >= x) {\n      big.next = pro;\n      big = big.next;\n      //小于时，放到 small 链表上\n    } else {\n      small.next = pro;\n      small = small.next;\n    }\n    pro = pro.next;\n  }\n  //细节\n  big.next = null;\n  //合\n  small.next = headbig.next;\n  return headsmall.next;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def partition(self, head: ListNode, x: int) -> ListNode:\n        pro = head\n        big = ListNode(-1)\n        small = ListNode(-1)\n        headbig = big\n        headsmall = small\n        # 分\n        while pro is not None:\n            # 大于时，放到 big 链表上\n            if pro.val >= x:\n                big.next = pro\n                big = big.next\n            # 小于时，放到 small 链表上\n            else:\n                small.next = pro\n                small = small.next\n            pro = pro.next\n        # 细节\n        big.next = None\n        # 合\n        small.next = headbig.next\n        return headsmall.next\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func partition(_ head: ListNode?, _ x: Int) -> ListNode? {\n        var pro = head\n        var big = ListNode(-1)\n        var small = ListNode(-1)\n        var headbig = big\n        var headsmall = small\n        //分\n        while pro != nil {\n            //大于时，放到 big 链表上\n            if pro!.val >= x {\n                big.next = pro\n                big = big.next!\n            //小于时，放到 small 链表上\n            } else {\n                small.next = pro\n                small = small.next!\n            }\n            pro = pro?.next\n        }\n        //细节\n        big.next = nil\n        //合\n        small.next = headbig.next\n        return headsmall.next\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc partition(head *ListNode, x int) *ListNode {\n    big, small := &ListNode{}, &ListNode{}\n    headBig, headSmall := big, small\n    temp := head\n    for temp != nil {\n        // 分开存\n        if temp.Val < x {\n            small.Next = temp\n            small = small.Next\n        } else {\n            big.Next = temp\n            big = big.Next\n        }\n        temp = temp.Next\n    }\n    // 最后一个节点指向nil\n    big.Next = nil\n    // 存小数的链表和存大数的连起来\n    small.Next = headBig.Next\n    return headSmall.Next\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/leetcode92反转链表2.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n今天我们来说一下反转链表 2，其实这个和 1 的思路差不多，今天先说一个比较好理解的方法，完全按照反转链表 1 的方法来解决，大家看这个题目之前要先看一下[【动画模拟】leetcode 206 反转链表](https://github.com/chefyuan/algorithm-base/blob/main/animation-simulation/%E9%93%BE%E8%A1%A8%E7%AF%87/leetcode206%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.md)。\n\n下面我们先来看一下题目。\n\n#### [92. 反转链表 II](https://leetcode-cn.com/problems/reverse-linked-list-ii/)\n\n给你单链表的头指针 `head` 和两个整数 `left` 和 `right` ，其中 `left <= right` 。请你反转从位置 `left` 到位置 `right` 的链表节点，返回 **反转后的链表** 。\n\n**示例 1：**\n\n![img](https://assets.leetcode.com/uploads/2021/02/19/rev2ex2.jpg)\n\n> 输入：head = [1,2,3,4,5], left = 2, right = 4\n> 输出：[1,4,3,2,5]\n\n**示例 2：**\n\n> 输入：head = [5], left = 1, right = 1\n> 输出：[5]\n\n含义就是让我们反转部分链表。也就是上面蓝色的部分，那我们怎么借助 1 的方法来解决呢？\n\n我们主要通过两部分来解决，先截取需要翻转的部分，然后再头尾交换即可。下面我们通过一个动画来看看具体步骤。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210327163804112.gif)\n\n是不是很容易理解，下面我们来看代码吧。\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public ListNode reverseBetween(ListNode head, int left, int right) {\n        //虚拟头节点\n        ListNode temp = new ListNode(-1);\n        temp.next = head;\n        ListNode pro = temp;\n        //来到 left 节点前的一个节点\n        int i = 0;\n        for (; i < left-1; ++i) {\n            pro = pro.next;\n        }\n        //保存 left 节点前的一个节点\n        ListNode leftNode = pro;\n        //来到 right 节点\n        for (; i < right; ++i) {\n            pro = pro.next;\n        }\n        //保存 right 节点后的一个节点\n        ListNode rightNode = pro.next;\n        //切断链表\n        pro.next = null;//切断 right 后的部分\n        ListNode newhead = leftNode.next;//保存 left 节点\n        leftNode.next = null;//切断 left 前的部分\n        //反转\n        leftNode.next = reverse(newhead);\n        //重新接头\n        newhead.next = rightNode;\n        return temp.next;\n\n    }\n    //和反转链表1代码一致\n    public ListNode reverse (ListNode head) {\n        ListNode low = null;\n        ListNode pro = head;\n        while (pro != null) {\n            ListNode temp = pro;\n            pro = pro.next;\n            temp.next = low;\n            low = temp;\n        }\n        return low;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode* reverseBetween(ListNode* head, int left, int right) {\n        //虚拟头节点\n        ListNode * temp = new ListNode(-1);\n        temp->next = head;\n        ListNode * pro = temp;\n        //来到 left 节点前的一个节点\n        int i = 0;\n        for (; i < left-1; ++i) {\n            pro = pro->next;\n        }\n        //保存 left 节点前的一个节点\n        ListNode * leftNode = pro;\n        //来到 right 节点\n        for (; i < right; ++i) {\n            pro = pro->next;\n        }\n        //保存 right 节点后的一个节点\n        ListNode * rightNode = pro->next;\n        //切断链表\n        pro->next = nullptr;//切断 right 后的部分\n        ListNode * newhead = leftNode->next;//保存 left 节点\n        leftNode->next = nullptr;//切断 left 前的部分\n        //反转\n        leftNode->next = reverse(newhead);\n        //重新接头\n        newhead->next = rightNode;\n        return temp->next;\n    }\n    //和反转链表1代码一致\n    ListNode * reverse (ListNode * head) {\n        ListNode * low = nullptr;\n        ListNode * pro = head;\n        while (pro != nullptr) {\n            ListNode * temp = pro;\n            pro = pro->next;\n            temp->next = low;\n            low = temp;\n        }\n        return low;\n    }\n};\n```\n\nJS Code:\n\n```js\nvar reverseBetween = function (head, left, right) {\n  //虚拟头节点\n  let temp = new ListNode(-1);\n  temp.next = head;\n  let pro = temp;\n  //来到 left 节点前的一个节点\n  let i = 0;\n  for (; i < left - 1; ++i) {\n    pro = pro.next;\n  }\n  //保存 left 节点前的一个节点\n  let leftNode = pro;\n  //来到 right 节点\n  for (; i < right; ++i) {\n    pro = pro.next;\n  }\n  //保存 right 节点后的一个节点\n  let rightNode = pro.next;\n  //切断链表\n  pro.next = null; //切断 right 后的部分\n  let newhead = leftNode.next; //保存 left 节点\n  leftNode.next = null; //切断 left 前的部分\n  //反转\n  leftNode.next = reverse(newhead);\n  //重新接头\n  newhead.next = rightNode;\n  return temp.next;\n};\n\n//和反转链表1代码一致\nvar reverse = function (head) {\n  let low = null;\n  let pro = head;\n  while (pro) {\n    let temp = pro;\n    pro = pro.next;\n    temp.next = low;\n    low = temp;\n  }\n  return low;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:\n        # 虚拟头节点\n        temp = ListNode(-1)\n        temp.next = head\n        pro = temp\n        # 来到 left 节点前的一个节点\n        for _ in range(left - 1):\n            pro = pro.next\n        # 保存 left 节点前的第一个节点\n        leftNode = pro\n        for _ in range(right - left + 1):\n            pro = pro.next\n        # 保存 right 节点后的节点\n        rightNode = pro.next\n        # 切断链表\n        pro.next = None  # 切断 right 后的部分\n        newhead = leftNode.next  # 保存 left 节点\n        leftNode.next = None  # 切断 left 前的部分\n        # 反转\n        leftNode.next = self.reverse(newhead)\n        # 重新接头\n        newhead.next = rightNode\n        return temp.next\n\n    # 和反转链表1代码一致\n    def reverse(self, head):\n        low = None\n        pro = head\n        while pro is not None:\n            temp = pro\n            pro = pro.next\n            temp.next = low\n            low = temp\n        return low\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func reverseBetween(_ head: ListNode?, _ left: Int, _ right: Int) -> ListNode? {\n        // 虚拟头结点\n        var temp = ListNode(-1)\n        temp.next = head\n        var pro:ListNode? = temp\n        // 来到 left 节点前的一个节点\n        var i = 0\n        for n in i..<left - 1 {\n            pro = pro?.next\n            i += 1\n        }\n        // 保存 left 节点前的一个节点\n        var leftNode = pro\n        // 来到 right 节点\n        for n in i..<right {\n            pro = pro?.next\n        }\n        // 保存 right 节点后的一个节点\n        var rightNode:ListNode? = pro?.next\n        // 切断链表\n        pro?.next = nil // 切断 right 后的部分\n        var newHead:ListNode? = leftNode?.next // 保存 left 节点\n        leftNode?.next = nil // 切断 left 前的部分\n        // 反转\n        leftNode?.next = reverse(newHead)\n        // 重新接头\n        newHead?.next = rightNode\n        return temp.next\n    }\n    // 和反转链表1代码一致\n    func reverse(_ head: ListNode?) -> ListNode? {\n        var low:ListNode?\n        var pro = head\n        while pro != nil {\n            var temp = pro\n            pro = pro?.next\n            temp?.next = low\n            low = temp\n        }\n        return low\n    }\n}\n```\n\nGoCode:\n\n```go\nfunc reverseBetween(head *ListNode, left int, right int) *ListNode {\n    root := &ListNode{\n        Next: head,\n    }\n    temp := root\n    i := 0\n    // left的前一个节点\n    for ; i < left - 1; i++ {\n        temp = temp.Next\n    }\n    leftNode := temp\n    // right的后一个节点\n    for ; i < right; i++ {\n        temp = temp.Next\n    }\n    rightNode := temp.Next\n    // 切断链表\n    temp.Next = nil\n    newhead := leftNode.Next\n    leftNode.Next = nil\n\n    // 反转后将3段链表接上。\n    leftNode.Next = reverse(newhead)\n    newhead.Next = rightNode\n    return root.Next\n}\n\nfunc reverse(head *ListNode) *ListNode {\n    var pre *ListNode\n    cur := head\n    for cur != nil {\n        temp := cur\n        cur = cur.Next\n        temp.Next = pre\n        pre = temp\n    }\n    return pre\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/剑指Offer25合并两个排序的链表.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [剑指 Offer 25. 合并两个排序的链表](https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/)\n\n将两个升序链表合并为一个新的 **升序** 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。\n\n示例：\n\n```\n输入：1->2->4, 1->3->4\n输出：1->1->2->3->4->4\n```\n\n今天的题目思路很简单，但是一遍 AC 也是不容易的。链表大部分题目考察的都是考生代码的完整性和鲁棒性，所以有些题目我们看着思路很简单，但是想直接通过还是需要下一翻工夫的，所以建议大家将所有链表的题目都自己写一下。实在没有时间做的同学，可以自己在脑子里打一遍代码，想清每一行代码的作用。\n\n迭代法：\n\n因为我们有两个升序链表，我们需要将其合并，那么我们需要创建一个新节点 headpre，然后我们利用双指针思想，每个链表放置一个指针，然后进行遍历并对比当前指针指向的值。然后 headpre.next 指向较小值的那个节点，不断迭代，直至到达某一有序链表底部，此时一个链表遍历完成，然后我们将未完全遍历的链表接在我们接在合并链表之后即可。\n\n这是我们迭代做法，另外这个题目还有一个递归方法，目前先不写，等链表掌握差不多的时候会单独写一篇关于递归的文章，也算是为树的题目做铺垫。\n\n动图讲解：\n\n![合并数组](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/合并数组.216f4nn4lti8.gif)\n\n**题目代码**\n\nJava Code:\n\n```java\n class Solution {\n    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {\n         ListNode headpro = new ListNode(-1);\n         ListNode headtemp = headpro;\n         while (l1 != null && l2 != null) {\n             //接上大的那个\n             if (l1.val >= l2.val) {\n                 headpro.next = l2;\n                 l2 = l2.next;\n             }\n             else {\n                 headpro.next = l1;\n                 l1 = l1.next;\n             }\n             headpro = headpro.next;\n         }\n         headpro.next = l1 != null ? l1:l2;\n         return headtemp.next;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {\n        ListNode * headpro = new ListNode(-1);\n        ListNode * headtemp = headpro;\n        while (l1 != nullptr && l2 != nullptr) {\n             //接上大的那个\n             if (l1->val >= l2->val) {\n                 headpro->next = l2;\n                 l2 = l2->next;\n             }\n             else {\n                 headpro->next = l1;\n                 l1 = l1->next;\n             }\n             headpro = headpro->next;\n         }\n         headpro->next = l1 != nullptr ? l1: l2;\n         return headtemp->next;\n    }\n};\n```\n\nJS Code:\n\n```js\nvar mergeTwoLists = function (l1, l2) {\n  let headpro = new ListNode(-1);\n  let headtemp = headpro;\n  while (l1 && l2) {\n    //接上大的那个\n    if (l1.val >= l2.val) {\n      headpro.next = l2;\n      l2 = l2.next;\n    } else {\n      headpro.next = l1;\n      l1 = l1.next;\n    }\n    headpro = headpro.next;\n  }\n  headpro.next = l1 != null ? l1 : l2;\n  return headtemp.next;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:\n        headpro = ListNode(-1)\n        headtemp = headpro\n        while l1 and l2:\n            # 接上大的那个\n            if l1.val >= l2.val:\n                headpro.next = l2\n                l2 = l2.next\n            else:\n                headpro.next = l1\n                l1 = l1.next\n            headpro = headpro.next\n        headpro.next = l1 if l1 is not None else l2\n        return headtemp.next\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func mergeTwoLists(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {\n        var l1 = l1, l2 = l2\n        var headpro: ListNode? = ListNode(-1)\n        var headtemp = headpro\n        while l1 != nil && l2 != nil {\n            //接上大的那个\n            if l1!.val >= l2!.val {\n                headpro?.next = l2\n                l2 = l2!.next\n            } else {\n                headpro?.next = l1\n                l1 = l1!.next\n            }\n            headpro = headpro?.next\n        }\n        headpro?.next = l1 != nil ? l1 : l2\n        return headtemp?.next\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode {\n    root := &ListNode{}\n    node := root\n    for l1 != nil && l2 != nil {\n        if l1.Val < l2.Val {\n            node.Next = l1\n            l1 = l1.Next\n        } else {\n            node.Next = l2\n            l2 = l2.Next\n        }\n        node = node.Next\n    }\n\t// node接上l1或l2剩下的节点\n    if l1 != nil {\n        node.Next = l1\n    } else {\n        node.Next = l2\n    }\n    return root.Next\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/剑指Offer52两个链表的第一个公共节点.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [剑指 Offer 52. 两个链表的第一个公共节点](https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) & [160. 相交链表](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/)\n\n### 前言\n\n今天给大家带来一个不是那么难的题目，这个题目的解答方法很多，只要能 AC 的就是好方法，虽然题目不是特别难但是也是剑指 offer 上的经典题目所以大家要记得打卡呀。\n\n然后今天我们的链表板块就算结束啦。周末的时候我会对链表的题目做一个总结，俗话说温故而知新嘛。好啦废话不多说，我们一起来看一下今天的题目吧！\n\n题目描述：\n\n输入两个链表，找出它们的第一个公共节点。如下图，返回黄色结点即可。\n\n![image-20201029215837844](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/image-20201029215837844.7ezoerpghyk0.png)\n\n题目表达是不是也很简单，这个题目我的方法一共有两个，一种就是用 HashSet 进行存储，一种就是利用双指针，大家有更好的可以在下面讨论呀。\n\n### HashSet\n\n这个方法是比较简单的，主要思路就是，先遍历一个链表将链表的所有值都存到 Hashset 中，然后再遍历另一个链表，如果发现某个结点在 Hashset 中已经存在那我们直接返回该节点即可，代码也很简单。\n\n**题目代码**\n\nJava Code:\n\n```java\npublic class Solution {\n    public ListNode getIntersectionNode (ListNode headA, ListNode headB) {\n        ListNode tempa = headA;\n        ListNode tempb = headB;\n        //定义Hashset\n        HashSet<ListNode> arr = new HashSet<ListNode>();\n        //遍历链表A，将所有值都存到arr中\n        while (tempa != null) {\n            arr.add(tempa);\n            tempa = tempa.next;\n        }\n        //遍历列表B，如果发现某个结点已在arr中则直接返回该节点\n        while (tempb != null) {\n            if (arr.contains(tempb)) {\n                return tempb;\n            }\n            tempb = tempb.next;\n        }\n        //若上方没有返回，此刻tempb为null\n        return tempb;\n\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode * getIntersectionNode(ListNode *headA, ListNode *headB) {\n        ListNode * tempa = headA;\n        ListNode * tempb = headB;\n        //定义Hashset\n        set <ListNode *> arr;\n        //遍历链表A，将所有值都存到arr中\n        while (tempa != nullptr) {\n            arr.insert(tempa);\n            tempa = tempa->next;\n        }\n        //遍历列表B，如果发现某个结点已在arr中则直接返回该节点\n        while (tempb != nullptr) {\n            if (arr.find(tempb) != arr.end()) {\n                return tempb;\n            }\n            tempb = tempb->next;\n        }\n        //若上方没有返回，此刻tempb为null\n        return tempb;\n    }\n};\n```\n\nJS Code:\n\n```js\nvar getIntersectionNode = function (headA, headB) {\n  let tempa = headA;\n  let tempb = headB;\n  //定义Hashset\n  let arr = new Set();\n  //遍历链表A，将所有值都存到arr中\n  while (tempa) {\n    arr.add(tempa);\n    tempa = tempa.next;\n  }\n  //遍历列表B，如果发现某个结点已在arr中则直接返回该节点\n  while (tempb) {\n    if (arr.has(tempb)) {\n      return tempb;\n    }\n    tempb = tempb.next;\n  }\n  //若上方没有返回，此刻tempb为null\n  return tempb;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:\n        tempa = headA\n        tempb = headB\n        # 定义Hashset\n        arr = set()\n        # 遍历链表A，将所有值都存到arr中\n        while tempa is not None:\n            arr.add(tempa)\n            tempa = tempa.next\n        # 遍历列表B，如果发现某个结点已在arr中则直接返回该节点\n        while tempb is not None:\n            if tempb in arr:\n                return tempb\n            tempb = tempb.next\n        # 若上方没有返回，此刻tempb为null\n        return tempb\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func getIntersectionNode(_ headA: ListNode?, _ headB: ListNode?) -> ListNode? {\n        var tempa = headA\n        var tempb = headB\n        var arr:Set<ListNode> = []\n        //遍历链表A，将所有值都存到arr中\n        while tempa != nil {\n            arr.insert(tempa!)\n            tempa = tempa?.next\n        }\n        //遍历列表B，如果发现某个结点已在arr中则直接返回该节点\n        while tempb != nil {\n            if arr.contains(tempb!) {\n                return tempb\n            }\n            tempb = tempb?.next\n        }\n        //若上方没有返回，此刻tempb为null\n        return tempb\n    }\n}\nextension ListNode: Hashable, Equatable {\n    public func hash(into hasher: inout Hasher) {\n        hasher.combine(val)\n        hasher.combine(ObjectIdentifier(self))\n    }\n    public static func ==(lhs: ListNode, rhs: ListNode) -> Bool {\n        return lhs === rhs\n    }\n}\n```\n\n下面这个方法比较巧妙，不是特别容易想到，大家可以自己实现一下，这个方法也是利用我们的双指针思想。\n\n下面我们直接看动图吧，特别直观，一下就可以搞懂。\n\n![第一次相交的点](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/第一次相交的点.5nbxf5t3hgk0.gif)\n\n是不是一下就懂了呀，我们利用双指针，当某一指针遍历完链表之后，然后掉头去另一个链表的头部，继续遍历。因为速度相同所以他们第二次遍历的时候肯定会相遇，是不是很浪漫啊！\n\n**题目代码**\n\nJava Code:\n\n```java\npublic class Solution {\n    public ListNode getIntersectionNode (ListNode headA, ListNode headB) {\n        //定义两个节点\n        ListNode tempa = headA;\n        ListNode tempb = headB;\n        //循环\n        while (tempa != tempb) {\n          //如果不为空就指针下移，为空就跳到另一链表的头部\n           tempa = tempa != null ? tempa.next: headB;\n           tempb = tempb != null ? tempb.next: headA;\n        }\n        return tempa;//返回tempb也行\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode * getIntersectionNode(ListNode *headA, ListNode *headB) {\n        //定义两个节点\n        ListNode * tempa = headA;\n        ListNode * tempb = headB;\n        //循环\n        while (tempa != tempb) {\n           //如果不为空就指针下移，为空就跳到另一链表的头部\n           tempa = tempa != nullptr ? tempa->next: headB;\n           tempb = tempb != nullptr ? tempb->next: headA;\n        }\n        return tempa;//返回tempb也行\n    }\n};\n```\n\nJS Code:\n\n```js\nvar getIntersectionNode = function (headA, headB) {\n  //定义两个节点\n  let tempa = headA;\n  let tempb = headB;\n  //循环\n  while (tempa != tempb) {\n    //如果不为空就指针下移，为空就跳到另一链表的头部\n    tempa = tempa != null ? tempa.next : headB;\n    tempb = tempb != null ? tempb.next : headA;\n  }\n  return tempa; //返回tempb也行\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:\n        # 定义两个节点\n        tempa = headA\n        tempb = headB\n        # 循环\n        while tempa is not tempb:\n            # 如果不为空就指针下移，为空就跳到另一链表的头部\n            tempa = tempa.next if tempa is not None else headB\n            tempb = tempb.next if tempb is not None else headA\n        return tempa  # 返回tempb也行\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func getIntersectionNode(_ headA: ListNode?, _ headB: ListNode?) -> ListNode? {\n        //定义两个节点\n        var tempa = headA\n        var tempb = headB\n        //循环\n        while tempa != tempb {\n            // 如果不为空就指针下移，为空就跳到另一链表的头部\n            tempa = tempa != nil ? tempa?.next : headB\n            tempb = tempb != nil ? tempb?.next : headA\n        }\n        return tempa //返回tempb也行\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc getIntersectionNode(headA, headB *ListNode) *ListNode {\n    tempA, tempB := headA, headB\n    for tempA != tempB {\n        // 如果不为空就指针下移，为空就跳到另一链表的头部\n        if tempA == nil {\n            tempA = headB\n        } else {\n            tempA = tempA.Next\n        }\n        if tempB == nil {\n            tempB = headA\n        } else {\n            tempB = tempB.Next\n        }\n    }\n    return tempA\n}\n```\n\n好啦，链表的题目就结束啦，希望大家能有所收获，下周就要更新新的题型啦，继续坚持，肯定会有收获的。\n\n<br/>\n\n> 贡献者[@jaredliw](https://github.com/jaredliw)注：\n>\n> 在这里带大家来看看一些其他的解题方法，虽然没有双指针有效，但还是值得一试。\n>\n> 1. 两个链表各遍历一次，找出长度。根据长度差 k，让较长的那个链表先走 k 步。之后再两个指针一起走，由于起点一样，两个指针必将一起到达公共节点。\n> 2. 将其中一条链表的头和尾相连，公共节点就是环的入口，直接套用之前学过的算法就可以啦。（这解法看得我拍腿叫好）\n"
  },
  {
    "path": "animation-simulation/链表篇/剑指offer22倒数第k个节点.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [剑指 Offer 22. 链表中倒数第 k 个节点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/)\n\n题目：\n\n输入一个链表，输出该链表中倒数第 k 个节点。为了符合大多数人的习惯，本题从 1 开始计数，即链表的尾节点是倒数第 1 个节点。例如，一个链表有 6 个节点，从头节点开始，它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。\n\n题目分析：\n\n自己思考一下：\n\n我们遇到这个题目，可能会有什么答题思路呢？\n\n你看我说的对不对，是不是会想到先遍历一遍链表知道 链表节点的个数，然后再计算出倒数第 n 个节点。\n\n比如链表长度为 10，倒数第 3 个节点，不就是正数第 8 个节点呀，这种方法当然可以啦，是可以实现的，那么我们再思考一下有没有其他方法呢？哦，对，我们可以将链表元素保存到数组里面，然后直接就可以知道倒数第 k 个节点了。这个方法确实比刚才那个方法省时间了，但是所耗的空间更多了，那我们还有什么方法吗？\n\n我们可以继续利用我们的双指针呀，但是我们应该怎么做呢？\n\n双指针法：\n\n首先一个指针移动 K-1 位（这里可以根据你的初始化指针决定），然后另一个指针开始启动，他俩移动速度一样，所以他俩始终相差 K-1 位，当第一个指针到达链表尾部时，第二个指针的指向则为倒数第 K 个节点。\n\n![](https://img-blog.csdnimg.cn/img_convert/506c4d70f4c50c66994711c8506462a8.gif)\n\n感觉这个方法既巧妙又简单，大家可以自己动手打一下，这个题目是经典题目。\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public ListNode getKthFromEnd (ListNode head, int k) {\n        //特殊情况\n        if (head == null) {\n            return head;\n        }\n        //初始化两个指针\n        ListNode pro = new ListNode(-1);\n        ListNode after = new ListNode(-1);\n        //定义指针指向\n        pro = head;\n        after = head;\n        //先移动绿指针到指定位置\n        for (int i = 0; i < k-1; i++) {\n            pro = pro.next;\n        }\n        //两个指针同时移动\n        while (pro.next != null) {\n            pro = pro.next;\n            after = after.next;\n        }\n        //返回倒数第k个节点\n        return after;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode * getKthFromEnd(ListNode * head, int k) {\n         //特殊情况\n        if (head == nullptr) {\n            return head;\n        }\n        //初始化两个指针\n        ListNode * pro = new ListNode(-1);\n        ListNode * after = new ListNode(-1);\n        //定义指针指向\n        pro = head;\n        after = head;\n        //先移动绿指针到指定位置\n        for (int i = 0; i < k-1; i++) {\n            pro = pro->next;\n        }\n        //两个指针同时移动\n        while (pro->next != nullptr) {\n            pro = pro->next;\n            after = after->next;\n        }\n        //返回倒数第k个节点\n        return after;\n    }\n};\n```\n\nJS Code:\n\n```javascript\nvar getKthFromEnd = function (head, k) {\n  //特殊情况\n  if (!head) return head;\n  //初始化两个指针, 定义指针指向\n  let pro = head,\n    after = head;\n  //先移动绿指针到指定位置\n  for (let i = 0; i < k - 1; i++) {\n    pro = pro.next;\n  }\n  //两个指针同时移动\n  while (pro.next) {\n    pro = pro.next;\n    after = after.next;\n  }\n  //返回倒数第k个节点\n  return after;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:\n        # 特殊情况\n        if head is None:\n            return head\n        # 初始化两个指针, 定义指针指向\n        pro = head\n        after = head\n        # 先移动绿指针到指定位置\n        for _ in range(k - 1):\n            pro = pro.next\n        # 两个指针同时移动\n        while pro.next is not None:\n            pro = pro.next\n            after = after.next\n        # 返回倒数第k个节点\n        return after\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func getKthFromEnd(_ head: ListNode?, _ k: Int) -> ListNode? {\n        //特殊情况\n        if head == nil {\n            return head\n        }\n        //初始化两个指针\n        var pro = head, after = head\n        //先移动绿指针到指定位置\n        for i in 0..<k-1 {\n            pro = pro?.next\n        }\n        //两个指针同时移动\n        while pro?.next != nil {\n            pro = pro?.next\n            after = after?.next\n        }\n        //返回倒数第k个节点\n        return after\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc getKthFromEnd(head *ListNode, k int) *ListNode {\n    if head == nil { return head }\n    pro, after := head, head\n    //先移动绿指针到指定位置\n    for i := 0; i < k - 1; i++ {\n        pro = pro.Next\n    }\n    for pro.Next != nil {\n        pro = pro.Next\n        after = after.Next\n    }\n    return after\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/面试题 02.03. 链表中间节点.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [876. 链表的中间结点](https://leetcode-cn.com/problems/middle-of-the-linked-list/)\n\n给定一个头结点为 head 的非空单链表，返回链表的中间结点。\n\n如果有两个中间结点，则返回第二个中间结点。\n\n**示例 1：**\n\n```java\n输入：[1,2,3,4,5]\n输出：3\n```\n\n> 说明：因为只有一个中间节点。\n\n**示例 2：**\n\n```java\n输入：[1,2,3,4,5,6]\n输出：4\n```\n\n> 说明：有两个中间节点所以返回后面那个。\n\n**题目解析：**\n\n又精心筛选了一个题目，本来想写一下删除节点的题目，然后发现这个题目更符合目前的节奏，所以先写一下这个题目，明天再给大家写删除节点的题目。\n\n大家先不要看我的题解，先自己想一下怎么做。这个这个题目是想让我们找出中间节点，昨天的题目是让我们倒数第 K 个节点，想一下这两个题目有什么联系呢？\n\n先说一下刚开始刷题的小伙伴可能会想到的题解，两次遍历链表，第一次遍历获取链表长度，第二次遍历获取中间链表。\n\n这个方法很 OK，利用数组先将所有链表元素存入数组里，然后再直接获得中间节点。这个也很 OK，那么我们有没有一次遍历，且不开辟辅助空间的方法呢？\n\n昨天的题目是一前一后双指针，两个指针之间始终相差 k-1 位，我们今天也利用一下双指针的做法吧。\n\n这种类型的双指针是我们做链表的题目经常用到的，叫做快慢指针。\n\n一个指针走的快，一个指针走的慢，这个题目我们可以让快指针一次走两步，慢指针一次走一步，当快指针到达链表尾部的时候，慢指针不就到达中间节点了吗？\n\n链表中节点的个数有可能为奇数也有可能为偶数，这是两种情况，但是我们输出是相同的，那就是输出 slow 指针指向的节点，也就是两个中间节点的第二个。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20210321131249789.gif)\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public ListNode middleNode(ListNode head) {\n        ListNode fast = head;//快指针\n        ListNode slow = head;//慢指针\n        //循环条件，思考一下跳出循环的情况\n        while (fast!=null && fast.next != null) {\n            fast = fast.next.next;\n            slow = slow.next;\n        }\n        //返回slow指针指向的节点\n        return slow;\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode* middleNode(ListNode* head) {\n        ListNode * fast = head;//快指针\n        ListNode * slow = head;//慢指针\n        //循环条件，思考一下跳出循环的情况\n        while (fast != nullptr && fast->next != nullptr) {\n            fast = fast->next->next;\n            slow = slow->next;\n        }\n        //返回slow指针指向的节点\n        return slow;\n    }\n};\n```\n\nJS Code:\n\n```js\nvar middleNode = function (head) {\n  let fast = head; //快指针\n  let slow = head; //慢指针\n  //循环条件，思考一下跳出循环的情况\n  while (fast && fast.next) {\n    fast = fast.next.next;\n    slow = slow.next;\n  }\n  //返回slow指针指向的节点\n  return slow;\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def middleNode(self, head: ListNode) -> ListNode:\n        fast = head  # 快指针\n        slow = head  # 慢指针\n        # 循环条件，思考一下跳出循环的情况\n        while fast is not None and fast.next is not None:\n            fast = fast.next.next\n            slow = slow.next\n        # 返回slow指针指向的节点\n        return slow\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func middleNode(_ head: ListNode?) -> ListNode? {\n        var fast = head //快指针\n        var slow = head //慢指针\n        //循环条件，思考一下跳出循环的情况\n        while fast != nil && fast?.next != nil {\n            fast = fast?.next?.next\n            slow = slow?.next\n        }\n        //返回slow指针指向的节点\n        return slow\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc middleNode(head *ListNode) *ListNode {\n\t// 快慢指针\n    fast, slow := head, head\n    for fast != nil && fast.Next != nil {\n        fast = fast.Next.Next\n        slow = slow.Next\n    }\n    return slow\n}\n```\n"
  },
  {
    "path": "animation-simulation/链表篇/面试题 02.05. 链表求和.md",
    "content": "> 如果阅读时，发现错误，或者动画不可以显示的问题可以添加我微信好友 **[tan45du_one](https://raw.githubusercontent.com/tan45du/tan45du.github.io/master/个人微信.15egrcgqd94w.jpg)** ，备注 github + 题目 + 问题 向我反馈\n>\n> 感谢支持，该仓库会一直维护，希望对各位有一丢丢帮助。\n>\n> 另外希望手机阅读的同学可以来我的 <u>[**公众号：程序厨**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u> 两个平台同步，想要和题友一起刷题，互相监督的同学，可以在我的小屋点击<u>[**刷题小队**](https://raw.githubusercontent.com/tan45du/test/master/微信图片_20210320152235.2pthdebvh1c0.png)</u>进入。\n\n#### [面试题 02.05. 链表求和](https://leetcode-cn.com/problems/sum-lists-lcci/)\n\n之前我们一起做了链表中的几个经典题型，找到倒数第 k 个节点，找链表中点，判断链表中环的起点，合并链表，反转链表，删除链表中重复值。这些是链表中的经典问题，面试中也经常会考的问题，然后下面我们继续做一道链表题目，也是面试中经常会考的题目，链表求和问题。\n\n另外有一些小伙伴说，虽然一天一道题不算多，但是每天读题，做题加消化稍微有点跟不上，所以我打算每个周的工作日进行更新题目，到周末的时候对本周的题目进行总结，然后为大家再写一些别的东西。下面我们一起来看一下今天的题目吧。\n\n> 为保证严谨性，所有文章中的代码都在网站进行验证，大家可以放心食用。\n\n题目描述：\n\n给定两个用链表表示的整数，每个节点包含一个数位。\n\n这些数位是反向存放的，也就是个位排在链表首部。\n\n编写函数对这两个整数求和，并用链表形式返回结果。\n\n示例 1：\n\n```java\n输入：(7 -> 1 -> 6) + (5 -> 9 -> 2)，即 617 + 295\n输出：2 -> 1 -> 9，即 912\n```\n\n示例 2：\n\n```java\n输入：(9 -> 9) + (9 -> 9)，即 99 + 99\n输出：8 -> 9 -> 1\n```\n\n示例 3：\n\n```java\n输入：(5) + (5)，即 5 + 5\n输出：0 -> 1\n```\n\n**题目解析：**\n\n这个题目很容易理解，就是将链表数值进行求和，刚开始做题的同学可能会有这种思路，这个题目我们分别遍历两个链表得到他们的数，然后进行相加，再放到新的链表中，但是这样是行不通的，\n\n因为我们需要考虑溢出的情况，java 中 int 型的范围为 -2147483648 到 +2147483648，即 -2^31 到 2^31。\n\n所以链表比较长的话进行求和就会溢出，所以我们不能提取过之后再进行相加，\n\n我们应该对链表的每一位进行相加，然后通过链表的和，判断是否需要像下一位进行传递，\n\n就好比小时候我们用竖式进行加法一样，判断两位相加是否大于 10，大于 10 则进 1。\n\n了解了思路，但是想完全实现代码也不是特别容易，这里需要注意的三个点就是，\n\n1. 我们需要根据两个链表的长度，不断对新链表添加节点。\n\n2. 需要创建一个变量用来保存进位值。\n\n3. 当跳出循环之后，需要根据进位值来判断需不需要再对链表长度加 1。\n\n这三条可以结合代码理解进行。\n\n注：进位值只能是 0 或 1，因为每一位最大为 9，9+9=18；\n\n![链表求和](https://cdn.jsdelivr.net/gh/tan45du/photobed@master/photo/链表求和.1yh4ymdee3k0.gif)\n\n注：这里需要注意得时，链表遍历结束，我们应该跳出循环，但是我们的 nlist 仍在尾部添加了 1 节点，那是因为跳出循环时，summod 值为 1，所以我们需要在尾部再添加一个节点。\n\n**题目代码**\n\nJava Code:\n\n```java\nclass Solution {\n    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {\n         //待会儿要返回的链表\n         ListNode nList = new ListNode(-1);//哑节点\n         ListNode tempnode = nList;\n         //用来保存进位值，初始化为0\n         int summod = 0;\n         while(l1 != null || l2 != null) {\n             //如果l1的链表为空则l1num为0，若是不为空，则为链表的节点值\n             //判断是否为空，为空就设为0\n             int l1num = l1 == null ? 0 : l1.val;\n             int l2num = l2 == null ? 0 : l2.val;\n             //将链表的值和进位值相加，得到为返回链表的值\n             int sum = l1num+l2num+summod;\n             //更新进位值，例18/10=1，9/10=0\n             summod = sum/10;\n             //新节点保存的值，18%8=2，则添加2\n             sum = sum%10;\n             //添加节点\n             tempnode.next = new ListNode(sum);\n             //移动指针\n             tempnode = tempnode.next;\n             if (l1 != null) {\n                 l1 = l1.next;\n             }\n             if (l2 != null) {\n                 l2 = l2.next;\n             }\n         }\n         //最后根据进位值判断需不需要继续添加节点\n         if (summod != 0) {\n             tempnode.next = new ListNode(summod);\n         }\n         return nList.next;//去除哑节点\n    }\n}\n```\n\nC++ Code:\n\n```cpp\nclass Solution {\npublic:\n    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {\n         //待会儿要返回的链表\n         ListNode * nList = new ListNode(-1);//哑节点\n         ListNode * tempnode = nList;\n         //用来保存进位值，初始化为0\n         int summod = 0;\n         while(l1 != nullptr || l2 != nullptr) {\n             //如果l1的链表为空则l1num为0，若是不为空，则为链表的节点值\n             //判断是否为空，为空就设为0\n             int l1num = l1 == nullptr ? 0 : l1->val;\n             int l2num = l2 == nullptr ? 0 : l2->val;\n             //将链表的值和进位值相加，得到为返回链表的值\n             int sum = l1num + l2num + summod;\n             //更新进位值，例18/10=1，9/10=0\n             summod = sum / 10;\n             //新节点保存的值，18%8=2，则添加2\n             sum = sum % 10;\n             //添加节点\n             tempnode->next = new ListNode(sum);\n             //移动指针\n             tempnode = tempnode->next;\n             if (l1 != nullptr) {\n                 l1 = l1->next;\n             }\n             if (l2 != nullptr) {\n                 l2 = l2->next;\n             }\n         }\n         //最后根据进位值判断需不需要继续添加节点\n         if (summod != 0) {\n             tempnode->next = new ListNode(summod);\n         }\n         return nList->next;//哑节点\n    }\n};\n```\n\nJS Code:\n\n```js\nvar addTwoNumbers = function (l1, l2) {\n  //待会儿要返回的链表\n  let nList = new ListNode(-1); //哑节点\n  let tempnode = nList;\n  //用来保存进位值，初始化为0\n  let summod = 0;\n  while (l1 || l2) {\n    //如果l1的链表为空则l1num为0，若是不为空，则为链表的节点值\n    //判断是否为空，为空就设为0\n    let l1num = l1 === null ? 0 : l1.val;\n    let l2num = l2 === null ? 0 : l2.val;\n    //将链表的值和进位值相加，得到为返回链表的值\n    let sum = l1num + l2num + summod;\n    //更新进位值，例18/10=1，9/10=0\n    summod = ~~(sum / 10);\n    //新节点保存的值，18%8=2，则添加2\n    sum = sum % 10;\n    //添加节点\n    tempnode.next = new ListNode(sum);\n    //移动指针\n    tempnode = tempnode.next;\n    if (l1) {\n      l1 = l1.next;\n    }\n    if (l2) {\n      l2 = l2.next;\n    }\n  }\n  //最后根据进位值判断需不需要继续添加节点\n  if (summod !== 0) {\n    tempnode.next = new ListNode(summod);\n  }\n  return nList.next; //去除哑节点\n};\n```\n\nPython Code:\n\n```python\nclass Solution:\n    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:\n        # 待会儿要返回的链表\n        nList = ListNode(-1)  # 哑节点\n        tempnode = nList\n        # 用来保存进位值，初始化为0\n        summod = 0\n        while l1 is not None o l2 is not None:\n            # 如果l1的链表为空则l1num为0，若是不为空，则为链表的节点值\n            # 判断是否为空，为空就设为0\n            l1num = 0 if l1 is None else l1.val\n            l2num = 0 if l2 is None else l2.val\n            # 将链表的值和进位值相加，得到为返回链表的值\n            sum_ = l1num + l2num + summod\n            # 更新进位值，例18/10=1，9/10=0\n            # 新节点保存的值，1 %8=2，则添加2\n            # 注：这里使用divmod函数，对上方的代码进行了一丢丢的简化\n            summod, sum_ = divmod(sum_, 10)\n            # 添加节点\n            tempnode.next = ListNode(sum_)\n            # 移动指针\n            tempnode = tempnode.next\n            if l1 is not None:\n                l1 = l1.next\n            if l2 is not None:\n                l2 = l2.next\n        # 最后根据进位值判断需不需要继续添加节点\n        if summod != 0:\n            tempnode.next = ListNode(summod)\n        return nList.next  # 去除哑节点\n```\n\nSwift Code：\n\n```swift\nclass Solution {\n    func addTwoNumbers(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {\n        var l1 = l1, l2 = l2\n        var nList = ListNode(-1) // 哑节点\n        var tempnode = nList\n        // 用来保存进位值，初始化为0\n        var summod = 0\n        while l1 != nil || l2 != nil {\n            // 链表的节点值\n            let l1num = l1?.val ?? 0\n            let l2num = l2?.val ?? 0\n            // 将链表的值和进位值相加，得到为返回链表的值\n            var sum = l1num + l2num + summod\n            // 更新进位值，例18/10=1，9/10=0\n            summod = sum / 10\n            // 新节点保存的值，18%8=2，则添加2\n            sum = sum % 10\n            // 添加节点\n            tempnode.next = ListNode(sum)\n            // 移动指针\n            tempnode = tempnode.next!\n            if l1 != nil {\n                l1 = l1?.next\n            }\n            if l2 != nil {\n                l2 = l2?.next\n            }\n        }\n        // 最后根据进位值判断需不需要继续添加节点\n        if (summod != 0) {\n            tempnode.next = ListNode(summod)\n        }\n        return nList.next //去除哑节点\n    }\n}\n```\n\nGo Code:\n\n```go\nfunc addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {\n    root := &ListNode{}\n    temp := root\n    // 用来保存进位值，初始化为0\n    mod := 0\n    for (l1 != nil || l2 != nil) {\n        l1num := 0\n        if l1 != nil { l1num = l1.Val }\n        l2num := 0\n        if l2 != nil { l2num = l2.Val }\n        // 将链表的值和进位值相加，得到为返回链表的值\n        sum := l1num + l2num + mod\n        // 更新进位值，例18/10=1，9/10=0\n        mod = sum / 10\n        // 新节点保存的值，18%8=2，则添加2\n        sum = sum % 10\n        newNode := &ListNode{\n            Val: sum,\n        }\n        temp.Next = newNode\n        temp = temp.Next\n        if l1 != nil { l1 = l1.Next }\n        if l2 != nil { l2 = l2.Next }\n    }\n    if mod != 0 {\n        newNode := &ListNode{\n            Val: mod,\n        }\n        temp.Next = newNode\n    }\n    return root.Next\n}\n```\n"
  }
]