-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.json
1 lines (1 loc) · 530 KB
/
index.json
1
[{"categories":[],"content":" 关于此专栏此专栏内容为 LeetCode 剑指 Offer (第二版) 的 Java 版本题解,我会按照 LeetCode 官方给题目标注的出现频率由高到低排序。 希望能帮助到更多学习算法的朋友们。 解题思路与实现代码来源于官方题解、评论区大佬及网络,非 100% 原创内容,此专栏为我在刷题过程中整理的笔记,仅做收录与整理。 如有侵权,请发送邮件联系我删除相关内容。 ","date":"2021-08-15","objectID":"/posts/series/leetcode-jianzhioffer-java/0.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"开篇","uri":"/posts/series/leetcode-jianzhioffer-java/0.html#关于此专栏"},{"categories":["Mark"],"content":"内容不定期持续更新,感谢每一位开源项目作者为全世界开发者做出的杰出贡献。本文主要分享一些我平时收集到的一些好物,东西可能比较杂乱。涵盖开源项目、免费的工具站、好用的客户端工具、优秀的付费网站和来路不明的沙雕网站。 ","date":"2019-12-31","objectID":"/posts/mark/1.html:0:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#"},{"categories":["Mark"],"content":" Github 开源项目","date":"2019-12-31","objectID":"/posts/mark/1.html:0:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#github-开源项目"},{"categories":["Mark"],"content":" 功能性网站","date":"2019-12-31","objectID":"/posts/mark/1.html:1:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#功能性网站"},{"categories":["Mark"],"content":" 1、兰空图床使用 thinkphp + mysql 开发,界面简洁易用,支持第三方云储存和远程 FTP 存储。并且有开源的浏览器插件支持。(真香) 官网:https://www.lsky.pro GitHub:https://github.com/wisp-x/lsky-pro 浏览器扩展:https://github.com/wisp-x/lsky-pro-chrome-extension ","date":"2019-12-31","objectID":"/posts/mark/1.html:1:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1兰空图床"},{"categories":["Mark"],"content":" 2、基于 php+html 的轻量级网络测试站点HTML5 Speedtest 可用于查看下载速度、ping 速度、上传速度;很小巧方便,直接打开页面点击一下 start 就能自动检测,你本机到远程服务器的网络状况,快速得到观测数据 Github:https://github.com/librespeed/speedtest ","date":"2019-12-31","objectID":"/posts/mark/1.html:1:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2基于-phphtml-的轻量级网络测试站点"},{"categories":["Mark"],"content":" 3、flarum 论坛基于 PHP Laravel 框架构建的一款优雅简洁论坛软件。发帖及跟帖回复使用 markdown 语法是一个亮点。 官网:https://flarum.org 中文官网:https://flarum.org.cn Github:https://github.com/flarum/flarum ","date":"2019-12-31","objectID":"/posts/mark/1.html:1:3","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#3flarum-论坛"},{"categories":["Mark"],"content":" 命令行工具","date":"2019-12-31","objectID":"/posts/mark/1.html:2:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#命令行工具"},{"categories":["Mark"],"content":" 1、lux(annie)基于 Go 开发的一款命令行视频下载器,支持的平台很多,包括 MacOS、Windows、Linux 等。目前支持 bilibili 和 youtube 的播放列表批量下载,其他网站只支持单个视频下载。 Github:https://github.com/iawia002/lux ","date":"2019-12-31","objectID":"/posts/mark/1.html:2:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1luxannie"},{"categories":["Mark"],"content":" 2、youtube-dl下载 youtube 视频的神器 Github:https://github.com/ytdl-org/youtube-dl 官网:http://ytdl-org.github.io/youtube-dl/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:2:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2youtube-dl"},{"categories":["Mark"],"content":" 开发工具","date":"2019-12-31","objectID":"/posts/mark/1.html:3:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#开发工具"},{"categories":["Mark"],"content":" 1、AnotherRedisDesktopManager一个 GUI Redis 桌面管理器,兼容 Linux、Windows、Mac。更重要的是,在加载大量的键时,它不会崩溃。 Github:https://github.com/qishibo/AnotherRedisDesktopManager ","date":"2019-12-31","objectID":"/posts/mark/1.html:3:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1anotherredisdesktopmanager"},{"categories":["Mark"],"content":" 2、SwitchHostsSwitchHosts 是一个管理、切换多个 hosts 方案的工具。 Github:https://github.com/oldj/SwitchHosts 官网:https://swh.app/zh/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:3:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2switchhosts"},{"categories":["Mark"],"content":" 3、docsifydocsify 可以快速帮你生成文档网站。不同于 GitBook、Hexo 的地方是它不会生成静态的 .html 文件,所有转换工作都是在运行时。如果你想要开始使用它,只需要创建一个 index.html 就可以开始编写文档并直接部署在 GitHub Pages。 Github:https://github.com/docsifyjs/docsify/ 官网:https://docsify.js.org/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:3:3","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#3docsify"},{"categories":["Mark"],"content":" UI 框架","date":"2019-12-31","objectID":"/posts/mark/1.html:4:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#ui-框架"},{"categories":["Mark"],"content":" 1、LayUI对非专业前端的开发人员非常友好,组件丰富,极低门槛,开箱即用。 官网:https://www.layui.com GitHub:https://github.com/sentsin/layui ","date":"2019-12-31","objectID":"/posts/mark/1.html:4:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1layui"},{"categories":["Mark"],"content":" 2、vant-weapp有赞出品的开源小程序 UI 组件库 官网:https://youzan.github.io/vant-weapp GitHub:https://github.com/youzan/vant-weapp ","date":"2019-12-31","objectID":"/posts/mark/1.html:4:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2vant-weapp"},{"categories":["Mark"],"content":" 3、ElementUI饿了么开发的基于 Vue 2.0 的桌面端组件库 官网:https://element.eleme.cn ","date":"2019-12-31","objectID":"/posts/mark/1.html:4:3","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#3elementui"},{"categories":["Mark"],"content":" WordPress 主题","date":"2019-12-31","objectID":"/posts/mark/1.html:5:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#wordpress-主题"},{"categories":["Mark"],"content":" 1、argon-theme博客、文章向主题 Github:https://github.com/solstice23/argon-theme Demo:https://solstice23.top/archives/746 ","date":"2019-12-31","objectID":"/posts/mark/1.html:5:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1argon-theme"},{"categories":["Mark"],"content":" 2、Puock博客、文章主题 Github:https://github.com/Licoy/wordpress-theme-puock Demo:https://www.licoy.cn ","date":"2019-12-31","objectID":"/posts/mark/1.html:5:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2puock"},{"categories":["Mark"],"content":" 3、WebStack网址导航主题 Github:https://github.com/owen0o0/WebStack ","date":"2019-12-31","objectID":"/posts/mark/1.html:5:3","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#3webstack"},{"categories":["Mark"],"content":" Hugo 主题","date":"2019-12-31","objectID":"/posts/mark/1.html:6:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#hugo-主题"},{"categories":["Mark"],"content":" 1、DoIt个人博客主题 Github:https://github.com/HEIGE-PCloud/DoIt Demo:https://hugodoit.pages.dev/zh-cn/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:6:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1doit"},{"categories":["Mark"],"content":" 2、hugo-theme-meme个人博客主题 Github:https://github.com/reuixiy/hugo-theme-meme Demo:https://io-oi.me/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:6:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2hugo-theme-meme"},{"categories":["Mark"],"content":" JavaScript","date":"2019-12-31","objectID":"/posts/mark/1.html:7:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#javascript"},{"categories":["Mark"],"content":" 1、intro.js网站页面 / 新功能 引导流程插件 官网:http://introjs.com GitHub:https://github.com/usablica/intro.js ","date":"2019-12-31","objectID":"/posts/mark/1.html:7:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1introjs"},{"categories":["Mark"],"content":" 2、clipboard.jsjs 实现一键复制文本到剪贴板插件 官网:https://clipboardjs.com GitHub:https://github.com/zenorocha/clipboard.js ","date":"2019-12-31","objectID":"/posts/mark/1.html:7:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2clipboardjs"},{"categories":["Mark"],"content":" 3、viewer.jsjs 图片浏览插件 官网:https://fengyuanchen.github.io/viewerjs Github:https://github.com/fengyuanchen/viewerjs ","date":"2019-12-31","objectID":"/posts/mark/1.html:7:3","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#3viewerjs"},{"categories":["Mark"],"content":" 4、context.js右键菜单插件 官网:http://lab.jakiestfu.com/contextjs Github:https://github.com/jakiestfu/Context.js ","date":"2019-12-31","objectID":"/posts/mark/1.html:7:4","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#4contextjs"},{"categories":["Mark"],"content":" 5、moment.jsJavaScript 日期处理类库,多语言支持 官网:https://momentjs.com / http://momentjs.cn ","date":"2019-12-31","objectID":"/posts/mark/1.html:7:5","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#5momentjs"},{"categories":["Mark"],"content":" 6、highlight.js代码高亮 / 支持 189 种编程语言 / 95 种高亮样式 Github:https://github.com/highlightjs/highlight.js 官网:https://highlightjs.org / https://highlightjs.org/static/demo ","date":"2019-12-31","objectID":"/posts/mark/1.html:7:6","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#6highlightjs"},{"categories":["Mark"],"content":" 7、Pangu.js中文字和英文、数字、符号之间自动插入空格 Github:https://github.com/vinta/pangu.js 官网:暂无 ","date":"2019-12-31","objectID":"/posts/mark/1.html:7:7","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#7pangujs"},{"categories":["Mark"],"content":" M3U 直播源","date":"2019-12-31","objectID":"/posts/mark/1.html:8:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#m3u-直播源"},{"categories":["Mark"],"content":" 1、wtv解决电脑、手机看电视直播的苦恼,收集各种直播源,电视直播网站。建议配合 potplayer 使用。 Github:https://github.com/biancangming/wtv ","date":"2019-12-31","objectID":"/posts/mark/1.html:8:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1wtv"},{"categories":["Mark"],"content":" 客户端软件","date":"2019-12-31","objectID":"/posts/mark/1.html:9:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#客户端软件"},{"categories":["Mark"],"content":" 1、ContextMenuManager一个纯粹的 Windows 右键菜单管理程序 Github:https://github.com/BluePointLilac/ContextMenuManager 官网:https://bluepointlilac.github.io/ContextMenuManager ","date":"2019-12-31","objectID":"/posts/mark/1.html:9:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1contextmenumanager"},{"categories":["Mark"],"content":" 2、思源笔记一款本地优先的个人知识管理系统,支持完全离线使用,非常优秀的一款类 notion 应用。 Github:https://github.com/siyuan-note/siyuan 官网:https://b3log.org/siyuan/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:9:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2思源笔记"},{"categories":["Mark"],"content":" 工具站点","date":"2019-12-31","objectID":"/posts/mark/1.html:0:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#工具站点"},{"categories":["Mark"],"content":" AI","date":"2019-12-31","objectID":"/posts/mark/1.html:1:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#ai"},{"categories":["Mark"],"content":" 1、chatmind输入问题/文章/数据,一键生成思维导图,支持上下文改写扩充对话,例如:新产品上线推广流程 官网:https://www.chatmind.tech ","date":"2019-12-31","objectID":"/posts/mark/1.html:1:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1chatmind"},{"categories":["Mark"],"content":" 开发","date":"2019-12-31","objectID":"/posts/mark/1.html:2:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#开发"},{"categories":["Mark"],"content":" 1、requestcatcher.comDebug web hooks, http clients, etc. Request Catcher will create a subdomain on which you can test an application. All requests sent to any path on the subdomain are forwarded to your browser in real time. 官网:https://requestcatcher.com ","date":"2019-12-31","objectID":"/posts/mark/1.html:2:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1requestcatchercom"},{"categories":["Mark"],"content":" 2、json.cnjson 格式化 官网:http://json.cn ","date":"2019-12-31","objectID":"/posts/mark/1.html:2:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2jsoncn"},{"categories":["Mark"],"content":" 3、searchcode是一个源码搜索引擎,目前支持从 Github、Bitbucket、Google Code、CodePlex、SourceForge 和 Fedora Project 平台搜索公开的源码。 官网:https://searchcode.com ","date":"2019-12-31","objectID":"/posts/mark/1.html:2:3","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#3searchcode"},{"categories":["Mark"],"content":" 4、Github 开源库 star 增长曲线图https://star-history.com/ https://starchart.cc/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:2:4","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#4github-开源库-star-增长曲线图"},{"categories":["Mark"],"content":" 设计","date":"2019-12-31","objectID":"/posts/mark/1.html:3:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#设计"},{"categories":["Mark"],"content":" 1、创客贴付费网站,但提供一些免费资源,比较灵活的在线设计工具。风格比较偏向 MG 动画,可以搞出很花哨的 banner 和长图海报之类的,算是个不错的设计资源网站。 官网:https://www.chuangkit.com ","date":"2019-12-31","objectID":"/posts/mark/1.html:3:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1创客贴"},{"categories":["Mark"],"content":" 书籍","date":"2019-12-31","objectID":"/posts/mark/1.html:4:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#书籍"},{"categories":["Mark"],"content":" 1、鸠摩搜索鸠摩是一个电子书搜索引擎,界面简洁、内容纯净,专业而高效。 官网:https://www.jiumodiary.com ","date":"2019-12-31","objectID":"/posts/mark/1.html:4:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1鸠摩搜索"},{"categories":["Mark"],"content":" 2、thefuture 书籍搜索小站,知名度不高,但试着搜索了几本书,感觉还挺好用。 官网:https://bks.thefuture.top ","date":"2019-12-31","objectID":"/posts/mark/1.html:4:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2thefuture-书籍搜索"},{"categories":["Mark"],"content":" 3、z-library可以找到不少英文原版书籍 官网:https://zh.z-lib.org/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:4:3","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#3z-library"},{"categories":["Mark"],"content":" 4、全国图书馆参考咨询联盟由中国公共、教育、科技系统 图书馆合作建立的公益性服务机构,能找到不少书籍,但下载比较麻烦。 官网:http://www.ucdrs.superlib.net/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:4:4","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#4全国图书馆参考咨询联盟"},{"categories":["Mark"],"content":" 生产力","date":"2019-12-31","objectID":"/posts/mark/1.html:5:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#生产力"},{"categories":["Mark"],"content":" 1、GitMind 在线思维导图https://gitmind.cn ","date":"2019-12-31","objectID":"/posts/mark/1.html:5:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1gitmind-在线思维导图"},{"categories":["Mark"],"content":" 图像","date":"2019-12-31","objectID":"/posts/mark/1.html:6:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#图像"},{"categories":["Mark"],"content":" 1、AI 人工智能图片无损放大https://bigjpg.com ","date":"2019-12-31","objectID":"/posts/mark/1.html:6:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1ai-人工智能图片无损放大"},{"categories":["Mark"],"content":" 2、tinypng 图片在线压缩https://tinypng.com/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:6:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2tinypng-图片在线压缩"},{"categories":["Mark"],"content":" 3、Squoosh(谷歌图片在线压缩 / 开源)https://squoosh.app/ ","date":"2019-12-31","objectID":"/posts/mark/1.html:6:3","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#3squoosh谷歌图片在线压缩--开源"},{"categories":["Mark"],"content":" 4、消除图片中的背景 / AI 自动抠图https://www.remove.bg/zh ","date":"2019-12-31","objectID":"/posts/mark/1.html:6:4","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#4消除图片中的背景--ai-自动抠图"},{"categories":["Mark"],"content":" 5、中国哲学书电子化计划中国哲学书电子化计划是一个线上开放电子图书馆,为中外学者提供中国历代传世文献,力图超越印刷媒体限制,通过电子科技探索新方式与古代文献进行沟通。收藏的文本已超过三万部著作,并有五十亿字之多,故为历代中文文献资料库最大者。 https://ctext.org/zhs ","date":"2019-12-31","objectID":"/posts/mark/1.html:6:5","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#5中国哲学书电子化计划"},{"categories":["Mark"],"content":" 内容创作","date":"2019-12-31","objectID":"/posts/mark/1.html:7:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#内容创作"},{"categories":["Mark"],"content":" 1、Emoji大全 | Emoji表情符号词典 📓https://www.emojiall.com/zh-hans ","date":"2019-12-31","objectID":"/posts/mark/1.html:7:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1emoji大全--emoji表情符号词典-"},{"categories":["Mark"],"content":" 2、DeepL 翻译https://www.deepl.com/translator ","date":"2019-12-31","objectID":"/posts/mark/1.html:7:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2deepl-翻译"},{"categories":["Mark"],"content":" KMS","date":"2019-12-31","objectID":"/posts/mark/1.html:8:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#kms"},{"categories":["Mark"],"content":" 1、沧水 KMShttps://kms.cangshui.net ","date":"2019-12-31","objectID":"/posts/mark/1.html:8:1","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#1沧水-kms"},{"categories":["Mark"],"content":" 2、moerats KMShttps://www.moerats.com/kms ","date":"2019-12-31","objectID":"/posts/mark/1.html:8:2","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#2moerats-kms"},{"categories":["Mark"],"content":" 有趣的站点 台风路径查询:http://typhoon.zjwater.gov.cn 黑客模拟器:http://geektyper.com 世界护照大全:https://www.passportindex.org/cn 查无此人 / AI 生成人脸:https://thispersondoesnotexist.com 网页版 windows93:http://www.windows93.net 假装系统在升级:http://fakeupdate.net ","date":"2019-12-31","objectID":"/posts/mark/1.html:0:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#有趣的站点"},{"categories":["Mark"],"content":" 其他 中央媒体 \u0026 全国333个地级城市晚报日报 / 电视直播点播:https://laosheng.top/fly/ 全历史:https://www.allhistory.com 西窗烛:http://lib.xcz.im/library ","date":"2019-12-31","objectID":"/posts/mark/1.html:0:0","series":null,"tags":[],"title":"[ 置顶 ] 🚩 Github 项目 / 工具站点 / 有趣的站点","uri":"/posts/mark/1.html#其他"},{"categories":[],"content":" 一、题目剑指 Offer 24. 反转链表 难度简单 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。 示例: 输入: 1-\u003e2-\u003e3-\u003e4-\u003e5-\u003eNULL 输出: 5-\u003e4-\u003e3-\u003e2-\u003e1-\u003eNULL 限制: 0 \u003c= 节点个数 \u003c= 5000 注意:本题与主站 206 题相同:https://leetcode-cn.com/problems/reverse-linked-list/ ","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#二解法"},{"categories":[],"content":" 2.1、遍历法(双指针)","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#21遍历法双指针"},{"categories":[],"content":" 核心思想:在访问各节点时修改其 next 引用指向。在此过程中,可借助辅助变量(指针)来保存当前节点的 next ,以此为基础遍历整个链表。 ","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#核心思想"},{"categories":[],"content":" 复杂度分析: 时间复杂度 O(N):遍历链表使用线性大小时间。 空间复杂度 O(1): 变量 pre 和 cur 使用常数大小额外空间。 ","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#复杂度分析"},{"categories":[],"content":" 代码:/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode reverseList(ListNode head) { // 上一个节点 ListNode prev = null; // 当前节点 ListNode cursor = head; while (cursor != null) { // 辅助变量 next , 用以保存当前节点指向的下一个结点. ListNode next = cursor.next; // 让当前节点 cursor.next = prev; // 循环处理的关键, 让当前节点作为 prev , 让辅助变量 next 作为当节点, 并进入下一个循环 prev = cursor; cursor = next; } // 当 cursor 为 null 时, 整个链表已经被反转过来了 return prev; } } ","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#代码"},{"categories":[],"content":" 2.2、递归法","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#22递归法"},{"categories":[],"content":" 核心思想:使用递归法遍历链表,当越过尾节点后终止递归,在回溯时修改各节点的 next 引用指向。 ","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#核心思想-1"},{"categories":[],"content":" 复杂度分析: 时间复杂度 O(N):遍历链表使用线性大小时间。 空间复杂度 O(N): 遍历链表的递归深度达到 N ,系统使用 O(N) 大小额外空间。 ","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#复杂度分析-1"},{"categories":[],"content":" 代码:/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode reverseList(ListNode head) { // 调用递归并返回 return recur(head, null); } private ListNode recur(ListNode cur, ListNode pre) { System.out.println(cur); // 当 cur 为 null 时, pre 指向尾结点, 此时将 pre 作为反转链表的表头,开始回溯. if (cur == null) return pre; // 递归调用函数, 此时将 root 看作是一个已经被反转过的链表的表头. ListNode root = recur(cur.next, cur); // 将当前节点的 next 指向上一个节点,也就是 prev cur.next = pre; // 将反转过的链表的表头传递返回给上一级 return root; } } ","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#代码-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/solution/fan-zhuan-lian-biao-by-leetcode-solution-jvs5/ https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/solution/jian-zhi-offer-24-fan-zhuan-lian-biao-die-dai-di-2/ ","date":"2021-08-18","objectID":"/posts/series/leetcode-jianzhioffer-java/1.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"1、剑指 Offer 24. 反转链表","uri":"/posts/series/leetcode-jianzhioffer-java/1.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 09. 用两个栈实现队列 难度简单 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 ) 示例 1: 输入: [\"CQueue\",\"appendTail\",\"deleteHead\",\"deleteHead\"] [[],[3],[],[]] 输出:[null,null,3,-1] 示例 2: 输入: [\"CQueue\",\"deleteHead\",\"appendTail\",\"appendTail\",\"deleteHead\",\"deleteHead\"] [[],[],[5],[2],[],[]] 输出:[null,-1,null,null,5,2] 提示: 1 \u003c= values \u003c= 10000 最多会对 appendTail、deleteHead 进行 10000 次调用 ","date":"2021-08-20","objectID":"/posts/series/leetcode-jianzhioffer-java/2.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"2、剑指 Offer 09. 用两个栈实现队列","uri":"/posts/series/leetcode-jianzhioffer-java/2.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-08-20","objectID":"/posts/series/leetcode-jianzhioffer-java/2.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"2、剑指 Offer 09. 用两个栈实现队列","uri":"/posts/series/leetcode-jianzhioffer-java/2.html#二解法"},{"categories":[],"content":" 2.1、双栈法","date":"2021-08-20","objectID":"/posts/series/leetcode-jianzhioffer-java/2.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"2、剑指 Offer 09. 用两个栈实现队列","uri":"/posts/series/leetcode-jianzhioffer-java/2.html#21双栈法"},{"categories":[],"content":" 核心思想:维护两个栈,第一个栈(正序支持插入操作,第二个栈支持删除操作。 根据栈先进后出的特性,我们每次往第一个栈里插入元素后,第一个栈的底部元素是最后插入的元素,第一个栈的顶部元素是下一个待删除的元素。为了维护队列先进先出的特性,我们引入第二个栈,用第二个栈维护待删除的元素,在执行删除操作的时候我们首先看下第二个栈是否为空。如果为空,我们将第一个栈里的元素一个个弹出插入到第二个栈里,这样第二个栈里元素的顺序就是待删除的元素的顺序,要执行删除操作的时候我们直接弹出第二个栈的元素返回即可。 ","date":"2021-08-20","objectID":"/posts/series/leetcode-jianzhioffer-java/2.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"2、剑指 Offer 09. 用两个栈实现队列","uri":"/posts/series/leetcode-jianzhioffer-java/2.html#核心思想"},{"categories":[],"content":" 复杂度分析:**时间复杂度:**对于插入和删除操作,时间复杂度均为 O(1)。插入不多说,对于删除操作,虽然看起来是 O(n) 的时间复杂度,但是仔细考虑下每个元素只会「至多被插入和弹出 stack2 一次」,因此均摊下来每个元素被删除的时间复杂度仍为 O(1)。 空间复杂度:O(n)。需要使用两个栈存储已有的元素。 ","date":"2021-08-20","objectID":"/posts/series/leetcode-jianzhioffer-java/2.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"2、剑指 Offer 09. 用两个栈实现队列","uri":"/posts/series/leetcode-jianzhioffer-java/2.html#复杂度分析"},{"categories":[],"content":" 代码(优化前):这个是我第一次解题时的写法,因为我是实时维护正序栈与逆序栈的,所以效率比较底下。 class CQueue { // 正序栈 A :队列正序(栈顶元素即为队列头) private Stack\u003cInteger\u003e stackA = null; // 逆序栈 B :队列逆序 private Stack\u003cInteger\u003e stackB = null; public CQueue() { // 初始化双栈 this.stackA = new Stack\u003c\u003e(); this.stackB = new Stack\u003c\u003e(); } // 在队列尾部插入整数 public void appendTail(int value) { // 压入逆序栈 stackB.push(value); // 更新正序栈 stackA = reverse(stackB); } // 在队列头部删除整数 若队列中没有元素,deleteHead 操作返回 -1 public int deleteHead() { // 如果队列中没有元素,则返回 -1 if (stackA.isEmpty()) { return -1; } // 正序栈的栈顶元素出站 int result = stackA.pop(); // 更新逆序栈 stackB = reverse(stackA); return result; } // 获取一个栈的逆序栈 public Stack\u003cInteger\u003e reverse(Stack\u003cInteger\u003e stack) { Stack\u003cInteger\u003e temp = (Stack\u003cInteger\u003e) stack.clone(); Stack\u003cInteger\u003e result = new Stack\u003c\u003e(); while (!temp.isEmpty()) { result.add(temp.pop()); } return result; } } ","date":"2021-08-20","objectID":"/posts/series/leetcode-jianzhioffer-java/2.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"2、剑指 Offer 09. 用两个栈实现队列","uri":"/posts/series/leetcode-jianzhioffer-java/2.html#代码优化前"},{"categories":[],"content":" 代码(优化后):此版本为参考官方题解的解题思路与评论区 kd35 大佬的代码,也是本解法中核心思想的实现。 class CQueue { // 栈 A :队列正序 private Stack\u003cInteger\u003e stackA = null; // 栈 B :队列逆序 private Stack\u003cInteger\u003e stackB = null; public CQueue() { // 初始化双栈 this.stackA = new Stack\u003c\u003e(); this.stackB = new Stack\u003c\u003e(); } // 在队列尾部插入整数 public void appendTail(int value) { // 压入逆序栈 stackB.push(value); } // 在队列头部删除整数 若队列中没有元素,deleteHead 操作返回 -1 public int deleteHead() { if (!stackA.isEmpty()) { // 正序栈存在元素, 将栈顶元素出栈 return stackA.pop(); } else { // 更新正序栈:此时正序栈中不存在元素,将逆序栈中的元素依次出栈,并压入正序栈中 while (!stackB.isEmpty()) { stackA.push(stackB.pop()); } // 如果更新后的正序栈依然为空, 则说明队列中此时没有元素, 返回 -1 // 如果更新后的正序栈不为空, 则将正序栈的栈顶出栈 return stackA.isEmpty() ? -1 : stackA.pop(); } } } ","date":"2021-08-20","objectID":"/posts/series/leetcode-jianzhioffer-java/2.html:1:4","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"2、剑指 Offer 09. 用两个栈实现队列","uri":"/posts/series/leetcode-jianzhioffer-java/2.html#代码优化后"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/solution/mian-shi-ti-09-yong-liang-ge-zhan-shi-xian-dui-l-3/ ","date":"2021-08-20","objectID":"/posts/series/leetcode-jianzhioffer-java/2.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"2、剑指 Offer 09. 用两个栈实现队列","uri":"/posts/series/leetcode-jianzhioffer-java/2.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 03. 数组中重复的数字 难度简单 找出数组中重复的数字。 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。 示例 1: 输入: [2, 3, 1, 0, 2, 5, 3] 输出:2 或 3 限制: 2 \u003c= n \u003c= 100000 ","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#二解法"},{"categories":[],"content":" 2.1、HashSet 遍历法","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#21hashset-遍历法"},{"categories":[],"content":" 核心思想:在遍历过程中使用 HashSet 存储已经遇到的数字,如果遇到的一个数字已经在 HashSet 中,则当前的数字是重复数字。 ","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#核心思想"},{"categories":[],"content":" 复杂度分析: 时间复杂度:O(n):遍历数组一遍。使用哈希集合(HashSet),添加元素的时间复杂度为 O(1)O(1),故总的时间复杂度是 O(n)O(n)。 空间复杂度:O(n):不重复的每个元素都可能存入集合,因此占用 O(n)O(n) 额外空间。 ","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#复杂度分析"},{"categories":[],"content":" 代码:class Solution { // HashSet 法 public int findRepeatNumber(int[] nums) { HashSet\u003cInteger\u003e set = new HashSet\u003c\u003e(); int repeatNumber = -1; for (int num : nums) { if (set.add(num) == false) { repeatNumber = num; } } return repeatNumber; } } ","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#代码"},{"categories":[],"content":" 2.2、交换法","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#22交换法"},{"categories":[],"content":" 核心思想:题目规定:在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。 说明数组元素的 索引 和 值 是 一对多 的关系。 因此,可遍历数组并通过交换操作,使元素的 索引 与 值 一一对应( 即 nums[ i ] = i )。因而,就能通过索引映射对应的值,起到与字典等价的作用。 遍历中,第一次遇到数字 xx 时,将其交换至索引 xx 处;而当第二次遇到数字 xx 时,一定有 nums[x] = x ,此时即可得到一组重复数字。 ","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#核心思想-1"},{"categories":[],"content":" 复杂度分析: 时间复杂度 O(N): 遍历数组使用 O(N) ,每轮遍历的判断和交换操作使用 O(1) 。 空间复杂度 O(1): 使用常数复杂度的额外空间。 ","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#复杂度分析-1"},{"categories":[],"content":" 代码:class Solution { // 原地交换 public int findRepeatNumber(int[] nums) { int i = 0; while (i \u003c= nums.length) { if (nums[i] == i) { i++; continue; } if (nums[i] == nums[nums[i]]) { return nums[i]; } int temp = nums[nums[i]]; nums[nums[i]] = nums[i]; nums[i] = temp; } return -1; } } ","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#代码-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/solution/mian-shi-ti-03-shu-zu-zhong-zhong-fu-de-shu-zi-b-4/ https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/solution/mian-shi-ti-03-shu-zu-zhong-zhong-fu-de-shu-zi-yua/ ","date":"2021-08-22","objectID":"/posts/series/leetcode-jianzhioffer-java/3.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"3、剑指 Offer 03. 数组中重复的数字","uri":"/posts/series/leetcode-jianzhioffer-java/3.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 51. 数组中的逆序对 难度困难 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。 示例 1: 输入: [7,5,6,4] 输出: 5 限制: 0 \u003c= 数组长度 \u003c= 50000 ","date":"2021-08-24","objectID":"/posts/series/leetcode-jianzhioffer-java/4.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"4、剑指 Offer 51. 数组中的逆序对","uri":"/posts/series/leetcode-jianzhioffer-java/4.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-08-24","objectID":"/posts/series/leetcode-jianzhioffer-java/4.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"4、剑指 Offer 51. 数组中的逆序对","uri":"/posts/series/leetcode-jianzhioffer-java/4.html#二解法"},{"categories":[],"content":" 2.1、归并排序统计法","date":"2021-08-24","objectID":"/posts/series/leetcode-jianzhioffer-java/4.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"4、剑指 Offer 51. 数组中的逆序对","uri":"/posts/series/leetcode-jianzhioffer-java/4.html#21归并排序统计法"},{"categories":[],"content":" 核心思路:求逆序对和归并排序有什么关系呢?关键就在于「归并」当中「并」的过程。 归并排序体现了 “分而治之” 的算法思想,具体为: 分: 不断将数组从中点位置划分开(即二分法),将整个数组的排序问题转化为子数组的排序问题; 治: 划分到子数组长度为 1 时,开始向上合并,不断将 较短排序数组 合并为 较长排序数组,直至合并至原数组时完成排序; 合并阶段 本质上是 合并两个排序数组 的过程,而每当遇到 左子数组当前元素 \u003e 右子数组当前元素 时,意味着 「左子数组当前元素 至 末尾元素」 与 「右子数组当前元素」 构成了若干 「逆序对」 。 举个例子来说明:假设我们有两个已排序的序列等待合并,分别是 L = { 8, 12, 16, 22, 100 } 和 R = { 9, 26, 55, 64, 91 } 。一开始我们用指针 i = 0 指向 L 的首部,j = 0 指向 R 的头部。记已经合并好的部分为 M 。 L = [8, 12, 16, 22, 100] R = [9, 26, 55, 64, 91] M = [] | | i j 我们发现 i 指向的元素小于 j 指向的元素,于是把 i 指向的元素放入答案,并把 i 后移一位。这个时候我们把左边的 8 加入了答案,此时右边没有数比 8 小。 L = [8, 12, 16, 22, 100] R = [9, 26, 55, 64, 91] M = [8] | | i j 接着我们继续合并,此时i指向的元素(12)大于j指向的元素(9),那么我们可知i后面的元素也大于 9 ,也就是 12/16/22/100 都大于 9 ,由此我们就得到了 4 对逆序对。 然后把 9 加入答案,此时 i 指向 12,j 指向 26 。 L = [8, 12, 16, 22, 100] R = [9, 26, 55, 64, 91] M = [8, 9] | | i j 以此类推,等合并完这两个已排序的序列后,我们就算出了本轮的逆序对数量。 因此,本题的关键是使用归并排序中的合并阶段来统计逆序对数量的。 ","date":"2021-08-24","objectID":"/posts/series/leetcode-jianzhioffer-java/4.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"4、剑指 Offer 51. 数组中的逆序对","uri":"/posts/series/leetcode-jianzhioffer-java/4.html#核心思路"},{"categories":[],"content":" 复杂度分析: 时间复杂度 O(N * log N): 其中 N 为数组长度;归并排序使用 O(N * log N) 时间; 空间复杂度 O(N)*O*(*N*) : 辅助数组 tmp 占用 O(N) 大小的额外空间; ","date":"2021-08-24","objectID":"/posts/series/leetcode-jianzhioffer-java/4.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"4、剑指 Offer 51. 数组中的逆序对","uri":"/posts/series/leetcode-jianzhioffer-java/4.html#复杂度分析"},{"categories":[],"content":" 代码:为简化代码,「 当 j = r + 1 时 」 与 「 当 tmp[ i ] \u003c= tmp[ j ] 时 」 两判断项可合并。 class Solution { int[] nums, tmp; public int reversePairs(int[] nums) { this.nums = nums; tmp = new int[nums.length]; return mergeSort(0, nums.length - 1); } private int mergeSort(int left, int right) { // 终止条件 if (left \u003e= right) return 0; // 递归划分 int mid = (left + right) / 2; int result = mergeSort(left, mid) + mergeSort(mid + 1, right); // 合并阶段 int i = left, j = mid + 1; // 将 left~right 区间的值拷贝到 tmp 数组的对应区间中 for (int k = left; k \u003c= right; k++) tmp[k] = nums[k]; // 根据 tmp 中对应区间的值操作 nums 数组 for (int k = left; k \u003c= right; k++) { if (i == mid + 1) // i 已经走到尽头 nums[k] = tmp[j++]; else if (j == right + 1 || tmp[i] \u003c= tmp[j]) // j 已经走到尽头 || j 位置的值比 i 位置的值大 nums[k] = tmp[i++]; else { // i 位置的值比 j 位置的值大 nums[k] = tmp[j++]; // 统计逆序对( 推论:i~mid 位置的数都比 j 位置的数大,因此此次统计到的逆序对数量为 mid-i+1 ) result += mid - i + 1; } } return result; } } ","date":"2021-08-24","objectID":"/posts/series/leetcode-jianzhioffer-java/4.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"4、剑指 Offer 51. 数组中的逆序对","uri":"/posts/series/leetcode-jianzhioffer-java/4.html#代码"},{"categories":[],"content":" REF官方题解:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/shu-zu-zhong-de-ni-xu-dui-by-leetcode-solution/ 评论区题解: https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/jian-zhi-offer-51-shu-zu-zhong-de-ni-xu-pvn2h/ ","date":"2021-08-24","objectID":"/posts/series/leetcode-jianzhioffer-java/4.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"4、剑指 Offer 51. 数组中的逆序对","uri":"/posts/series/leetcode-jianzhioffer-java/4.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 42. 连续子数组的最大和 难度简单 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。 要求时间复杂度为O(n)。 示例1: 输入: nums = [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 提示: 1 \u003c= arr.length \u003c= 10^5 -100 \u003c= arr[i] \u003c= 100 注意:本题与主站 53 题相同:https://leetcode-cn.com/problems/maximum-subarray/ ","date":"2021-08-26","objectID":"/posts/series/leetcode-jianzhioffer-java/5.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"5、剑指 Offer 42. 连续子数组的最大和","uri":"/posts/series/leetcode-jianzhioffer-java/5.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-08-26","objectID":"/posts/series/leetcode-jianzhioffer-java/5.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"5、剑指 Offer 42. 连续子数组的最大和","uri":"/posts/series/leetcode-jianzhioffer-java/5.html#二解法"},{"categories":[],"content":" 2.1、动态规划","date":"2021-08-26","objectID":"/posts/series/leetcode-jianzhioffer-java/5.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"5、剑指 Offer 42. 连续子数组的最大和","uri":"/posts/series/leetcode-jianzhioffer-java/5.html#21动态规划"},{"categories":[],"content":" 核心思路状态定义:设动态规划列表 dp ,dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和。 为何定义最大和 dp[i] 中必须包含元素 nums[i] :保证 dp[i] 递推到 dp[i+1] 的正确性;如果不包含 nums[i] ,递推时则不满足题目的 连续子数组 要求。 转移方程: 若 dp[i−1]≤0 ,说明 dp[i - 1] 对 dp[i] 产生负贡献,即 dp[i-1] + nums[i] 还不如 nums[i] 本身大。 当 dp[i - 1] \u003e 0 时:执行 dp[i] = dp[i-1] + nums[i] ; 当 dp[i - 1]≤0 时:执行 dp[i] = nums[i] ; 初始状态: dp[0] = nums[0],即以 nums[0] 结尾的连续子数组最大和为 nums[0] 。 返回值: 返回 dp 列表中的最大值,代表全局最大值。 ","date":"2021-08-26","objectID":"/posts/series/leetcode-jianzhioffer-java/5.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"5、剑指 Offer 42. 连续子数组的最大和","uri":"/posts/series/leetcode-jianzhioffer-java/5.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N),线性遍历数组 nums 即可获得结果,使用 O(N) 时间。 空间复杂度:O(1),使用常数大小的额外空间。 ","date":"2021-08-26","objectID":"/posts/series/leetcode-jianzhioffer-java/5.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"5、剑指 Offer 42. 连续子数组的最大和","uri":"/posts/series/leetcode-jianzhioffer-java/5.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public int maxSubArray(int[] nums) { // 将 nums 的首个元素作为初始最大值 int max = nums[0]; // 记录 dp[i-1] 的值,对于dp[0]而言,其前面的dp[-1]=0 int former = 0; // 记录 dp[i] 的值 int cur = nums[0]; for (int num : nums) { cur = num; if (former \u003e 0) cur += former; if (cur \u003e max) max = cur; former = cur; } return max; } } ","date":"2021-08-26","objectID":"/posts/series/leetcode-jianzhioffer-java/5.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"5、剑指 Offer 42. 连续子数组的最大和","uri":"/posts/series/leetcode-jianzhioffer-java/5.html#code"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/solution/mian-shi-ti-42-lian-xu-zi-shu-zu-de-zui-da-he-do-2/ ","date":"2021-08-26","objectID":"/posts/series/leetcode-jianzhioffer-java/5.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"5、剑指 Offer 42. 连续子数组的最大和","uri":"/posts/series/leetcode-jianzhioffer-java/5.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 38. 字符串的排列 难度中等403收藏分享切换为英文接收动态反馈 输入一个字符串,打印出该字符串中字符的所有排列。 你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。 示例: 输入:s = \"abc\" 输出:[\"abc\",\"acb\",\"bac\",\"bca\",\"cab\",\"cba\"] 限制: 1 \u003c= s 的长度 \u003c= 8 ","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#二解法"},{"categories":[],"content":" 2.1、回溯 / DFS + 剪枝","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#21回溯---dfs--剪枝"},{"categories":[],"content":" 核心思想:我们将这个问题看作有 n 个排列成一行的空位,我们需要从左往右依次填入题目给定的 n 个字符,每个字符只能使用一次。首先可以想到穷举的算法,即从左往右每一个空位都依次尝试填入一个字符,看是否能填完这 n 个空位,编程实现时,我们可以用「回溯法」来模拟这个过程,也就是 DFS 。 按照题目中给定的要求,生成字符串的所有排列之后,里面不能有重复元素,也就是说我们需要按照这个规则来进行剪枝,减少不必要的运算。 对于一个长度为 n 的字符串(假设字符互不重复),其排列方案数共有:n×(n−1)×(n−2)…×2×1 排列方案的生成: 通过字符交换,先固定第 1 位字符( n 种情况)、再固定第 2 位字符( n-1 种情况)、… 、最后固定第 n 位字符( 1 种情况)。 重复排列方案与剪枝: 当字符串存在重复字符时,排列方案中也存在重复的排列方案。为排除重复方案,需在固定某位字符时,保证 “每种字符只在此位固定一次” ,即遇到重复字符时不交换,直接跳过。从 DFS 角度看,此操作称为 “剪枝” 。 递归解析: 终止条件: 当 x = len(chars) - 1 时,代表所有位已固定(最后一位只有 11 种情况),则将当前组合 chars 转化为字符串并加入 result ,并返回; 递推参数: 当前固定位 x ; 递推工作: 初始化一个 Set ,用于排除重复的字符;将第 x 位字符与 i ∈ [ x, len(chars) ] 字符分别交换,并进入下层递归; 剪枝: 若 chars[i] 在 Set 中,代表其是重复字符,因此 “剪枝” ; 将 chars[i] 加入 Set ,以便之后遇到重复字符时剪枝; 固定字符: 将字符 chars[i] 和 chars 交换,即固定 chars[i] 为当前位字符; 开启下层递归: 调用 dfs(x + 1) ,即开始固定第 x + 1 个字符; 还原交换: 将字符 chars[i] 和 chars 交换(还原之前的交换); ","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#核心思想"},{"categories":[],"content":" 复杂度分析: 时间复杂度 O(N!N): N 为字符串 s 的长度;时间复杂度和字符串排列的方案数成线性关系,方案数为 N×(N−1)×(N−2)…×2×1 ,即复杂度为 O(N!) ;字符串拼接操作 join() 使用 O(N) ;因此总体时间复杂度为 O(N! × N) 。 空间复杂度 O(N^2) : 全排列的递归深度为 N ,系统累计使用栈空间大小为 O(N);递归中辅助 Set 累计存储的字符数量最多为 N+(N−1)+…+2+1=(N+1)N/2 ,即占用 O(N^2) 的额外空间。 ","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#复杂度分析"},{"categories":[],"content":" 代码:// 回溯法 / dfs + 剪枝 class Solution { List\u003cString\u003e result = new LinkedList\u003c\u003e(); char[] chars; public String[] permutation(String s) { chars = s.toCharArray(); dfs(0); return result.toArray(new String[result.size()]); } void dfs(int index) { if (index == chars.length - 1) { result.add(String.valueOf(chars)); // 添加排列方案 return; } HashSet\u003cCharacter\u003e set = new HashSet\u003c\u003e(); for (int i = index; i \u003c chars.length; i++) { if (set.contains(chars[i])) continue; // 重复,因此剪枝 set.add(chars[i]); swap(i, index); // 交换,将 c[i] 固定在第 x 位 dfs(index + 1); // 开启固定第 x + 1 位字符 swap(i, index); // 恢复交换 } } void swap(int a, int b) { char tmp = chars[a]; chars[a] = chars[b]; chars[b] = tmp; } } ","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#代码"},{"categories":[],"content":" 2.2、下一个排列","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#22下一个排列"},{"categories":[],"content":" 核心思想:我们可以这样思考:当我们已知了当前的一个排列,我们能不能快速得到字典序中下一个更大的排列呢? 答案是肯定的,参见「31. 下一个排列的官方题解」,当我们已知了当前的一个排列,我们可以在 O(n)O(n) 的时间内计算出字典序下一个中更大的排列。这与 C++ 中的 next_permutation 函数功能相同。 具体地,我们首先对给定的字符串中的字符进行排序,即可得到当前字符串的第一个排列,然后我们不断地计算当前字符串的字典序中下一个更大的排列,直到不存在更大的排列为止即可。 这个方案的优秀之处在于,我们得到的所有排列都不可能重复,这样我们就无需进行去重的操作。同时因为无需使用回溯法,没有栈的开销,算法时间复杂度的常数较小。 ","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#核心思想-1"},{"categories":[],"content":" 复杂度分析:**时间复杂度:**O(n×n!),其中 n 为给定字符串的长度。我们需要 O(nlogn) 的时间得到第一个排列,nextPermutation 函数的时间复杂度为 O(n),我们至多执行该函数 O(n!) 次,因此总时间复杂度为 O(n×n!+nlogn)=O(n×n!)。 **空间复杂度:**O(1),注意返回值不计入空间复杂度。 ","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#复杂度分析-1"},{"categories":[],"content":" 代码:class Solution { public String[] permutation(String s) { // 存储所有排列的变量 List\u003cString\u003e ret = new ArrayList\u003cString\u003e(); // string 转换为 char 数组,对其排序 char[] arr = s.toCharArray(); Arrays.sort(arr); // 循环计算下一个更大的排列 do { ret.add(new String(arr)); } while (nextPermutation(arr)); // 构造符合题目要求的数据格式 -\u003e String 数组 int size = ret.size(); String[] retArr = new String[size]; for (int i = 0; i \u003c size; i++) { retArr[i] = ret.get(i); } return retArr; } // 求下一个更大的排列 public boolean nextPermutation(char[] arr) { // i 从 arr 的倒数第二个位置开始向前找,直到符合 arr[i] \u003c arr[i + 1] 为止 int i = arr.length - 2; while (i \u003e= 0 \u0026\u0026 arr[i] \u003e= arr[i + 1]) { i--; } if (i \u003c 0) { return false; } // j 从 arr 的最后一个位置开始找,直到符合 arr[i] \u003c arr[j] 为止 int j = arr.length - 1; while (j \u003e= 0 \u0026\u0026 arr[i] \u003e= arr[j]) { j--; } // 交换 arr[i] 和 arr[j] 的值 swap(arr, i, j); // 将 arr 中下标从 i+1 开始,直到 arr 的末尾的元素反转 reverse(arr, i + 1); return true; } // 交换 char 数组下标为 i 和 j 的值 public void swap(char[] arr, int i, int j) { char temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } // 反转 char 数组的部分元素, 下标从 start 开始到末尾 public void reverse(char[] arr, int start) { int left = start, right = arr.length - 1; while (left \u003c right) { swap(arr, left, right); left++; right--; } } } ","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#代码-1"},{"categories":[],"content":" 三、解法补充说明","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#三解法补充说明"},{"categories":[],"content":" DFS + 剪枝以 DFS+剪枝 这个解法来说,实际上你可以把问题分解为两个: 如何列出字符串的全排列? 如何对全排列去重? 我们使用 DFS 生成全排列,并且在生成的过程中剪枝,提高计算效率。 DFS 生成全排列 public class Main { public static void main(String[] args) { String str = \"1233\"; Solution solution = new Solution(); System.out.println(Arrays.toString(solution.permutation(str))); } } // 示范如何使用 dfs 穷举字符串排列(不去重/剪枝) class Solution { List\u003cString\u003e result = new LinkedList\u003c\u003e(); char[] chars = null; public String[] permutation(String s) { chars = s.toCharArray(); dfs(0); return result.toArray(new String[result.size()]); } public void dfs(int index) { if (index == chars.length - 1) { result.add(String.valueOf(chars)); return; } for (int i = index; i \u003c chars.length; i++) { swap(i, index); dfs(index + 1); swap(i, index); } } public void swap(int a, int b) { char temp = chars[a]; chars[a] = chars[b]; chars[b] = temp; } } 此时在这个基础上进行改造,做剪枝处理,也就得到了最终的答案。 ","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#dfs--剪枝"},{"categories":[],"content":" 下一个排列先给字符串进行排序,就可以得到第一个排列,然后我们不断地计算当前字符串的字典序中下一个更大的排列,直到不存在更大的排列为止即可。 这个方案的优秀之处在于,我们得到的所有排列都不可能重复,这样我们就无需进行去重的操作。同时因为无需使用回溯法,没有栈的开销,算法时间复杂度的常数较小。 ","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#下一个排列"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/solution/zi-fu-chuan-de-pai-lie-by-leetcode-solut-hhvs/ https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/solution/mian-shi-ti-38-zi-fu-chuan-de-pai-lie-hui-su-fa-by/ ","date":"2021-08-28","objectID":"/posts/series/leetcode-jianzhioffer-java/6.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"6、剑指 Offer 38. 字符串的排列","uri":"/posts/series/leetcode-jianzhioffer-java/6.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 22. 链表中倒数第k个节点 难度简单 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。 例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。 示例: 给定一个链表: 1-\u003e2-\u003e3-\u003e4-\u003e5, 和 k = 2. 返回链表 4-\u003e5. ","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#二解法"},{"categories":[],"content":" 2.1、双指针二次遍历","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#21双指针二次遍历"},{"categories":[],"content":" 核心思路: 先遍历统计链表长度,记为 count ; 设置一个指针走 (n-k) 步,即可找到链表倒数第 k 个节点。 这也是最容易想到的解法 ","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#核心思路"},{"categories":[],"content":" 复杂度分析:时间复杂度 O(N) : 需要两次遍历,因此至多为 2N 。 空间复杂度 O(1) : 需要一个辅助指针,使用常数大小的额外空间。 ","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#复杂度分析"},{"categories":[],"content":" 代码:需要注意的是,此解法没有考虑以下几种特殊情况。 head为空指针; k大于链表的长度; 输入的参数k为0; class Solution { public ListNode getKthFromEnd(ListNode head, int k) { ListNode cursor = head; // 第一次遍历链表,计算总节点数 int count = 0; while (cursor != null) { count++; cursor = cursor.next; } // 第二次遍历链表,找到倒数第k个元素 int index = count - k; while (index \u003e 0) { head = head.next; index--; } return head; } } ","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#代码"},{"categories":[],"content":" 2.2、双指针单次遍历","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#22双指针单次遍历"},{"categories":[],"content":" 核心思路:不需要知道链表长度,指针 1 先走 k-1 步,然后指针 2 和指针 1 同时前进,当指针 1 指向链表最后一个元素时,指针 2 即为所求。 ","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#核心思路-1"},{"categories":[],"content":" 复杂度分析:时间复杂度 O(N) : N 为链表长度;总体看, 指针 1 走了 N 步, 指针 走了 (N-k)(N−k) 步。 空间复杂度 O(1): 双指针 former , latter 使用常数大小的额外空间。 ","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#复杂度分析-1"},{"categories":[],"content":" 代码:需要注意的是,此解法没有考虑以下几种特殊情况。 head为空指针; k大于链表的长度; 输入的参数k为0; class Solution { public ListNode getKthFromEnd(ListNode head, int k) { ListNode former = head, latter = head; // 让指针 1 先走 k 步 for (int i = 0; i \u003c k; i++) former = former.next; // 让指针1 和指针 2 同步移动, 待指针 2 走至链表尾结点时, 指针 1 指向的结点就是符合题意的答案 while (former != null) { former = former.next; latter = latter.next; } return latter; } } ","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#代码-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/solution/mian-shi-ti-22-lian-biao-zhong-dao-shu-di-kge-j-11/ ","date":"2021-08-29","objectID":"/posts/series/leetcode-jianzhioffer-java/7.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"7、剑指 Offer 22. 链表中倒数第k个节点","uri":"/posts/series/leetcode-jianzhioffer-java/7.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 11. 旋转数组的最小数字 难度简单 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。 示例 1: 输入:[3,4,5,1,2] 输出:1 示例 2: 输入:[2,2,2,0,1] 输出:0 注意:本题与主站 154 题相同:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/ ","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#二解法"},{"categories":[],"content":" 2.1、遍历求最小值(不推荐)","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#21遍历求最小值不推荐"},{"categories":[],"content":" 核心思想:遍历数组,求最小值。 ","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#核心思想"},{"categories":[],"content":" 复杂度分析:时间复杂度:O(N) 。 空间复杂度:O(1)。min 变量使用常数大小的额外空间。 ","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#复杂度分析"},{"categories":[],"content":" 代码:class Solution { public int minArray(int[] numbers) { int min = numbers[0]; for (int i = 1; i \u003c numbers.length; i++) { if (numbers[i] \u003c min) { min = numbers[i]; } } return min; } } ","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#代码"},{"categories":[],"content":" 2.2、二分法(推荐)","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#22二分法推荐"},{"categories":[],"content":" 核心思想:排序数组的查找问题首先考虑使用 二分法 解决,其可将 遍历法 的 线性级别 时间复杂度降低至 对数级别 。 寻找旋转数组的最小元素即为寻找 右排序数组 的首个元素 nums[ x ] ,称 x 为 旋转点。 右排序数组:在 [ 3, 4, 5, 1, 2 ] 序列中,我们将 [ 1, 2 ] 称为右排序数组; 旋转点:在 [ 3, 4, 5, 1, 2 ] 序列中,我们将右排序数组的首个元素称为旋转点; ","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#核心思想-1"},{"categories":[],"content":" 复杂度分析:时间复杂度: O(log₂N) 。在特例情况下(例如 [ 1,1,1,1 ]),会退化到 O(N)。 空间复杂度: O(1)。i, j,mid 变量使用常数大小的额外空间。 ","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#复杂度分析-1"},{"categories":[],"content":" 代码:class Solution { public int minArray(int[] numbers) { int i = 0, j = numbers.length - 1; while (i \u003c j) { int mid = (i + j) / 2; if (numbers[mid] \u003e numbers[j]) { // 旋转点 x (最小值)一定在 [mid+1,j] 闭区间内 i = mid + 1; } else if (numbers[mid] \u003c numbers[j]) { // 旋转点 x (最小值)一定在 [i,mid] 闭区间内 j = mid; } else { // 旋转点在 [i,j-1] j--; } } return numbers[i]; } } ","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#代码-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/solution/mian-shi-ti-11-xuan-zhuan-shu-zu-de-zui-xiao-shu-3/ ","date":"2021-09-01","objectID":"/posts/series/leetcode-jianzhioffer-java/8.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"8、剑指 Offer 11. 旋转数组的最小数字","uri":"/posts/series/leetcode-jianzhioffer-java/8.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 29. 顺时针打印矩阵 难度简单 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。 示例 1: 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5] 示例 2: 输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 输出:[1,2,3,4,8,12,11,10,9,5,6,7] 限制: 0 \u003c= matrix.length \u003c= 100 0 \u003c= matrix[i].length \u003c= 100 注意:本题与主站 54 题相同:https://leetcode-cn.com/problems/spiral-matrix/ ","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#二解法"},{"categories":[],"content":" 2.1、四相边界法","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#21四相边界法"},{"categories":[],"content":" 核心思想:已知顺时针打印矩阵的顺序是 “从左向右、从上向下、从右向左、从下向上” 循环,考虑设定矩阵的“左、上、右、下”四个边界,模拟以上矩阵遍历顺序。 上边界 ( top )、下边界 ( bottom ); 左边界 ( left )、右边界 ( right ); 打印方向 1. 根据边界打印 2. 边界向内收缩 3. 是否打印完毕 从左向右 左边界l ,右边界 r 上边界 t 加 11 是否 t \u003e b 从上向下 上边界 t ,下边界b 右边界 r 减 11 是否 l \u003e r 从右向左 右边界 r ,左边界l 下边界 b 减 11 是否 t \u003e b 从下向上 下边界 b ,上边界t 左边界 l 加 11 是否 l \u003e r ","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#核心思想"},{"categories":[],"content":" 复杂度分析:时间复杂度:O(MN), M, N 分别为矩阵行数和列数。 空间复杂度:O(1), 四个边界 l , r , t , b 使用常数大小的 额外 空间( res 为必须使用的空间)。 ","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#复杂度分析"},{"categories":[],"content":" 代码:class Solution { public int[] spiralOrder(int[][] matrix) { if (matrix.length == 0) return new int[0]; int l = 0, r = matrix[0].length - 1, t = 0, b = matrix.length - 1, x = 0; int[] res = new int[(r + 1) * (b + 1)]; while (true) { for (int i = l; i \u003c= r; i++) res[x++] = matrix[t][i]; // left to right. if (++t \u003e b) break; for (int i = t; i \u003c= b; i++) res[x++] = matrix[i][r]; // top to bottom. if (l \u003e --r) break; for (int i = r; i \u003e= l; i--) res[x++] = matrix[b][i]; // right to left. if (t \u003e --b) break; for (int i = b; i \u003e= t; i--) res[x++] = matrix[i][l]; // bottom to top. if (++l \u003e r) break; } return res; } } ","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#代码"},{"categories":[],"content":" 2.2、辅助矩阵法","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#22辅助矩阵法"},{"categories":[],"content":" 核心思想:可以模拟打印矩阵的路径。初始位置是矩阵的左上角,初始方向是向右,当路径超出界限或者进入之前访问过的位置时,顺时针旋转,进入下一个方向。 判断路径是否进入之前访问过的位置需要使用一个与输入矩阵大小相同的辅助矩阵 visited,其中的每个元素表示该位置是否被访问过。当一个元素被访问时,将 visited 中的对应位置的元素设为已访问。 如何判断路径是否结束?由于矩阵中的每个元素都被访问一次,因此路径的长度即为矩阵中的元素数量,当路径的长度达到矩阵中的元素数量时即为完整路径,将该路径返回。 ","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#核心思想-1"},{"categories":[],"content":" 复杂度分析:时间复杂度:O(mn),其中 m 和 n 分别是输入矩阵的行数和列数。矩阵中的每个元素都要被访问一次。 空间复杂度:O(mn),需要创建一个大小为 m×n 的矩阵 visited 记录每个位置是否被访问过。 ","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#复杂度分析-1"},{"categories":[],"content":" 代码:class Solution { public int[] spiralOrder(int[][] matrix) { // 边界条件 if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return new int[0]; } // 获取矩阵的行列数 int rows = matrix.length, columns = matrix[0].length; // 构造辅助矩阵 boolean[][] visited = new boolean[rows][columns]; // 计算矩阵元素个数总和 int total = rows * columns; // 答案 int[] result = new int[total]; // 当前坐标 int row = 0, column = 0; // 方向 int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 当前是哪个方向 int directionIndex = 0; for (int i = 0; i \u003c total; i++) { // 将当前元素写入 result result[i] = matrix[row][column]; // 在辅助矩阵中标注已访问过 visited[row][column] = true; // 计算当前方向的下一个坐标 int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1]; // 判断下一个坐标是否超限或已被访问过 if (nextRow \u003c 0 || nextRow \u003e= rows || nextColumn \u003c 0 || nextColumn \u003e= columns || visited[nextRow][nextColumn]) { // 顺时针转向 90° directionIndex = (directionIndex + 1) % 4; } // 按照当前方向,走一步 row += directions[directionIndex][0]; column += directions[directionIndex][1]; } return result; } } ","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#代码-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/solution/shun-shi-zhen-da-yin-ju-zhen-by-leetcode-solution/ https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/solution/mian-shi-ti-29-shun-shi-zhen-da-yin-ju-zhen-she-di/ ","date":"2021-09-03","objectID":"/posts/series/leetcode-jianzhioffer-java/9.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"9、剑指 Offer 29. 顺时针打印矩阵","uri":"/posts/series/leetcode-jianzhioffer-java/9.html#ref"},{"categories":[],"content":" 一、题目 剑指 Offer 13. 机器人的运动范围 难度中等地上有一个 m 行 n 列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当 k 为 18 时,机器人能够进入方格 [35, 37] ,因为 3+5+3+7=18 。但它不能进入方格 [35, 38],因为 3+5+3+8=19 。请问该机器人能够到达多少个格子? 示例 1: 输入:m = 2, n = 3, k = 1 输出:3 示例 2: 输入:m = 3, n = 1, k = 0 输出:1 提示: 1 \u003c= n,m \u003c= 100 0 \u003c= k \u003c= 20 ","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#一题目"},{"categories":[],"content":" 一、题目 剑指 Offer 13. 机器人的运动范围 难度中等地上有一个 m 行 n 列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当 k 为 18 时,机器人能够进入方格 [35, 37] ,因为 3+5+3+7=18 。但它不能进入方格 [35, 38],因为 3+5+3+8=19 。请问该机器人能够到达多少个格子? 示例 1: 输入:m = 2, n = 3, k = 1 输出:3 示例 2: 输入:m = 3, n = 1, k = 0 输出:1 提示: 1 \u003c= n,m \u003c= 100 0 \u003c= k \u003c= 20 ","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#剑指-offer-13-机器人的运动范围httpsleetcode-cncomproblemsji-qi-ren-de-yun-dong-fan-wei-lcof-难度中等"},{"categories":[],"content":" 二、解法","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#二解法"},{"categories":[],"content":" 2.1、DFS + 剪枝","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#21dfs--剪枝"},{"categories":[],"content":" 核心思路如果我们将行坐标和列坐标数位之和大于 k 的格子看作障碍物,那么这道题就是一道很传统的搜索题目,我们可以使用广度优先搜索或者深度优先搜索来解决它。与 矩阵中的路径 类似,是典型的搜索 \u0026 回溯问题。 但需要注意的是,本题不是要求找最长路径,而是可达坐标的累计数量。也就是说不需要将走过的路径恢复至未走过的状态,只需要统计走过的格子的累计数量最大值即可。 使用一个m * n大小的矩阵存储所有单元格的索引,记作 visited。借助这个访问记录矩阵,你就可以知道机器人已经走过了哪些格子,在这个过程中就可以统计走过的格子数量了。 同时这道题还有一个隐藏的优化:我们在搜索的过程中搜索方向可以缩减为向右和向下,而不必再向上和向左进行搜索。 矩阵中 满足数位和的解 构成的几何形状形如多个 等腰直角三角形 。 ","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(MN) ,最差情况下,机器人遍历矩阵所有单元格。 空间复杂度:最差情况下,visited 内存储矩阵所有单元格的索引,使用 O(MN) 的额外空间。 ","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { // 访问记录矩阵 private int[][] visited = null; // 参数 private int m = 0; private int n = 0; private int k = 0; // 入口 public int movingCount(int m, int n, int k) { // 初始化矩阵访问记录 visited = new int[m][n]; // 初始化参数 this.m = m; this.n = n; this.k = k; // 起点坐标 int row = 0, column = 0; return move(row, column, 0); } // 以 row, column 为起点移动一步 private int move(int row, int column, int max) { // 检查 row, column 是否超限 if (row \u003c 0 || row \u003e= this.m || column \u003c 0 || column \u003e= this.n) { return max; } // 检查坐标是否合法 if (checkXY(row, column, this.k) == false) { return max; } // 检查该坐标是否已经走过 if (visited[row][column] != 0) { return max; } max += 1; // 写入访问记录, 防止重复访问 visited[row][column] = 1; // 上下左右四个方向递归调用 // max = Math.max(max, move(row - 1, column, max)); max = Math.max(max, move(row + 1, column, max)); // max = Math.max(max, move(row, column - 1, max)); max = Math.max(max, move(row, column + 1, max)); return max; } // 检查坐标是否合法 private boolean checkXY(int x, int y, int k) { // 边界条件 if (x \u003c 0 || y \u003c 0) { return false; } // 计算 x y 的数位和 int sum = shuWeiSum(x) + shuWeiSum(y); return sum \u003e k ? false : true; } // 求 number 的数位和 private int shuWeiSum(int number) { int sum = 0; while (number != 0) { sum += number % 10; number /= 10; } return sum; } } ","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#code"},{"categories":[],"content":" 2.2、BFS","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#22bfs"},{"categories":[],"content":" 核心思路BFS 和 DFS 两者目标都是遍历整个矩阵,不同点在于搜索顺序不同。DFS 是朝一个方向走到底,再回退,以此类推;BFS 则是按照“平推”的方式向前搜索。通常利用队列实现 BFS 。 ","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#核心思路-1"},{"categories":[],"content":" 复杂度分析时间复杂度:O(MN) ,最差情况下,机器人遍历矩阵所有单元格,此时时间复杂度为 O(MN)。 空间复杂度:O(MN) ,最差情况下,visited 内存储矩阵所有单元格的索引,使用 O(MN) 的额外空间。 ","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#复杂度分析-1"},{"categories":[],"content":" Codeclass Solution { public int movingCount(int m, int n, int k) { // 辅助矩阵 boolean[][] visited = new boolean[m][n]; // 可达坐标累计个数 int result = 0; // 等待访问的坐标队列 Queue\u003cint[]\u003e queue = new LinkedList\u003cint[]\u003e(); // 设置初始坐标 queue.add(new int[]{0, 0, 0, 0}); // 循环访问, 直到坐标队列为空 while (queue.size() \u003e 0) { // 取数 int[] tmp = queue.poll(); // 获取当前坐标行列值 int row = tmp[0], column = tmp[1]; // 检查行列坐标是否超限 if (row \u003e= m || column \u003e= n) { continue; } // 检查坐标是否符合规则(障碍物判断) if (checkXY(row, column, k) == false) { continue; } // 检查该坐标是否已被访问过 if (visited[row][column] == true) { continue; } // 标记当前坐标已经访问过 visited[row][column] = true; // 累计可达坐标+1 result++; // 加入下方向的坐标 queue.add(new int[]{row + 1, column}); // 加入右方向的坐标 queue.add(new int[]{row, column + 1}); } return result; } // 检查坐标是否合法 private boolean checkXY(int x, int y, int k) { // 边界条件 if (x \u003c 0 || y \u003c 0) { return false; } // 计算 x y 的数位和 int sum = shuWeiSum(x) + shuWeiSum(y); return sum \u003e k ? false : true; } // 求 number 的数位和 private int shuWeiSum(int number) { int sum = 0; while (number != 0) { sum += number % 10; number /= 10; } return sum; } } ","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#code-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/solution/ben-ti-he-qi-ta-hui-su-suan-fa-de-ti-mu-ir9le/ https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/solution/ji-qi-ren-de-yun-dong-fan-wei-by-leetcode-solution/ ","date":"2021-09-05","objectID":"/posts/series/leetcode-jianzhioffer-java/10.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"10、剑指 Offer 13. 机器人的运动范围","uri":"/posts/series/leetcode-jianzhioffer-java/10.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 06. 从尾到头打印链表 难度简单 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。 示例 1: 输入:head = [1,3,2] 输出:[2,3,1] 限制: 0 \u003c= 链表长度 \u003c= 10000 ","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#二解法"},{"categories":[],"content":" 2.1、辅助栈","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#21辅助栈"},{"categories":[],"content":" 核心思想:栈的特点是后进先出,即最后压入栈的元素最先弹出。考虑到栈的这一特点,使用栈将链表元素顺序倒置。从链表的头节点开始,依次将每个节点的值压入栈内,然后依次弹出栈内的元素并存储到数组中。 ","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#核心思想"},{"categories":[],"content":" 复杂度分析:时间复杂度:O(n)。正向遍历一遍链表,然后从栈弹出全部节点,等于又反向遍历一遍链表。 空间复杂度:O(n)。额外使用一个栈存储链表中的每个节点。 ","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#复杂度分析"},{"categories":[],"content":" 代码:class Solution { public int[] reversePrint(ListNode head) { Stack\u003cInteger\u003e stack = new Stack\u003c\u003e(); // 遍历链表, 入栈 while (head != null) { stack.push(head.val); head = head.next; } int[] result = new int[stack.size()]; // 循环出栈,构造数据 for (int i = 0; i \u003c result.length; i++) { result[i] = stack.pop(); } return result; } } ","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#代码"},{"categories":[],"content":" 2.1、递归法","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#21递归法"},{"categories":[],"content":" 核心思想:利用递归: 先走至链表末端,回溯时依次将节点值加入列表 ,这样就可以实现链表值的倒序输出。 递推阶段: 每次传入 head.next ,以 head == null(即走过链表尾部节点)为递归终止条件,此时直接返回。 回溯阶段: 层层回溯时,将当前节点值加入列表,即tmp.add(head.val)。 最终,将列表 tmp 转化为数组 res ,并返回即可。 ","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#核心思想-1"},{"categories":[],"content":" 复杂度分析:时间复杂度:O(N),遍历链表,递归 N 次。 空间复杂度:O(N), 系统递归需要使用 O(N) 的栈空间。 ","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#复杂度分析-1"},{"categories":[],"content":" 代码:class Solution { ArrayList\u003cInteger\u003e arrayList = new ArrayList\u003c\u003e(); public int[] reversePrint(ListNode head) { // 边界值 if (head == null) { return new int[]{}; } reverse(head); int[] result = new int[arrayList.size()]; for (int i = 0; i \u003c arrayList.size(); i++) { result[i] = arrayList.get(i); } return result; } // 在递归遍历链表的回溯过程中利用 ArrayList 记录数据, 天然就是逆序的 public void reverse(ListNode node) { if (node.next == null) { arrayList.add(node.val); return; } // 递归调用 reverse(node.next); // 回溯阶段 arrayList.add(node.val); } } ","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#代码-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/solution/mian-shi-ti-06-cong-wei-dao-tou-da-yin-lian-biao-b/ ","date":"2021-09-06","objectID":"/posts/series/leetcode-jianzhioffer-java/11.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"11、剑指 Offer 06. 从尾到头打印链表","uri":"/posts/series/leetcode-jianzhioffer-java/11.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 07. 重建二叉树 难度中等 输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 示例 1: Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] Output: [3,9,20,null,null,15,7] 示例 2: Input: preorder = [-1], inorder = [-1] Output: [-1] 限制: 0 \u003c= 节点个数 \u003c= 5000 注意:本题与主站 105 题重复:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ ","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#二解法"},{"categories":[],"content":" 2.1、递归法","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#21递归法"},{"categories":[],"content":" 核心思想:对于任意一颗树而言,前序遍历的形式总是如下所示,且根节点总是前序遍历中的第一个节点。 [ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ] 而中序遍历的形式总是 [ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ] 只要我们在中序遍历中定位到根节点,那么我们就可以分别知道左子树和右子树中的节点数目。由于同一颗子树的前序遍历和中序遍历的长度显然是相同的,因此我们就可以对应到前序遍历的结果中,对上述形式中的所有左右括号进行定位。 这样以来,我们就知道了左子树的前序遍历和中序遍历结果,以及右子树的前序遍历和中序遍历结果,我们就可以递归地对构造出左子树和右子树,再将这两颗子树接到根节点的左右位置。 细节 在中序遍历中对根节点进行定位时,一种简单的方法是直接扫描整个中序遍历的结果并找出根节点,但这样做的时间复杂度较高。我们可以考虑使用哈希表来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键表示一个元素(节点的值),值表示其在中序遍历中的出现位置。在构造二叉树的过程之前,我们可以对中序遍历的列表进行一遍扫描,就可以构造出这个哈希映射。在此后构造二叉树的过程中,我们就只需要 O(1) 的时间对根节点进行定位了。 ","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#核心思想"},{"categories":[],"content":" 复杂度分析:时间复杂度:O(n),其中 n 是树中的节点个数。 空间复杂度:O(n),除去返回的答案需要的 O(n) 空间之外,我们还需要使用 O(n) 的空间存储哈希映射,以及 O(h)(其中 h 是树的高度)的空间表示递归时栈空间。这里 h \u003c n,所以总空间复杂度为 O(n)。 ","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#复杂度分析"},{"categories":[],"content":" 代码:class Solution { // 前序遍历值 int[] preorder = null; // 中序遍历值 int[] inorder = null; // 保存中序遍历中每个元素各自出现的下标,方便定位 HashMap\u003cInteger, Integer\u003e map = new HashMap\u003c\u003e(); public TreeNode buildTree(int[] preorder, int[] inorder) { // 初始化参数 this.preorder = preorder; this.inorder = inorder; for (int i = 0; i \u003c inorder.length; i++) { map.put(inorder[i], i); } // 递归调用 TreeNode result = myBuildTree(0, preorder.length - 1, 0, inorder.length - 1); return result; } /** * 给定一个树的前序遍历与中序遍历,递归生成树结构 * * @param preorderStartIndex 前序遍历下标区间 start * @param preorderEndIndex 前序遍历下标区间 end * @param inorderStartIndex 中序遍历下标区间 start * @param inorderEndIndex 中序遍历下标区间 end * @return */ public TreeNode myBuildTree(int preorderStartIndex, int preorderEndIndex, int inorderStartIndex, int inorderEndIndex) { if (preorderStartIndex \u003e preorderEndIndex || inorderStartIndex \u003e inorderEndIndex) { // 如果下标超限,则说明该节点为 null return null; } // 先取得根节点的值 int rootVal = this.preorder[preorderStartIndex]; // 构造出一个新节点 TreeNode node = new TreeNode(rootVal); // 获取根节点的值处于中序遍历中的下标 int rootIndex = this.map.get(rootVal); // 计算左子树元素的个数 int leftSubtreeSize = rootIndex - inorderStartIndex; // 递归处理左子树 node.left = myBuildTree(preorderStartIndex + 1, preorderStartIndex + leftSubtreeSize, inorderStartIndex, rootIndex - 1); // 递归处理右子树 node.right = myBuildTree(preorderStartIndex + 1 + leftSubtreeSize, preorderEndIndex, rootIndex + 1, inorderEndIndex); return node; } } ","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#代码"},{"categories":[],"content":" 2.2、迭代法(未完待续)","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#22迭代法未完待续"},{"categories":[],"content":" 核心思想:","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#核心思想-1"},{"categories":[],"content":" 复杂度分析:","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#复杂度分析-1"},{"categories":[],"content":" 代码:// 迭代法 class Solution { public TreeNode buildTree(int[] preorder, int[] inorder) { if (preorder == null || preorder.length == 0) { return null; } // 初始化根节点 TreeNode root = new TreeNode(preorder[0]); Deque\u003cTreeNode\u003e stack = new LinkedList\u003cTreeNode\u003e(); stack.push(root); int inorderIndex = 0; for (int i = 1; i \u003c preorder.length; i++) { int preorderVal = preorder[i]; TreeNode node = stack.peek(); // 用前序数组一直构建左子树 if (node.val != inorder[inorderIndex]) { node.left = new TreeNode(preorderVal); stack.push(node.left); } else { // 碰到了inorder[inorderIndex],表示到了左下角,这时就需要往上走并处理右子树 while (!stack.isEmpty() \u0026\u0026 stack.peek().val == inorder[inorderIndex]) { node = stack.pop(); inorderIndex++; } node.right = new TreeNode(preorderVal); stack.push(node.right); } } return root; } } ","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#代码-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/solution/mian-shi-ti-07-zhong-jian-er-cha-shu-by-leetcode-s/ ","date":"2021-09-07","objectID":"/posts/series/leetcode-jianzhioffer-java/12.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"12、剑指 Offer 07. 重建二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/12.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 04. 二维数组中的查找 难度中等 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 示例: 现有矩阵 matrix 如下: [ [1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30] ] 给定 target = 5,返回 true。 给定 target = 20,返回 false。 限制: 0 \u003c= n \u003c= 1000 0 \u003c= m \u003c= 1000 **注意:**本题与主站 240 题相同:https://leetcode-cn.com/problems/search-a-2d-matrix-ii/ ","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#二解法"},{"categories":[],"content":" 2.1、旋转法(线性查找)","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#21旋转法线性查找"},{"categories":[],"content":" 核心思想:如下图所示,我们将矩阵逆时针旋转 45° ,并将其转化为图形式,发现其类似于 二叉搜索树 ,即对于每个元素,其左分支元素更小、右分支元素更大。因此,通过从 “根节点” 开始搜索,遇到比 target 大的元素就向左,反之向右,即可找到目标值 target 。 “根节点” 对应的是矩阵的 “左下角” 和 “右上角” 元素,也就是上图标注蓝色的 3 和 7,实际上以这两个元素为起点搜索都可以。 ","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#核心思想"},{"categories":[],"content":" 复杂度分析:时间复杂度:O(M+N),其中,N 和 M 分别为矩阵行数和列数,此算法最多循环 M+N 次。 空间复杂度:O(1), i, j 指针使用常数大小额外空间。 ","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#复杂度分析"},{"categories":[],"content":" 代码:以左下角为起点,向上搜索 class Solution { public boolean findNumberIn2DArray(int[][] matrix, int target) { // 将左下角元素作为起点(初始下标) int i = matrix.length - 1, j = 0; // 循环寻找 while (i \u003e= 0 \u0026\u0026 j \u003c matrix[0].length) { if (matrix[i][j] \u003e target) { i--; } else if (matrix[i][j] \u003c target) { j++; } else { return true; } } return false; } } 或以右上角为起点,向下搜索 class Solution { public boolean findNumberIn2DArray(int[][] matrix, int target) { if (matrix.length == 0) { return false; } // 将右上角元素作为起点(初始下标) int i = 0, j = matrix[0].length - 1; // 循环寻找 while (i \u003c matrix.length \u0026\u0026 j \u003e= 0) { if (matrix[i][j] \u003e target) { j--; } else if (matrix[i][j] \u003c target) { i++; } else { return true; } } return false; } } ","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#代码"},{"categories":[],"content":" 2.2、暴力法","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#22暴力法"},{"categories":[],"content":" 核心思想:如果不考虑二维数组排好序的特点,则直接遍历整个二维数组的每一个元素,判断目标值是否在二维数组中存在。 依次遍历二维数组的每一行和每一列。如果找到一个元素等于目标值,则返回 true。如果遍历完毕仍未找到等于目标值的元素,则返回 false。 ","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#核心思想-1"},{"categories":[],"content":" 复杂度分析:时间复杂度:O(nm)。二维数组中的每个元素都被遍历,因此时间复杂度为二维数组的大小。 空间复杂度:O(1)。 ","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#复杂度分析-1"},{"categories":[],"content":" 代码:class Solution { public boolean findNumberIn2DArray(int[][] matrix, int target) { // 处理边界条件 if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { return false; } // 获取矩阵的行列值 int rows = matrix.length, columns = matrix[0].length; // 遍历矩阵每个元素查找 for (int i = 0; i \u003c rows; i++) { for (int j = 0; j \u003c columns; j++) { if (matrix[i][j] == target) { return true; } } } return false; } } ","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#代码-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/solution/mian-shi-ti-04-er-wei-shu-zu-zhong-de-cha-zhao-zuo/ https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/solution/mian-shi-ti-04-er-wei-shu-zu-zhong-de-cha-zhao-b-3/ ","date":"2021-09-09","objectID":"/posts/series/leetcode-jianzhioffer-java/13.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"13、剑指 Offer 04. 二维数组中的查找","uri":"/posts/series/leetcode-jianzhioffer-java/13.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 59 - I. 滑动窗口的最大值 难度困难 给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。 示例: 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 输出: [3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7 提示: 你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。 注意:本题与主站 239 题相同:https://leetcode-cn.com/problems/sliding-window-maximum/ ","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#二解法"},{"categories":[],"content":" 2.1、优先队列","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#21优先队列"},{"categories":[],"content":" 核心思路对于「最大值」,我们可以想到一种非常合适的数据结构,那就是优先队列(堆),其中的大根堆可以帮助我们实时维护一系列元素中的最大值。 对于本题而言,初始时,我们将数组 nums 的前 k 个元素放入优先队列中。每当我们向右移动窗口时,我们就可以把一个新的元素放入优先队列中,此时堆顶的元素就是堆中所有元素的最大值。然而这个最大值可能并不在滑动窗口中,在这种情况下,这个值在数组 nums 中的位置出现在滑动窗口左边界的左侧。因此,当我们后续继续向右移动窗口时,这个值就永远不可能出现在滑动窗口中了,我们可以将其永久地从优先队列中移除。 我们不断地移除堆顶的元素,直到其确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组 (num,index),表示元素 num 在数组中的下标为 index。 ","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(n log n),其中 n 是数组 nums 的长度。在最坏情况下,数组 nums 中的元素单调递增,那么最终优先队列中包含了所有元素,没有元素被移除。由于将一个元素放入优先队列的时间复杂度为 O(log n),因此总时间复杂度为 O(n log n)。 空间复杂度:O(n),即为优先队列需要使用的空间。这里所有的空间复杂度分析都不考虑返回的答案需要的 O(n) 空间,只计算额外的空间使用。 ","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public int[] maxSlidingWindow(int[] nums, int k) { // 边界条件 / 特殊情况 if (nums.length == 0 || k == 0) return new int[0]; int n = nums.length; // 为了方便判断堆顶元素与滑动窗口的位置关系 // 我们在优先队列中存储二元组 (num,index),表示元素 num 在数组中的下标为 index。 PriorityQueue\u003cint[]\u003e queue = new PriorityQueue\u003cint[]\u003e(new Comparator\u003cint[]\u003e() { public int compare(int[] pair1, int[] pair2) { // 值是否相同? // 如果不同,则根据数值大小来判断权重 // 如果相同,则根据索引大小来判断权重 return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1]; } }); // 将数组 nums 的前 k 个元素放入优先队列中 for (int i = 0; i \u003c k; ++i) { queue.offer(new int[]{nums[i], i}); } // answer int[] ans = new int[n - k + 1]; // 第一个结果 -\u003e 数组 nums 的前 k 个元素中的最大值 -\u003e 大根堆的堆顶元素 ans[0] = queue.peek()[0]; // 循环处理剩下的元素 for (int i = k; i \u003c n; ++i) { // 把一个新的元素放入优先队列中 queue.offer(new int[]{nums[i], i}); // 去除出现在滑动窗口左边界的左侧的值,因为这个值永远不可能出现在滑动窗口中了 while (queue.peek()[1] \u003c= i - k) { queue.poll(); } // 向 answer 中写入当前滑动窗口内的最大值 ans[i - k + 1] = queue.peek()[0]; } return ans; } } ","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#code"},{"categories":[],"content":" 2.2、单调队列","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#22单调队列"},{"categories":[],"content":" 核心思路借助一个双端队列,存储单调递减的值,记作 deque。滑动窗口每向右移动一格,实时更新一次这个双端队列。如下图所示: 借助 deque 这个双端队列,我们可以将获取滑动窗口内最大值的时间复杂度从 O(k) 降低至 O(1),这也是本题的难点。 回忆 剑指Offer 30. 包含 min 函数的栈 ,其使用 单调栈 实现了随意入栈、出栈情况下的 O(1) 时间获取 “栈内最小值” 。本题同理,不同点在于 “出栈操作” 删除的是 “列表尾部元素” ,而 “窗口滑动” 删除的是 “列表首部元素” 。 ","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#核心思路-1"},{"categories":[],"content":" 复杂度分析时间复杂度:O(n),其中 n 为数组 nums 长度;线性遍历 nums 占用 O(n) ;每个元素最多仅入队和出队一次,因此单调队列 deque 占用 O(2n) 。 空间复杂度:O(k) ,双端队列 deque 中最多同时存储 k 个元素(即窗口大小)。 ","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#复杂度分析-1"},{"categories":[],"content":" Codeclass Solution { public int[] maxSlidingWindow(int[] nums, int k) { // 边界条件 / 特殊情况 if (nums.length == 0 || k == 0) return new int[0]; // 双端队列 仅包含窗口内的元素,且严格 Deque\u003cInteger\u003e deque = new LinkedList\u003c\u003e(); // 答案 int[] answer = new int[nums.length - k + 1]; // 未形成窗口 for (int i = 0; i \u003c k; i++) { // 删除 deque 中所有小于 nums[i] 的元素 while (!deque.isEmpty() \u0026\u0026 deque.peekLast() \u003c nums[i]) deque.removeLast(); // 向 deque 末尾添加新元素 nums[i] deque.addLast(nums[i]); } // deque 的第一个元素即为当前窗口的最大值 answer[0] = deque.peekFirst(); // 形成窗口后 for (int i = k; i \u003c nums.length; i++) { // i-k 是已经在区间外了, 如果首位等于 nums[i-k], 那么说明此时首位值已经不再区间内了,需要删除 if (deque.peekFirst() == nums[i - k]) deque.removeFirst(); // 删除 deque 中所有小于 nums[i] 的元素 while (!deque.isEmpty() \u0026\u0026 deque.peekLast() \u003c nums[i]) deque.removeLast(); // 向 deque 末尾添加新元素 nums[i] deque.addLast(nums[i]); // deque 的第一个元素即为当前窗口的最大值 answer[i - k + 1] = deque.peekFirst(); } return answer; } } ","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#code-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/solution/hua-dong-chuang-kou-de-zui-da-zhi-by-lee-ymyo/ https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/solution/mian-shi-ti-59-i-hua-dong-chuang-kou-de-zui-da-1-6/ ","date":"2021-09-11","objectID":"/posts/series/leetcode-jianzhioffer-java/14.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"14、剑指 Offer 59 - I. 滑动窗口的最大值","uri":"/posts/series/leetcode-jianzhioffer-java/14.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 25. 合并两个排序的链表 难度简单 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。 示例1: 输入:1-\u003e2-\u003e4, 1-\u003e3-\u003e4 输出:1-\u003e1-\u003e2-\u003e3-\u003e4-\u003e4 限制: 0 \u003c= 链表长度 \u003c= 1000 注意:本题与主站 21 题相同:https://leetcode-cn.com/problems/merge-two-sorted-lists/ ","date":"2021-09-12","objectID":"/posts/series/leetcode-jianzhioffer-java/15.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"15、剑指 Offer 25. 合并两个排序的链表","uri":"/posts/series/leetcode-jianzhioffer-java/15.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-12","objectID":"/posts/series/leetcode-jianzhioffer-java/15.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"15、剑指 Offer 25. 合并两个排序的链表","uri":"/posts/series/leetcode-jianzhioffer-java/15.html#二解法"},{"categories":[],"content":" 2.1、伪节点 + 迭代","date":"2021-09-12","objectID":"/posts/series/leetcode-jianzhioffer-java/15.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"15、剑指 Offer 25. 合并两个排序的链表","uri":"/posts/series/leetcode-jianzhioffer-java/15.html#21伪节点--迭代"},{"categories":[],"content":" 核心思路根据题目描述, 链表 L1 和 L2 是递增的,因此容易想到使用双指针遍历两链表,根据值的大小关系确定节点添加顺序,两节点指针交替前进,直至遍历完毕。 引入伪头节点: 由于初始状态合并链表中无节点,因此循环第一轮时无法将节点添加到合并链表中。解决方案:初始化一个辅助节点 dumdum 作为合并链表的伪头节点,将各节点添加至 dumdum 之后。 ","date":"2021-09-12","objectID":"/posts/series/leetcode-jianzhioffer-java/15.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"15、剑指 Offer 25. 合并两个排序的链表","uri":"/posts/series/leetcode-jianzhioffer-java/15.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(M+N) :M,N 分别为链表 L1, L2 的长度,合并操作需遍历两链表。 空间复杂度:O(1):节点引用 result, cursor 使用常数大小的额外空间。 ","date":"2021-09-12","objectID":"/posts/series/leetcode-jianzhioffer-java/15.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"15、剑指 Offer 25. 合并两个排序的链表","uri":"/posts/series/leetcode-jianzhioffer-java/15.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { // 初始化 result,生成一个伪节点,让其充当链表头 ListNode result = new ListNode(0); // 游标 ListNode cursor = result; // 当 l1 与 l2 都不为 null 时 while (l1 != null \u0026\u0026 l2 != null) { if (l1.val \u003c l2.val) { cursor.next = l1; l1 = l1.next; } else { cursor.next = l2; l2 = l2.next; } cursor = cursor.next; } // 如果其中一个为 null, 则将另外一个链表的所有元素都拼接到 result 后面 cursor.next = l1 != null ? l1 : l2; // 返回伪节点的后续所有结点 return result.next; } } ","date":"2021-09-12","objectID":"/posts/series/leetcode-jianzhioffer-java/15.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"15、剑指 Offer 25. 合并两个排序的链表","uri":"/posts/series/leetcode-jianzhioffer-java/15.html#code"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/solution/mian-shi-ti-25-he-bing-liang-ge-pai-xu-de-lian-b-2/ ","date":"2021-09-12","objectID":"/posts/series/leetcode-jianzhioffer-java/15.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"15、剑指 Offer 25. 合并两个排序的链表","uri":"/posts/series/leetcode-jianzhioffer-java/15.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 40. 最小的k个数 难度简单 输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。 示例 1: 输入:arr = [3,2,1], k = 2 输出:[1,2] 或者 [2,1] 示例 2: 输入:arr = [0,1,2,1], k = 1 输出:[0] 限制: 0 \u003c= k \u003c= arr.length \u003c= 10000 0 \u003c= arr[i] \u003c= 10000 ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#二解法"},{"categories":[],"content":" 2.1、排序(面试时也许会被直接 PASS ,不推荐使用)","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#21排序面试时也许会被直接-pass-不推荐使用"},{"categories":[],"content":" 核心思路对原数组从小到大排序后取出前 k 个数即可。 ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(n log n),其中 n 是数组 arr 的长度,算法的时间复杂度即排序的时间复杂度。 空间复杂度:O(log n),排序所需额外的空间复杂度为 O(log n)。 ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public int[] getLeastNumbers(int[] arr, int k) { int[] result = new int[k]; Arrays.sort(arr); for (int i = 0; i \u003c k; ++i) { result[i] = arr[i]; } return result; } } ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#code"},{"categories":[],"content":" 2.2、快速排序思想","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#22快速排序思想"},{"categories":[],"content":" 核心思路题目只要求返回最小的 k 个数,对这 k 个数的顺序并没有要求。因此,只需要将数组划分为 最小的 k 个数 和 其他数字 两部分即可,而快速排序的哨兵划分可完成此目标。 根据快速排序原理,如果某次哨兵划分后 基准数正好是第 k+1 小的数字 ,那么此时基准数左边的所有数字便是题目所求的 最小的 k 个数 。 根据此思路,考虑在每次哨兵划分后,判断基准数在数组中的索引是否等于 k ,若 true 则直接返回此时数组的前 k 个数字即可。 ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#核心思路-1"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N), 因为我们是要找下标为 k 的元素,第一次切分的时候需要遍历整个数组 (0 ~ n) 找到了下标是 j 的元素,假如 k 比 j 小的话,那么我们下次切分只要遍历数组 (0~k-1)的元素,反之如果 k 比 j 大的话,那下次切分只要遍历数组 (k+1~n) 的元素,总之可以看作每次调用 partition 遍历的元素数目都是上一次遍历的 1/2,因此时间复杂度是 N + N/2 + N/4 + … + N/N = 2N, 因此时间复杂度是 O(N)。 空间复杂度:O(log N),划分函数的平均递归深度为 O(log N)。 ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#复杂度分析-1"},{"categories":[],"content":" Codeclass Solution { // 入口 public int[] getLeastNumbers(int[] arr, int k) { // 边界条件 if (k \u003e= arr.length) return arr; // 递归调用 return quickSort(arr, k, 0, arr.length - 1); } // quick_sort() 的功能不是排序整个数组,而是搜索并返回最小的 k 个数. private int[] quickSort(int[] arr, int k, int left, int right) { int i = left, j = right; while (i \u003c j) { while (i \u003c j \u0026\u0026 arr[j] \u003e= arr[left]) j--; while (i \u003c j \u0026\u0026 arr[i] \u003c= arr[left]) i++; swap(arr, i, j); } swap(arr, i, left); if (i \u003e k) return quickSort(arr, k, left, i - 1); if (i \u003c k) return quickSort(arr, k, i + 1, right); return Arrays.copyOf(arr, k); } private void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#code-1"},{"categories":[],"content":" 2.3、堆","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:3:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#23堆"},{"categories":[],"content":" 核心思路我们用一个大根堆实时维护数组的前 k 小值。首先将前 k 个数插入大根堆中,随后从第 k+1 个数开始遍历,如果当前遍历到的数比大根堆的堆顶的数要小,就把堆顶的数弹出,再插入当前遍历到的数。最后将大根堆里的数存入数组返回即可。 ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:3:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#核心思路-2"},{"categories":[],"content":" 复杂度分析时间复杂度:时间复杂度:O(n log k),其中 n 是数组 arr 的长度。由于大根堆实时维护前 k 小值,所以插入删除都是 O(log k) 的时间复杂度,最坏情况下数组里 n 个数都会插入,所以一共需要 O(n log k) 的时间复杂度。 空间复杂度:O(k),因为大根堆里最多 k 个数。 ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:3:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#复杂度分析-2"},{"categories":[],"content":" Codeclass Solution { public int[] getLeastNumbers(int[] arr, int k) { // 边界情况 if (k == 0) { return new int[]{}; } // 结果 int[] result = new int[k]; // 优先级队列 PriorityQueue\u003cInteger\u003e queue = new PriorityQueue\u003c\u003e(new Comparator\u003cInteger\u003e() { // 自定义比较器, 使之成为大顶堆. 默认为大顶堆 public int compare(Integer num1, Integer num2) { return num2 - num1; } }); // 向优先队列中插入 arr 的前 k 个元素 for (int i = 0; i \u003c k; ++i) { queue.offer(arr[i]); } // 循环处理剩下的的元素 for (int i = k; i \u003c arr.length; ++i) { // 如果队首元素(也就是队列中权值最大的那个元素)大于当前的值 if (queue.peek() \u003e arr[i]) { // 删除队首元素 queue.poll(); // 向优先队列中插入当前元素 queue.offer(arr[i]); } } // 依次取出优先级队列的元素,构造数据 for (int i = 0; i \u003c k; ++i) { result[i] = queue.poll(); } return result; } } ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:3:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#code-2"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/zui-xiao-de-kge-shu-by-leetcode-solution/ https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/3chong-jie-fa-miao-sha-topkkuai-pai-dui-er-cha-sou/ https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/jian-zhi-offer-40-zui-xiao-de-k-ge-shu-j-9yze/ ","date":"2021-09-14","objectID":"/posts/series/leetcode-jianzhioffer-java/16.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"16、剑指 Offer 40. 最小的k个数","uri":"/posts/series/leetcode-jianzhioffer-java/16.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 62. 圆圈中最后剩下的数字 难度简单 0,1,···,n-1 这 n 个数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 m 个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。 例如,0、1、2、3、4 这 5 个数字组成一个圆圈,从数字 0 开始每次删除第 3 个数字,则删除的前 4 个数字依次是 2、0、4、1,因此最后剩下的数字是 3。 示例 1: 输入: n = 5, m = 3 输出: 3 示例 2: 输入: n = 10, m = 17 输出: 2 限制: 1 \u003c= n \u003c= 10^5 1 \u003c= m \u003c= 10^6 ","date":"2021-09-15","objectID":"/posts/series/leetcode-jianzhioffer-java/17.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["约瑟夫环","数学推导"],"title":"17、剑指 Offer 62. 圆圈中最后剩下的数字","uri":"/posts/series/leetcode-jianzhioffer-java/17.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-15","objectID":"/posts/series/leetcode-jianzhioffer-java/17.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["约瑟夫环","数学推导"],"title":"17、剑指 Offer 62. 圆圈中最后剩下的数字","uri":"/posts/series/leetcode-jianzhioffer-java/17.html#二解法"},{"categories":[],"content":" 2.1、动态规划","date":"2021-09-15","objectID":"/posts/series/leetcode-jianzhioffer-java/17.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["约瑟夫环","数学推导"],"title":"17、剑指 Offer 62. 圆圈中最后剩下的数字","uri":"/posts/series/leetcode-jianzhioffer-java/17.html#21动态规划"},{"categories":[],"content":" 核心思路设 dp[n,m] 表示在 n 个数字序列(0~n-1)中不断删除第 m 个数字后,最后剩下那一个数。 假设 n=5,m=3,则 dp[5,3] 的初始化数组是: 0 1 2 3 4 若 n=4,m=3,则 dp[5-1,3] 也就是 dp[4,3] 的初始化数组是: 0 1 2 3 从初始化数组可以看出,dp[5,3] 和 dp[4,3] 最终的结果是不一样的。而 dp[5,3] 删掉一个数字后的数组应该是: 3 4 0 1 这个数组咱们给他取名为 dp’[4,3],它是不是跟 dp[4,3] 有一些联系呢?咱们把他俩的初始数组放在一起比较: dp[4,3]:0 1 2 3 dp’[4,3]:3 4 0 1 从上面可能还是看不出具体区别,因此我们来看 dp[n-1,m] 和 dp’[n-1,m]的初始数组区别: dp[n-1,m] 0 1 … n-1-m n-m … n-3 n-2 dp’[n-1,m] m m+1 … n-1 0 … m-3 m-2 观察上面的表格,我们发现 dp[n-1,m] 和 dp’[n-1,m] 的初始数组有如下规律: dp'[n-1,m] = ( dp[n-1,m] + m ) % n 因为 dp[n,m] = dp’[n-1,m],所以咱们就找出了 dp[n,m] 到 dp[n-1,m] 之间的映射关系,也就是: dp[n,m] = ( dp[n−1,m] + m ) % n 考虑边界条件,n 肯定是要大于1的;此外当 n=1 时,就只剩下一个数了,那就是 0。因此得出状态转移方程为: dp[n,m]=0, n=1 dp[n,m] = ( dp[n−1,m] + m ) % n, 1\u003cn 观察状态转移方程式,发现 m 是不变的而且 dp[n,m] 只跟 dp[n-1,m] 有关,因此可以直接使用一个变量来代替 dp[n,m],然后从下往上递推实现。 另外,因为 dp[5,3] 的初始数组是 0 1 2 3 4,值与下标相同,即上述规律适用于数组下标。 ","date":"2021-09-15","objectID":"/posts/series/leetcode-jianzhioffer-java/17.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["约瑟夫环","数学推导"],"title":"17、剑指 Offer 62. 圆圈中最后剩下的数字","uri":"/posts/series/leetcode-jianzhioffer-java/17.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(n) 空间复杂度:O(1) ","date":"2021-09-15","objectID":"/posts/series/leetcode-jianzhioffer-java/17.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":["约瑟夫环","数学推导"],"title":"17、剑指 Offer 62. 圆圈中最后剩下的数字","uri":"/posts/series/leetcode-jianzhioffer-java/17.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public int lastRemaining(int n, int m) { int answer = 0; for (int i = 2; i \u003c= n; i++) { answer = (answer + m) % i; } return answer; } } ","date":"2021-09-15","objectID":"/posts/series/leetcode-jianzhioffer-java/17.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":["约瑟夫环","数学推导"],"title":"17、剑指 Offer 62. 圆圈中最后剩下的数字","uri":"/posts/series/leetcode-jianzhioffer-java/17.html#code"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/tai-jiao-bi-ye-ye-neng-dong-dong-tai-gui-zmwj/ ","date":"2021-09-15","objectID":"/posts/series/leetcode-jianzhioffer-java/17.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["约瑟夫环","数学推导"],"title":"17、剑指 Offer 62. 圆圈中最后剩下的数字","uri":"/posts/series/leetcode-jianzhioffer-java/17.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 48. 最长不含重复字符的子字符串 难度中等 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。 示例 1: 输入: \"abcabcbb\" 输出: 3 解释: 因为无重复字符的最长子串是 \"abc\",所以其长度为 3。 示例 2: 输入: \"bbbbb\" 输出: 1 解释: 因为无重复字符的最长子串是 \"b\",所以其长度为 1。 示例 3: 输入: \"pwwkew\" 输出: 3 解释: 因为无重复字符的最长子串是 \"wke\",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,\"pwke\" 是一个子序列,不是子串。 提示: s.length \u003c= 40000 注意:本题与主站 3 题相同:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/ ","date":"2021-09-17","objectID":"/posts/series/leetcode-jianzhioffer-java/18.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"18、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/18.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-17","objectID":"/posts/series/leetcode-jianzhioffer-java/18.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"18、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/18.html#二解法"},{"categories":[],"content":" 2.1、滑动窗口法","date":"2021-09-17","objectID":"/posts/series/leetcode-jianzhioffer-java/18.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"18、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/18.html#21滑动窗口法"},{"categories":[],"content":" 核心思路:记 left = 0 为滑动窗口的左边界下标,right = 0为右边界下标。 借助哈希表,在遍历字符串 s 时,使用哈希表 map 记录各字符最后一次出现的索引位置。 在遍历过程中,不断右移滑动窗口的右下标 right,借助 map 判断 s[right] 字符是否已经遍历过。 根据 s[right] 是否为第一次出现,有以下 2 种情况: 是:则 map 中不存在 key = s[right],此时无需更新左边界 left; 否:则 map 中存在 key = s[right],取 value ,得到 s[right] 末次出现的下标,记为 index 。更新左边界 left = Math.max ( index + 1, left ) ; 更新 s[right] 末次出现的下标,记录滑动窗口的长度最大值 right - left + 1 即可。 ","date":"2021-09-17","objectID":"/posts/series/leetcode-jianzhioffer-java/18.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"18、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/18.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N) 空间复杂度:O(1),字符的 ASCII 码范围为 00 ~ 127 ,哈希表 map 最多使用 O(128) = O(1) 大小的额外空间。 ","date":"2021-09-17","objectID":"/posts/series/leetcode-jianzhioffer-java/18.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"18、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/18.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public int lengthOfLongestSubstring(String s) { // 遍历字符串 s 时,使用哈希表 map 统计各字符最后一次出现的索引位置 HashMap\u003cCharacter, Integer\u003e map = new HashMap(); // left = 滑动窗口的左边界: result = 滑动窗口长度的最大值 int left = 0, result = 0; // 遍历字符串 s for (int right = 0; right \u003c s.length(); right++) { // 当前字符 char c = s.charAt(right); // 如果 map 中存在 key = 当前字符 if (map.containsKey(c)) { // 更新滑动窗口的左边界 // 左边界只允许向右移 left = Math.max(map.get(c) + 1, left); } // 更新当前字符末次出现的下标 map.put(s.charAt(right), right); // 记录滑动窗口的长度最大值 result = Math.max(result, right - left + 1); } return result; } } ","date":"2021-09-17","objectID":"/posts/series/leetcode-jianzhioffer-java/18.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"18、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/18.html#code"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/solution/mian-shi-ti-48-zui-chang-bu-han-zhong-fu-zi-fu-d-9/ ","date":"2021-09-17","objectID":"/posts/series/leetcode-jianzhioffer-java/18.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"18、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/18.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 46. 把数字翻译成字符串 难度中等 给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。 示例 1: 输入: 12258 输出: 5 解释: 12258有5种不同的翻译,分别是\"bccfi\", \"bwfi\", \"bczi\", \"mcfi\"和\"mzi\" 提示: 0 \u003c= num \u003c 2^31 ","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#二解法"},{"categories":[],"content":" 2.1、动态规划 + 字符串遍历","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#21动态规划--字符串遍历"},{"categories":[],"content":" 核心思路动态规划推导过程: 根据题意,$ num = x_{1} x_{2} \\ldots x_{i-2} x_{i-1} x_{i} \\ldots x_{n-1} x_{n} $ 记数字 num 第 i 位数字为 $ x_{i} $ 例如 $ 12258 = x_{1} x_{2} x_{3} x_{4} x_{5} $ 当整体翻译 $ x_{1} x_{2} $ 时,$ x_{1} x_{2} x_{3} x_{4} x_{5} $ 的方案数为 $ f(i-2) $ 当单独翻译 $ x_{1} $ 时,$ x_{1} x_{2} x_{3} x_{4} x_{5} $ 的方案数为 $ f(i-1) $ 方案的递推关系: $ f(i)=\\left\\{\\begin{array}{c}f(i-2)+f(i-1), \\text { 若数字 } x_{i-1} x_{i} \\text { 可被翻译 } \\\\ f(i-1), \\text { 若数字 } x_{i-1} x_{i} \\text { 不可被翻译 }\\end{array}\\right. $ 根据题意,可被翻译的两位数区间:$ x_{i-1}=0 $ 时,组成的两位数是无法被翻译的(例如 00, 01, 02,⋯ ),因此区间为 [10, 25] 。 因此,状态转移方程: $ dp[i]= \\begin{cases}d p[i-1]+d p[i-2] \u0026 , 10 x_{i-1}+x_{i} \\in[10,25] \\ d p[i-1] \u0026 , 10 x_{i-1}+x_{i} \\in[0,10) \\cup(25,99]\\end{cases} $ 初始状态:dp[0] = dp[1] = 1 ,即 “无数字” 和 “第 1 位数字” 的翻译方法数量均为 1 当 num 第 1,2 位的组成的数字 ∈[10,25] 时,显然应有 2 种翻译方法,即 dp[2] = dp[1] + dp[0] = 2 ,而显然 dp[1] = 1 ,因此推出 dp[0] = 1 。 字符串遍历法: 为获取数字的各位 $ x_{i} $ ,考虑先将数字 $ num $ 转化为字符串 s ,通过遍历 s 实现动态规划。 空间使用优化: 由于 dp[i] 只与 dp[i - 1] 有关,因此可使用两个变量 a, b 分别记录 dp[i], dp[i - 1] ,两变量交替前进即可。此方法可省去 dp 列表使用的 O(N) 的额外空间。 ","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N),N 为字符串 s 的长度(即数字 num 的位数 log(num) ),其决定了循环次数。 空间复杂度:O(N),字符串 s 使用 O(N) 大小的额外空间。 ","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public int translateNum(int num) { // int 转为字符串 String s = String.valueOf(num); // dp[1]=dp[0]=1, a b 作为缓存变量, 替代 dp 数组, 节省空间 int a = 1, b = 1; // 从 dp[2] 开始算 for (int i = 2; i \u003c= s.length(); i++) { // 向前取两位字符 String tmp = s.substring(i - 2, i); // c=dp[i], 判断 tmp 是否在区间 [10,25] 中 // * 若在, 令 dp[i]=dp[i-1]+dp[i-2], 即 c=a+b // * 若不在, 令 dp[i]=dp[i-1] int c = tmp.compareTo(\"10\") \u003e= 0 \u0026\u0026 tmp.compareTo(\"25\") \u003c= 0 ? a + b : a; // 更新 dp[i] 前, 将 dp[i-1] 的值存入 dp[i-2], 即 b=a b = a; // 更新 dp[i] a = c; } return a; } } ","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#code"},{"categories":[],"content":" 2.2、动态规划 + 数字求余","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#22动态规划--数字求余"},{"categories":[],"content":" 核心思路此题的动态规划计算是 对称 的 ,即 从左向右 遍历(从第 dp[2] 计算至 dp[n] )和 从右向左 遍历(从第 dp[n - 2] 计算至 dp[0] )所得方案数一致。 上述方法虽然已经节省了 dp 列表的空间占用,但字符串 s 仍使用了 O(N) 大小的额外空间。 利用求余运算 num % 10 和求整运算 num // 10 ,可获取数字 num 的各位数字(获取顺序为个位、十位、百位…)。 自此,字符串 s 的空间占用也被省去,空间复杂度从 O(N) 降至 O(1) 。 ","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#核心思路-1"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N) ,N 为字符串 s 的长度(即数字 num 的位数 log(num) ),其决定了循环次数。 空间复杂度:O(1) ,几个变量使用常数大小的额外空间。 ","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#复杂度分析-1"},{"categories":[],"content":" Codeclass Solution { public int translateNum(int num) { int a = 1, b = 1, x, y = num % 10; while(num != 0) { num /= 10; x = num % 10; int tmp = 10 * x + y; int c = (tmp \u003e= 10 \u0026\u0026 tmp \u003c= 25) ? a + b : a; b = a; a = c; y = x; } return a; } } ","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#code-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/solution/mian-shi-ti-46-ba-shu-zi-fan-yi-cheng-zi-fu-chua-6/ ","date":"2021-09-19","objectID":"/posts/series/leetcode-jianzhioffer-java/19.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["动态规划"],"title":"19、剑指 Offer 46. 把数字翻译成字符串","uri":"/posts/series/leetcode-jianzhioffer-java/19.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 10- I. 斐波那契数列 难度简单 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下: F(0) = 0, F(1) = 1 F(N) = F(N - 1) + F(N - 2), 其中 N \u003e 1. 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 示例 1: 输入:n = 2 输出:1 示例 2: 输入:n = 5 输出:5 提示: 0 \u003c= n \u003c= 100 ","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#二解法"},{"categories":[],"content":" 2.1、动态规划","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#21动态规划"},{"categories":[],"content":" 核心思路:斐波那契数的边界条件是 F(0)=0 和 F(1)=1。当 n\u003e1 时,每一项的和都等于前两项的和,因此有如下递推关系: F(n)=F(n-1)+F(n-2) 由于斐波那契数存在递推关系,因此可以使用动态规划求解。动态规划的状态转移方程即为上述递推关系,边界条件为 F(0) 和 F(1)。 计算过程中,答案需要取模 1e9+7。 ","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#核心思路"},{"categories":[],"content":" 复杂度分析:时间复杂度 O(N):计算 f(n) 需循环 n 次,每轮循环内计算操作使用 O(1)。 空间复杂度 O(1):几个标志变量使用常数大小的额外空间。 ","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#复杂度分析"},{"categories":[],"content":" Code:class Solution { public int fib(int n) { final int MOD = 1000000007; if (n \u003c 2) { return n; } int p = 0, q = 0, r = 1; for (int i = 2; i \u003c= n; ++i) { p = q; q = r; r = (p + q) % MOD; } return r; } } ","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#code"},{"categories":[],"content":" 2.2、矩阵快速幂(待续)动态规划的时间复杂度是 O(n)。使用矩阵快速幂的方法可以降低时间复杂度。 ","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#22矩阵快速幂待续"},{"categories":[],"content":" 核心思路:","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#核心思路-1"},{"categories":[],"content":" 复杂度分析:时间复杂度:O(log n)。 空间复杂度:O(1)。 ","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#复杂度分析-1"},{"categories":[],"content":" Code:","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#code-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/solution/fei-bo-na-qi-shu-lie-by-leetcode-solutio-hbss/ ","date":"2021-09-21","objectID":"/posts/series/leetcode-jianzhioffer-java/20.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"20、剑指 Offer 10- I. 斐波那契数列","uri":"/posts/series/leetcode-jianzhioffer-java/20.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 10- II. 青蛙跳台阶问题 难度简单 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 示例 1: 输入:n = 2 输出:2 示例 2: 输入:n = 7 输出:21 示例 3: 输入:n = 0 输出:1 提示: 0 \u003c= n \u003c= 100 注意:本题与主站 70 题相同:https://leetcode-cn.com/problems/climbing-stairs/ ","date":"2021-09-22","objectID":"/posts/series/leetcode-jianzhioffer-java/21.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"21、剑指 Offer 10- II. 青蛙跳台阶问题","uri":"/posts/series/leetcode-jianzhioffer-java/21.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-22","objectID":"/posts/series/leetcode-jianzhioffer-java/21.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"21、剑指 Offer 10- II. 青蛙跳台阶问题","uri":"/posts/series/leetcode-jianzhioffer-java/21.html#二解法"},{"categories":[],"content":" 2.1、动态规划","date":"2021-09-22","objectID":"/posts/series/leetcode-jianzhioffer-java/21.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"21、剑指 Offer 10- II. 青蛙跳台阶问题","uri":"/posts/series/leetcode-jianzhioffer-java/21.html#21动态规划"},{"categories":[],"content":" 核心思路此类求 多少种可能性 的题目一般都有 递推性质 ,即 f(n) 和 f(n-1) ... f(1) 之间是有联系的。 设跳上 n 级台阶有 f(n) 种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 1 级或 2 级台阶。 当为 1 级台阶: 剩 n-1 个台阶,此情况共有 f(n-1) 种跳法; 当为 2 级台阶: 剩 n-2 个台阶,此情况共有 f(n-2) 种跳法。 f(n) 为以上两种情况之和,即 f(n)=f(n-1)+f(n-2) ,以上递推性质为斐波那契数列。本题可转化为 求斐波那契数列第 n 项的值。与 面试题10- I. 斐波那契数列 等价,唯一的不同在于起始数字不同。 青蛙跳台阶问题: f(0)=1 , f(1)=1 , f(2)=2 ; 斐波那契数列问题: f(0)=0 , f(1)=1 , f(2)=1 。 本题如果使用递归实现,时间会超限,因此使用循环。 ","date":"2021-09-22","objectID":"/posts/series/leetcode-jianzhioffer-java/21.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"21、剑指 Offer 10- II. 青蛙跳台阶问题","uri":"/posts/series/leetcode-jianzhioffer-java/21.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N) , 计算 f(n) 需循环 n 次,每轮循环内计算操作使用 O(1) 。 空间复杂度:O(1) ,几个标志变量使用常数大小的额外空间。 ","date":"2021-09-22","objectID":"/posts/series/leetcode-jianzhioffer-java/21.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"21、剑指 Offer 10- II. 青蛙跳台阶问题","uri":"/posts/series/leetcode-jianzhioffer-java/21.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public int numWays(int n) { int a = 1, b = 1, c; for (int i = 0; i \u003c n; i++) { c = (a + b) % 1000000007; a = b; b = c; } return a; } } ","date":"2021-09-22","objectID":"/posts/series/leetcode-jianzhioffer-java/21.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"21、剑指 Offer 10- II. 青蛙跳台阶问题","uri":"/posts/series/leetcode-jianzhioffer-java/21.html#code"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/solution/mian-shi-ti-10-ii-qing-wa-tiao-tai-jie-wen-ti-dong/ ","date":"2021-09-22","objectID":"/posts/series/leetcode-jianzhioffer-java/21.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"21、剑指 Offer 10- II. 青蛙跳台阶问题","uri":"/posts/series/leetcode-jianzhioffer-java/21.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 37. 序列化二叉树 难度困难236收藏分享切换为英文接收动态反馈 请实现两个函数,分别用来序列化和反序列化二叉树。 你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。 **提示:**输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。 示例: 输入:root = [1,2,3,null,null,4,5] 输出:[1,2,3,null,null,4,5] 注意:本题与主站 297 题相同:https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/ ","date":"2021-09-24","objectID":"/posts/series/leetcode-jianzhioffer-java/22.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"22、剑指 Offer 37. 序列化二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/22.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-24","objectID":"/posts/series/leetcode-jianzhioffer-java/22.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"22、剑指 Offer 37. 序列化二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/22.html#二解法"},{"categories":[],"content":" 2.1、BFS / 层序遍历","date":"2021-09-24","objectID":"/posts/series/leetcode-jianzhioffer-java/22.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"22、剑指 Offer 37. 序列化二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/22.html#21bfs--层序遍历"},{"categories":[],"content":" 核心思路通常使用的前序、中序、后序、层序遍历记录的二叉树的信息不完整,即唯一的输出序列可能对应着多种二叉树可能性。题目要求的 序列化 和 反序列化 是 可逆操作 。因此,序列化的字符串应携带 完整的二叉树信息 。 观察题目示例,序列化的字符串实际上是二叉树的 “层序遍历”(BFS)结果,本文也采用层序遍历。 为完整表示二叉树,将叶节点下的 null 也记录。在此基础上,对于列表中任意某节点 node ,其左子节点 node.left 和右子节点 node.right 在序列中的位置都是 唯一确定的。 因此,序列化使用层序遍历实现,借助队列,对二叉树做层序遍历,并将越过叶节点的 null 也打印出来。 按照层序遍历的规则,也可实现反序列化。利用队列按层构建二叉树,借助一个指针 i 指向节点 node 的左、右子节点,每构建一个 node 的左、右子节点,指针 i 就向右移动 1 位。 ","date":"2021-09-24","objectID":"/posts/series/leetcode-jianzhioffer-java/22.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"22、剑指 Offer 37. 序列化二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/22.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(n),在序列化和反序列化函数中,我们只访问每个节点一次,因此时间复杂度为 O(n),其中 n 是节点数,即树的大小。 空间复杂度:最差情况下,队列 queue 同时存储 (N+1)/2 个节点,因此使用 O(N) 额外空间。 ","date":"2021-09-24","objectID":"/posts/series/leetcode-jianzhioffer-java/22.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"22、剑指 Offer 37. 序列化二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/22.html#复杂度分析"},{"categories":[],"content":" Codeclass Codec { // 序列化 public String serialize(TreeNode root) { // 特例处理 if (root == null) return \"[]\"; // 结果 StringBuilder result = new StringBuilder(\"[\"); // 队列 包含根节点 root Queue\u003cTreeNode\u003e queue = new LinkedList\u003cTreeNode\u003e() {{ add(root); }}; // 当队列不为空时, 循环处理 while (!queue.isEmpty()) { // 节点出队, 记为 node TreeNode node = queue.poll(); // 若 node 不为 null if (node != null) { // 将 node.val 拼接到结果中 result.append(node.val + \",\"); // 将 node 的左, 右子节点加入队列, 等待下一轮处理 queue.add(node.left); queue.add(node.right); } else { // 将 null 拼接到结果中 result.append(\"null,\"); } } // 删除末尾的 , 号 result.deleteCharAt(result.length() - 1); // 拼接 ] 号 result.append(\"]\"); // 将 StringBuilder 转为 String return result.toString(); } // 反序列化 public TreeNode deserialize(String data) { // 特例处理 if (data.equals(\"[]\")) return null; // 序列化列表 - 先去掉首尾中括号,再用逗号分割为字符串数组 String[] vals = data.substring(1, data.length() - 1).split(\",\"); // 根节点 root - 值为 vals[0] TreeNode root = new TreeNode(Integer.parseInt(vals[0])); // 队列 - 包含根节点 root Queue\u003cTreeNode\u003e queue = new LinkedList\u003cTreeNode\u003e() {{ add(root); }}; // 按层构建, 指针 i int i = 1; // 当队列不为空时, 按层循环构建整棵树 while (!queue.isEmpty()) { // 节点出队, 记为 node TreeNode node = queue.poll(); // 若 vals[i] 不为 null if (!vals[i].equals(\"null\")) { // 构建 node 的左子节点:node.left 的值为 vals[i] node.left = new TreeNode(Integer.parseInt(vals[i])); // 将 node.left 入队 queue.add(node.left); } // 下标后移一位 i++; // 若 vals[i] 不为 null if (!vals[i].equals(\"null\")) { // 构建 node 的右子节点:node.left 的值为 vals[i] node.right = new TreeNode(Integer.parseInt(vals[i])); // 将 node.left 入队 queue.add(node.right); } // 下标后移一位 i++; } return root; } } ","date":"2021-09-24","objectID":"/posts/series/leetcode-jianzhioffer-java/22.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"22、剑指 Offer 37. 序列化二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/22.html#code"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/solution/mian-shi-ti-37-xu-lie-hua-er-cha-shu-ceng-xu-bian-/ ","date":"2021-09-24","objectID":"/posts/series/leetcode-jianzhioffer-java/22.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"22、剑指 Offer 37. 序列化二叉树","uri":"/posts/series/leetcode-jianzhioffer-java/22.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 26. 树的子结构 难度中等 输入两棵二叉树 A 和 B,判断 B 是不是 A 的子结构。(约定空树不是任意一个树的子结构) B 是 A 的子结构, 即 A 中有出现和 B 相同的结构和节点值。 例如: 给定的树 A: 3 / \\ 4 5 / \\ 1 2 给定的树 B: 4 / 1 返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。 示例 1: 输入:A = [1,2,3], B = [3,1] 输出:false 示例 2: 输入:A = [3,4,5,1,2], B = [4,1] 输出:true 限制: 0 \u003c= 节点个数 \u003c= 10000 ","date":"2021-09-26","objectID":"/posts/series/leetcode-jianzhioffer-java/23.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"23、剑指 Offer 26. 树的子结构","uri":"/posts/series/leetcode-jianzhioffer-java/23.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-26","objectID":"/posts/series/leetcode-jianzhioffer-java/23.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"23、剑指 Offer 26. 树的子结构","uri":"/posts/series/leetcode-jianzhioffer-java/23.html#二解法"},{"categories":[],"content":" 2.1、递归法","date":"2021-09-26","objectID":"/posts/series/leetcode-jianzhioffer-java/23.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"23、剑指 Offer 26. 树的子结构","uri":"/posts/series/leetcode-jianzhioffer-java/23.html#21递归法"},{"categories":[],"content":" 核心思路若树 B 是树 A 的子结构,则子结构的根节点可能为树 A 的任意一个节点。因此,判断树 B 是否是树 A 的子结构,需完成以下两步工作: 先序遍历树 A 中的每个节点 Na;(对应函数 isSubStructure(A, B)) 判断树 A 中以 Na 为根节点的子树是否包含树 B。(对应函数 recur(A, B)) ","date":"2021-09-26","objectID":"/posts/series/leetcode-jianzhioffer-java/23.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"23、剑指 Offer 26. 树的子结构","uri":"/posts/series/leetcode-jianzhioffer-java/23.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(MN) ,其中 M,N 分别为树 A 和 树 B 的节点数量;先序遍历树 A 占用 O(M) ,每次调用 recur(A, B) 判断占用 O(N)。 空间复杂度:O(M) ,当树 A 和树 B 都退化为链表时,递归调用深度最大。当 M≤N 时,遍历树 A 与递归判断的总递归深度为 M ;当 M\u003eN 时,最差情况为遍历至树 A 叶子节点,此时总递归深度为 M。 ","date":"2021-09-26","objectID":"/posts/series/leetcode-jianzhioffer-java/23.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"23、剑指 Offer 26. 树的子结构","uri":"/posts/series/leetcode-jianzhioffer-java/23.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public boolean isSubStructure(TreeNode A, TreeNode B) { // 如果 A 或 B 为空 if (A == null || B == null) { return false; } // B、A 相同 if (recur(A, B)) { return true; } // B 在 A 的左子树中 if (isSubStructure(A.left, B)) { return true; } // B 在 A 的右子树中 if (isSubStructure(A.right, B)) { return true; } return false; } // 递归判断树 A 是否包含 树 B boolean recur(TreeNode A, TreeNode B) { if (B == null) { return true; } if (A == null || A.val != B.val) { return false; } return recur(A.left, B.left) \u0026\u0026 recur(A.right, B.right); } } ","date":"2021-09-26","objectID":"/posts/series/leetcode-jianzhioffer-java/23.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"23、剑指 Offer 26. 树的子结构","uri":"/posts/series/leetcode-jianzhioffer-java/23.html#code"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/solution/mian-shi-ti-26-shu-de-zi-jie-gou-xian-xu-bian-li-p/ ","date":"2021-09-26","objectID":"/posts/series/leetcode-jianzhioffer-java/23.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"23、剑指 Offer 26. 树的子结构","uri":"/posts/series/leetcode-jianzhioffer-java/23.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 57. 和为s的两个数字 难度简单 输入一个递增排序的数组和一个数字 s ,在数组中查找两个数,使得它们的和正好是 s 。如果有多对数字的和等于 s ,则输出任意一对即可。 示例 1: 输入:nums = [2,7,11,15], target = 9 输出:[2,7] 或者 [7,2] 示例 2: 输入:nums = [10,26,30,31,47,60], target = 40 输出:[10,30] 或者 [30,10] 限制: $ 1 \u003c= nums.length \u003c= 10^5 $ $ 1 \u003c= nums[i] \u003c= 10^6 $ ","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#二解法"},{"categories":[],"content":" 2.1、对撞双指针法","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#21对撞双指针法"},{"categories":[],"content":" 核心思路利用 HashMap 可以通过遍历数组找到数字组合,时间和空间复杂度均为 O(N) 。但本题的 $nums$ 是 排序数组 ,因此可使用 双指针法 将空间复杂度降低至 O(1) 。 算法流程: 初始化: 双指针 $i , j$ 分别指向数组 $nums $的左右两端 (俗称对撞双指针)。 循环搜索: 当双指针相遇时跳出; 计算和 $s = nums[i] + nums[j]$ ; 若 $s \u003e target$ ,则指针 $j$ 向左移动,即执行 $j = j - 1$ ; 若 $s \u003c target$ ,则指针 $i$ 向右移动,即执行 $i = i + 1$ ; 若 $s = target$ ,立即返回数组 $[ nums[i], nums[j] ]$ ; 返回空数组,代表无和为 $target$ 的数字组合。 正确性证明: 记每个状态为 $S(i, j)$ ,即 $S(i, j) = nums[i] + nums[j]$ 。假设 $S(i, j) \u003c target$ ,则执行 $i = i + 1$ ,即状态切换至 $S(i + 1, j)$ 。 状态 $S(i, j)$ 切换至 $S(i + 1, j)$ ,则会消去一行元素,相当于 消去了状态集合 ${S(i, i + 1), S(i, i + 2), …, S(i, j - 2), S(i, j - 1), S(i, j) }$ 。(由于双指针都是向中间收缩,因此这些状态之后不可能再遇到)。 由于 $nums$ 是排序数组,因此这些 消去的状态 都一定满足 $S(i, j) \u003c target$ ,即这些状态都 不是解 。 结论: 以上分析已证明 “每次指针 $i$ 的移动操作,都不会导致解的丢失” ,即指针 $i$ 的移动操作是 安全的 ;同理,对于指针 $j$ 可得出同样推论;因此,此双指针法是正确的。 ","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#核心思路"},{"categories":[],"content":" 核心思路利用 HashMap 可以通过遍历数组找到数字组合,时间和空间复杂度均为 O(N) 。但本题的 $nums$ 是 排序数组 ,因此可使用 双指针法 将空间复杂度降低至 O(1) 。 算法流程: 初始化: 双指针 $i , j$ 分别指向数组 $nums $的左右两端 (俗称对撞双指针)。 循环搜索: 当双指针相遇时跳出; 计算和 $s = nums[i] + nums[j]$ ; 若 $s \u003e target$ ,则指针 $j$ 向左移动,即执行 $j = j - 1$ ; 若 $s \u003c target$ ,则指针 $i$ 向右移动,即执行 $i = i + 1$ ; 若 $s = target$ ,立即返回数组 $[ nums[i], nums[j] ]$ ; 返回空数组,代表无和为 $target$ 的数字组合。 正确性证明: 记每个状态为 $S(i, j)$ ,即 $S(i, j) = nums[i] + nums[j]$ 。假设 $S(i, j) \u003c target$ ,则执行 $i = i + 1$ ,即状态切换至 $S(i + 1, j)$ 。 状态 $S(i, j)$ 切换至 $S(i + 1, j)$ ,则会消去一行元素,相当于 消去了状态集合 ${S(i, i + 1), S(i, i + 2), …, S(i, j - 2), S(i, j - 1), S(i, j) }$ 。(由于双指针都是向中间收缩,因此这些状态之后不可能再遇到)。 由于 $nums$ 是排序数组,因此这些 消去的状态 都一定满足 $S(i, j) \u003c target$ ,即这些状态都 不是解 。 结论: 以上分析已证明 “每次指针 $i$ 的移动操作,都不会导致解的丢失” ,即指针 $i$ 的移动操作是 安全的 ;同理,对于指针 $j$ 可得出同样推论;因此,此双指针法是正确的。 ","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#算法流程"},{"categories":[],"content":" 核心思路利用 HashMap 可以通过遍历数组找到数字组合,时间和空间复杂度均为 O(N) 。但本题的 $nums$ 是 排序数组 ,因此可使用 双指针法 将空间复杂度降低至 O(1) 。 算法流程: 初始化: 双指针 $i , j$ 分别指向数组 $nums $的左右两端 (俗称对撞双指针)。 循环搜索: 当双指针相遇时跳出; 计算和 $s = nums[i] + nums[j]$ ; 若 $s \u003e target$ ,则指针 $j$ 向左移动,即执行 $j = j - 1$ ; 若 $s \u003c target$ ,则指针 $i$ 向右移动,即执行 $i = i + 1$ ; 若 $s = target$ ,立即返回数组 $[ nums[i], nums[j] ]$ ; 返回空数组,代表无和为 $target$ 的数字组合。 正确性证明: 记每个状态为 $S(i, j)$ ,即 $S(i, j) = nums[i] + nums[j]$ 。假设 $S(i, j) \u003c target$ ,则执行 $i = i + 1$ ,即状态切换至 $S(i + 1, j)$ 。 状态 $S(i, j)$ 切换至 $S(i + 1, j)$ ,则会消去一行元素,相当于 消去了状态集合 ${S(i, i + 1), S(i, i + 2), …, S(i, j - 2), S(i, j - 1), S(i, j) }$ 。(由于双指针都是向中间收缩,因此这些状态之后不可能再遇到)。 由于 $nums$ 是排序数组,因此这些 消去的状态 都一定满足 $S(i, j) \u003c target$ ,即这些状态都 不是解 。 结论: 以上分析已证明 “每次指针 $i$ 的移动操作,都不会导致解的丢失” ,即指针 $i$ 的移动操作是 安全的 ;同理,对于指针 $j$ 可得出同样推论;因此,此双指针法是正确的。 ","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#正确性证明"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N),N 为数组 $nums $ 的长度;双指针共同线性遍历整个数组。 空间复杂度:O(1),变量 $i, j$ 使用常数大小的额外空间。 ","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#复杂度分析"},{"categories":[],"content":" Code因题目限定 $ 1 \u003c= nums[i] \u003c= 10^6 $ ,所以判断条件用相加后的结果不会溢出。如果两个操作数都是正整数,则使用$target - nums[i]$ 跟 $nums[j]$ 比较,这样保证不会溢出。因为 $nums[i]$ 可能是负数, $target - nums[i]$ 也可能越界,因此用 long 型去比可能是最安全的。 同样的例子还有二分查找,$(left + right) / 2$ 可以用 $left + ((rigth - left) » 1))$ 代替。 class Solution { public int[] twoSum(int[] nums, int target) { int i = 0, j = nums.length - 1; while(i \u003c j) { int s = nums[i] + nums[j]; if(s \u003c target) { i++; } else if(s \u003e target) { j--; } else { return new int[] { nums[i], nums[j] }; } } return new int[0]; } } ","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#code"},{"categories":[],"content":" 2.2、哈希表","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#22哈希表"},{"categories":[],"content":" 核心思路使用暴力枚举的时间复杂度较高的原因是寻找 $target - nums[i]$ 的时间复杂度过高。因此,使用哈希表可以将寻找 $target - nums[i]$ 的时间复杂度降低到从 O(N) 降低到 O(1)。 因本题的 $nums$ 是 排序数组 ,实际上使用 双指针法 可以将空间复杂度也降低至 O(1) ,但如果 $nums$ 是无序的,可以使用哈希表解法。 ","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#核心思路-1"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N),其中 N 是数组中的元素数量。对于每一个 $nums[i]$ ,我们可以 O(1) 地寻找 $target - nums[i]$ 。 空间复杂度:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。 ","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#复杂度分析-1"},{"categories":[],"content":" Codeclass Solution { public int[] twoSum(int[] nums, int target) { // 初始化哈希表 Map\u003cInteger, Integer\u003e hashtable = new HashMap\u003c\u003e(nums.length); for (int i = 0; i \u003c nums.length; ++i) { // 查询哈希表中是否存在 target - nums[i] if (hashtable.containsKey(target - nums[i])) { // 如果存在, 返回 [ target - nums[i], nums[i] ] return new int[]{hashtable.get(target - nums[i]), nums[i]}; } // 将 nums[i] 插入到哈希表中,保证不会让 nums[i] 和自己匹配 hashtable.put(nums[i], nums[i]); } // 没有找到符合条件的解, 返回 [ 0, 0 ] return new int[0]; } } ","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#code-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof/solution/mian-shi-ti-57-he-wei-s-de-liang-ge-shu-zi-shuang-/ https://leetcode-cn.com/problems/two-sum/solution/liang-shu-zhi-he-by-leetcode-solution/ ","date":"2021-09-28","objectID":"/posts/series/leetcode-jianzhioffer-java/24.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["数学推导","双指针","HashMap"],"title":"24、剑指 Offer 57. 和为s的两个数字","uri":"/posts/series/leetcode-jianzhioffer-java/24.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 45. 把数组排成最小的数 难度中等 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 示例 1: 输入: [10,2] 输出: \"102\" 示例 2: 输入: [3,30,34,5,9] 输出: \"3033459\" 提示: 0 \u003c nums.length \u003c= 100 说明: 输出结果可能非常大,所以你需要返回一个字符串而不是整数 拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0 ","date":"2021-09-29","objectID":"/posts/series/leetcode-jianzhioffer-java/25.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"25、剑指 Offer 45. 把数组排成最小的数","uri":"/posts/series/leetcode-jianzhioffer-java/25.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-29","objectID":"/posts/series/leetcode-jianzhioffer-java/25.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"25、剑指 Offer 45. 把数组排成最小的数","uri":"/posts/series/leetcode-jianzhioffer-java/25.html#二解法"},{"categories":[],"content":" 2.1、自定义规则排序","date":"2021-09-29","objectID":"/posts/series/leetcode-jianzhioffer-java/25.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"25、剑指 Offer 45. 把数组排成最小的数","uri":"/posts/series/leetcode-jianzhioffer-java/25.html#21自定义规则排序"},{"categories":[],"content":" 核心思路此题求拼接起来的最小数字,本质上是一个排序问题。设数组 nums 中任意两数字的字符串为 x 和 y ,则规定 排序判断规则 为: 若拼接字符串 x + y \u003e y + x,则 x 的权重比 y 大; 反之,若 x + y \u003c y + x,则 y 的权重比 x 大; 排序完成后,权重大的数排前面,权重小的数排后面。根据以上规则,套用任何排序方法对 nums 执行排序即可。 ","date":"2021-09-29","objectID":"/posts/series/leetcode-jianzhioffer-java/25.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"25、剑指 Offer 45. 把数组排成最小的数","uri":"/posts/series/leetcode-jianzhioffer-java/25.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N logN) ,N 为最终返回值的字符数量( strs 列表的长度 ≤ N );使用快排或内置函数的平均时间复杂度为 O(NlogN) ,最差为 O(N^2)。 空间复杂度:O(N) ,字符串列表 strs 占用线性大小的额外空间。 ","date":"2021-09-29","objectID":"/posts/series/leetcode-jianzhioffer-java/25.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"25、剑指 Offer 45. 把数组排成最小的数","uri":"/posts/series/leetcode-jianzhioffer-java/25.html#复杂度分析"},{"categories":[],"content":" Code内置排序 class Solution { public String minNumber(int[] nums) { // nums[] -\u003e string[] String[] strs = new String[nums.length]; for (int i = 0; i \u003c nums.length; i++) strs[i] = String.valueOf(nums[i]); // 自定义规则排序 // Arrays.sort(strs, (x, y) -\u003e (x + y).compareTo(y + x)); Arrays.sort(strs, new Comparator\u003cString\u003e() { @Override public int compare(String s1, String s2) { return (s1 + s2).compareTo((s2 + s1)); } }); // 拼接排序后的结果 StringBuilder res = new StringBuilder(); for (String s : strs) res.append(s); return res.toString(); } } ","date":"2021-09-29","objectID":"/posts/series/leetcode-jianzhioffer-java/25.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"25、剑指 Offer 45. 把数组排成最小的数","uri":"/posts/series/leetcode-jianzhioffer-java/25.html#code"},{"categories":[],"content":" Code快速排序 class Solution { public String minNumber(int[] nums) { String[] strs = new String[nums.length]; for(int i = 0; i \u003c nums.length; i++) strs[i] = String.valueOf(nums[i]); quickSort(strs, 0, strs.length - 1); StringBuilder res = new StringBuilder(); for(String s : strs) res.append(s); return res.toString(); } void quickSort(String[] strs, int l, int r) { if(l \u003e= r) return; int i = l, j = r; String tmp = strs[i]; while(i \u003c j) { while((strs[j] + strs[l]).compareTo(strs[l] + strs[j]) \u003e= 0 \u0026\u0026 i \u003c j) j--; while((strs[i] + strs[l]).compareTo(strs[l] + strs[i]) \u003c= 0 \u0026\u0026 i \u003c j) i++; tmp = strs[i]; strs[i] = strs[j]; strs[j] = tmp; } strs[i] = strs[l]; strs[l] = tmp; quickSort(strs, l, i - 1); quickSort(strs, i + 1, r); } } ","date":"2021-09-29","objectID":"/posts/series/leetcode-jianzhioffer-java/25.html:1:4","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"25、剑指 Offer 45. 把数组排成最小的数","uri":"/posts/series/leetcode-jianzhioffer-java/25.html#code-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/solution/mian-shi-ti-45-ba-shu-zu-pai-cheng-zui-xiao-de-s-4/ ","date":"2021-09-29","objectID":"/posts/series/leetcode-jianzhioffer-java/25.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"25、剑指 Offer 45. 把数组排成最小的数","uri":"/posts/series/leetcode-jianzhioffer-java/25.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 05. 替换空格 难度简单 请实现一个函数,把字符串 s 中的每个空格替换成\"%20\"。 示例 1: 输入:s = \"We are happy.\" 输出:\"We%20are%20happy.\" 限制: 0 \u003c= s 的长度 \u003c= 10000 ","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#二解法"},{"categories":[],"content":" 2.1、遍历添加","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#21遍历添加"},{"categories":[],"content":" 核心思路在 Python 和 Java 等语言中,字符串都被设计成「不可变」的类型,即无法直接修改字符串的某一位字符,需要新建一个字符串实现。 ","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N)。遍历使用 O(N),每轮添加(修改)字符操作使用 O(1); 空间复杂度:O(N)。新建的 StringBuilder 使用了线性大小的额外空间。 ","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public String replaceSpace(String s) { StringBuilder result = new StringBuilder(); for (Character c : s.toCharArray()) { if (c == ' ') { result.append(\"%20\"); } else { result.append(c); } } return result.toString(); } } ","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#code"},{"categories":[],"content":" 2.2、String 内置 replace 函数","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#22string-内置-replace-函数"},{"categories":[],"content":" 核心思路不说了,懂得都懂。 ","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#核心思路-1"},{"categories":[],"content":" 复杂度分析时间复杂度: 空间复杂度: ","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#复杂度分析-1"},{"categories":[],"content":" Codeclass Solution { public String replaceSpace(String s) { return s.replace(\" \", \"%20\"); } } ","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#code-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/solution/mian-shi-ti-05-ti-huan-kong-ge-ji-jian-qing-xi-tu-/ ","date":"2021-09-30","objectID":"/posts/series/leetcode-jianzhioffer-java/26.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"26、剑指 Offer 05. 替换空格","uri":"/posts/series/leetcode-jianzhioffer-java/26.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 17. 打印从1到最大的n位数 难度简单 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。 示例 1: 输入: n = 1 输出: [1,2,3,4,5,6,7,8,9] 说明: 用返回一个整数列表来代替打印 n 为正整数 ","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#一题目"},{"categories":[],"content":" 二、解法这道题在面试环节时,基本都要考察大数情况的。 ","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#二解法"},{"categories":[],"content":" 2.1、迭代法","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#21迭代法"},{"categories":[],"content":" 核心思路由于本题要求返回 int 类型数组,相当于默认所有数字都在 int32 整型取值范围内,因此不考虑大数越界问题。 只需定义区间 $[1, 10^n - 1]$ 和步长 1 ,通过 for 循环生成结果列表并返回即可。 ","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:$O(10^n)$,生成长度为 $10^n$ 的列表需使用 $O(10^n)$ 时间。 空间复杂度: $O(1)$,建立列表需使用 $O(1)$ 大小的额外空间( 列表作为返回结果,不计入额外空间 )。 ","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { public int[] printNumbers(int n) { int end = (int)Math.pow(10, n) - 1; int[] answer = new int[end]; for(int i = 0; i \u003c end; i++) { answer[i] = i + 1; } return answer; } } ","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#code"},{"categories":[],"content":" 2.2、大数打印解法","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#22大数打印解法"},{"categories":[],"content":" 核心思路实际上,本题的主要考点是大数越界情况下的打印。需要解决以下三个问题: 表示大数的变量类型 无论是 short / int / long … 任意变量类型,数字的取值范围都是有限的。因此,大数的表示应用字符串 String 类型。 生成数字的字符串集 使用 int 类型时,每轮可通过 +1 生成下个数字,而此方法无法应用至 String 类型。并且, String 类型的数字的进位操作效率较低,例如 “9999” 至 “10000” 需要从个位到千位循环判断,进位 4 次。 观察可知,生成的列表实际上是 n 位 0 - 9 的 全排列 ,因此可避开进位操作,通过递归生成数字的 String 列表。 递归生成全排列 基于分治算法的思想,先固定高位,向低位递归,当个位已被固定时,添加数字的字符串。例如当 n = 2 时(数字范围 1 - 99 ),固定十位为 0 - 9 ,按顺序依次开启递归,固定个位 0 - 9 ,终止递归并添加数字字符串。 根据以上方法,可初步编写全排列代码: class Solution { StringBuilder answer; // 解 int n; // 题目给定的 n char[] num, charset = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; // num=缓冲区, charset=字符集 public String printNumbers(int n) { this.n = n; answer = new StringBuilder(); // 数字字符串集 num = new char[n]; // 定义长度为 n 的字符列表 dfs(0); // 开启全排列递归 answer.deleteCharAt(answer.length() - 1); // 删除最后多余的逗号 return answer.toString(); // 转化为字符串并返回 } void dfs(int x) { if (x == n) { // 终止条件:已固定完所有位 answer.append(String.valueOf(num) + \",\"); // 拼接 num 并添加至 res 尾部,使用逗号隔开 return; } for (char i : charset) { // 遍历 ‘0‘ - ’9‘ num[x] = i; // 固定第 x 位为 i dfs(x + 1); // 开启固定第 x + 1 位 } } } 在此方法下,各数字字符串被逗号隔开,共同组成长字符串。返回的数字集字符串如下所示: 输入:n = 1 输出:\"0,1,2,3,4,5,6,7,8,9\" 输入:n = 2 输出:\"00,01,02,...,10,11,12,...,97,98,99\" 输入:n = 3 输出:\"000,001,002,...,100,101,102,...,997,998,999\" 观察可知,当前的生成方法仍有以下问题: 诸如 00, 01, 02, ⋯ 应显示为 0, 1, 2, ⋯ ,即应 删除高位多余的 0 ; 此方法从 0 开始生成,而题目要求 列表从 1 开始 ; 以上两个问题的解决方法如下: 改造上述实现的 dfs(x) 函数 令其成为“dfs(x,n) = 生成长度为 n 的数字字符串,当前正在确定下标为 x 的那一位”,这样可以将不同位长的数值全排列拆分出来。 原先的 dfs(3) 要将 1~3 位的数值全排列全部计算出来,改造后将会拆分为 dfs(0,1)+dfs(0,2)+dfs(0,3)。 在字符集上做文章 通过判断 dfs(x,n) 中 x 是否为 0 ,若是,说明当前还在固定下标为 0 的那一位, 即第一位数字(数值的最高位),此时只需遍历字符集的 ‘1’~‘9’, 反之则遍历 ‘0’~‘9’ 通过上述两个步骤,即可解决上述的两个问题。 ","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#核心思路-1"},{"categories":[],"content":" 复杂度分析时间复杂度:$O(10^n)$,生成长度为 $10^n$ 的列表需使用 $O(10^n)$ 时间。 空间复杂度: $O(10^n)$,结果列表的长度为 $10^n-1$ ,各数字字符串的长度区间为 $1, 2, …, n$ ,因此占用 $O(10^n)$ 大小的额外空间。 ","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#复杂度分析-1"},{"categories":[],"content":" Code为 正确表示大数 ,以下代码的返回值为数字字符串集拼接而成的长字符串。 class Solution { // 解 StringBuilder answer; // 字符集 char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; // 缓冲区 char[] num; public String printNumbers(int n) { // 结果 answer = new StringBuilder(); // 开启全排列递归 for (int i = 1; i \u003c= n; i++) { // 定义长度为 n 的字符数组缓冲区 num = new char[i]; // 生成长度为 n 的数字字符串,正在确定下标为 0 的那一位 dfs(0, i); } // 删除最后多余的逗号 answer.deleteCharAt(answer.length() - 1); // 转化为字符串并返回 return answer.toString(); } // 生成长度为 n 的数字字符串,当前正在确定下标为 x 的那一位(缓冲区 num 的数组下标) void dfs(int x, int n) { // 终止条件:已固定完所有位 if (x == n) { // 拼接 num 并添加至 res 尾部,使用逗号隔开 answer.append(String.valueOf(num) + \",\"); return; } // 去除前导0:若当前还在固定下标为 0 的那一位, 则说明是第一位数字(即数值最高位), 只遍历字符集的 '1'~'9', 反之则遍历 '0'~'9' int start = (x == 0) ? 1 : 0; for (int index = start; index \u003c chars.length; index++) { // 固定第 x 位为 i, 对应到缓冲区下标为 x-1 num[x] = chars[index]; // 开启固定第 x + 1 位 dfs(x + 1, n); } } } 本题要求输出 int 类型数组。为 运行通过 ,可在添加数字字符串 s 前,将其转化为 int 类型。代码如下所示: class Solution { // 当前已求解数字个数, 作为游标使用 int count = 0; // 解 int[] answer; // 字符集 char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; // 缓冲区 char[] num; public int[] printNumbers(int n) { // 根据解的已知个数初始化空间 answer = new int[(int) Math.pow(10, n) - 1]; // 开启全排列递归 for (int i = 1; i \u003c= n; i++) { // 定义长度为 n 的字符数组缓冲区 num = new char[i]; // 生成长度为 n 的数字字符串,正在确定下标为 0 的那一位 dfs(0, i); } // 转化为字符串并返回 return answer; } // 生成长度为 n 的数字字符串,当前正在确定下标为 x 的那一位(缓冲区 num 的数组下标) void dfs(int x, int n) { // 终止条件:已固定完所有位 if (x == n) { // 将缓冲区的字符串转换为 int , 加入解 answer[count++] = Integer.parseInt(String.valueOf(num)); return; } // 去除前导0:若当前还在固定下标为 0 的那一位, 则说明是第一位数字(即数值最高位), 只遍历字符集的 '1'~'9', 反之则遍历 '0'~'9' int start = (x == 0) ? 1 : 0; for (int index = start; index \u003c chars.length; index++) { // 固定第 x 位为 i, 对应到缓冲区下标为 x-1 num[x] = chars[index]; // 开启固定第 x + 1 位 dfs(x + 1, n); } } } ","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#code-1"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/solution/mian-shi-ti-17-da-yin-cong-1-dao-zui-da-de-n-wei-2/ ","date":"2021-10-01","objectID":"/posts/series/leetcode-jianzhioffer-java/27.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["大数","全排列"],"title":"27、剑指 Offer 17. 打印从1到最大的n位数","uri":"/posts/series/leetcode-jianzhioffer-java/27.html#ref"},{"categories":[],"content":" 一、题目剑指 Offer 36. 二叉搜索树与双向链表 难度中等 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。 为了让您更好地理解问题,以下面的二叉搜索树为例: 我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。 下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。 特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。 注意:本题与主站 426 题相同:https://leetcode-cn.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/ 注意:此题对比原题有改动。 ","date":"2021-10-02","objectID":"/posts/series/leetcode-jianzhioffer-java/28.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["二叉搜索树","双向链表","中序遍历"],"title":"28、剑指 Offer 36. 二叉搜索树与双向链表","uri":"/posts/series/leetcode-jianzhioffer-java/28.html#一题目"},{"categories":[],"content":" 二、解法","date":"2021-10-02","objectID":"/posts/series/leetcode-jianzhioffer-java/28.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["二叉搜索树","双向链表","中序遍历"],"title":"28、剑指 Offer 36. 二叉搜索树与双向链表","uri":"/posts/series/leetcode-jianzhioffer-java/28.html#二解法"},{"categories":[],"content":" 2.1、中序遍历","date":"2021-10-02","objectID":"/posts/series/leetcode-jianzhioffer-java/28.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["二叉搜索树","双向链表","中序遍历"],"title":"28、剑指 Offer 36. 二叉搜索树与双向链表","uri":"/posts/series/leetcode-jianzhioffer-java/28.html#21中序遍历"},{"categories":[],"content":" 核心思路本文解法基于性质:二叉搜索树的中序遍历为 递增序列 。 将 二叉搜索树 转换成一个 “排序的循环双向链表” ,其中包含三个要素: 排序链表: 节点应从小到大排序,因此应使用 中序遍历 “从小到大”访问树的节点。 双向链表: 在构建相邻节点的引用关系时,设前驱节点 pre 和当前节点 cur ,不仅应构建 pre.right = cur ,也应构建 cur.left = pre 。 循环链表: 设链表头节点 head 和尾节点 tail ,则应构建 head.left = tail 和 tail.right = head 。 中序遍历 为对二叉树作 “左、根、右” 顺序遍历,递归实现如下: // 打印中序遍历 void dfs(Node root) { if(root == null) return; dfs(root.left); // 左 System.out.println(root.val); // 根 dfs(root.right); // 右 } 根据以上分析,考虑使用中序遍历访问树的各节点 cur ;并在访问每个节点时构建 cur 和前驱节点 pre 的引用指向;中序遍历完成后,最后构建头节点和尾节点的引用指向即可。 算法流程dfs(cur): 递归法中序遍历: 终止条件: 当节点 cur 为空,代表越过叶节点,直接返回; 递归左子树,即 dfs(cur.left) ; 构建链表: 当 pre 为空时: 代表正在访问链表头节点,记为 head ; 当 pre 不为空时: 修改双向节点引用,即 pre.right = cur , cur.left = pre ; 保存 cur : 更新 pre = cur ,即节点 cur 是后继节点的 pre ; 递归右子树,即 dfs(cur.right) ; treeToDoublyList(root): 特例处理: 若节点 root 为空,则直接返回; 初始化: 空节点 pre ; 转化为双向链表: 调用 dfs(root) ; 构建循环链表: 中序遍历完成后,head 指向头节点, pre 指向尾节点,因此修改 head 和 pre 的双向节点引用即可; 返回值: 返回链表的头节点 head 即可; ","date":"2021-10-02","objectID":"/posts/series/leetcode-jianzhioffer-java/28.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["二叉搜索树","双向链表","中序遍历"],"title":"28、剑指 Offer 36. 二叉搜索树与双向链表","uri":"/posts/series/leetcode-jianzhioffer-java/28.html#核心思路"},{"categories":[],"content":" 核心思路本文解法基于性质:二叉搜索树的中序遍历为 递增序列 。 将 二叉搜索树 转换成一个 “排序的循环双向链表” ,其中包含三个要素: 排序链表: 节点应从小到大排序,因此应使用 中序遍历 “从小到大”访问树的节点。 双向链表: 在构建相邻节点的引用关系时,设前驱节点 pre 和当前节点 cur ,不仅应构建 pre.right = cur ,也应构建 cur.left = pre 。 循环链表: 设链表头节点 head 和尾节点 tail ,则应构建 head.left = tail 和 tail.right = head 。 中序遍历 为对二叉树作 “左、根、右” 顺序遍历,递归实现如下: // 打印中序遍历 void dfs(Node root) { if(root == null) return; dfs(root.left); // 左 System.out.println(root.val); // 根 dfs(root.right); // 右 } 根据以上分析,考虑使用中序遍历访问树的各节点 cur ;并在访问每个节点时构建 cur 和前驱节点 pre 的引用指向;中序遍历完成后,最后构建头节点和尾节点的引用指向即可。 算法流程dfs(cur): 递归法中序遍历: 终止条件: 当节点 cur 为空,代表越过叶节点,直接返回; 递归左子树,即 dfs(cur.left) ; 构建链表: 当 pre 为空时: 代表正在访问链表头节点,记为 head ; 当 pre 不为空时: 修改双向节点引用,即 pre.right = cur , cur.left = pre ; 保存 cur : 更新 pre = cur ,即节点 cur 是后继节点的 pre ; 递归右子树,即 dfs(cur.right) ; treeToDoublyList(root): 特例处理: 若节点 root 为空,则直接返回; 初始化: 空节点 pre ; 转化为双向链表: 调用 dfs(root) ; 构建循环链表: 中序遍历完成后,head 指向头节点, pre 指向尾节点,因此修改 head 和 pre 的双向节点引用即可; 返回值: 返回链表的头节点 head 即可; ","date":"2021-10-02","objectID":"/posts/series/leetcode-jianzhioffer-java/28.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":["二叉搜索树","双向链表","中序遍历"],"title":"28、剑指 Offer 36. 二叉搜索树与双向链表","uri":"/posts/series/leetcode-jianzhioffer-java/28.html#算法流程"},{"categories":[],"content":" 复杂度分析时间复杂度:O(N),N 为二叉树的节点数,中序遍历需要访问所有节点。 空间复杂度:O(N),最差情况下,即树退化为链表时,递归深度达到 N,系统使用 O(N) 栈空间。 ","date":"2021-10-02","objectID":"/posts/series/leetcode-jianzhioffer-java/28.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":["二叉搜索树","双向链表","中序遍历"],"title":"28、剑指 Offer 36. 二叉搜索树与双向链表","uri":"/posts/series/leetcode-jianzhioffer-java/28.html#复杂度分析"},{"categories":[],"content":" Codeclass Solution { Node pre, head; public Node treeToDoublyList(Node root) { if (root == null) { return null; } // 中序遍历, 构建双向链表 dfs(root); // 此时中序遍历已经完成, head 指向头节点, pre 指向尾节点 // 下面进行头节点和尾节点的相互指向, 这两句的顺序也是可以颠倒的, // 将头结点的 left 指向尾结点 head.left = pre; // 将尾结点的 right 指向头结点 pre.right = head; return head; } void dfs(Node cur) { // 终止条件:当节点 cur 为空,代表越过叶节点 if (cur == null) { // 直接返回 return; } // 递归左子树 dfs(cur.left); // 构建链表 if (pre != null) { // 当 pre 不为空时:修改双向节点引用 pre.right = cur; } else { // 当 pre 为空时, 代表正在访问链表头节点 head = cur; } cur.left = pre; // 保存 cur : 更新 pre = cur ,即节点 cur 是后继节点的 pre pre = cur; // 递归右子树 dfs(cur.right); } } ","date":"2021-10-02","objectID":"/posts/series/leetcode-jianzhioffer-java/28.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":["二叉搜索树","双向链表","中序遍历"],"title":"28、剑指 Offer 36. 二叉搜索树与双向链表","uri":"/posts/series/leetcode-jianzhioffer-java/28.html#code"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/solution/mian-shi-ti-36-er-cha-sou-suo-shu-yu-shuang-xian-5/ ","date":"2021-10-02","objectID":"/posts/series/leetcode-jianzhioffer-java/28.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":["二叉搜索树","双向链表","中序遍历"],"title":"28、剑指 Offer 36. 二叉搜索树与双向链表","uri":"/posts/series/leetcode-jianzhioffer-java/28.html#ref"},{"categories":[],"content":" 一、题目 剑指 Offer 20. 表示数值的字符串 难度中等请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。 数值(按顺序)可以分成以下几个部分: 若干空格 一个 小数 或者 整数 (可选)一个 'e' 或 'E' ,后面跟着一个 整数 若干空格 小数(按顺序)可以分成以下几个部分: (可选)一个符号字符('+' 或 '-') 下述格式之一: 至少一位数字,后面跟着一个点 '.' 至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字 一个点 '.' ,后面跟着至少一位数字 整数(按顺序)可以分成以下几个部分: (可选)一个符号字符('+' 或 '-') 至少一位数字 部分数值列举如下: [\"+100\", \"5e2\", \"-123\", \"3.1416\", \"-1E-16\", \"0123\"] 部分非数值列举如下: [\"12e\", \"1a3.14\", \"1.2.3\", \"+-5\", \"12e+5.4\"] 示例 1: 输入:s = \"0\" 输出:true 示例 2: 输入:s = \"e\" 输出:false 示例 3: 输入:s = \".\" 输出:false 示例 4: 输入:s = \" .1 \" 输出:true 提示: 1 \u003c= s.length \u003c= 20 s 仅含英文字母(大写和小写),数字(0-9),加号 '+' ,减号 '-' ,空格 ' ' 或者点 '.' 。 ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#一题目"},{"categories":[],"content":" 一、题目 剑指 Offer 20. 表示数值的字符串 难度中等请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。 数值(按顺序)可以分成以下几个部分: 若干空格 一个 小数 或者 整数 (可选)一个 'e' 或 'E' ,后面跟着一个 整数 若干空格 小数(按顺序)可以分成以下几个部分: (可选)一个符号字符('+' 或 '-') 下述格式之一: 至少一位数字,后面跟着一个点 '.' 至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字 一个点 '.' ,后面跟着至少一位数字 整数(按顺序)可以分成以下几个部分: (可选)一个符号字符('+' 或 '-') 至少一位数字 部分数值列举如下: [\"+100\", \"5e2\", \"-123\", \"3.1416\", \"-1E-16\", \"0123\"] 部分非数值列举如下: [\"12e\", \"1a3.14\", \"1.2.3\", \"+-5\", \"12e+5.4\"] 示例 1: 输入:s = \"0\" 输出:true 示例 2: 输入:s = \"e\" 输出:false 示例 3: 输入:s = \".\" 输出:false 示例 4: 输入:s = \" .1 \" 输出:true 提示: 1 \u003c= s.length \u003c= 20 s 仅含英文字母(大写和小写),数字(0-9),加号 '+' ,减号 '-' ,空格 ' ' 或者点 '.' 。 ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#剑指-offer-20-表示数值的字符串httpsleetcode-cncomproblemsbiao-shi-shu-zhi-de-zi-fu-chuan-lcof-难度中等"},{"categories":[],"content":" 二、解法","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#二解法"},{"categories":[],"content":" 2.1、确定有限状态自动机(官方题解)","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:1:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#21确定有限状态自动机官方题解"},{"categories":[],"content":" 核心思路预备知识 确定有限状态自动机(以下简称「自动机」)是一类计算模型。它包含一系列状态,这些状态中: 有一个特殊的状态,被称作「初始状态」。 还有一系列状态被称为「接受状态」,它们组成了一个特殊的集合。其中,一个状态可能既是「初始状态」,也是「接受状态」。 起初,这个自动机处于「初始状态」。随后,它顺序地读取字符串中的每一个字符,并根据当前状态和读入的字符,按照某个事先约定好的「转移规则」,从当前状态转移到下一个状态;当状态转移完成后,它就读取下一个字符。当字符串全部读取完毕后,如果自动机处于某个「接受状态」,则判定该字符串「被接受」;否则,判定该字符串「被拒绝」。 注意:如果输入的过程中某一步转移失败了,即不存在对应的「转移规则」,此时计算将提前中止。在这种情况下我们也判定该字符串「被拒绝」。 一个自动机,总能够回答某种形式的「对于给定的输入字符串 S,判断其是否满足条件 P」的问题。在本题中,条件 P 即为「构成合法的表示数值的字符串」。 自动机驱动的编程,可以被看做一种暴力枚举方法的延伸:它穷尽了在任何一种情况下,对应任何的输入,需要做的事情。 自动机在计算机科学领域有着广泛的应用。在算法领域,它与大名鼎鼎的字符串查找算法「KMP」算法有着密切的关联;在工程领域,它是实现「正则表达式」的基础。 问题描述 在 C++ 文档 中,描述了一个合法的数值字符串应当具有的格式。具体而言,它包含以下部分: 符号位,即 ++、-− 两种符号 整数部分,即由若干字符 0-90−9 组成的字符串 小数点 小数部分,其构成与整数部分相同 指数部分,其中包含开头的字符 \\text{e}e(大写小写均可)、可选的符号位,和整数部分 相比于 C++ 文档而言,本题还有一点额外的不同,即允许字符串首末两端有一些额外的空格。 在上面描述的五个部分中,每个部分都不是必需的,但也受一些额外规则的制约,如: 如果符号位存在,其后面必须跟着数字或小数点。 小数点的前后两侧,至少有一侧是数字。 思路与算法 根据上面的描述,现在可以定义自动机的「状态集合」了。那么怎么挖掘出所有可能的状态呢?一个常用的技巧是,用「当前处理到字符串的哪个部分」当作状态的表述。根据这一技巧,不难挖掘出所有状态: 起始的空格 符号位 整数部分 左侧有整数的小数点 左侧无整数的小数点(根据前面的第二条额外规则,需要对左侧有无整数的两种小数点做区分) 小数部分 字符 e 指数部分的符号位 指数部分的整数部分 末尾的空格 下一步是找出「初始状态」和「接受状态」的集合。根据题意,「初始状态」应当为状态 1,而「接受状态」的集合则为状态 3、状态 4、状态 6、状态 9 以及状态 10。换言之,字符串的末尾要么是空格,要么是数字,要么是小数点,但前提是小数点的前面有数字。 最后,需要定义「转移规则」。结合数值字符串应当具备的格式,将自动机转移的过程以图解的方式表示出来: 比较上图与「预备知识」一节中对自动机的描述,可以看出有一点不同: 我们没有单独地考虑每种字符,而是划分为若干类。由于全部 10 个数字字符彼此之间都等价,因此只需定义一种统一的「数字」类型即可。对于正负号也是同理。 在实际代码中,我们需要处理转移失败的情况。例如当位于状态 1(起始空格)时,没有对应字符 e 的状态。为了处理这种情况,我们可以创建一个特殊的拒绝状态。如果当前状态下没有对应读入字符的「转移规则」,我们就转移到这个特殊的拒绝状态。一旦自动机转移到这个特殊状态,我们就可以立即判定该字符串不「被接受」。 ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:1:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#核心思路"},{"categories":[],"content":" 复杂度分析时间复杂度:O(n),其中 n 为字符串的长度。我们需要遍历字符串的每个字符,其中状态转移所需的时间复杂度为 O(1)。 空间复杂度:O(1),只需要创建固定大小的状态转移表。 ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:1:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#复杂度分析"},{"categories":[],"content":" Code:class Solution { // 有限状态集合 enum State { // 起始的空格 STATE_INITIAL, // 整数部分符号位 STATE_INT_SIGN, // 整数部分 STATE_INTEGER, // 左侧有整数的小数点 STATE_POINT, // 左侧无整数的小数点(根据前面的第二条额外规则,需要对左侧有无整数的两种小数点做区分) STATE_POINT_WITHOUT_INT, // 小数部分 STATE_FRACTION, // 字符 e STATE_EXP, // 指数部分的符号位 STATE_EXP_SIGN, // 指数部分的整数部分 STATE_EXP_NUMBER, // 末尾的空格 STATE_END } // 字符类型集合 enum CharType { // 数字 CHAR_NUMBER, // 字符 e CHAR_EXP, // 小数点 CHAR_POINT, // 正负号 CHAR_SIGN, // 空格 CHAR_SPACE, // 非法字符 CHAR_ILLEGAL } public boolean isNumber(String s) { // 状态机 Map\u003cState, Map\u003cCharType, State\u003e\u003e transfer = new HashMap\u003cState, Map\u003cCharType, State\u003e\u003e(); // 空格(初始状态) 的所有可转移状态 Map\u003cCharType, State\u003e initialMap = new HashMap\u003cCharType, State\u003e() {{ // 空格 put(CharType.CHAR_SPACE, State.STATE_INITIAL); // 整数 put(CharType.CHAR_NUMBER, State.STATE_INTEGER); // 小数点 put(CharType.CHAR_POINT, State.STATE_POINT_WITHOUT_INT); // 正负号 put(CharType.CHAR_SIGN, State.STATE_INT_SIGN); }}; transfer.put(State.STATE_INITIAL, initialMap); // 整数部分符号位 的所有可转移状态 Map\u003cCharType, State\u003e intSignMap = new HashMap\u003cCharType, State\u003e() {{ put(CharType.CHAR_NUMBER, State.STATE_INTEGER); put(CharType.CHAR_POINT, State.STATE_POINT_WITHOUT_INT); }}; transfer.put(State.STATE_INT_SIGN, intSignMap); // 整数部分 的所有可转移状态 Map\u003cCharType, State\u003e integerMap = new HashMap\u003cCharType, State\u003e() {{ put(CharType.CHAR_NUMBER, State.STATE_INTEGER); put(CharType.CHAR_EXP, State.STATE_EXP); put(CharType.CHAR_POINT, State.STATE_POINT); put(CharType.CHAR_SPACE, State.STATE_END); }}; transfer.put(State.STATE_INTEGER, integerMap); // 左侧有整数的小数点 的所有可转移状态 Map\u003cCharType, State\u003e pointMap = new HashMap\u003cCharType, State\u003e() {{ put(CharType.CHAR_NUMBER, State.STATE_FRACTION); put(CharType.CHAR_EXP, State.STATE_EXP); put(CharType.CHAR_SPACE, State.STATE_END); }}; transfer.put(State.STATE_POINT, pointMap); // 左侧无整数的小数点(根据前面的第二条额外规则,需要对左侧有无整数的两种小数点做区分) 的所有可转移状态 Map\u003cCharType, State\u003e pointWithoutIntMap = new HashMap\u003cCharType, State\u003e() {{ put(CharType.CHAR_NUMBER, State.STATE_FRACTION); }}; transfer.put(State.STATE_POINT_WITHOUT_INT, pointWithoutIntMap); // 小数部分 Map\u003cCharType, State\u003e fractionMap = new HashMap\u003cCharType, State\u003e() {{ put(CharType.CHAR_NUMBER, State.STATE_FRACTION); put(CharType.CHAR_EXP, State.STATE_EXP); put(CharType.CHAR_SPACE, State.STATE_END); }}; transfer.put(State.STATE_FRACTION, fractionMap); // 字符 e 的所有可转移状态 Map\u003cCharType, State\u003e expMap = new HashMap\u003cCharType, State\u003e() {{ put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER); put(CharType.CHAR_SIGN, State.STATE_EXP_SIGN); }}; transfer.put(State.STATE_EXP, expMap); // 指数部分的符号位 的所有可转移状态 Map\u003cCharType, State\u003e expSignMap = new HashMap\u003cCharType, State\u003e() {{ put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER); }}; transfer.put(State.STATE_EXP_SIGN, expSignMap); // 指数部分的整数部分 的所有可转移状态 Map\u003cCharType, State\u003e expNumberMap = new HashMap\u003cCharType, State\u003e() {{ put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER); put(CharType.CHAR_SPACE, State.STATE_END); }}; transfer.put(State.STATE_EXP_NUMBER, expNumberMap); // 末尾的空格 的所有可转移状态 Map\u003cCharType, State\u003e endMap = new HashMap\u003cCharType, State\u003e() {{ put(CharType.CHAR_SPACE, State.STATE_END); }}; transfer.put(State.STATE_END, endMap); int length = s.length(); // 初始状态 State state = State.STATE_INITIAL; // 循环处理每个字符, 模拟状态转移 for (int i = 0; i \u003c length; i++) { // 获取当前字符的类型 CharType type = toCharType(s.charAt(i)); if (!transfer.get(state).containsKey(type)) { return false; } else { state = transfer.get(state).get(type); } } return state == State.STATE_INTEGER || state == State.STATE_POINT || state == State.STATE_FRACTION || state == State.STATE_EXP_NUMBER || state == State.STATE_END; } // 获取字符的类型 public CharType toCharType(char ch) { if (ch \u003e= '0' \u0026\u0026 ch \u003c= '9') { return CharType.CHAR_NUMBER; } else if (ch == 'e' || ch == 'E') { return CharType.CHAR_EXP; } else if (ch == '.') { return CharType.CHAR_POINT; } else if (ch == '+' || ch == '-') { return CharType.CHAR_SIGN; } else if (ch == ' ') { return CharType.CHAR_SPACE; } else { return CharType.CHAR_ILLEGAL; } } } ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:1:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#code"},{"categories":[],"content":" 2.2、确定有限状态自动机(评论区)","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:2:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#22确定有限状态自动机评论区"},{"categories":[],"content":" 核心思路本题使用有限状态自动机。根据字符类型和合法数值的特点,先定义状态,再画出状态转移图,最后编写代码即可。 字符类型: 空格 「 」、数字「 0—9 」 、正负号 「 +− 」 、小数点 「 . 」 、幂符号 「 eE 」 。 状态定义: 按照字符串从左到右的顺序,定义以下 9 种状态。 开始的空格 幂符号前的正负号 小数点前的数字 小数点、小数点后的数字 当小数点前为空格时,小数点、小数点后的数字 幂符号 幂符号后的正负号 幂符号后的数字 结尾的空格 结束状态: 合法的结束状态有 2, 3, 7, 8 。 ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:2:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#核心思路-1"},{"categories":[],"content":" 复杂度分析时间复杂度:其中 N 为字符串 s 的长度,判断需遍历字符串,每轮状态转移的使用 O(1) 时间。 空间复杂度:states 和 p 使用常数大小的额外空间。 ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:2:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#复杂度分析-1"},{"categories":[],"content":" Code:class Solution { public boolean isNumber(String s) { HashMap[] states = { new HashMap() {{ put(' ', 0); put('s', 1); put('d', 2); put('.', 4); }}, // 0. start with 'blank' new HashMap() {{ put('d', 2); put('.', 4); }}, // 1. 'sign' before 'e' new HashMap() {{ put('d', 2); put('.', 3); put('e', 5); put(' ', 8); }}, // 2. 'digit' before 'dot' new HashMap() {{ put('d', 3); put('e', 5); put(' ', 8); }}, // 3. 'digit' after 'dot' new HashMap() {{ put('d', 3); }}, // 4. 'digit' after 'dot' (‘blank’ before 'dot') new HashMap() {{ put('s', 6); put('d', 7); }}, // 5. 'e' new HashMap() {{ put('d', 7); }}, // 6. 'sign' after 'e' new HashMap() {{ put('d', 7); put(' ', 8); }}, // 7. 'digit' after 'e' new HashMap() {{ put(' ', 8); }} // 8. end with 'blank' }; int p = 0; char t; for(char c : s.toCharArray()) { if(c \u003e= '0' \u0026\u0026 c \u003c= '9') t = 'd'; // digit else if(c == '+' || c == '-') t = 's'; // sign else if(c == 'e' || c == 'E') t = 'e'; // e or E else if(c == '.' || c == ' ') t = c; // dot, blank else t = '?'; // unknown if(!states[p].containsKey(t)) return false; p = (int)states[p].get(t); } return p == 2 || p == 3 || p == 7 || p == 8; } } ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:2:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#code-1"},{"categories":[],"content":" 2.3、常规解法","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:3:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#23常规解法"},{"categories":[],"content":" 核心思路 什么有限状态自动机的我不太懂,常规解法可以做就不想搞这种可能只有这道题会用到的解法。 我首先想到的是判断否 false 而不是判断是 true,毕竟有这么多条件满足才能判断 true,但是只要有一个条件不满足就可以判断 false,最后代码的效率也还可以,那么接下来进入正题吧: 首先定义了四个flag,对应四种字符 是否有数字:hasNum 是否有e:hasE 是否有正负符号:hasSign 是否有点:hasDot 其余还定义了字符串长度 n 以及字符串索引 index 先处理一下开头的空格,index 相应的后移 然后进入循环,遍历字符串 如果当前字符 c 是数字:将 hasNum 置为 true ,index 往后移动一直到非数字或遍历到末尾位置;如果已遍历到末尾 ( index == n ) ,结束循环 如果当前字符 c 是 ’e’ 或 ‘E’ :如果 e 已经出现或者当前 e 之前没有出现过数字,返回 fasle ;否则令 hasE = true ,并且将其他 3 个 flag 全部置为 false ,因为要开始遍历 e 后面的新数字了 如果当前字符 c 是 + 或-:如果已经出现过 + 或 - 或者已经出现过数字或者已经出现过 ‘.’ ,返回 flase ;否则令 hasSign = true 如果当前字符 c 是 ‘.’ :如果已经出现过 ‘.’ 或者已经出现过 ’e’ 或 ‘E’ ,返回 false ;否则令 hasDot = true 如果当前字符 c 是 ’ ’ :结束循环,因为可能是末尾的空格了,但也有可能是字符串中间的空格,在循环外继续处理 如果当前字符 c 是除了上面 5 种情况以外的其他字符,直接返回 false 处理空格,index 相应的后移 如果当前索引 index 与字符串长度相等,说明遍历到了末尾,但是还要满足 hasNum 为 true 才可以最终返回 true ,因为如果字符串里全是符号没有数字的话是不行的,而且 e 后面没有数字也是不行的,但是没有符号是可以的,所以 4 个 flag 里只要判断一下 hasNum 就行;所以最后返回的是 hasNum \u0026\u0026 index == n 如果字符串中间有空格,按以上思路是无法遍历到末尾的,index 不会与 n 相等,返回的就是 false ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:3:1","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#核心思路-2"},{"categories":[],"content":" 复杂度分析时间复杂度:O(n) 空间复杂度:O(1) ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:3:2","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#复杂度分析-2"},{"categories":[],"content":" Code:class Solution { public boolean isNumber(String s) { int n = s.length(); int index = 0; boolean hasNum = false, hasE = false; boolean hasSign = false, hasDot = false; while(index \u003c n \u0026\u0026 s.charAt(index) == ' ') index++; while(index \u003c n){ while(index \u003c n \u0026\u0026 s.charAt(index) \u003e= '0' \u0026\u0026 s.charAt(index) \u003c= '9'){ index++; hasNum = true; } if(index == n){ break; } char c = s.charAt(index); if(c == 'e' || c == 'E'){ if(hasE || !hasNum){ return false; } hasE = true; hasNum = false; hasSign = false; hasDot = false; }else if(c == '+' || c == '-'){ if(hasSign || hasNum || hasDot){ return false; } hasSign = true; }else if(c == '.'){ if(hasDot || hasE){ return false; } hasDot = true; }else if(c == ' '){ break; }else{ return false; } index++; } while(index \u003c n \u0026\u0026 s.charAt(index) == ' ') index++; return hasNum \u0026\u0026 index == n; } } ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:3:3","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#code-2"},{"categories":[],"content":" REFhttps://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/solution/biao-shi-shu-zhi-de-zi-fu-chuan-by-leetcode-soluti/ https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/solution/mian-shi-ti-20-biao-shi-shu-zhi-de-zi-fu-chuan-y-2/ https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/solution/jian-zhi-offer-20-biao-shi-shu-zhi-de-zi-060v/ ","date":"2021-10-03","objectID":"/posts/series/leetcode-jianzhioffer-java/29.html:0:0","series":["LeetCode-剑指offer-第二版-java"],"tags":[],"title":"29、剑指 Offer 20. 表示数值的字符串","uri":"/posts/series/leetcode-jianzhioffer-java/29.html#ref"},{"categories":["折腾工坊"],"content":"看了那么多短视频、知乎和 B 站的你,为什么啥都没学到? 欢迎来到 折腾工坊,我是無糖,今天我想跟你聊聊我是如何整理信息的。 大约在一年前,我开始学习某一领域的知识时遇到了一些问题: 学习路径如此复杂,我究竟学到哪儿了? 每天都在学,每天都在忘,细节完全想不起来,怎么办? 逛知乎 / B 站时遇到不错的图文和视频我看完也收藏了,为什么要用的时候想不起来了? ","date":"2022-06-18","objectID":"/posts/how-to-organize-information-effectively.html:0:0","series":[],"tags":["效率","笔记","语雀","思源笔记","Git"],"title":"如何有效的整理信息?","uri":"/posts/how-to-organize-information-effectively.html#"},{"categories":["折腾工坊"],"content":" 一、笔记和笔记软件的区别我认为上述几个问题的本质原因是由于吸收的知识过于碎片化,无法形成知识网络,既不清楚学到了哪里,也记不住繁多的细节。后来我就开始在网上寻找一些线上笔记应用来尝试解决我遇到的问题。 在协同办公的浪潮下,许多笔记软件也开始崭露头角,像 Notion、语雀 都是我非常喜欢的产品。在体验了一段时间之后,我觉得数字化笔记也许是一个不错的出路,所以就有了这篇文章。 如果你想了解一个行业或一个领域,系统化的学习是不可避免的。在这个过程中,如果没有笔记软件的帮助,我想大概率你会碰到文章开头我提到的三个问题。 为了让你对笔记软件有一个直观的感受,先给你看一下我是怎么做笔记的。下面是我在学习 Java 虚拟机过程中积累下来的笔记: JVM - 语雀 对于学生时代的笔记,我认为有几个缺点: 难以建立目录,无法快速查找目标知识点(这几乎是致命缺点) 信息不够丰富(不能放图) 笔记与笔记之间难以形成关联(在无法建立目录的前提下,笔记与笔记之间就像是孤岛) 难以修改 / 更正(你懂得) 写的急,字迹丑(这点是我的问题) 上述几点是纸质化笔记天生的劣势,如果你的字迹比较潦草,会进一步加重反复查阅的负担,久而久之记笔记就会成为一种机械性的行为,记完再也不看。 笔记是为了复习服务的,而学习是需要讲究学习成效的,付出和收获不成正比的奇怪现象源自于只注重了付出,却没有重视付出的努力是否有效。 所以我认为建立一个线上知识库是相当有必要的。你记录下来的笔记,经过反复查阅后自然会沉淀为你的知识。 ","date":"2022-06-18","objectID":"/posts/how-to-organize-information-effectively.html:0:0","series":[],"tags":["效率","笔记","语雀","思源笔记","Git"],"title":"如何有效的整理信息?","uri":"/posts/how-to-organize-information-effectively.html#一笔记和笔记软件的区别"},{"categories":["折腾工坊"],"content":" 二、我是如何使用笔记软件的?为了服务于反复查阅这个目标,我认为一个笔记软件至少要有以下几个功能 知识库目录(为知识分层,将不同方向的知识归类整合) 文章大纲(让你在10秒内明白这篇文章大概在讲什么?快速跳转到你需要的部分) 文章主体(正文) JVM - 语雀 这是我心目中最好的三段式布局,也是我如何使用笔记软件的关键。 有了这样一个线上知识库,你就可以把知乎 / B 站 / 短视频里学习到的内容转移到你的笔记中,在日积月累的反复查阅中,最后成为你真正掌握的的东西。 你要做的只是为知识分层,然后坚持下来。 演示软件叫语雀,是阿里系的一个笔记产品。 ","date":"2022-06-18","objectID":"/posts/how-to-organize-information-effectively.html:0:0","series":[],"tags":["效率","笔记","语雀","思源笔记","Git"],"title":"如何有效的整理信息?","uri":"/posts/how-to-organize-information-effectively.html#二我是如何使用笔记软件的"},{"categories":["折腾工坊"],"content":" 三、写在最后那么作为程序员,不整点活是不可能的。在使用了语雀一年后,我从逐渐担心语雀是否会停止运营,发展到对互联网产品的数据安全备份的不信任,所以我决定使用开源的笔记产品来代替语雀。 在千挑万选之后,我选择了思源笔记(https://github.com/siyuan-note/siyuan),数据同步我是用 Git 来完成的(我自己有 Git 私服,不需要借助 Github 和 Gitee 这样的在线托管平台)。把数据把握在自己手里,才有安全感。 我们没有必要追求极致的效率,只是不希望把大量的时间浪费在无谓的事情上,剩下来的时间用来好好生活不是更好吗。 每种工具和方法的探索都源自于需求,所以我们可能没办法套用别人的经验。我们需要自己做出适当的调整,而如何调整需要我们对自己有更清楚的认知,对工具和方法有深入的了解,很多问题只能在尝试过程中才能理解,在讨论中才能辨明。 我们常常在一款工具用得不顺手的时候,才能更真切得体会到自己的发力习惯,然后决定去改变自己还是更换工具。 体会和理解工具的过程,也是调整我们对自己认知的过程,找到自己的节奏就好。 ","date":"2022-06-18","objectID":"/posts/how-to-organize-information-effectively.html:0:0","series":[],"tags":["效率","笔记","语雀","思源笔记","Git"],"title":"如何有效的整理信息?","uri":"/posts/how-to-organize-information-effectively.html#三写在最后"},{"categories":["科技研究所"],"content":"让我们从一个非常简单的思想实验开始。 假设 A、B、C 三个素不相识的人待在一个房间里进行口头交流,此时 A 想要秘密地“传输”一条消息给 B,但又不能让 C 知道。为方便起见,假设这条消息只是 0 到 9 之间的一个数字。 需要注意的是,A 说的任何话 B 和 C 都能听到,且 A 不能偷偷摸摸地耍小把戏,比如传纸条给 B 。那么 A 要如何才能做到呢? 欢迎来到 科技研究所,我是無糖。 ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:0:0","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#"},{"categories":["科技研究所"],"content":" 一、如何告诉 B ?在告诉你如何做到之前,我需要先向你解释两个数学的基本运算。 取模运算 幂运算 然后我会通过 A 如何告诉 B 这个简单的思想实验来向你解释迪菲-赫尔曼密钥交换协议的大致流程。 之所以是协议 流程而不是算法 流程,是因为这个过程中需要 B 的参与。 「协」字,代表的意思是必须有两个以上的参与者; 「议」字,代表的意思是对参与者的⼀种行为约定和规范。 好了,那么你现在已经知道这件事单靠 A 一个人努力是不行的了。 ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:0:0","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#一如何告诉-b-"},{"categories":["科技研究所"],"content":" 1.1、取模运算取模运算( mod )是求两个数相除的余数。x mod y = z,我们将 y 称为模数 ,z 称为余数。 我举几个例子: 5 mod 2 = 1 ,因为 5 除以 2 商 2 余1; 10 mod 3 = 1 ,因为 10 除以 3 商 3 余1; ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:1:0","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#11取模运算"},{"categories":["科技研究所"],"content":" 1.2、幂运算幂运算,又称指数运算,表达式为 $ x^{n} $ ,其中 $ x $ 称为底数 ,$ n $ 称为指数。通常指数写成上标,放在底数的右边。 我举几个例子: $ 2^{3}=8 $ ,因为 $ 2 * 2 * 2=8 $ ; $ 3^{4}=81 $ ,因为 $ 3 * 3 * 3 * 3 =81 $ ; ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:2:0","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#12幂运算"},{"categories":["科技研究所"],"content":" 1.3、协议流程现在你已经理解取模运算和幂运算这两个基本数学运算了,接下来我来解释一下这件事要怎么做。 ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:3:0","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#13协议流程"},{"categories":["科技研究所"],"content":" 1)双方各挑选一个私人数字(不公开)为保证数学计算上尽可能简单,我们将在这个例子中使用非常小的数字。因此,假设 A 挑选了 8 作为私人数字 B 挑选了 9 作为私人数字 私人数字是不对外公开的,自己默默记住就可以。 ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:3:1","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#1双方各挑选一个私人数字不公开"},{"categories":["科技研究所"],"content":" 2)双方约定两个数(公开)双方需要约定两个数,分别作为取模运算的模数和幂运算的底数。 为保证数学计算上尽可能简单,我也会使用非常小的数字。 因此,假设 A 与 B 约定了 模数=11,底数=2。 ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:3:2","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#2双方约定两个数公开"},{"categories":["科技研究所"],"content":" 3)双方计算各自的 PPN(公开)双方使用自己的私人数字、双方约定好的模数和底数计算各自的 PPN 。 计算公式: $ PPN=约定的底数^{自己的私人数字} mod 约定的模数 $ 这个公式用文字写出来可能会显得有点儿诡异,但实际却很简单。 A 的 PPN = $ 2^{8} mod 11 = 256 mod 11 = 3 $ B 的 PPN = $ 2^{9} mod 11 = 512 mod 11 = 6 $ ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:3:3","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#3双方计算各自的-ppn公开"},{"categories":["科技研究所"],"content":" 4)计算共享密钥(不公开)双方使用自己的私人数字、对方的 PPN 和约定好的模数计算共享密钥。 计算公式:$ 共享密钥=对方的PPN^{自己私人数字} mod 约定的模数 $ 同样的,这个公式用文字写出来可能会显得有点儿诡异,但实际却很简单。 A 计算出来的共享密钥 = $ 6^{8} mod 11 = 1679616 mod 11 = 4 $ B 计算出来的共享密钥 = $ 3^{9} mod 11 = 19 683 mod 11 = 4 $ 现在你发现,A 与 B 都算出来了同样一个数字 4 了,这就回答了文章开头的思想实验。 此处 A 与 B 计算出的共享密钥即为 A 要传输给 B 的“信息”。 C 因为不知道 A 与 B 的私人数字,即使 C 知道了 A 与 B 公开约定的两个数(取模运算的模数和幂运算的底数)和 PPN ,也是无法计算出与相同的共享密钥的。 整个协议过程中,除了 A、B(通信双方)的私人数字(私人密钥)不对外公开,其他都是透明可见的。共享密钥的保密性不依赖此协议流程的保密性,即使协议的计算流程公开,共享密钥也是安全的。 ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:3:4","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#4计算共享密钥不公开"},{"categories":["科技研究所"],"content":" 二、关于迪菲-赫尔曼密钥交换协议通过上面的思想实验,我们知道运用幂运算和取模运算就可以让通信双方在完全没有对方任何预先信息的条件下,通过不安全信道共同建立起一个安全的共享密钥,而一旦建立了共享密钥,这两台电脑就能使用对称加密对后续所有的通信进行加密了。 本文描述的协议流程被称为迪菲–赫尔曼密钥交换机制(DH)。这一机制以怀特菲德·迪菲(Whitfield Diffie)和马丁·赫尔曼(MartinHellman)的名字命名,他们俩于 1976 年首次发表了这一算法。每次你访问一个安全网站(以“https:”而非“http:”开头)时,你的计算机和与其通信的网站服务器之间都会建立一个共享密钥,使用的方法是迪菲–赫尔曼机制或工作原理类似的替代方法之一。除了最早的 DH(迪菲-赫尔曼密钥交换协议)之外,现在已经有 DHE / ECDHE 等变种了。 迪菲-赫尔曼密钥交换协议被发明后不久就出现了 RSA (非对称加密算法),现在的 HTTPS 协议就是通过 RSA+ECDHE 来保证确保通信安全的。 虽然迪菲-赫尔曼密钥交换本身是一个匿名(无认证)的密钥交换协议,它却是很多认证协议的基础。 ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:0:0","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#二关于迪菲-赫尔曼密钥交换协议"},{"categories":["科技研究所"],"content":" 2.1、实践中的公钥加密在实践中使用数字要远比我们在例子中使用的数字大。我们使用的模数很小(11),因此计算起来就很简单。但如果你选择的模数很小,那么私人数字的取值范围也会很小(因为你只能使用比模数小的私人数字)。而这也意味着有人可以使用计算机试出所有可能的私人数字,直到找到一个数字生成你的 PPN 。在上面的例子中,只有 11 个可能的私人数字,因此这个系统非常轻易就能被破解。相反,迪菲–赫尔曼机制在现实中运用时通常会使用几百个数位长的钟大小,这让可能的私人数字多得不可想象(比一万亿的一万亿次方还多得多)。即便如此,我们也需要小心地选取公开数字,以确保它们具有正确的数学性质。 最重要的是,模数必须是一个素数——只有1和其自身两个除数。另一个有趣的要求是,约定的底数必须是约定的模数的本原根(primitive root)。这也意味着底数的幂最终将循环遍 [0, 模数) 区间上每个可能的值。在前文使用的例子中, 2 和 6 都是 11 的本原根,但 3 却不是—— 3 的幂循环的值有 3、9、5、4 和 1,却没有 2、6、7、8 和 10 。 ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:1:0","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#21实践中的公钥加密"},{"categories":["科技研究所"],"content":" 2.2、为什么知道了 PPN 也无法反推出私人数字在上面的思想实验中,我们知道: A 的 PPN = $ 2^{8} mod 11 = 256 mod 11 = 3 $ 对于 C 来说,假设 $ A 的私人数字=x $,则有: A 的 PPN = $ 2^{x} mod 11 = 3 $ 那么 C 为什么无法通过 A 的 PPN 反推出他的私人数字? 因为当模数 11 是一个很大的质数时,由于还没有一种方法能让计算机高效地计算离散对数,即使知道了底数(2) 和 A 的 PPN(3),也几乎算不出来 $ x $ 的值。 离散对数求解难就是 DH 的数学基础理论。 ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:2:0","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#22为什么知道了-ppn-也无法反推出私人数字"},{"categories":["科技研究所"],"content":" REF图解 ECDHE 密钥交换算法 改变未来的九大算法 第三章 公钥加密 ","date":"2022-01-16","objectID":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html:0:0","series":[],"tags":["密码学","加密通信","迪菲赫尔曼","密钥交换","密钥协商"],"title":"如何在非安全信道进行安全通信? - 迪菲赫尔曼密钥交换协议基本原理","uri":"/posts/the-basic-principle-of-diffie-hellman-key-exchange-protocol.html#ref"},{"categories":["news"],"content":"jsdelivr 是一个免费开源的 CDN 服务站点。在过去的几年中,不少开发者使用它加速网站的静态资源文件。它依靠在中国大陆地区提供的免费且高速的 CDN 服务,深受广大站长和开发者好评。 jsdelivr 官网 然而非常不幸的是,昨天 jsDelivr 在国内的故障,并不是像往常一样的因为 SSL 证书过期导致资源无法访问,而是域名备案被吊销,导致国内 CDN 提供商网宿移除了 jsDelivr 的账号。目前 jsDelivr 国内线路为 Fastly。 昨天,jsDelivr 团队主要负责人 Dmitriy Akulov 在 jsDelivr 官方 GitHub 仓库的一条 issue 下发表了以下说明: 官方说明 ","date":"2021-12-21","objectID":"/posts/jsdelivr-unexpectedly-lost-its-icp-license-in-china.html:0:0","series":null,"tags":["jsDelivr"],"title":"jsDelivr 中国区 ICP 许可证被吊销","uri":"/posts/jsdelivr-unexpectedly-lost-its-icp-license-in-china.html#"},{"categories":["news"],"content":" REFhttps://github.com/jsdelivr/jsdelivr/issues/18348#issuecomment-997777996 ","date":"2021-12-21","objectID":"/posts/jsdelivr-unexpectedly-lost-its-icp-license-in-china.html:0:0","series":null,"tags":["jsDelivr"],"title":"jsDelivr 中国区 ICP 许可证被吊销","uri":"/posts/jsdelivr-unexpectedly-lost-its-icp-license-in-china.html#ref"},{"categories":["科技研究所"],"content":"TOTP 算法的基本原理和实现 欢迎来到 科技研究所,我是無糖。 ","date":"2021-12-05","objectID":"/posts/about-time-based-one-time-password.html:0:0","series":[],"tags":[],"title":"为什么QQ令牌生成验证码不需要联网?","uri":"/posts/about-time-based-one-time-password.html#"},{"categories":["科技研究所"],"content":" 一、QQ令牌QQ 令牌是腾讯公司专为保护 QQ 帐号及游戏帐号安全的密保产品。QQ 令牌于 2010 年 7 月 6 日正式发布。无需联网,每隔 60 秒自动更新的动态密码是这款密保产品最大的特点,在登录、支付、游戏装备转让等敏感操作需要验证身份时,用户可以自主选择是否开启 QQ 令牌验证。 QQ 令牌有手机 APP 和硬件实体两种。 QQ 令牌现已更名为 QQ 安全中心,动态密码更新间隔下调至 30 秒。 ","date":"2021-12-05","objectID":"/posts/about-time-based-one-time-password.html:0:0","series":[],"tags":[],"title":"为什么QQ令牌生成验证码不需要联网?","uri":"/posts/about-time-based-one-time-password.html#一qq令牌"},{"categories":["科技研究所"],"content":" 1.1、手机 APP 手机 APP ","date":"2021-12-05","objectID":"/posts/about-time-based-one-time-password.html:1:0","series":[],"tags":[],"title":"为什么QQ令牌生成验证码不需要联网?","uri":"/posts/about-time-based-one-time-password.html#11手机-app"},{"categories":["科技研究所"],"content":" 1.2、硬件 QQ令牌 1代 QQ令牌 2代 那么 QQ 令牌为什么无需联网,就能每隔 30 秒自动更新动态密码?答案是 TOTP 算法,下面我们简单介绍一下它的基本原理和实现。 ","date":"2021-12-05","objectID":"/posts/about-time-based-one-time-password.html:2:0","series":[],"tags":[],"title":"为什么QQ令牌生成验证码不需要联网?","uri":"/posts/about-time-based-one-time-password.html#12硬件"},{"categories":["科技研究所"],"content":" 二、TOTP 算法TOTP 的全称是\"基于时间的一次性密码\"(Time-based One-time Password)。它是公认的可靠解决方案,已经写入国际标准 RFC6238。 通常情况下,客户端在首次配置完成后,就不再需要与服务端通讯了。借助 TOTP 算法,客户端可以在离线环境下生成 30 秒有效期的 6 位数字验证码。因为不需要联网就能生成短时效的动态密码,所以安全性比较高,很多网站和应用都采用这种形式做两步验证(2FA)。 ","date":"2021-12-05","objectID":"/posts/about-time-based-one-time-password.html:0:0","series":[],"tags":[],"title":"为什么QQ令牌生成验证码不需要联网?","uri":"/posts/about-time-based-one-time-password.html#二totp-算法"},{"categories":["科技研究所"],"content":" 2.1、如何离线生成一个 30 秒有效期的 6 位数字验证码?那么手机客户端和服务器是如何保证在 30 秒期间都得到同一个 6 位数字验证码(哈希)呢?答案就是下面的公式。 TC = floor((unixtime(now) − unixtime(T0)) / TS) 上面的公式中,TC 表示一个时间计数器,floor 是向下取整函数,unixtime(now)是当前 Unix 时间戳,unixtime(T0)是约定的起始时间点的时间戳,默认是0,也就是1970年1月1日。TS 则是哈希有效期的时间长度,默认是30秒。因此,上面的公式就变成下面的形式。 TC = floor(unixtime(now) / 30) 所以,只要在 30 秒以内,TC 的值都是一样的。前提是服务器和手机的时间必须同步。接下来,就可以算出哈希了。 TOTP = HASH(SecretKey, TC) 上面代码中,HASH就是约定的哈希函数,默认是 SHA-1。 ","date":"2021-12-05","objectID":"/posts/about-time-based-one-time-password.html:1:0","series":[],"tags":[],"title":"为什么QQ令牌生成验证码不需要联网?","uri":"/posts/about-time-based-one-time-password.html#21如何离线生成一个-30-秒有效期的-6-位数字验证码"},{"categories":["科技研究所"],"content":" 2.2、TOTP 的 JavaScript 实现TOTP 很容易写,各个语言都有实现。下面我用 JavaScript 实现2fa来演示一下真实代码。 首先,安装这个模块。 npm install --save 2fa 然后,生成一个32位字符的密钥。 var tfa = require('2fa'); tfa.generateKey(32, function(err, key) { console.log(key); }); // b5jjo0cz87d66mhwa9azplhxiao18zlx 现在就可以生成哈希了。 var tc = Math.floor(Date.now() / 1000 / 30); var totp = tfa.generateCode(key, tc); console.log(totp); // 683464 ","date":"2021-12-05","objectID":"/posts/about-time-based-one-time-password.html:2:0","series":[],"tags":[],"title":"为什么QQ令牌生成验证码不需要联网?","uri":"/posts/about-time-based-one-time-password.html#22totp-的-javascript-实现"},{"categories":["科技研究所"],"content":" REFhttps://aq.qq.com/cn2/manage/qqtoken/qqtoken_pub http://www.ruanyifeng.com/blog/2017/11/2fa-tutorial.html ","date":"2021-12-05","objectID":"/posts/about-time-based-one-time-password.html:0:0","series":[],"tags":[],"title":"为什么QQ令牌生成验证码不需要联网?","uri":"/posts/about-time-based-one-time-password.html#ref"},{"categories":["3000问"],"content":" 一、内存泄漏和内存溢出内存泄漏 ( Memory Leak ) 指的是已分配的内存由于某种原因导致程序未释放或无法释放,造成程序继续占用已经不再使用的内存空间,令内存资源空耗。 内存溢出 ( Out Of Memory,简称 OOM ) 是指系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。 由于内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更难检测。因为内存泄漏的产生原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏通常不会直接产生可观察的错误现象,而是逐渐积累,直到 OOM。 内存泄漏最终会导致内存溢出。 ","date":"2021-12-03","objectID":"/posts/what-are-memory-leaks-and-out-of-memory.html:0:0","series":[],"tags":[],"title":"内存泄漏和内存溢出是什么?","uri":"/posts/what-are-memory-leaks-and-out-of-memory.html#一内存泄漏和内存溢出"},{"categories":["3000问"],"content":" 二、内存泄漏的分类以产生的方式来分类,内存泄漏可以分为四类: ","date":"2021-12-03","objectID":"/posts/what-are-memory-leaks-and-out-of-memory.html:0:0","series":[],"tags":[],"title":"内存泄漏和内存溢出是什么?","uri":"/posts/what-are-memory-leaks-and-out-of-memory.html#二内存泄漏的分类"},{"categories":["3000问"],"content":" 2.1、常发性内存泄漏发生内存泄漏的代码会被多次执行到,每次被执行时都会导致一块内存泄漏。 ","date":"2021-12-03","objectID":"/posts/what-are-memory-leaks-and-out-of-memory.html:1:0","series":[],"tags":[],"title":"内存泄漏和内存溢出是什么?","uri":"/posts/what-are-memory-leaks-and-out-of-memory.html#21常发性内存泄漏"},{"categories":["3000问"],"content":" 2.2、偶发性内存泄漏发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。 ","date":"2021-12-03","objectID":"/posts/what-are-memory-leaks-and-out-of-memory.html:2:0","series":[],"tags":[],"title":"内存泄漏和内存溢出是什么?","uri":"/posts/what-are-memory-leaks-and-out-of-memory.html#22偶发性内存泄漏"},{"categories":["3000问"],"content":" 2.3、一次性内存泄漏发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。 ","date":"2021-12-03","objectID":"/posts/what-are-memory-leaks-and-out-of-memory.html:3:0","series":[],"tags":[],"title":"内存泄漏和内存溢出是什么?","uri":"/posts/what-are-memory-leaks-and-out-of-memory.html#23一次性内存泄漏"},{"categories":["3000问"],"content":" 2.4、隐式内存泄漏程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。 具体的例子等以后再补全… ","date":"2021-12-03","objectID":"/posts/what-are-memory-leaks-and-out-of-memory.html:4:0","series":[],"tags":[],"title":"内存泄漏和内存溢出是什么?","uri":"/posts/what-are-memory-leaks-and-out-of-memory.html#24隐式内存泄漏"},{"categories":["3000问"],"content":" REFhttps://zh.wikipedia.org/wiki/内存泄漏 https://baike.baidu.com/item/内存溢出 https://zhuanlan.zhihu.com/p/103274367 ","date":"2021-12-03","objectID":"/posts/what-are-memory-leaks-and-out-of-memory.html:0:0","series":[],"tags":[],"title":"内存泄漏和内存溢出是什么?","uri":"/posts/what-are-memory-leaks-and-out-of-memory.html#ref"},{"categories":["折腾工坊"],"content":"自建 Git Server 有什么用?不用再忍受 Github 龟速网、无限数量私有库、数据不用托管在 Github 上,自己说了算。 本文会以 CentOS7 + Nginx + Gogs 为例,教你如何安装配置,拥有一个属于自己的 “Github” 。 ","date":"2021-11-30","objectID":"/posts/how-to-build-a-private-git-server-with-gogs.html:0:0","series":null,"tags":["Gogs","Git","Git Server"],"title":"如何拥有一个属于自己的 Github ?","uri":"/posts/how-to-build-a-private-git-server-with-gogs.html#"},{"categories":["折腾工坊"],"content":" Github 的过去自从 GitHub 在 2018 年被微软收购后,每个账号都能免费创建 5 个私有库了,当时非常高兴,觉得 Github 有微软在背后站台,未来可期。但 3 年后却发生了一件事,又一次提醒了我,原来 Github 已经被资本收购了。 2021 年 7 月,由微软、OpenAI、GitHub 联合出品的自动代码生成 AI - Copilot 在推出后的第二天就深陷侵犯版权的质疑声中,惨遭开源社区网友炮轰。为了训练 Copilot ,GitHub 官方确认了他们会使用所有 GitHub 公开代码来训练 Copilot,并且不区分 License 类别。而在之前,明明官方有过 “Copilot是在GPL代码的基础上训练出来的” 这种说法,但之后 GitHub 的 CEO 又表示:“每天都有数百名 GitHub 的开发者在使用 Copilot ,如果预览版进展顺利的话,我们将计划在未来某个时候将其扩展为付费产品。\",完全就是一副视 General Public License 如无物的样子。 富人和大公司带头冲版权,一边冲一边告诉大众,你们要遵守版权,不可以学我。(哄堂大笑 ","date":"2021-11-30","objectID":"/posts/how-to-build-a-private-git-server-with-gogs.html:0:0","series":null,"tags":["Gogs","Git","Git Server"],"title":"如何拥有一个属于自己的 Github ?","uri":"/posts/how-to-build-a-private-git-server-with-gogs.html#github-的过去"},{"categories":["折腾工坊"],"content":" Why Gogs ?大公司普遍使用的是内部开发的 Git Server,像我们这样的个人开发者,选择一个开源 Git Server 自行搭建是比较适合的。 我知道有几个不错的开源 Git Server 可以考虑: GitLab Gogs Gitea GitStack 最终,我选择 Gogs 的主要原因是它资源占用极低,你甚至可以用树莓派来搭建一个 Gogs,1C2G 的配置轻轻松松带 Gogs。 比之 GitLab,Gogs 在资源占用和性能上的优势是非常明显的,因此它非常适合在硬件配置不高的服务器搭建。 官网:https://gogs.io 演示站:https://try.gogs.io/ Github:https://github.com/gogs/gogs ","date":"2021-11-30","objectID":"/posts/how-to-build-a-private-git-server-with-gogs.html:0:0","series":null,"tags":["Gogs","Git","Git Server"],"title":"如何拥有一个属于自己的 Github ?","uri":"/posts/how-to-build-a-private-git-server-with-gogs.html#why-gogs-"},{"categories":["折腾工坊"],"content":" 一、安装 Gogsadduser git #创建用户 git passwd git #设置git用户的密码 groupadd git #创建用户组 git usermod -G git git #将git用户添加到git用户组中 su git #切换到git用户shell cd ~ #进入/home/git wget https://dl.gogs.io/0.12.3/gogs_0.12.3_linux_amd64.zip #下载gogs unzip gogs_0.12.3_linux_amd64.zip #解压gogs压缩包 su #切回root用户 cp /home/git/gogs/scripts/systemd/gogs.service /usr/lib/systemd/system/ #复制service文件 systemctl enable gogs.service #启用gogs服务(开机自启) systemctl start gogs #启动gogs systemctl status gogs #查看gogs运行状态 安装完成后,就可以用访问 http://ip:3000 配置 Gogs 了,不过我建议你别急着填下面的配置,先把 SSL 证书搞定,接着往下看吧。 如果无法访问,说明服务器入方向的 3000 端口没有打开。 请在服务器供应商(如阿里云或腾讯云等)提供的控制面板设置对应的 防火墙/安全组 规则,开放入方向的 3000 端口。 如果你使用了类似于宝塔之类的控制面板,请检查面板防火墙中是否中开放了 3000 端口。 ","date":"2021-11-30","objectID":"/posts/how-to-build-a-private-git-server-with-gogs.html:0:0","series":null,"tags":["Gogs","Git","Git Server"],"title":"如何拥有一个属于自己的 Github ?","uri":"/posts/how-to-build-a-private-git-server-with-gogs.html#一安装-gogs"},{"categories":["折腾工坊"],"content":" 二、配置域名与HTTPS","date":"2021-11-30","objectID":"/posts/how-to-build-a-private-git-server-with-gogs.html:0:0","series":null,"tags":["Gogs","Git","Git Server"],"title":"如何拥有一个属于自己的 Github ?","uri":"/posts/how-to-build-a-private-git-server-with-gogs.html#二配置域名与https"},{"categories":["折腾工坊"],"content":" 2.1、配置域名访问将域名解析到服务器 IP 上,在 Nginx 配置文件中新增如下配置: server { listen 80; server_name git.yourdomain.com; # 域名修改为你自己的 location / { proxy_pass http://localhost:3000; } access_log /www/wwwlogs/yourdomain.access.log; error_log /www/wwwlogs/yourdomain.error.log; } 这样就可以通过 HTTP 的方式访问 Gogs 了:http://git.yourdomain.com 。 我们接着配置 SSL 证书,先不急着填写 Gogs 的配置,直接一步到位。 ","date":"2021-11-30","objectID":"/posts/how-to-build-a-private-git-server-with-gogs.html:1:0","series":null,"tags":["Gogs","Git","Git Server"],"title":"如何拥有一个属于自己的 Github ?","uri":"/posts/how-to-build-a-private-git-server-with-gogs.html#21配置域名访问"},{"categories":["折腾工坊"],"content":" 2.2、配置 SSL 证书你可以按照下面的 Nginx 配置为模板,来配置你的 Gogs 站点。 根据你的需求,你可以自由选择是否强制 HTTPS。 server { listen 80; listen 443 ssl http2; server_name git.yourdomain.com; # 域名修改为你自己的 ssl_certificate /xxx/xxx/xxx.pem; # 证书修改为你自己的 ssl_certificate_key /xxx/xxx/xxx.pem; # 证书修改为你自己的 ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; add_header Strict-Transport-Security \"max-age=31536000\"; # HTTP 重定向到 HTTPS # if ($server_port !~ 443){ # rewrite ^(/.*)$ https://$host$1 permanent; # } # error_page 497 https://$host$request_uri; # 反向代理 location / { proxy_pass http://localhost:3000; } #禁止访问的文件或目录 location ~ ^/(\\.user.ini|\\.htaccess|\\.git|\\.svn|\\.project|LICENSE|README.md) { return 404; } access_log /www/wwwlogs/yourdomain.access.log; error_log /www/wwwlogs/yourdomain.error.log; } 配置完成后,你就可以通过 HTTPS 的方式访问 Gogs 了:https://git.yourdomain.com 现在我们可以开始填写 Gogs 的配置了。 ","date":"2021-11-30","objectID":"/posts/how-to-build-a-private-git-server-with-gogs.html:2:0","series":null,"tags":["Gogs","Git","Git Server"],"title":"如何拥有一个属于自己的 Github ?","uri":"/posts/how-to-build-a-private-git-server-with-gogs.html#22配置-ssl-证书"},{"categories":["折腾工坊"],"content":" 三、配置 Gogs此时我们已经配置好了 HTTPS 访问,那么在配置 Gogs 中有几处域名的设置需要注意。下面是一份样例配置,几处需要注意的域名我用红框标注出来了。 填写完配置后,点击立即安装,就完成了。 那么剩下的就跟 Github 一样,添加 SSH Key 到自己的账户中,就可以愉快建库冲浪了。 Gogs 除了安装引导中的配置项,其实还有不少额外的配置。 在 custom/conf/app.ini 文件中修改相应配置项的值即可。 完整配置项样例:https://github.com/gogs/gogs/blob/main/conf/app.ini 旧版本配置文件手册参考(中文):https://www.bookstack.cn/read/gogs_zh/advanced-configuration_cheat_sheet.md ","date":"2021-11-30","objectID":"/posts/how-to-build-a-private-git-server-with-gogs.html:0:0","series":null,"tags":["Gogs","Git","Git Server"],"title":"如何拥有一个属于自己的 Github ?","uri":"/posts/how-to-build-a-private-git-server-with-gogs.html#三配置-gogs"},{"categories":["折腾工坊"],"content":" REFhttps://gogs.io/docs/installation/install_from_binary https://gogs.io/docs/installation/configuration_and_run https://www.zhihu.com/question/283143882/answer/434872781 https://www.bookstack.cn/read/gogs_zh/advanced-configuration_cheat_sheet.md ","date":"2021-11-30","objectID":"/posts/how-to-build-a-private-git-server-with-gogs.html:0:0","series":null,"tags":["Gogs","Git","Git Server"],"title":"如何拥有一个属于自己的 Github ?","uri":"/posts/how-to-build-a-private-git-server-with-gogs.html#ref"},{"categories":["折腾工坊"],"content":"作为硬盘来说,SSD 已经很快了,但内存更快。 本文简单介绍一下使用内存盘为系统加速的几种姿势。 ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:0:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#"},{"categories":["折腾工坊"],"content":" 一、内存盘是什么?内存盘即 RAM Disk,市面上实现 RAM Disk 的软件有很多,其主要功能是通过独特的软件算法将物理内存模拟成一个虚拟硬盘,在这个虚拟硬盘上的读写操作均在内存中完成。由于内存的读写速度远超硬盘( HDD 与 SSD ),因此这个虚拟硬盘具有非常高的数据读写速度。 ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:0:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#一内存盘是什么"},{"categories":["折腾工坊"],"content":" 二、比 SSD 更优秀的读写性能在使用内存盘为系统加速之前,我想先向你展示一下内存盘(RAM Disk)与固态硬盘(Solid State Disk - SSD)之间的读写速度差距有多大。 这是三星 970 EVO Plus 1TB 的读写速度: 三星 970 EVO Plus 1TB 这是 4GB 内存盘的读写速度: 4GB 内存盘 从 CrystalDiskMark 给出的测试数据看,即便是与三星 970 EVO Plus 1T 这种中高端 SSD 相比,内存盘的性能依然是令人惊叹的。 4K 随机读写性能: 单线程 4K 读:提升了 18 倍; 单线程 4K 写:提升了 6 倍; 读 - 访问时间:降低至 5%; 写 - 访问时间:降低至 16%; 顺序读写性能: 单线程顺序读:提升了 6 倍; 单线程顺序写:提升了 3 倍; ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:0:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#二比-ssd-更优秀的读写性能"},{"categories":["折腾工坊"],"content":" 三、内存盘能用来存什么?由于内存盘是使用物理内存模拟成一个虚拟硬盘,所以该虚拟硬盘的容量受限于内存大小。你可以根据自己的应用场景来设置内存盘的大小,一般来说 512M ~ 4G 左右较为合适。 你大概率是不可能往内存盘里写入大体积文件的(例如电影或几个 G 大小的压缩包等),因此内存盘的顺序读写性能带来的提升其实你感知不到,所以内存盘其强大的 4K 随机读写性能才是我们应该关注的重点。 好了,现在我们知道内存盘的特点了: 容量较小 4K 随机读写性能强大 那么将其作为缓存空间使用就再合适不过了。 需要注意的是,下面的几个应用场景中的附带截图, G 盘是内存盘。 ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:0:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#三内存盘能用来存什么"},{"categories":["折腾工坊"],"content":" 3.1、Windows 临时文件我将 Windows 临时文件夹设置在内存盘中,这样可以直接为操作系统加速。 ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:1:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#31windows-临时文件"},{"categories":["折腾工坊"],"content":" 3.2、浏览器缓存浏览器加载页面时会向缓存目录中写入大量小文件,而且浏览器还是日常高频使用的软件,非常适合使用内存盘加速。 如果你使用的是 Chrome 或 Edge ,想要修改缓存目录就有些麻烦了。其他浏览器基本都是支持自定义缓存目录的。 以 Edge 为例,输入 edge://version/ 命令,找到配置文件目录: Chrome 浏览器输入:chrome://version/ 进入该目录后,删除 Cache 这个原缓存文件夹,打开 CMD(以管理员身份运行),使用 Mklink 命令建立原缓存目录与新缓存目录(位于内存盘中)的软连接。 ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:2:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#32浏览器缓存"},{"categories":["折腾工坊"],"content":" 3.3、IDE虽然普通用户会从将浏览器缓存和 Windows 临时文件转移到内存盘中受益,但 IT 专业人士可能会发现内存盘更多的优势。 例如,程序员可以通过将中间编译器输出重定向到内存盘来加速开发过程,数据库管理员可以通过使用内存盘来提高性能,如 tempdb,等等。 我做 Java 后端开发的,平时使用 jetbrains 家的 IntelliJ IDEA 编译器作为日常开发工具。 于是乎我直接把整个 idea 装到内存盘里去了… 无论是启动 idea、编译或运行项目,基本都要比原来快一截。 如果你不做额外设置的话,内存盘里的数据在重启系统后全部丢失,安装在内存盘的软件也会随之消失。 因此,你如果需要重启电脑后保留内存盘里数据,你需要做一些额外设置,市面上的 RAM Disk 软件基本都支持这个功能。 是的,我把 VS Code 也装内存盘里了。 ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:3:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#33ide"},{"categories":["折腾工坊"],"content":" 四、RAM Disk 软件推荐我个人比较推荐你使用 SoftPerfect RAM Disk,特点是界面简洁,性能好。 官网:https://www.softperfect.com/products/ramdisk/ 度盘:https://pan.baidu.com/s/1z9elPiX103hloTwXj-NW5w 提取码:kcik ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:0:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#四ram-disk-软件推荐"},{"categories":["折腾工坊"],"content":" 五、测试环境补充","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:0:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#五测试环境补充"},{"categories":["折腾工坊"],"content":" 5.1、鲁大师硬件参数 ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:1:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#51鲁大师硬件参数"},{"categories":["折腾工坊"],"content":" 5.2、AIDA64 Cache \u0026 Memory Benchmark ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:2:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#52aida64-cache--memory-benchmark"},{"categories":["折腾工坊"],"content":" REF https://www.romexsoftware.com/zh-cn/primo-ramdisk/overview.html https://www.softperfect.com/products/ramdisk/ ","date":"2021-11-21","objectID":"/posts/using-ram-disks-to-speed-up-your-system.html:0:0","series":[],"tags":["内存盘","加速","系统"],"title":"内存盘能为我们带来什么惊喜?","uri":"/posts/using-ram-disks-to-speed-up-your-system.html#ref"},{"categories":null,"content":" 算法总纲 循序渐进地学习算法,一步一个脚印。 阅读全文 Spring Boot 2 循序渐进地学习 Spring Boot ,一步一个脚印。 阅读全文 ","date":"2021-11-01","objectID":"/special.html:0:0","series":null,"tags":null,"title":"专栏","uri":"/special.html#"},{"categories":["软件"],"content":"在编写论文 、博客等内容时,想要输入数学公式就比较麻烦了。如果你使用 Mathpix Snip,只需要截个图,Mathpix Snip就可以将截图中的公式自动转化为 LaTex 代码表达式,我们只需要简单地修改修改就可以直接插入到LaTex或Word中,公式较为清晰规范的话是不需要修改的。而且可以识别手写的公式。 官网:https://mathpix.com/ 下载:https://mathpix.com/#downloads 如果使用 edu 教育邮箱注册 mathpix 账户,每天有 100 次的免费额度。 ","date":"2021-10-10","objectID":"/posts/mathpix.html:0:0","series":[],"tags":[],"title":"Mathpix Snip - Convert images to LaTeX","uri":"/posts/mathpix.html#"},{"categories":["Mark"],"content":"本文以 Windows 系统与小米安卓系统 MIUI 为例,教你如何获取连接过的 WIFI 的密码。 ","date":"2021-10-08","objectID":"/posts/how-to-get-the-wifi-password-that-has-been-connected-to-the-windows-system.html:0:0","series":[],"tags":["WIFI","密码","Windows","MIUI"],"title":"Windows 如何获取连接过的 WIFI 的密码?","uri":"/posts/how-to-get-the-wifi-password-that-has-been-connected-to-the-windows-system.html#"},{"categories":["Mark"],"content":" 一、Windows以管理员 身份运行 cmd ,运行netsh wlan show profile 查看所有连接过的 WIFI,如下所示: C:\\Windows\\system32\u003enetsh wlan show profile 接口 WLAN 上的配置文件: 组策略配置文件(只读) --------------------------------- \u003c无\u003e 用户配置文件 ------------- 所有用户配置文件 : TP-LINK_4C31E6 所有用户配置文件 : REBOX-01_5G 所有用户配置文件 : Redmi_3FD4_5G 接着运行:netsh wlan export profile folder=C:\\ key=clear 导出配置信息,如下所示: C:\\Windows\\system32\u003enetsh wlan export profile folder=C:\\ key=clear 接口配置文件“TP-LINK_4C31E6”已成功保存在文件“C:\\WLAN-TP-LINK_4C31E6.xml”中。 接口配置文件“REBOX-01_5G”已成功保存在文件“C:\\WLAN-REBOX-01_5G.xml”中。 接口配置文件“Redmi_3FD4_5G”已成功保存在文件“C:\\WLAN-Redmi_3FD4_5G.xml”中。 在 C:\\WLAN-REBOX-01_5G.xml 配置文件中,keyMaterial 标签中的 rebox1930 就是 WIFI 密码了。 \u003c?xml version=\"1.0\"?\u003e \u003cWLANProfile xmlns=\"http://www.microsoft.com/networking/WLAN/profile/v1\"\u003e \u003cname\u003eREBOX-01_5G\u003c/name\u003e \u003cSSIDConfig\u003e \u003cSSID\u003e \u003chex\u003e5245424F582D30315F3547\u003c/hex\u003e \u003cname\u003eREBOX-01_5G\u003c/name\u003e \u003c/SSID\u003e \u003c/SSIDConfig\u003e \u003cconnectionType\u003eESS\u003c/connectionType\u003e \u003cconnectionMode\u003emanual\u003c/connectionMode\u003e \u003cMSM\u003e \u003csecurity\u003e \u003cauthEncryption\u003e \u003cauthentication\u003eWPA2PSK\u003c/authentication\u003e \u003cencryption\u003eAES\u003c/encryption\u003e \u003cuseOneX\u003efalse\u003c/useOneX\u003e \u003c/authEncryption\u003e \u003csharedKey\u003e \u003ckeyType\u003epassPhrase\u003c/keyType\u003e \u003cprotected\u003efalse\u003c/protected\u003e \u003ckeyMaterial\u003erebox1930\u003c/keyMaterial\u003e \u003c/sharedKey\u003e \u003c/security\u003e \u003c/MSM\u003e \u003cMacRandomization xmlns=\"http://www.microsoft.com/networking/WLAN/profile/v3\"\u003e \u003cenableRandomization\u003efalse\u003c/enableRandomization\u003e \u003crandomizationSeed\u003e4220620975\u003c/randomizationSeed\u003e \u003c/MacRandomization\u003e \u003c/WLANProfile\u003e ","date":"2021-10-08","objectID":"/posts/how-to-get-the-wifi-password-that-has-been-connected-to-the-windows-system.html:0:0","series":[],"tags":["WIFI","密码","Windows","MIUI"],"title":"Windows 如何获取连接过的 WIFI 的密码?","uri":"/posts/how-to-get-the-wifi-password-that-has-been-connected-to-the-windows-system.html#一windows"},{"categories":["Mark"],"content":" 二、小米安卓系统 MIUI系统设置 -\u003e WLAN,点击分享密码 截图保存此二维码 打开微信,扫描这个二维码,就可以看到红线处的密码 rebox1930 了。 ","date":"2021-10-08","objectID":"/posts/how-to-get-the-wifi-password-that-has-been-connected-to-the-windows-system.html:0:0","series":[],"tags":["WIFI","密码","Windows","MIUI"],"title":"Windows 如何获取连接过的 WIFI 的密码?","uri":"/posts/how-to-get-the-wifi-password-that-has-been-connected-to-the-windows-system.html#二小米安卓系统-miui"},{"categories":["news"],"content":"知乎送给大 V 的月饼吃了之后拉肚子,原因是这批月饼用的糖醇(麦芽糖醇)代替糖,而摄入大量糖醇会导致腹泻,有些人喝太多代糖饮料会腹泻也是同样原因,这回真“泻药”了。。。 不得不说,月饼的外包装还是挺好看的。 from 即刻 - 油百万 from 即刻 - 油百万 from 即刻 - 油百万 from 即刻 - 油百万 from 即刻 - 油百万 知乎官方微博原文截图 官方微博账号道歉声明 微博原文中的 P3,是此次月饼采销的负责人发言。 此次月饼采销负责人发言 在官方出面道歉前,其实已经有知乎用户给出了相关解释。 知乎用户爆料 官方账号道歉声明微博下对控评不满的网友们。 网友们 网友们 网友们 ","date":"2021-09-08","objectID":"/posts/news/zhihu-apologizes-for-the-mooncake-incident.html:0:0","series":[],"tags":["知乎","致歉"],"title":"知乎就月饼吃坏肚子致歉","uri":"/posts/news/zhihu-apologizes-for-the-mooncake-incident.html#"},{"categories":["news"],"content":" REF即刻 - 油百万:https://m.okjike.com/originalPosts/6136198602cb2b0010e286d9 即刻 - 油百万:https://m.okjike.com/originalPosts/613d9d6a67d511001031ad9c 微博 - 知乎官微道歉声明原文:https://weibo.com/1904769205/KxfvMybtj ","date":"2021-09-08","objectID":"/posts/news/zhihu-apologizes-for-the-mooncake-incident.html:0:0","series":[],"tags":["知乎","致歉"],"title":"知乎就月饼吃坏肚子致歉","uri":"/posts/news/zhihu-apologizes-for-the-mooncake-incident.html#ref"},{"categories":null,"content":" 申请友链留言模板名称:無糖的小宇宙 站点:https://sugarless.top/ 图片:https://sugarless.top/images/avatar.png 说明:Life is too short to not live wild. 本站友链展示地址:https://sugarless.top/friends.html ","date":"2021-09-03","objectID":"/message.html:0:0","series":null,"tags":null,"title":"留言板","uri":"/message.html#申请友链留言模板"},{"categories":["Python"],"content":"有兴趣的可以自行部署在服务器上,做成每日自动提交,或者 webhook 触发等。 百度提交页面:https://ziyuan.baidu.com/linksubmit/index 通过此页面可以向百度搜索主动推送资源,同时百度也提供了 API 提交接口,这样可以缩短爬虫发现网站链接的时间,但百度不保证收录和展现效果。 以本站为例,百度提供了 API 提交的样例参数。 ","date":"2021-08-31","objectID":"/posts/baidu-seo-submission-tool-developed-using-python.html:0:0","series":null,"tags":["Python","SEO","百度","脚本"],"title":"Python 百度 SEO 工具脚本 | 百度普通收录 API 提交工具","uri":"/posts/baidu-seo-submission-tool-developed-using-python.html#"},{"categories":["Python"],"content":" 源码解析站点当前的 sitemap.xml 文件内容,向百度批量提交 url 。 使用前需要注意替换真实参数: site token sitemap_path # -*- coding: UTF-8 -*- import requests import json import re class Pusher: # 初始化参数 def __init__(self, site, token, sitemap_path): self.site = site self.token = token self.sitemap_path = sitemap_path # 批量提交 url def post(self, urls): post_url = f\"http://data.zz.baidu.com/urls?site={self.site}\u0026token={self.token}\" headers = { 'User-Agent': 'curl/7.12.1', 'Host': 'data.zz.baidu.com', 'Content-Type': 'text/plain', 'Content-Length': str(len(urls)), } response = requests.post(post_url, headers=headers, data=urls) if response.status_code != 200: print(f\"推送失败(异常状态码):{response.status_code}\") print() return None response = response.text response = json.loads(response) if \"error\" in response: print(f\"推送失败(error)\") print(response) print() return None if \"success\" not in response: print(f\"推送异常(百度已更改返回体格式)\") print(response) print() return None print(f\"已成功推送 {response['success']} 条 url\") print(f\"当天剩余的可推送 url 条数 ---\u003e {response['remain']}\") print() return None # 获取站点 sitemap.xml 文件内容, 解析 url def get_url_from_sitemap(self): url = f\"{self.site}{self.sitemap_path}\" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', } response = requests.get(url, headers=headers) if response.status_code != 200: print(f\"获取站点 xml 文件失败(异常状态码):{response.status_code}\") print() exit(0) sitemap_xml = response.text urls = re.findall(r'\u003cloc\u003e(.+?)\u003c/loc\u003e', sitemap_xml, re.S) if urls is None or urls == [] or urls == \"\": print(f\"解析 url 失败(该站点不存在 sitemap.xml 或内容为空)\") print() exit(0) print(f\"获取站点 xml 文件成功, 共有:{len(urls)} 条 url\") print(urls) print() return urls # 入口 def main(self): urls = self.get_url_from_sitemap() body = \"\" for url in urls: body += f\"{url}\\n\" self.post(body) if __name__ == '__main__': # 站点 site = \"https://sugarless.top\" # 百度提交页面提供的 token token = \"JvNXvshHiebMYkve\" # 站点 sitemap uri # sitemap_path = \"/zh-cn/sitemap.xml\" sitemap_path = \"/sitemap.xml\" pusher = Pusher(site, token, sitemap_path) pusher.main() ","date":"2021-08-31","objectID":"/posts/baidu-seo-submission-tool-developed-using-python.html:0:0","series":null,"tags":["Python","SEO","百度","脚本"],"title":"Python 百度 SEO 工具脚本 | 百度普通收录 API 提交工具","uri":"/posts/baidu-seo-submission-tool-developed-using-python.html#源码"},{"categories":["Python"],"content":" REFhttps://blog.csdn.net/minge89/article/details/108031025 ","date":"2021-08-31","objectID":"/posts/baidu-seo-submission-tool-developed-using-python.html:0:0","series":null,"tags":["Python","SEO","百度","脚本"],"title":"Python 百度 SEO 工具脚本 | 百度普通收录 API 提交工具","uri":"/posts/baidu-seo-submission-tool-developed-using-python.html#ref"},{"categories":["How-to-use"],"content":"如果你提交版本后需要手动进入服务器 shell 去 git pull ,那么本文或许对你有些帮助。 ","date":"2021-08-18","objectID":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html:0:0","series":null,"tags":["github","webhook","宝塔面板","自动化部署"],"title":"如何使用 Github webhook 配合宝塔 webhook 自动化部署","uri":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html#"},{"categories":["How-to-use"],"content":" 一、关于 webhook为了让你直观的了解到 webhook 能做什么,我例举了两个比较常见的应用场景: 你在 Github 上开源了一个项目,你希望在 git push 提交新版本到 Github 后,部署了宝塔面板的服务器能自动执行 git pull 命令,从 Github 拉取最新版本代码。 你在 gitee 或自建的 gogs (一款开源 git server )上托管代码,你希望在 git push 提交新版本到远程仓库后,服务器能自动执行 git pull 命令,从远程仓库拉取最新版本的代码,并重启服务。 实际上,你可以把它看做一种事件触发器。当满足某种特定条件时,执行一系列特定操作。其中,一方是事件发起者,另一方是事件接收者。 以第一个例子来解释,Github 接收到 push 后,发起一个 HTTP(S) 请求,响应者接收到请求后执行特定命令或业务逻辑。Github 是事件发起者,部署了宝塔面板的服务器是事件执行者。 ","date":"2021-08-18","objectID":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html:0:0","series":null,"tags":["github","webhook","宝塔面板","自动化部署"],"title":"如何使用 Github webhook 配合宝塔 webhook 自动化部署","uri":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html#一关于-webhook"},{"categories":["How-to-use"],"content":" 二、配置宝塔面板 webhook我的博客部署在腾讯云的服务器上,因为博客框架用的是 hugo ,所以生成的站点源码就是纯静态网页。(之所以不直接白嫖 Github pages 服务,是因为百度 spider 被 Github ban 了)。 所以我希望在写完文章后 push 给 Github 后,部署了宝塔面板的腾讯云服务器能 cd 到博客源码目录下执行 git pull 命令,这样就可以从 Github 拉取最新的版本覆盖服务器上当前的版本了,因为是静态资源,所以无需重启什么服务,git pull 完成后即更新完毕。 ","date":"2021-08-18","objectID":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html:0:0","series":null,"tags":["github","webhook","宝塔面板","自动化部署"],"title":"如何使用 Github webhook 配合宝塔 webhook 自动化部署","uri":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html#二配置宝塔面板-webhook"},{"categories":["How-to-use"],"content":" 1)安装宝塔 WebHook 插件去软件商店可以找到这玩意儿 ","date":"2021-08-18","objectID":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html:1:0","series":null,"tags":["github","webhook","宝塔面板","自动化部署"],"title":"如何使用 Github webhook 配合宝塔 webhook 自动化部署","uri":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html#1安装宝塔-webhook-插件"},{"categories":["How-to-use"],"content":" 2)添加 WebHook ","date":"2021-08-18","objectID":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html:2:0","series":null,"tags":["github","webhook","宝塔面板","自动化部署"],"title":"如何使用 Github webhook 配合宝塔 webhook 自动化部署","uri":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html#2添加-webhook"},{"categories":["How-to-use"],"content":" 3)配置执行命令cd /www/wwwroot/sugarlesss.github.io; git pull; 配置完成后,就可以看到一条 webhook 规则了。 ","date":"2021-08-18","objectID":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html:3:0","series":null,"tags":["github","webhook","宝塔面板","自动化部署"],"title":"如何使用 Github webhook 配合宝塔 webhook 自动化部署","uri":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html#3配置执行命令"},{"categories":["How-to-use"],"content":" 4)获取 webhook url点击查看秘钥,蓝色选中的部分就是宝塔面板提供的 webhook url ,当链接被访问时,就会执行我们上一步配置的命令,自动 git pull 了。 ","date":"2021-08-18","objectID":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html:4:0","series":null,"tags":["github","webhook","宝塔面板","自动化部署"],"title":"如何使用 Github webhook 配合宝塔 webhook 自动化部署","uri":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html#4获取-webhook-url"},{"categories":["How-to-use"],"content":" 三、配置 Github webhook还是以我的博客仓库为例,在仓库的 Settings -\u003e Webhooks 页面右上角 add webhook 。 将宝塔提供的 webhook url 填入此处的 Payload URL,并将下方的 Content type 修改为 application/json ,否则 Github 请求此 URL 时,宝塔会返回 403 错误。 ","date":"2021-08-18","objectID":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html:0:0","series":null,"tags":["github","webhook","宝塔面板","自动化部署"],"title":"如何使用 Github webhook 配合宝塔 webhook 自动化部署","uri":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html#三配置-github-webhook"},{"categories":["How-to-use"],"content":" 注意事项 在服务器上生成一对 SSH 秘钥,将公钥配置到你的 Github 账号后,服务器就可以使用 git pull 命令拉取最新版本,不需要输入账号密码了。 如果在配置 Github webhook 时,Content type 你忘记修改为 application/json ,宝塔会返回 403 错误,你配置的一系列自定义命令就无法执行了。 ","date":"2021-08-18","objectID":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html:0:0","series":null,"tags":["github","webhook","宝塔面板","自动化部署"],"title":"如何使用 Github webhook 配合宝塔 webhook 自动化部署","uri":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html#注意事项"},{"categories":["How-to-use"],"content":" REFhttps://www.cnblogs.com/blibli/p/11331509.html ","date":"2021-08-18","objectID":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html:0:0","series":null,"tags":["github","webhook","宝塔面板","自动化部署"],"title":"如何使用 Github webhook 配合宝塔 webhook 自动化部署","uri":"/posts/how-to-use-github-webhook-and-btpanel-webhook.html#ref"},{"categories":["互联网观察"],"content":"如何理解自 2020 年 3 月 23 日起,B 站视频的 AV 号将全面升级为 BV 号这件事? ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:0:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#"},{"categories":["互联网观察"],"content":" 认识一下 B 站根据B 站 2020 年末提交给美国证券交易委员会(SEC)的FORM 20-F 年度报告(2020 年度报告,关键词 Ordinary Shares Beneficially Owned)股权结构上看: 陈睿(董事长/CEO)持股 14.2%,拥有 44.6% 的投票权; 徐逸(创始人/总裁)持股 8%,拥有 24.7% 的投票权; 腾讯持股 12.4%,阿里持股 6.7%; ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:0:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#认识一下-b-站"},{"categories":["互联网观察"],"content":" 一、AV 号与 BV 号有何不同?个人认为 AV 号和 BV 号的区别可以从以下两方面看: 直观规律:原来的 AV 号是纯数字的稿件序列号(具备连续性),当前的 BV 号是一段由数字和大小写字母组成的近似字符串(从直观上看不具备连续性); 最大值:原来的 AV 号是 8 位数,最大数值为 99999999(一个亿),当前的 BV 号长度已经为 10 位,最大数值为( 58 的 10 次方 = 4.3080420689941e+17 ); 那么,这两个差异意味着什么? ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:0:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#一av-号与-bv-号有何不同"},{"categories":["互联网观察"],"content":" 二、AV 号与 BV 号的差异意味着什么?根据 B 站 AV 号全面升级至 BV 号公告 给出的说法,是为了为了保护稿件信息安全,容纳更多投稿。 ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:0:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#二av-号与-bv-号的差异意味着什么"},{"categories":["互联网观察"],"content":" 2.1、保护稿件信息安全原先的 AV 号是稿件的序列号,是一串具备连续性的数字,每个稿件根据投稿时间的先后顺序分配一个唯一的 AV 号,也就是说,越晚投稿的视频,分配到的 AV 号数值越大。那么通过爬虫可以非常轻松的从 AV 号的起始位置 1 开始逐个爬取 B 站的视频,这样 B 站的核心数据(稿件)也就相当于对全网开放下载了。 Robots 协议对大公司可能还有些许威慑力,对于一些不明个人或营销号来说基本是没啥用的。 所以防止大量其他网站利用 AV 号的连续性,使用爬虫盗取 B 站核心数据(稿件),新的规则( BV 号)是无序且随机的,这就是 B 站声明中的 “保护稿件信息安全”。 ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:1:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#21保护稿件信息安全"},{"categories":["互联网观察"],"content":" 2.2、容纳更多投稿原来的 AV 号是 8 位数,最大数值为 99999999(一个亿),假设以 100 万个 UP 主按每 3 天 1 个稿件数量来计算,平均每个 UP 主累计投稿数量只要 100 个,1 亿的 AV 号连一年都不够用。 改成 BV 号后最大数值为( 58 的 10 次方 = 4.3080420689941e+17 ),为啥是这个数值呢?58 是因为 Base58 编码,10 次方是因为 BV 号的长度有 10 位。 所以从 AV 号过渡到 BV 号,的确可以容纳更多的稿件。 另外,短视频时代的到来,使得 B 站也在谋划短视频领域,甚至专门开了一个分区叫 VLOG。可以预见的是,站内的短视频稿件越来越多,有的 VLOG UP 主一天投稿 30 个,还有拿 B 站当 VLOG 网络存储用的。 下面是 09 年到 19 年 B 站的稿件数量变化图,十年过去了, UP 主越来越多,制作视频的工具也在不断进步,十年前的生产力与如今相比是不可同日而语的。 以上 2 点是可以根据 AV 号与 BV 号的差异推测而来的直接因素,也是 B 站的官方口径。然而我在评论区看到了一些其他说法,不一定真实,但可以给你提供一个新的视角和思路。 ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:2:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#22容纳更多投稿"},{"categories":["互联网观察"],"content":" 2.3、阻止第三方对 B 站的数据分析因为 AV 号是随时间递增的,所以通过 AV 号我们可以得知当前 B 站的总稿件数量,也可以轻松推断出日均新增稿件数量。也就是说由于 AV 号的连续性使得我们可以轻易地推算出 B 站的核心数据模型,对数据进行精准分析。对于 B 站来说,这也许不是他们希望看到的。 ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:3:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#23阻止第三方对-b-站的数据分析"},{"categories":["互联网观察"],"content":" 2.4、AV 号并没有弃用(已证实并亲测)B 站官方的声明中称:“自 2020 年 3 月 23 日起,AV号将全面升级为BV号。”,但目前根据我搜集到的数据显示,AV 号并没有弃用。 以半佛当前最新一期的视频为例: BV 号链接:https://www.bilibili.com/video/BV1C3411r79b AV 号链接:https://www.bilibili.com/video/av419667559 这两个链接可以跳转到同一个视频,而并没有显示视频不存在或 404 之类的错误提示。这意味着,实际上 AV 号仍然在新稿件上沿用。 但似乎 1 亿后的 AV 号不再连续,似乎是开始随机抽取的,当然,也有可能是我没有发现其规则。 根据知乎大神与评论区的解析,有人做了一个在线 BV 号转 AV 号的在线转换工具:https://b.gqxqd.cn/bv2av/ 目前新的 BV 号转出来的 AV 号已经不是连续的了,即 AV 号的生成已经换了新的方案。B 站从 AV 号升级到 BV 号后,旧的 AV 号是保留的,所以依然不影响爬取早期数据。那么为什么不让 8 位 AV 号之后的新 AV 号不再是连续的呢?多加几位数也是可以用下去的,如果 AV 号是 int 型存储的话应该还能存很久,毕竟能到2147483647 呢,再不行就用 bigint 呗… 关于这点,我也不明白 B 站在想什么。 ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:4:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#24av-号并没有弃用已证实并亲测"},{"categories":["互联网观察"],"content":" 2.5、破圈成长与去 ACG 化其实说去 ACG 化有点言过其实,但在 B 站破圈成长的过程中,一定会踊跃出新的内容,相比之下 ACG 内容和 ACG 爱好者就要做出相应的妥协,让出相应的流量,修改规则去适应大众。B 站的用户群体也从早已从喜欢二次元的青少年,变成了面向全民。 对于 AV 号,我估计肯定会有不少小伙伴会有 “AV+数字和字母=番号” 这一第一印象… 另外,从 B 站 APP 的启动页也能看出一些端倪,原来 2233 娘干杯小电视不过是个发卡,后来酒没了,最后人没了,独留个小电视在憨笑( B 站手机客户端 2015-2020 启动页面变化 )。 其实 B 站最早是借鉴 niconico 的 SM 号( smile video )的命名方式搞了 AV 号( acg video ),两个都是恶趣味,且都说自己不是那个意思。 bilibili 小电视也是模仿的 niconico 小电视,把 AV 号换掉也姑且能算做是摆脱恶趣味了。 ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:5:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#25破圈成长与去-acg-化"},{"categories":["互联网观察"],"content":" 2.6、减轻内容风控给用户带来的负面情绪内容风控对于任何一个 UGC 产品来说都是绕不开的话题,内容风控主要 “控” 啥? 政策层面的内容合规,要符合监管的要求; 社区氛围的良性导向,如谩骂、隐私等; 社区用户安全的保证,遏制黑灰产等; 从连续的 AV 号过渡到近似无序字符串的 BV 号,删除视频也变得隐秘了几分,B 站也许也希望在内容风控的基础上,尽量减少用户的抵触情绪。 ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:6:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#26减轻内容风控给用户带来的负面情绪"},{"categories":["互联网观察"],"content":" 2.7、技术原因有可能是因为 MySQL 大表做自增 id 不利于分表和分布式,这点就不展开了。 ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:7:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#27技术原因"},{"categories":["互联网观察"],"content":" 三、 BV 号规则与 Base58 / 64 编码在线 BV 号转 AV 号的在线转换工具:https://b.gqxqd.cn/bv2av/ ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:0:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#三-bv-号规则与-base58--64-编码"},{"categories":["互联网观察"],"content":" 3.1、BV 号的规则根据知乎大神与评论区的解析,BV 号的规律大概就是 AV 号异或某个随便选择的数,再随便加一个数,最后再转化成 58 进制(Base58),其数位顺序是打乱的,其索引代表 0~57 的 字母 / 数字 也是乱序的。 既然提到了 Base58 ,也顺便提一下讲 Base64 吧。 ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:1:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#31bv-号的规则"},{"categories":["互联网观察"],"content":" 3.2、Base58 编码总计 58 个字符,相比 Base64,Base58 不使用数字 “0”,字母大写 “O”,字母大写 “I”,和字母小写 “l”,以及 “+” 和 “/” 符号。 Base58 是用于 Bitcoin 中使用的一种独特的编码方式,主要用于产生 Bitcoin 的钱包地址。 Base58 编码的字符范围: 123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:2:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#32base58-编码"},{"categories":["互联网观察"],"content":" 3.3、Base64 编码64 个字符:英文大小写 26 * 2 = 52 个,阿拉伯数字 0 ~ 9 = 10 个,特殊字符 + / 两个,总计 64 个。 Base64 编码的字符范围: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:3:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#33base64-编码"},{"categories":["互联网观察"],"content":" REF投票权与股权不成正比的关系是怎么产生的 B 站 2020 年末提交给美国证券交易委员会(SEC)的FORM 20-F 年度报告(2020 年度报告,其中含股权结构,关键词 Ordinary Shares Beneficially Owned) B 站 AV 号全面升级至 BV 号公告 如何看待 2020 年 3 月 23 日哔哩哔哩将稿件的「av 号」变更为「BV 号」? 带你看 B 站十年 av 号爆炸式增长 B 站手机客户端 2015-2020 启动页面变化 从B站审核变慢现象,聊聊内容社区产品的内容风控要点 B 站关于评论区功能升级的公告 ","date":"2021-08-08","objectID":"/posts/bilibili-video-numbering-rules-upgrade.html:0:0","series":null,"tags":["B 站"],"title":"如何理解 B 站 AV 号规则升级为 BV 号这件事?","uri":"/posts/bilibili-video-numbering-rules-upgrade.html#ref"},{"categories":["Mark"],"content":"Cloud Toolkit 帮助开发者将本地应用程序一键部署到任意服务器。 修改完代码后,无需重复「打包-上传-登录服务器-替换-部署-重启」的繁琐流程。 内置终端 Terminal:在 IDE 内,开发者可通过内置终端 Terminal 快速登录远程服务器,减少频繁切换的烦恼。 不仅支持阿里云服务器 ECS,也可以用于所有支持标准 SSH 协议的机器。 文件上传:Cloud Toolkit 帮助开发者在 IDE 内,一键将本地或远程 URL 文件上传至服务器指定目录。 简化繁琐的上传环节,无需各种 FTP、SCP 工具频繁切换。 更为重要的是,文件上传完毕后,还支持文件解压缩、程序启动等命令执行。 cloud toolkit 官网:https://www.aliyun.com/product/cloudtoolkit ","date":"2021-08-06","objectID":"/posts/how-to-use-alibaba-cloud-toolkit.html:0:0","series":null,"tags":["Java","IDEA","Cloud Toolkit","Spring Boot","一键部署"],"title":"IDEA 使用 Alibaba Cloud Toolkit 插件一键部署 Spring Boot 项目","uri":"/posts/how-to-use-alibaba-cloud-toolkit.html#"},{"categories":["Mark"],"content":" 一、安装 Alibaba Cloud Toolkit 确保 IntelliJ IDEA 在 2018.1 或更高版本; 打开 Settings - Plugins 搜索安装 Alibaba Cloud Toolkit; 如果迟迟安装不上,可以试试设置一下 IDEA socks 代理或者去它的官网找离线安装包; Alibaba Cloud Toolkit ","date":"2021-08-06","objectID":"/posts/how-to-use-alibaba-cloud-toolkit.html:0:0","series":null,"tags":["Java","IDEA","Cloud Toolkit","Spring Boot","一键部署"],"title":"IDEA 使用 Alibaba Cloud Toolkit 插件一键部署 Spring Boot 项目","uri":"/posts/how-to-use-alibaba-cloud-toolkit.html#一安装-alibaba-cloud-toolkit"},{"categories":["Mark"],"content":" 二、配置","date":"2021-08-06","objectID":"/posts/how-to-use-alibaba-cloud-toolkit.html:0:0","series":null,"tags":["Java","IDEA","Cloud Toolkit","Spring Boot","一键部署"],"title":"IDEA 使用 Alibaba Cloud Toolkit 插件一键部署 Spring Boot 项目","uri":"/posts/how-to-use-alibaba-cloud-toolkit.html#二配置"},{"categories":["Mark"],"content":" 2.1、添加远程服务器在 idea 底部有一个 tab 是 Alibaba Cloud View ,右上角 Add Host Add Host 输入服务器的 IP 及 SSH 连接信息,我使用的是账号密码式。 配置 IP 和 SSH 连接信息 Edit Configurations Edit Configurations 选择 Deploy to Host Deploy to Host ","date":"2021-08-06","objectID":"/posts/how-to-use-alibaba-cloud-toolkit.html:1:0","series":null,"tags":["Java","IDEA","Cloud Toolkit","Spring Boot","一键部署"],"title":"IDEA 使用 Alibaba Cloud Toolkit 插件一键部署 Spring Boot 项目","uri":"/posts/how-to-use-alibaba-cloud-toolkit.html#21添加远程服务器"},{"categories":["Mark"],"content":" 2.2、添加详细配置 详细配置1 详细配置2 下面是上图添加详细配置时用到的命令: # 部署路径( Target Directory ) /www/wwwroot/SpringBootDeployTestJar # 部署前执行命令() pkill -f main-0.0.1-SNAPSHOT.jar; # 部署后执行命令 ( After deploy ) nohup java -jar /www/wwwroot/SpringBootDeployTestJar/main-0.0.1-SNAPSHOT.jar --server.port=18080\u003e /www/wwwroot/SpringBootDeployTestJar/nohup.out 2\u003e\u00261 \u0026 # 实时日志 tail -f /www/wwwroot/SpringBootDeployTestJar/nohup.out -n500 填写完成后 Apply 。 ","date":"2021-08-06","objectID":"/posts/how-to-use-alibaba-cloud-toolkit.html:2:0","series":null,"tags":["Java","IDEA","Cloud Toolkit","Spring Boot","一键部署"],"title":"IDEA 使用 Alibaba Cloud Toolkit 插件一键部署 Spring Boot 项目","uri":"/posts/how-to-use-alibaba-cloud-toolkit.html#22添加详细配置"},{"categories":["Mark"],"content":" 三、一键部署切换到一键部署,run。 一键部署 此时 idea 会自动连接上服务器的 shell ,使用上面配置的命令查看实时日志。 控制台实时日志 ","date":"2021-08-06","objectID":"/posts/how-to-use-alibaba-cloud-toolkit.html:0:0","series":null,"tags":["Java","IDEA","Cloud Toolkit","Spring Boot","一键部署"],"title":"IDEA 使用 Alibaba Cloud Toolkit 插件一键部署 Spring Boot 项目","uri":"/posts/how-to-use-alibaba-cloud-toolkit.html#三一键部署"},{"categories":["Mark"],"content":" REFhttps://www.bilibili.com/video/BV1ga4y1474K ","date":"2021-08-06","objectID":"/posts/how-to-use-alibaba-cloud-toolkit.html:0:0","series":null,"tags":["Java","IDEA","Cloud Toolkit","Spring Boot","一键部署"],"title":"IDEA 使用 Alibaba Cloud Toolkit 插件一键部署 Spring Boot 项目","uri":"/posts/how-to-use-alibaba-cloud-toolkit.html#ref"},{"categories":["Spring Boot"],"content":"在配置文件 application.properties 或 application.yaml 中,配置项如果填写的是中文,读取后是乱码,但是如果使用 unicode 编码方式进行书写,则可以正确识别。这是因为 .properties 文件的默认读取配置是使用的是 ISO_8859_1 编码,这个编码是不支持中文的。 ","date":"2021-08-05","objectID":"/posts/spring-boot-configuration-file-chinese-encoding-error.html:0:0","series":null,"tags":["Spring","Spring Boot","中文乱码"],"title":"Spring Boot 配置文件中文乱码","uri":"/posts/spring-boot-configuration-file-chinese-encoding-error.html#"},{"categories":["Spring Boot"],"content":" 解决方案","date":"2021-08-05","objectID":"/posts/spring-boot-configuration-file-chinese-encoding-error.html:0:0","series":null,"tags":["Spring","Spring Boot","中文乱码"],"title":"Spring Boot 配置文件中文乱码","uri":"/posts/spring-boot-configuration-file-chinese-encoding-error.html#解决方案"},{"categories":["Spring Boot"],"content":" 1、修改 IDEA 编码设置为 UTF-8 ,删除并重新创建 application.properties 文件我使用的是 IDEA ,首先 File-\u003esettings-\u003eCode style-\u003eFile Encoding ,把所有的编码都设为 UTF-8 ,删除并重新创建 application.properties 文件即可。 ","date":"2021-08-05","objectID":"/posts/spring-boot-configuration-file-chinese-encoding-error.html:1:0","series":null,"tags":["Spring","Spring Boot","中文乱码"],"title":"Spring Boot 配置文件中文乱码","uri":"/posts/spring-boot-configuration-file-chinese-encoding-error.html#1修改-idea-编码设置为-utf-8-删除并重新创建-applicationproperties-文件"},{"categories":["Spring Boot"],"content":" REFhttps://blog.csdn.net/chenfei2341/article/details/98854474 ","date":"2021-08-05","objectID":"/posts/spring-boot-configuration-file-chinese-encoding-error.html:0:0","series":null,"tags":["Spring","Spring Boot","中文乱码"],"title":"Spring Boot 配置文件中文乱码","uri":"/posts/spring-boot-configuration-file-chinese-encoding-error.html#ref"},{"categories":["3000问"],"content":"为什么 CDN 能加速网站访问? 当前大厂提供的 CDN 价格有多高? ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:0:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#"},{"categories":["3000问"],"content":" 一、CDN 是什么?CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN 的关键技术主要有内容存储和分发技术。 CDN 这个概念始于1996年,是美国麻省理工学院的一个研究小组为改善互联网的服务质量而提出的。为了能在传统IP网上发布丰富的宽带媒体内容,他们提出在现有互联网基础上建立一个内容分发平台专门为网站提供服务,并于 1999 年成立了专门的 CDN 服务公司,为 Yahoo 提供专业服务。由于 CDN 是为加快网络访问速度而被优化的网络覆盖层,因此被形象地称为“网络加速器”。 ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:0:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#一cdn-是什么"},{"categories":["3000问"],"content":" 二、CDN 加速原理","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:0:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#二cdn-加速原理"},{"categories":["3000问"],"content":" 2.1、原理以接入腾讯云 CDN 为例,假设业务源站域名为 www.test.com,域名接入 CDN 开始使用加速服务后,当用户发起 HTTP 请求时,实际的处理流程如下图所示: 详细说明如下: 用户向 www.test.com 下的某图片资源(如:1.jpg)发起请求,会先向 Local DNS 发起域名解析请求。 当 Local DNS 解析 www.test.com 时,会发现已经配置了 CNAME www.test.com.cdn.dnsv1.com,解析请求会发送至 Tencent DNS(GSLB),GSLB 为腾讯云自主研发的调度体系,会为请求分配最佳节点 IP。 Local DNS 获取 Tencent DNS 返回的解析 IP。 用户获取解析 IP。 用户向获取的 IP 发起对资源 1.jpg 的访问请求。 若该 IP 对应的节点缓存有 1.jpg,则会将数据直接返回给用户(10),此时请求结束。若该节点未缓存 1.jpg,则节点会向业务源站发起对 1.jpg 的请求(6、7、8),获取资源后,结合用户自定义配置的缓存策略(可参考产品文档中的缓存过期配置,将资源缓存至节点(9),并返回给用户(10),此时请求结束。 ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:1:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#21原理"},{"categories":["3000问"],"content":" 2.2、解决什么问题?CDN 有效地解决了目前互联网业务中网络层面的以下问题: 用户与业务服务器地域间物理距离较远,需要进行多次网络转发,传输延时较高且不稳定。 用户使用运营商与业务服务器所在运营商不同,请求需要运营商之间进行互联转发。 业务服务器网络带宽、处理能力有限,当接收到海量用户请求时,会导致响应速度降低、可用性降低。 且 CDN 接入比较简单,源站无需调整自身业务结构,或是进行复杂的操作配置,即可享受全球 CDN 加速服务。 ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:2:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#22解决什么问题"},{"categories":["3000问"],"content":" 三、CDN 究竟有多贵?这里我找了 5 家不同的云厂商 CDN 产品来对比他们的价格,但需要注意的是: 此处的价格对比仅针对中国大陆 仅对比 100G / 500G / 1TB 三个档位的资源包价格 资源包有效期都选择 1 年 先说结论:腾讯云的价格是最低的。 100G 1年有效期的资源包:价格从 17~21 元不等,腾讯云价格最低,七牛云价格最高。 500G 1年有效期的资源包:价格从 90-100 元不等,京东云价格最低,又拍云价格最高。 1TB 1年有效期的资源包:价格从 165-270 元不等,腾讯云价格最低,又拍云价格最高。 ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:0:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#三cdn-究竟有多贵"},{"categories":["3000问"],"content":" 3.1、七牛云100G 档 21 元 500G 档 99 元 1TB 档 189 元 CDN中国大陆资源包:官网链接 七牛云的特色是支持 全时段 / 日间 / 闲时 三种不同价格的资源包,价格也不同,可以根据自己的业务需求有针对性的购买。 ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:1:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#31七牛云"},{"categories":["3000问"],"content":" 3.2、又拍云100G 档 20 元 500G 档 100 元 1TB 档 270 元 CDN 大陆流量包购买:官网链接 又拍云有额外限制:100GB、500GB 属于特惠流量包,只能二选一,且在有效期三个月内仅可购买一次 ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:2:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#32又拍云"},{"categories":["3000问"],"content":" 3.3、京东云100G 档 18 元 500G 档 90 元 1TB 档 180 元 CDN资源包:官网链接 ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:3:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#33京东云"},{"categories":["3000问"],"content":" 3.4、腾讯云100G 档 17 元 500G 档 84 元 1TB 档 165 元 CDN流量包:官网链接 不愧是良心云,价格优势巨大。 ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:4:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#34腾讯云"},{"categories":["3000问"],"content":" 3.5、阿里云100G 档 20 元 500G 档 95 元 1TB 档 180 元 CDN/全站加速资源包:官网链接 ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:5:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#35阿里云"},{"categories":["3000问"],"content":" REFCDN 百度百科:https://baike.baidu.com/item/CDN 腾讯云 CDN 产品概述:https://cloud.tencent.com/document/product/228/2939 ","date":"2021-08-02","objectID":"/posts/what-is-cdn.html:0:0","series":null,"tags":["CDN"],"title":"CDN 是什么? 有多贵?","uri":"/posts/what-is-cdn.html#ref"},{"categories":["3000问"],"content":"4 核 8 线程,8 核 16 线程是啥意思? 为什么不是 4 核 4 线程或 8 核 32 线程? ","date":"2021-07-25","objectID":"/posts/about-hyper-threading-technology.html:0:0","series":null,"tags":["CPU","超线程"],"title":"为什么 CPU 的线程数是核心数的两倍?","uri":"/posts/about-hyper-threading-technology.html#"},{"categories":["3000问"],"content":" 一、关于超线程超线程是英特尔公司命名的技术(HT, Hyper-Threading),但是这项技术最早起源于 IBM ,学术一些的名字叫同时多线程(SMT,Simulate Multi-Threading)。 超线程技术其实是把一个 CPU 的物理核模拟成两个虚拟核用,通过此技术,英特尔实现在一个实体 CPU 物理核中,提供两个逻辑线程。让单个物理核心就能使用线程级的并行计算,进而兼容多线程操作系统和软件。超线程技术充分利用空闲 CPU 资源,在相同时间内完成更多工作。 虽然采用超线程技术能够使得单个物理核心同时执行两个线程,但是当两个线程同时需要某个资源时,其中一个线程必须让出资源暂时挂起,直到这些资源空闲以后才能继续。因此,超线程的性能并不等于两个物理核心的性能。而且,超线程技术的 CPU 需要芯片组、操作系统和应用软件的支持,才能比较理想地发挥该项技术的优势。 ","date":"2021-07-25","objectID":"/posts/about-hyper-threading-technology.html:0:0","series":null,"tags":["CPU","超线程"],"title":"为什么 CPU 的线程数是核心数的两倍?","uri":"/posts/about-hyper-threading-technology.html#一关于超线程"},{"categories":["3000问"],"content":" 二、为什么超线程只虚拟出两个逻辑核?先说一些耳熟能详的数字:四核八线程、八核十六线程等。这是典型的超线程技术应用,一个物理核心虚拟出两个逻辑核。 超线程的基本原理,用大白话讲就是:单核心工作量不饱满,给两个线程能提高利用率。就好比有的打工人业余时间能搞个副业一样,但可以压榨的工作量显然是有限的。 每个 CPU 核心里的 ALU,FPU 这些运算单元的数量是有限的,而超线程的目的之一就是在一个线程用运算单元少的情况下,让另外一个线程跑起来,不让运算单元闲着。但是现在的CPU那么发达,有分支预测乱序执行等技术,内存,IO的频率正在逐渐提高,已经可以让CPU的核处于忙碌状态,2 个已经差不多,无需搞出更多个线程了。 对于计算密集的程序,超线程甚至会拖累性能。 如果当一个线程整数,浮点运算各种多,当前核心运算单元没多少空闲了,这时候你再塞进了一个线程,这下子资源就紧张了。两线程就会互相抢资源,拖慢对方速度。所以,如果你的程序是单线程,关了超线程,免得别人抢你资源,如果是多线程,每个线程运算不大,超线程比较有用。 补充: 超线程并不仅限于两个逻辑核,如果想的话可以搞出来更多,代表是IBM的power系列:SMT8(每核8线程)的设计,也是相当恐怖了。但是这种设计其实并不是符合大部分通用应用的需求。 SMT的出发点还是还是处理器内部的执行指令的时候,因为许多原因(比如等待内存IO),导致CPU中的许多资源空闲,所以可以让CPU跑多个线程,进而提高资源利用率,提升性能。这种方法还有一个优点,对于一些存在缓存共享,负载不高的线程,通过这种方式,分配到相同核心的不同超线程下,可以节约切换开销。 但是这些自然不是免费,超线程的副作用存在于很多方面。 首先很明显,处理器核心中各种执行资源都是有限的,某些资源(比如ALU)在几个线程之间争用,一些资源需要直接平分给几个个线程。结果可能导致两个线程执行的速度都下降,甚至不如单线程运行,这在一些本身优化较好的SIMD程序中比较常见。所以我们很多日常用支持SMT的处理器,即使bios开启SMT,很多时候,尤其是低载情况下都是运行在1T模式的(也就是核心只有一个线程运行)。 各个线程的任务虽然跑在一个物理核心上,但是他们的指令,寄存器等等仍是需要分开的,处理器核心内部的其他执行资源也需要进行动态或者静态的分配,这些都需要在处理器中添加额外的调度,增加晶体管。与之对应,一个超频爱好者比较熟悉的现象就是关超线程超频,可能更凉快,同时频率更高。 这些多余的晶体管花在SMT上是否值得,拿来提升处理器单线程执行资源的利用率或者堆更多资源提升单线程性能,是不是会有更多收益就变成了一个值得权衡的问题。 对于Power这种SMT8的怪物,上面两个问题会因为SMT线程数增多而加剧,为了缓解这种情况需要堆更多的资源,这又会增加调度复杂度,需要更多的晶体管,为了解决只好把部分资源按超线程进行直接划分,这部分资源在单线程模式下也会无法利用,导致单核性能偏弱,这在很大程度不符合大部分通用应用中的需求(这个其实挺显然,很多时候都能体会到,一部分在于很多程序并没有很好的多线程优化而依赖于单线程性能,同时并行化到最后实际的瓶颈也在单线程。 ","date":"2021-07-25","objectID":"/posts/about-hyper-threading-technology.html:0:0","series":null,"tags":["CPU","超线程"],"title":"为什么 CPU 的线程数是核心数的两倍?","uri":"/posts/about-hyper-threading-technology.html#二为什么超线程只虚拟出两个逻辑核"},{"categories":["3000问"],"content":" REFhttps://www.zhihu.com/question/444841440/answer/1745910660 https://www.cnblogs.com/jiading/articles/12588488.html https://baike.baidu.com/item/%E8%B6%85%E7%BA%BF%E7%A8%8B ","date":"2021-07-25","objectID":"/posts/about-hyper-threading-technology.html:0:0","series":null,"tags":["CPU","超线程"],"title":"为什么 CPU 的线程数是核心数的两倍?","uri":"/posts/about-hyper-threading-technology.html#ref"},{"categories":["折腾工坊"],"content":"长时间的电话 / 会议录音 / 演讲视频之类的各种音视频媒体文件的信息,如果有长期存储的需求的话,你可能会因为体积积太大,不方便进行信息检索而头疼。但如果转成文字,润色后整理成文字稿,无论是从存储体积或是信息检索方面,都会比直接存储音视频媒体更方便些。 尝遍了市面上各种音频转文字的野鸡产品后,我最终还是选择用大厂提供的 API ,to B 的产品在价格和可定制性上肯定比 to C 的产品更有优势。 关于如何将视频转音频,手段很多,比如使用 FFmpeg 可以将视频转为音频: ffmpeg -i input.mp4 -vn -acodec copy audio.mp3 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:0:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#"},{"categories":["折腾工坊"],"content":" 一、音频转文字究竟有多贵?参考当前的大型互联网厂商提供的音频转文字服务定价,讯飞最贵、百度云论单价虽然最便宜,但起购门槛金额较高,比较适合商用走量、,腾讯云的价格最合适,而且最关键的是腾讯云每月都有附赠一定量的额度,可以无限白嫖。 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:0:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#一音频转文字究竟有多贵"},{"categories":["折腾工坊"],"content":" 1.1、讯飞语音转写:文档地址 产品价格:文档地址 套餐一( 20 小时) 168 元 / 单价 8.4 套餐二( 200 小时) 980 元 / 单价 4.9 套餐三( 1000 小时) 3900 元 / 单价 3.9 套餐四( 3000 小时) 10500 元 / 单价 3.5 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:1:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#11讯飞"},{"categories":["折腾工坊"],"content":" 1.2、百度云音频文件转写:文档地址 按小时包预付费:文档地址 按调用时长后付费:文档地址 按小时包预付费 套餐一( 1000 小时) 1200 元 / 单价 1.2 套餐二( 10000 小时) 9000 元 / 单价 0.9 套餐三( 100000 小时) 70000 元 / 单价 0.7 套餐四( 500000 小时) 300000 元 / 单价 0.6 按调用时长后付费 每小时 2 元,系统按用户实际使用,每小时出账单实时扣费,账户内需保留足量余额。 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:2:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#12百度云"},{"categories":["折腾工坊"],"content":" 1.3、腾讯云语音识别 ASR:https://cloud.tencent.com/product/asr 免费配额:https://console.cloud.tencent.com/asr/resourcebundle 资源包购买地址:https://buy.cloud.tencent.com/asr 录音文件识别(五小时内出结果) 套餐一( 60 小时) 90 元 / 单价 1.5 套餐二( 1000 小时) 1200 元 / 单价 1.2 套餐三( 10000 小时) 10000 元 / 单价 1 套餐四( 100000 小时) 80000 元 / 单价 0.8 套餐五( 300000 小时) 210000 元 / 单价 0.7 支持中文普通话、英语、粤语、日语、泰语。对时长5小时以内的录音文件进行识别,异步返回识别全部结果。 支持语音 URL 和本地语音文件两种请求方式。 语音 URL 的音频时长不能长于5小时,文件大小不超过512MB。 本地语音文件不能大于5MB。 提交录音文件识别请求后,在5小时内完成识别(半小时内发送超过1000小时录音或者2万条识别任务的除外),识别结果在服务端可保存7天 支持回调或轮询的方式获取结果 录音文件识别极速版(准实时) 套餐一( 30 小时) 72 元 / 单价 2.3 套餐二( 1000 小时) 1500 元 / 单价 1.5 套餐三( 10000 小时) 12000 元 / 单价 1.2 套餐四( 100000 小时) 110000 元 / 单价 1.1 套餐五( 300000 小时) 300000 元 / 单价 1 仅支持中文普通话,使用者通过 HTTPS POST 方式上传一段音频并在极短时间内同步返回识别结果,可满足音视频字幕、准实时质检等场景下对语音文件识别时效性的要求。 支持100MB以内音频文件的识别 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:3:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#13腾讯云"},{"categories":["折腾工坊"],"content":" 1.4、阿里云录音文件识别:文档地址 录音文件识别资源包:购买链接 录音文件识别(极速版)资源包:购买链接 录音文件识别(六小时内出结果) 套餐一( 40 小时) 100 元 / 单价 2.5 套餐二( 1000 小时) 1200 元 / 单价 1.2 套餐三( 20000 小时) 20000 元 / 单价 1 套餐四( 100000 小时) 90000 元 / 单价 0.9 套餐五( 250000 小时) 200000 元 / 单价 0.8 录音文件识别极速版(30分钟以内时长的音频转写完成时间不超过10秒) 套餐一( 40 小时) 90 元 / 单价 2.5 套餐二( 1000 小时) 1560 元 / 单价 1.56 套餐三( 20000 小时) 26000 元 / 单价 1.3 套餐四( 100000 小时) 117000 元 / 单价 1.17 套餐五( 250000 小时) 260000 元 / 单价 1.04 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:4:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#14阿里云"},{"categories":["折腾工坊"],"content":" 二、对接腾讯云在使用腾讯云 API 之前,你需要先获取三个必要的参数。 在腾讯云控制台账号信息页面查看账号 APPID:https://console.cloud.tencent.com/developer 访问管理页面获取 SecretID 和 SecretKey:https://console.cloud.tencent.com/cam/capi ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:0:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#二对接腾讯云"},{"categories":["折腾工坊"],"content":" 2.1、录音文件识别极速版(V2)极速版演示,目前腾讯云给的免费配额是每月 5 小时。 极速版仅支持中文普通话,通过 HTTPS POST 方式上传一段音频并在极短时间内同步返回识别结果,可满足音视频字幕、准实时质检等场景下对语音文件识别时效性的要求。支持100MB以内音频文件的识别。 1)将真实的值填入下方源代码的 APPID、SECRET_ID、SECRET_KEY 这三个参数中; 2)将源代码中的 audio 参数修改为本地音频文件的相对或绝对路径; 3)run it。 # 录音文件识别极速版 v2版本 # API 文档:https://cloud.tencent.com/document/api/1093/52097 # 签名生成:https://cloud.tencent.com/document/api/1093/52097#sign import json import requests import time import hashlib import hmac import base64 # 调用者身份 class Credential: def __init__(self, app_id, secret_id, secret_key): self.app_id = app_id self.secret_id = secret_id self.secret_key = secret_key # 服务端信息 class Server: def __init__(self, protocol, host, port, uri): self.protocol = protocol self.host = host self.port = port self.uri = uri # 打印日志 def show(title=\"\", content=object, level=\"INFO\"): print(\"[\" + level + \"] \" + title) print(content) print() # 获取时间戳 def getTimestamp(): t = time.time() # return t # 原始时间数据 return int(t) # 秒级时间戳 # return int(round(t * 1000)) # 毫秒级时间戳 # return int(round(t * 1000000)) # 微秒级时间戳 # 字典序排序 def format_sign_string(param): param = sorted(param.items(), key=lambda d: d[0]) signstr = f\"POST{server.host + server.uri + credential.app_id}\" for t in param: if 'appid' in t: signstr += str(t[1]) break signstr += \"?\" for x in param: tmp = x if 'appid' in x: continue for t in tmp: signstr += str(t) signstr += \"=\" signstr = signstr[:-1] signstr += \"\u0026\" signstr = signstr[:-1] return signstr # 签名 def sign(signstr, secret_key): hmacstr = hmac.new(secret_key.encode('utf-8'), signstr.encode('utf-8'), hashlib.sha1).digest() s = base64.b64encode(hmacstr) s = s.decode('utf-8') return s # 调用者信息对象初始化 # 在腾讯云控制台账号信息页面查看账号 APPID:https://console.cloud.tencent.com/developer # 访问管理页面获取 SecretID 和 SecretKey:https://console.cloud.tencent.com/cam/capi APPID = \"\" SECRET_ID = \"\" SECRET_KEY = \"\" credential = Credential(APPID, SECRET_ID, SECRET_KEY) # 服务端信息对象初始化 PROTOCOL = \"https://\" HOST = \"asr.cloud.tencent.com\" PORT = \"\" URI = \"/asr/flash/v1/\" server = Server(PROTOCOL, HOST, PORT, URI) # 构造参数 params = { # 用户在腾讯云注册账号 AppId 对应的 SecretId \"secretid\": f\"{credential.secret_id}\", # 引擎模型类型。8k_zh:8k 中文普通话通用;16k_zh:16k 中文普通话通用;16k_zh_video:16k 音视频领域。 \"engine_type\": \"16k_zh\", # 音频格式。支持 wav、pcm、ogg-opus、speex、silk、mp3、m4a、aac。 \"voice_format\": \"mp3\", # 当前 UNIX 时间戳,如果与当前时间相差超过3分钟,会报签名失败错误。 \"timestamp\": getTimestamp(), } # 是否开启说话人分离(目前支持中文普通话引擎),默认为0,0:不开启,1:开启。 # params[\"speaker_diarization\"] = 0; # 是否过滤脏词(目前支持中文普通话引擎),默认为0。0:不过滤脏词;1:过滤脏词;2:将脏词替换为 *。 # params[\"filter_dirty\"] = 0; # 是否过滤语气词(目前支持中文普通话引擎),默认为0。0:不过滤语气词;1:部分过滤;2:严格过滤。 # params[\"filter_modal\"] = 0; # 是否过滤标点符号(目前支持中文普通话引擎),默认为0。0:不过滤,1:过滤句末标点,2:过滤所有标点。 # params[\"filter_punc\"] = 0; # 是否进行阿拉伯数字智能转换,默认为1。0:全部转为中文数字;1:根据场景智能转换为阿拉伯数字。 params[\"convert_num_mode\"] = 1; # 是否显示词级别时间戳,默认为0。0:不显示;1:显示,不包含标点时间戳,2:显示,包含标点时间戳。 # params[\"word_info\"] = 0; # 是否只识别首个声道,默认为1。0:识别所有声道;1:识别首个声道。 # params[\"first_channel_only\"] = 1; show(\"生成 params\", params) # 获取 signature signstr = format_sign_string(params) signature = sign(signstr, SECRET_KEY) show(\"生成 signstr\", signstr) show(\"生成 signature\", signature) # 计算 URL URL = server.protocol + signstr[4::] show(\"计算 URL\", URL) # 构造 header headers = { \"Host\": \"asr.cloud.tencent.com\", \"Authorization\": f\"{signature}\", # \"Content-Type\": \"application/octet-stream\", # \"Content-Length\": \"请求长度,此处对应语音数据字节数,单位:字节\" } show(\"生成 headers\", headers) # 音频路径 audio = \"./外卖佣金到底有多高.mp3\" with open(audio, 'rb') as f: # 读取音频数据 data = f.read() # 调用腾讯云语音识别 API e = requests.post(URL, headers=headers, data=data) responese = json.loads(e.text) code = responese[\"code\"] if code != 0: show(\"识别失败\", responese) else: show(\"识别成功\", responese) # 一个channl_result对应一个声道的识别结果 # 大多数音频是单声道,对应一个channl_result for channl_result in responese[\"flash_result\"]: channel_id = channl_result['channel_id'] text = channl_result['text'] show(f\"channel_id: {channel_id}\", text) 输出 [INFO] 生成 params {'secretid': 'xxxxxxxxxxxxxxxxxxx', 'engine_type': '16k_zh', 'voice_format': 'mp3', 'timestamp': xxxxxxxxx, 'convert_num_mode': 1} [INFO] 生成 signstr POSTasr.cloud.tencent.com/asr/flash/v1/xxxxxxxxxxxxx?convert_num_mode=1\u0026engine_type=16k_zh\u0026secretid=xxxxxxxxxxxxxxxxxx\u0026timestamp=xxxxxxxxxx\u0026voice_format=mp3 [INFO] 生成 signature xxxxxxxxxxxxxxxxxxx= [INFO] 计算 URL https://asr.cloud.tencent.com/asr/flash/v1/xxxxxxxxx?convert_num_mode=1\u0026engine_type=16k_zh\u0026secretid=xxxxxxxxxxxxxxxxxxxxxxxxxx\u0026timestamp=xxxxxxx\u0026voice_format=mp3 [INFO] 生成 headers {'Host': 'asr.cloud.tencent.com', 'Authorization': 'xxxxxxxxxxxxxxxxxxxxx='} [INFO] 识别成功 {'request_id': 'xxxxxxxxxxxxxxxxxx', 'code': 0, 'message': '', 'audio_duration': xxxxxxxx, 'flash_result': [{'text': '测试音频转文字内容', 'start_time': xxxxxxxxx, 'end_time': xxxxxx, 'speaker_id': 0}]}]} [INFO] channel_id: 0 测试音频转文字内容 如果有特殊需求,根据可选的 params 参数去调整就好了。 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:1:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#21录音文件识别极速版v2"},{"categories":["折腾工坊"],"content":" 2.2、录音文件识别(V3)录音文件识别演示,目前腾讯云给的免费配额是每月 10 小时。 录音文件识别请求 API 文档:https://cloud.tencent.com/document/api/1093/37823 录音文件识别结果查询 API 文档:https://cloud.tencent.com/document/api/1093/37822 腾讯云 API 3.0 提供了配套的开发工具集 SDK:https://cloud.tencent.com/document/api/1093/37823#SDK ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:2:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#22录音文件识别v3"},{"categories":["折腾工坊"],"content":" 2.2.1、安装 Python SDK在使用录音文件识别时,需要通过 pip 方式安装腾讯云提供的 Python 版本 SDK: pip install --upgrade tencentcloud-sdk-python # 中国大陆地区的用户可以使用国内镜像源提高下载速度 pip install -i https://mirrors.tencent.com/pypi/simple/ --upgrade tencentcloud-sdk-python。 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:2:1","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#221安装-python-sdk"},{"categories":["折腾工坊"],"content":" 2.2.2、上传本地录音文件识别腾讯云提供的 API Explorer 可以很方便的生成代码和参数:https://console.cloud.tencent.com/api/explorer?Product=asr\u0026Version=2019-06-14\u0026Action=CreateRecTask\u0026SignVersion= 下面我提供一个自己的 demo ,运行前记得替换三个参数为真实值。其中 audio 是本地录音文件相对路径或绝对路径。 import json import base64 from tencentcloud.common import credential from tencentcloud.common.profile.client_profile import ClientProfile from tencentcloud.common.profile.http_profile import HttpProfile from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException from tencentcloud.asr.v20190614 import asr_client, models SecretId = \"xxxxxxxxxxxxxxxxxxxxxxxxx\" SecretKey = \"xxxxxxxxxxxxxxxxxxxxxxxxx\" audio = \"./audio.mp3\" # 将录音转为字符串 Data = \"\" with open(audio, 'rb') as f: # 读取音频数据 Data = f.read() Data = base64.b64encode(Data) Data = Data.decode() try: cred = credential.Credential(SecretId, SecretKey) httpProfile = HttpProfile() httpProfile.endpoint = \"asr.tencentcloudapi.com\" clientProfile = ClientProfile() clientProfile.httpProfile = httpProfile client = asr_client.AsrClient(cred, \"\", clientProfile) req = models.CreateRecTaskRequest() params = { ''' 引擎模型类型。 这里的 k 指的是采样率 电话场景: • 8k_en:电话8k英语; • 8k_zh:电话8k中文普通话通用; 非电话场景: • 16k_zh:16k 中文普通话通用; • 16k_zh_video:16k 音视频领域; • 16k_en:16k 英语; • 16k_ca:16k 粤语; • 16k_ja:16k 日语; • 16k_zh_edu 中文教育; • 16k_en_edu 英文教育; • 16k_zh_medical 医疗; • 16k_th 泰语; ''' \"EngineModelType\": \"16k_zh\", ''' 识别声道数。注意:录音识别会自动将音频转码为填写的识别声道数 1:单声道; 2:双声道(仅支持 8k_zh 引擎模)。 ''' \"ChannelNum\": 1, ''' 识别结果返回形式。 0: 识别结果文本(含分段时间戳); 1:词级别粒度的详细识别结果(不含标点,含语速值); 2:词级别粒度的详细识别结果(包含标点、语速值) ''' \"ResTextFormat\": 0, ''' 语音数据来源。 0:语音 URL; 1:语音数据(post body)。 ''' \"SourceType\": 1, ''' 语音数据,当SourceType 值为1时必须填写,为0可不写。 要base64编码(采用python语言时注意读取文件应该为string而不是byte,以byte格式读取后要decode()。 编码后的数据不可带有回车换行符)。音频数据要小于5MB。 ''' \"Data\": Data } req.from_json_string(json.dumps(params)) resp = client.CreateRecTask(req) print(resp.to_json_string()) except TencentCloudSDKException as err: print(err) 输出 { \"Data\":{ \"TaskId\":1234567890 }, \"RequestId\":\"f1234567-89a4-1234-12d3-d56bdd9aac1a\" } 请求成功后,返回的 JSON 中 Data -\u003e TaskId 就是我们此次上传任务的 ID ,需要拿这个 ID 去轮训另一个接口,查询是否成功。 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:2:2","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#222上传本地录音文件识别"},{"categories":["折腾工坊"],"content":" 2.2.3、查询录音文件识别结果腾讯云提供的 API Explorer 可以很方便的生成代码和参数:https://console.cloud.tencent.com/api/explorer?Product=asr\u0026Version=2019-06-14\u0026Action=DescribeTaskStatus\u0026SignVersion= 用 TaskID 查询识别结果。 import json from tencentcloud.common import credential from tencentcloud.common.profile.client_profile import ClientProfile from tencentcloud.common.profile.http_profile import HttpProfile from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException from tencentcloud.asr.v20190614 import asr_client, models SecretId = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" SecretKey = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\" TaskId = 1234567890 try: cred = credential.Credential(SecretId, SecretKey) httpProfile = HttpProfile() httpProfile.endpoint = \"asr.tencentcloudapi.com\" clientProfile = ClientProfile() clientProfile.httpProfile = httpProfile client = asr_client.AsrClient(cred, \"\", clientProfile) req = models.DescribeTaskStatusRequest() params = { \"TaskId\": TaskId } req.from_json_string(json.dumps(params)) resp = client.DescribeTaskStatus(req) print(resp.to_json_string()) except TencentCloudSDKException as err: print(err) 输出: { \"Data\":{ \"TaskId\":1234567890, \"Status\":2, \"StatusStr\":\"success\", \"Result\":\"[0:0.000,1:0.320] 识别结果。\\n[1:0.320,2:0.360] 识别结果。\\n[2:0.360,3:0.380] 识别结果。\\n[3:0.380,4:0.400] 识别结果。\\n[4:0.400,5:0.420] 识别结果。\\n\", \"ErrorMsg\":\"\", \"ResultDetail\":null }, \"RequestId\":\"12345678-1234-1234-1234-b11234567890\" } ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:2:3","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#223查询录音文件识别结果"},{"categories":["折腾工坊"],"content":" 2.3、一句话识别(V3)文档地址:https://cloud.tencent.com/document/product/1093/35646 API Explorer:https://console.cloud.tencent.com/api/explorer?Product=asr\u0026Version=2019-06-14\u0026Action=SentenceRecognition\u0026SignVersion= ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:3:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#23一句话识别v3"},{"categories":["折腾工坊"],"content":" 2.3.1、安装 Python SDK在使用录音文件识别时,需要通过 pip 方式安装腾讯云提供的 Python 版本 SDK: pip install --upgrade tencentcloud-sdk-python # 中国大陆地区的用户可以使用国内镜像源提高下载速度 pip install -i https://mirrors.tencent.com/pypi/simple/ --upgrade tencentcloud-sdk-python。 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:3:1","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#231安装-python-sdk"},{"categories":["折腾工坊"],"content":" 2.3.2、源码借助 API Explorer 生成的源码,我修改了一些东西: import json import base64 from tencentcloud.common import credential from tencentcloud.common.profile.client_profile import ClientProfile from tencentcloud.common.profile.http_profile import HttpProfile from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException from tencentcloud.asr.v20190614 import asr_client, models SecretId = \"xxxxxxxxxxxxxxxxxxxxxxxxxx\" SecretKey = \"xxxxxxxxxxxxxxxxxxxxxxxxxx\" audio = \"./一句话录音.mp3\" # 将录音转为字符串 Data = \"\" with open(audio, 'rb') as f: # 读取音频数据 Data = f.read() Data = base64.b64encode(Data) Data = Data.decode() try: cred = credential.Credential(SecretId, SecretKey) httpProfile = HttpProfile() httpProfile.endpoint = \"asr.tencentcloudapi.com\" clientProfile = ClientProfile() clientProfile.httpProfile = httpProfile client = asr_client.AsrClient(cred, \"\", clientProfile) req = models.SentenceRecognitionRequest() params = { \"ProjectId\": 0, \"SubServiceType\": 2, \"EngSerViceType\": \"16k_zh\", \"SourceType\": 1, \"Data\": Data, \"VoiceFormat\": \"mp3\", \"UsrAudioKey\": \"uniqueKey-1\", } req.from_json_string(json.dumps(params)) resp = client.SentenceRecognition(req) print(resp.to_json_string()) except TencentCloudSDKException as err: print(err) 输出: { \"Result\":\"一句话录音识别内容\", \"AudioDuration\":59996, \"WordSize\":0, \"WordList\":null, \"RequestId\":\"12345678-4307-46ae-1234-beb3eb051234\" } ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:3:2","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#232源码"},{"categories":["折腾工坊"],"content":" REF长时间的会议录音如何快速转化成文字:https://www.zhihu.com/question/21552953 如何用ffmpeg从mkv视频文件中提取音频?:https://www.zhihu.com/question/420452079 ","date":"2021-07-17","objectID":"/posts/about-extract-copywriting-from-audio-or-video.html:0:0","series":null,"tags":["音频处理","语音转文字","FFmpeg","腾讯云","Python"],"title":"关于音视频文案提取","uri":"/posts/about-extract-copywriting-from-audio-or-video.html#ref"},{"categories":["折腾工坊"],"content":"你还在为使用 hugo 发布文章时的繁琐步骤烦恼吗? 本文默认你已经掌握了 hugo + Github Pages 的博客搭建。 我会以 Windows 平台作为演示,其它系统可参照思路,大同小异。 ","date":"2021-07-14","objectID":"/posts/hugo-one-click-deployment.html:0:0","series":null,"tags":["hugo","一键","自动化部署"],"title":"Hugo - 如何一键发布新文章?","uri":"/posts/hugo-one-click-deployment.html#"},{"categories":["折腾工坊"],"content":" 一、安装 Git先安装 git bash ,官网:https://git-scm.com/downloads ","date":"2021-07-14","objectID":"/posts/hugo-one-click-deployment.html:0:0","series":null,"tags":["hugo","一键","自动化部署"],"title":"Hugo - 如何一键发布新文章?","uri":"/posts/hugo-one-click-deployment.html#一安装-git"},{"categories":["折腾工坊"],"content":" 二、使 public 目录成为一个 git repository博客源码存放目录:D:\\blog\\src D:\\blog\\src │ config.toml ├─archetypes ├─assets ├─content ├─data ├─layouts ├─public ├─resources ├─static └─themes 接下来将 git 仓库(xxx.github.io)的 .git 文件夹和 CNAME 文件复制一份到博客根目录的 public 文件夹中,使 public 目录成为一个 git 仓库(repository)。 这样 hugo 在 public 文件夹中生成的文件就可以直接使用 git 命令推送到 Github 了。 如果配合 Github Webhook 使用,就可以把 xxx.github.io 当做源站了。在此基础上,你可以自行购买域名做 CDN 或者在自己的服务器上部署一个镜像站。 ","date":"2021-07-14","objectID":"/posts/hugo-one-click-deployment.html:0:0","series":null,"tags":["hugo","一键","自动化部署"],"title":"Hugo - 如何一键发布新文章?","uri":"/posts/hugo-one-click-deployment.html#二使-public-目录成为一个-git-repository"},{"categories":["折腾工坊"],"content":" 三、配置一键命令我决定将 git blog 这个命令作为以后日常一键部署的命令。输入以下命令即可完成配置: git config --global alias.blog '!cd D:\\\\blog\\\\src;hugo;cd D:\\\\blog\\\\src\\\\public;git add .;git commit -m 'update';git push' 使用 alias 即可为一系列命令配置一个别名 ","date":"2021-07-14","objectID":"/posts/hugo-one-click-deployment.html:0:0","series":null,"tags":["hugo","一键","自动化部署"],"title":"Hugo - 如何一键发布新文章?","uri":"/posts/hugo-one-click-deployment.html#三配置一键命令"},{"categories":["折腾工坊"],"content":" 四、一键部署当你写好文章之后,就可以在任意目录执行这个命令: git blog 此时系统会自动执行下面的命令: cd D:\\\\blog\\\\src hugo cd D:\\\\blog\\\\src\\\\public git add . git commit -m 'update' git push 这样就达到了我们的目的:通过一个命令就可以更新博客文件,并自动推送到 github 上。 ","date":"2021-07-14","objectID":"/posts/hugo-one-click-deployment.html:0:0","series":null,"tags":["hugo","一键","自动化部署"],"title":"Hugo - 如何一键发布新文章?","uri":"/posts/hugo-one-click-deployment.html#四一键部署"},{"categories":["折腾工坊"],"content":" 补充如果你同时使用了 Github Pages 和 Gitee Pages 服务,并且还希望能一条命令同时推送到 Github 和 Gitee,那么你可以参考 https://zhuanlan.zhihu.com/p/341272233,修改 git blog 对应的命令即可。 给你提个醒:git push github ,git push gitee 。 希望本文能帮到正在使用 hugo 的你。 ","date":"2021-07-14","objectID":"/posts/hugo-one-click-deployment.html:0:0","series":null,"tags":["hugo","一键","自动化部署"],"title":"Hugo - 如何一键发布新文章?","uri":"/posts/hugo-one-click-deployment.html#补充"},{"categories":["软件"],"content":"WizeTree 是一款 Windows 平台下的硬盘空间分析器。 通过可视化(图形化、树形化)的布局,你可以直观地看到在你硬盘上大的文件和文件夹。 内置文件管理器,支持查看文件树、文件夹大小排序、文件类型分析 wiztree 使用 NTFS 文件系统的 MFT 进行文件分析 (与著名的软件 everything 原理相同) 比 spacesniffer 的速度快数十倍,几秒钟就能完成全盘文件大小分析。 官网:https://www.diskanalyzer.com/ 预览图 ","date":"2021-07-14","objectID":"/posts/wiztree-the-fastest-disk-space-analyzer.html:0:0","series":null,"tags":["硬盘"],"title":"WizTree - 最快的硬盘空间分析器","uri":"/posts/wiztree-the-fastest-disk-space-analyzer.html#"},{"categories":null,"content":" Hello, World! 一只 Java 后端攻城狮 Technology stackPL:Java / Php / Python / C++ DB:MySQL / Redis OS:Linux / Windows Server [email protected] 我的微信公众号無糖的碎碎念 我的开源项目https://github.com/sugarlesss/shub, 这是一个 .sh 脚本工具箱 ","date":"2021-07-13","objectID":"/about.html:0:0","series":null,"tags":null,"title":"About","uri":"/about.html#"},{"categories":null,"content":" Hello, World! 一只 Java 后端攻城狮 Technology stackPL:Java / Php / Python / C++ DB:MySQL / Redis OS:Linux / Windows Server [email protected] 我的微信公众号無糖的碎碎念 我的开源项目https://github.com/sugarlesss/shub, 这是一个 .sh 脚本工具箱 ","date":"2021-07-13","objectID":"/about.html:0:0","series":null,"tags":null,"title":"About","uri":"/about.html#technology-stack"},{"categories":null,"content":" Hello, World! 一只 Java 后端攻城狮 Technology stackPL:Java / Php / Python / C++ DB:MySQL / Redis OS:Linux / Windows Server [email protected] 我的微信公众号無糖的碎碎念 我的开源项目https://github.com/sugarlesss/shub, 这是一个 .sh 脚本工具箱 ","date":"2021-07-13","objectID":"/about.html:0:0","series":null,"tags":null,"title":"About","uri":"/about.html#email"},{"categories":null,"content":" Hello, World! 一只 Java 后端攻城狮 Technology stackPL:Java / Php / Python / C++ DB:MySQL / Redis OS:Linux / Windows Server [email protected] 我的微信公众号無糖的碎碎念 我的开源项目https://github.com/sugarlesss/shub, 这是一个 .sh 脚本工具箱 ","date":"2021-07-13","objectID":"/about.html:0:0","series":null,"tags":null,"title":"About","uri":"/about.html#我的微信公众号"},{"categories":null,"content":" Hello, World! 一只 Java 后端攻城狮 Technology stackPL:Java / Php / Python / C++ DB:MySQL / Redis OS:Linux / Windows Server [email protected] 我的微信公众号無糖的碎碎念 我的开源项目https://github.com/sugarlesss/shub, 这是一个 .sh 脚本工具箱 ","date":"2021-07-13","objectID":"/about.html:0:0","series":null,"tags":null,"title":"About","uri":"/about.html#我的开源项目"},{"categories":["How-to-use"],"content":"有些网页上的视频是分成多个 ts 片段的,无法被 chrome的 各种嗅探器插件捕获,但通过 F12 开发工具监测网络(Network)时,在过滤器中输入 m3u8,可以发现一个独立的 m3u8 文件,这个文件就是记录了所有 ts 文件片段的一个播放列表。 文件内容大致像这样: 如果没有发现独立的 m3u8 文件,有可能每一个 ts 文件的地址中也是含有这个 m3u8 文件的名称的。把这个 m3u8 文件的完整地址截取出来。针对这种情况,我就不具体举例了,因为我还没遇到过。 到 header tab 里,复制一下这个 m3u8 文件的完整 url ,画红线的这部分就是。 假设这个地址是:https://xxx.abc.com/xxx/a.m3u8 可以使用ffmpeg(FFmpeg)命令下载合并输出为一个视频文件 ffmpeg -i https://xxx.abc.com/xxx/a.m3u8 -c copy output.mp4 ","date":"2021-07-13","objectID":"/posts/how-to-use-ffmpeg-download-ts-streaming-video.html:0:0","series":null,"tags":["流媒体","FFmpeg"],"title":"如何使用 FFmpeg 下载 ts 流媒体视频","uri":"/posts/how-to-use-ffmpeg-download-ts-streaming-video.html#"},{"categories":null,"content":" 我的朋友们 CrownDaisy Less is more. 字节星球 丰富想象力,激发创造力. 青阳のBlog 一个爱琢磨的人 在花博客 科技花交流地 香菇肥牛 said, 我以前很喜欢吃这种零食。 Eason Yang’s Blog , 某厂后端开发人员,前端技术爱好者。擅长「知乎点赞」、「微博转发」和「twitter瞎侃」。 Kunping said, 有容乃大。 ","date":"2021-07-13","objectID":"/friends.html:0:0","series":null,"tags":null,"title":"友链 / 朋友们","uri":"/friends.html#我的朋友们"},{"categories":["Python"],"content":"问题:有些批量下载的视频会带固定前缀,在视频播放器的播放列表里显示非常不友好。 场景:在 “D:\\纪录片\\中国通史” 路径下有 100 集视频文件,每个文件都带有固定前缀 “www.baidu.com 出品 微信公众号 xxx” 字样。 ","date":"2021-06-21","objectID":"/posts/batch-file-fixed-prefix-removal-using-python.html:0:0","series":null,"tags":[],"title":"Python 批量去除文件固定前缀","uri":"/posts/batch-file-fixed-prefix-removal-using-python.html#"},{"categories":["Python"],"content":" 源码RemoveFixedPrefix.py ,因为源码使用了 python fstring 的特性,需要在 python \u003e= 3.6 的版本中使用。 # coding=utf-8 import os # 目标路径 srcPath = \"D:\\纪录片\\中国通史\" fileList = os.listdir(srcPath) # 固定前缀 FixedPrefix = \"www.baidu.com出品 微信公众号xxx\" for fileName in fileList: oldFilePath = f\"{srcPath}/{fileName}\" # 跳过目录 if os.path.isdir(oldFilePath): continue newFileName = fileName.replace(FixedPrefix, \"\") newFilePath = f\"{srcPath}/{newFileName}\" try: os.rename(oldFilePath, newFilePath) except Exception as e: print(e) print(newFileName) ","date":"2021-06-21","objectID":"/posts/batch-file-fixed-prefix-removal-using-python.html:0:0","series":null,"tags":[],"title":"Python 批量去除文件固定前缀","uri":"/posts/batch-file-fixed-prefix-removal-using-python.html#源码"},{"categories":["PHP"],"content":"结论推导 ","date":"2021-01-12","objectID":"/posts/how-to-use-php-to-calculate-the-number-of-seconds-left-in-the-day.html:0:0","series":null,"tags":[],"title":"PHP计算当天剩余秒数最方便和最快的方法","uri":"/posts/how-to-use-php-to-calculate-the-number-of-seconds-left-in-the-day.html#"},{"categories":["PHP"],"content":" 一、结论# 最方便 echo strtotime('23:59:59') - time(); #最快 echo 86400 - (time() + 28800) % 86400; ","date":"2021-01-12","objectID":"/posts/how-to-use-php-to-calculate-the-number-of-seconds-left-in-the-day.html:0:0","series":null,"tags":[],"title":"PHP计算当天剩余秒数最方便和最快的方法","uri":"/posts/how-to-use-php-to-calculate-the-number-of-seconds-left-in-the-day.html#一结论"},{"categories":["PHP"],"content":" 二、推导过程用 86400 减去今天已经过去了多少秒,即可求得今天还剩多少秒。 86400=24*3600,即一天的总秒数。 28800=8*3600,即 8 个小时的总秒数。 当前时间戳取模 86400 并不是今天已经过去了多少秒,因为时间戳起始时间并不是 0 点,而是 8 点整。所以,如果当前是早上 8 点整,取模 86400 后会等于 0,与我们的本意不符(求今天已经过去了多少秒)。 因此,要用当前时间戳加上 8 个小时的总秒数后再取模 86400,即可求得今天过去了多少秒。 结论是由下面的算法简化后得到的: 86400 - (time() + 8 * 3600) % 86400 ","date":"2021-01-12","objectID":"/posts/how-to-use-php-to-calculate-the-number-of-seconds-left-in-the-day.html:0:0","series":null,"tags":[],"title":"PHP计算当天剩余秒数最方便和最快的方法","uri":"/posts/how-to-use-php-to-calculate-the-number-of-seconds-left-in-the-day.html#二推导过程"},{"categories":["PHP"],"content":" 三、REFhttps://segmentfault.com/a/1190000019844608 ","date":"2021-01-12","objectID":"/posts/how-to-use-php-to-calculate-the-number-of-seconds-left-in-the-day.html:0:0","series":null,"tags":[],"title":"PHP计算当天剩余秒数最方便和最快的方法","uri":"/posts/how-to-use-php-to-calculate-the-number-of-seconds-left-in-the-day.html#三ref"},{"categories":["Python"],"content":"此前写过一篇基于 BeautifulSoup 库开发的 demo,这次用 xpath 写。 ","date":"2021-01-11","objectID":"/posts/python-get-baidu-live-hotspot.html:0:0","series":null,"tags":[],"title":"Python 爬取百度实时热点","uri":"/posts/python-get-baidu-live-hotspot.html#"},{"categories":["Python"],"content":" 源码# -*- coding:utf-8 -*- # import requests from lxml import etree def get_headers(): headers = {} headers[\"content-type\"] = \"text/html;\" headers[ \"user-agent\"] = \"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36\" headers[\"host\"] = \"top.baidu.com\" return headers url = 'http://top.baidu.com/buzz?b=1' response = requests.get(url, headers=get_headers()) response.encoding = 'gbk' html = response.text if response.status_code != 200: print(f'返回状态码:{response.status_code}') exit(0) # 调用HTML类进行初始化 html = etree.HTML(html) # 提取该页面所有标题 result_all = html.xpath('//*[@id=\"main\"]/div[2]/div/table/tr/td[2]/a[1]') # 打印所有提取出的新闻标题 for v in result_all: print(v.text) 在开发时遇到一个有意思的坑,如果你测试时使用浏览器复制的热点标题的 title xpath,你会发现获取不到标题。你只需要把 tbody 标签去掉,就可以正常获取到标题了。 ","date":"2021-01-11","objectID":"/posts/python-get-baidu-live-hotspot.html:0:0","series":null,"tags":[],"title":"Python 爬取百度实时热点","uri":"/posts/python-get-baidu-live-hotspot.html#源码"},{"categories":["Python"],"content":" 输出31省新增本土病例85例:河北82例 佩洛西:众议院将第二次弹劾特朗普 河北新增49例本地无症状感染者 金正恩被推举为朝鲜劳动党总书记 北京新增1例确诊 4例无症状感染者 百度宣布组建智能汽车公司 美发生连环枪击案 一留学生身亡 日本发现新型变异新冠病毒 死刑!曾春亮案一审宣判 青藏高原云南等地降温雨雪来了 拼多多回应员工匿名发帖被辞退 石家庄新增确诊曾去过武汉汉正街 吉林新增4例本土无症状:2对夫妻 袁咏仪 送包给我是张智霖的福分 宋小女:这个结局也挺好 2020年CPI较上年上涨2.5% 全棉时代道歉疑似打广告 韩媒:韩军发现朝鲜举行阅兵式迹象 国会骚乱后特朗普没联系过彭斯 天津处罚过马路的低头族 被放生秃鹫赖警局每天伙食费150 比特币暴跌超10% 20岁小姐姐当汽车兵驰骋川藏线 北京乘出租车网约车需扫健康宝 网上买菜莫名被开通美团月付 自低风险区返乡要检测?多地出通知 车厘子价格腰斩 山东长岛海边现冰冻奇观似鸳鸯锅 武汉向石家庄捐赠50吨蔬菜 康辉说和21岁最大差别是脸的宽度 ","date":"2021-01-11","objectID":"/posts/python-get-baidu-live-hotspot.html:0:0","series":null,"tags":[],"title":"Python 爬取百度实时热点","uri":"/posts/python-get-baidu-live-hotspot.html#输出"},{"categories":["Python"],"content":" REFhttps://blog.csdn.net/qq_36523839/article/details/79992002 ","date":"2021-01-11","objectID":"/posts/python-get-baidu-live-hotspot.html:0:0","series":null,"tags":[],"title":"Python 爬取百度实时热点","uri":"/posts/python-get-baidu-live-hotspot.html#ref"},{"categories":["Linux"],"content":"在高并发短连接的 TCP 服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量 socket 处于 TIME_WAIT 状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。 高并发可以让服务器在短时间范围内同时占用大量端口,而端口有个 0~65535 的范围,并不是很多,刨除系统和其他服务要用的,剩下的就更少了。 在这个场景中,短连接表示 “业务处理 + 传输数据的时间 远远小于 TIMEWAIT 超时的时间” 的连接。Linux 默认的 TIME_WAIT 时长一般是 60 秒。 ","date":"2021-01-03","objectID":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html:0:0","series":null,"tags":[],"title":"Linux 服务器出现大量 TIME_WAIT 状态的连接","uri":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html#"},{"categories":["Linux"],"content":" 查看默认 timewait 时长cat /proc/sys/net/ipv4/tcp_fin_timeout ","date":"2021-01-03","objectID":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html:0:0","series":null,"tags":[],"title":"Linux 服务器出现大量 TIME_WAIT 状态的连接","uri":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html#查看默认-timewait-时长"},{"categories":["Linux"],"content":" 查看连接状态统计netstat -an | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' ","date":"2021-01-03","objectID":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html:0:0","series":null,"tags":[],"title":"Linux 服务器出现大量 TIME_WAIT 状态的连接","uri":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html#查看连接状态统计"},{"categories":["Linux"],"content":" 优化内核参数vim /etc/sysctl.conf #追加内容 net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 30 ","date":"2021-01-03","objectID":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html:0:0","series":null,"tags":[],"title":"Linux 服务器出现大量 TIME_WAIT 状态的连接","uri":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html#优化内核参数"},{"categories":["Linux"],"content":" 释义开启 SYN Cookies。当出现 SYN 等待队列溢出时,启用 cookies 来处理,可防范少量 SYN 攻击,默认为 0,表示关闭。 net.ipv4.tcp_syncookies = 1 开启重用。允许将 TIME-WAIT sockets 重新用于新的 TCP 连接,默认为 0,表示关闭。 net.ipv4.tcp_tw_reuse = 1 开启 TCP 连接中 TIME-WAIT sockets 的快速回收,默认为 0,表示关闭。 net.ipv4.tcp_tw_recycle = 1 修改系統默认的 TIMEOUT 时间(FIN_WAIT_2 状态的时长) net.ipv4.tcp_fin_timeout ","date":"2021-01-03","objectID":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html:0:0","series":null,"tags":[],"title":"Linux 服务器出现大量 TIME_WAIT 状态的连接","uri":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html#释义"},{"categories":["Linux"],"content":" REFhttps://www.cnblogs.com/apanly/p/12431902.html https://zhuanlan.zhihu.com/p/79507132 ","date":"2021-01-03","objectID":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html:0:0","series":null,"tags":[],"title":"Linux 服务器出现大量 TIME_WAIT 状态的连接","uri":"/posts/how-to-resolve-a-large-number-of-tcp-connections-with-time_wait-status.html#ref"},{"categories":["How-to-use"],"content":"frp 是一个基于 Go 语言开发的专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。frp 上手难度低,配置起来并不算困难。 Github:https://github.com/fatedier/frp 官网:https://gofrp.org 通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网。前提是,内网能访问外网。而不是像特殊单位的纯内网环境,不允许访问外网。 举几个经典的应用场景: 开发人员在家办公,需要连接公司内网,进服务器 shell 调试 / 部署; 开发人员联调时,需要给外网提供部署在本地的 web 服务; 出差过程中,需要连接公司内网,登录内部系统审批流程; 访问家庭 NAS 服务器。 本文以服务端 centos7,客户端 windows 为例进行演示说明。 ","date":"2020-12-20","objectID":"/posts/how-to-use-frp.html:0:0","series":null,"tags":["frp","内网穿透"],"title":"如何使用 frp 内网穿透","uri":"/posts/how-to-use-frp.html#"},{"categories":["How-to-use"],"content":" 一、下载最新版本的 frp 到服务器下载页面:https://github.com/fatedier/frp/releases 根据自己的服务器架构,选择对应发行版本的文件下载。一般都是 x86 架构,下 linux_amd64.tar.gz 结尾的版本就可以。 当前最新版本是:0.34.3。 下载到 /root 目录: cd /root \u0026\u0026 wget https://github.com/fatedier/frp/releases/download/v0.34.3/frp_0.34.3_linux_amd64.tar.gz 解压到 /usr/local 下,重命名文件夹为 frp: tar -zxvf frp_0.34.3_linux_amd64.tar.gz --directory=/usr/local/ \u0026\u0026 mv frp_0.34.3_linux_amd64 frp ","date":"2020-12-20","objectID":"/posts/how-to-use-frp.html:0:0","series":null,"tags":["frp","内网穿透"],"title":"如何使用 frp 内网穿透","uri":"/posts/how-to-use-frp.html#一下载最新版本的-frp-到服务器"},{"categories":["How-to-use"],"content":" 二、配置 frp systemd 服务#创建systemd服务文件 vim /usr/lib/systemd/system/frp.service #写入以下内容,退出并保存 [Unit] Description=The nginx HTTP and reverse proxy server After=network.target remote-fs.target nss-lookup.target [Service] Type=simple ExecStart=/usr/local/frp/frps -c /usr/local/frp/frps.ini KillSignal=SIGQUIT TimeoutStopSec=5 KillMode=process PrivateTmp=true StandardOutput=syslog StandardError=inherit [Install] WantedBy=multi-user.target 重载配置文件,使之立即生效。 systemctl daemon-reload systemctl 命令: systemctl start frp #启动 systemctl stop frp #关闭 systemctl restart frp #重启 systemctl status frp #当前状态 systemctl enable frp #开机自启动 systemctl disable frp #取消开机自启动 ","date":"2020-12-20","objectID":"/posts/how-to-use-frp.html:0:0","series":null,"tags":["frp","内网穿透"],"title":"如何使用 frp 内网穿透","uri":"/posts/how-to-use-frp.html#二配置-frp-systemd-服务"},{"categories":["How-to-use"],"content":" 三、修改 frp 服务端配置#修改frp服务端配置文件 vim /usr/local/frp/frps.ini #按照以下内容配置,保存并退出 [common] bind_port = 7000 #frp服务端端口 vhost_http_port = 8080 #http访问端口 [web] #[]中的内容可以自己随便起,但不允许重复,只是起备注的功能而已。 type = http #服务类型,可以设为http,https custom_domains = xxx.keyboardman.fun #公网域名,记得域名的A记录要解析到外网主机的IP。 auth_token = 123456789 [ssh] #监听6000端口,外网SSH连接内网机器时,填写的IP即为frp服务端的IP和此处设定的6000端口。 listen_port = 6000 auth_token = 123456789 [tcp] #监听10889端口,转发消息给客户端。 listen_port = 10889 auth_token = 123456789 修改完 frp 服务端的配置文件后,需要检查服务器端口是否开放,上面的配置文件用到了 6000/7000/8080/10089 这 4 个端口。 重启 frp 服务端 systemctl restart frp web 配置项用于向外网提供本地 web 服务、ssh 配置项用于连接本地虚拟机的 shell、tcp 配置项用于转发 10889 端口消息给本机。 需要注意的是,以上配置中,web/ssh/tcp 的字眼并非是标准语法,而是我起的一个备注而已。 ","date":"2020-12-20","objectID":"/posts/how-to-use-frp.html:0:0","series":null,"tags":["frp","内网穿透"],"title":"如何使用 frp 内网穿透","uri":"/posts/how-to-use-frp.html#三修改-frp-服务端配置"},{"categories":["How-to-use"],"content":" 四、下载最新版本的 frp 到本机同样的,在 Github 的 release 页面下载对应版本的 frp:https://github.com/fatedier/frp/releases 本机是 windows,因此下载 windows_amd64.zip 版本即可。 下载地址:https://github.com/fatedier/frp/releases/download/v0.34.3/frp_0.34.3_windows_amd64.zip ","date":"2020-12-20","objectID":"/posts/how-to-use-frp.html:0:0","series":null,"tags":["frp","内网穿透"],"title":"如何使用 frp 内网穿透","uri":"/posts/how-to-use-frp.html#四下载最新版本的-frp-到本机"},{"categories":["How-to-use"],"content":" 五、配置客户端参数解压后进入目录,修改 frpc.ini 为如下内容: [common] server_addr = xx.xx.xx.xx #部署frp服务端的IP地址 server_port = 7000 #frp服务端的端口 privilege_token = 123456789 #验证token/密码 [web] type = http local_ip = 127.0.0.1 local_port = 5500 #web服务本地端口 remote_port = 8080 #frp服务端HTTP端口 custom_domains = xxx.keyboardman.fun #反代域名 #将远程6000端口的tcp流量转发到本地的22端口 [ssh] type = tcp local_ip = 127.0.0.1 local_port = 22 remote_port = 6000 #将远程10889端口的tcp流量转发到本地的10889端口 [tcp] type = tcp local_ip = 127.0.0.1 local_port = 10889 remote_port = 10889 此处的 web 项配置意为:frp 服务端会把访问 xxx.keyboardman.fun:8080 端口的请求代理至本地的 5500 端口。 ssh 项:将服务端 6000 端口的 tcp 流量转发至本地的 22 端口 tcp 项:将服务端 10889 端口的 tcp 流量转发至本地的 10889 端口 ","date":"2020-12-20","objectID":"/posts/how-to-use-frp.html:0:0","series":null,"tags":["frp","内网穿透"],"title":"如何使用 frp 内网穿透","uri":"/posts/how-to-use-frp.html#五配置客户端参数"},{"categories":["How-to-use"],"content":" 六、运行本机上运行 frp 客户端并测试效果打开 cmd,cd 到 frp 的目录下,frpc.exe 运行客户端 ","date":"2020-12-20","objectID":"/posts/how-to-use-frp.html:0:0","series":null,"tags":["frp","内网穿透"],"title":"如何使用 frp 内网穿透","uri":"/posts/how-to-use-frp.html#六运行本机上运行-frp-客户端并测试效果"},{"categories":["How-to-use"],"content":" 1)web 服务访问测试访问 http://xxx.keyboardman.fun:8080,frp 服务端会将请求代理至本地的 5500 端口。 ","date":"2020-12-20","objectID":"/posts/how-to-use-frp.html:1:0","series":null,"tags":["frp","内网穿透"],"title":"如何使用 frp 内网穿透","uri":"/posts/how-to-use-frp.html#1web-服务访问测试"},{"categories":["How-to-use"],"content":" 2)socket 消息测试 SSH 测试就不演示了,在本机上有搭建 VMware,使用 nat 转发即可把本机的 22 端口 tcp 流量转发到目标虚拟机的 22 端口去,也就是远程 SSH 连接公司内网的应用场景了。 远程连接时,IP 填 frp 服务端所在的 IP,端口填 6000,可以回头看一下上文 frp 服务端的配置,其中 ssh 项的监听端口我选择的就是 6000。 ","date":"2020-12-20","objectID":"/posts/how-to-use-frp.html:2:0","series":null,"tags":["frp","内网穿透"],"title":"如何使用 frp 内网穿透","uri":"/posts/how-to-use-frp.html#2socket-消息测试"},{"categories":["How-to-use"],"content":" REFhttps://zhuanlan.zhihu.com/p/129076009 ","date":"2020-12-20","objectID":"/posts/how-to-use-frp.html:0:0","series":null,"tags":["frp","内网穿透"],"title":"如何使用 frp 内网穿透","uri":"/posts/how-to-use-frp.html#ref"},{"categories":["PHP"],"content":"无限级分类树生成可以使用递归或引用实现,但递归效率太慢,使用引用特性实现会是一个更好的方式。 ","date":"2020-12-16","objectID":"/posts/infinite-level-classification-tree-based-on-php-reference-feature.html:0:0","series":null,"tags":[],"title":"PHP基于引用特性实现的无限级分类树","uri":"/posts/infinite-level-classification-tree-based-on-php-reference-feature.html#"},{"categories":["PHP"],"content":" 源码public function test() { // 初始数据 $items = array( array('id' =\u003e 1, 'pid' =\u003e 0, 'name' =\u003e '福建省'), array('id' =\u003e 2, 'pid' =\u003e 0, 'name' =\u003e '四川省'), array('id' =\u003e 3, 'pid' =\u003e 1, 'name' =\u003e '福州市'), array('id' =\u003e 4, 'pid' =\u003e 2, 'name' =\u003e '成都市'), array('id' =\u003e 5, 'pid' =\u003e 2, 'name' =\u003e '乐山市'), array('id' =\u003e 6, 'pid' =\u003e 4, 'name' =\u003e '成华区'), array('id' =\u003e 7, 'pid' =\u003e 4, 'name' =\u003e '龙泉驿区'), array('id' =\u003e 8, 'pid' =\u003e 6, 'name' =\u003e '崔家店路'), array('id' =\u003e 9, 'pid' =\u003e 7, 'name' =\u003e '龙都南路'), array('id' =\u003e 10, 'pid' =\u003e 8, 'name' =\u003e 'A店铺'), array('id' =\u003e 11, 'pid' =\u003e 9, 'name' =\u003e 'B店铺'), array('id' =\u003e 12, 'pid' =\u003e 8, 'name' =\u003e 'C店铺'), array('id' =\u003e 13, 'pid' =\u003e 1, 'name' =\u003e '泉州市'), array('id' =\u003e 14, 'pid' =\u003e 13, 'name' =\u003e '南安县'), array('id' =\u003e 15, 'pid' =\u003e 13, 'name' =\u003e '惠安县'), array('id' =\u003e 16, 'pid' =\u003e 14, 'name' =\u003e 'A镇'), array('id' =\u003e 17, 'pid' =\u003e 14, 'name' =\u003e 'B镇'), array('id' =\u003e 18, 'pid' =\u003e 16, 'name' =\u003e 'A村'), array('id' =\u003e 19, 'pid' =\u003e 16, 'name' =\u003e 'B村'), ); // 根据初始数据,生成一个以 id 为 key/下标 的数组,方便根据 pid 判断是否存在父级元素。 $items = array_column($items,null,'id'); //使用 php 的 \u0026 引用特性,遍历一次循环即可生成无限级分类树。(其他高级语言中也有类似的特性,诸如 C++ 的指针和 JAVA 的引用) $tree = []; foreach ($items as $item) { $id = $item['id']; $pid = $item['pid']; if (isset($items[$pid])) $items[$pid]['children'][] = \u0026$items[$id]; else $tree[] = \u0026$items[$id]; } $this-\u003esuccess('ok',$tree); } ","date":"2020-12-16","objectID":"/posts/infinite-level-classification-tree-based-on-php-reference-feature.html:0:0","series":null,"tags":[],"title":"PHP基于引用特性实现的无限级分类树","uri":"/posts/infinite-level-classification-tree-based-on-php-reference-feature.html#源码"},{"categories":["PHP"],"content":" 输出{ \"code\": 1, \"msg\": \"ok\", \"data\": [ { \"id\": 1, \"pid\": 0, \"name\": \"福建省\", \"children\": [ { \"id\": 3, \"pid\": 1, \"name\": \"福州市\" }, { \"id\": 13, \"pid\": 1, \"name\": \"泉州市\", \"children\": [ { \"id\": 14, \"pid\": 13, \"name\": \"南安县\", \"children\": [ { \"id\": 16, \"pid\": 14, \"name\": \"A镇\", \"children\": [ { \"id\": 18, \"pid\": 16, \"name\": \"A村\" }, { \"id\": 19, \"pid\": 16, \"name\": \"B村\" } ] }, { \"id\": 17, \"pid\": 14, \"name\": \"B镇\" } ] }, { \"id\": 15, \"pid\": 13, \"name\": \"惠安县\" } ] } ] }, { \"id\": 2, \"pid\": 0, \"name\": \"四川省\", \"children\": [ { \"id\": 4, \"pid\": 2, \"name\": \"成都市\", \"children\": [ { \"id\": 6, \"pid\": 4, \"name\": \"成华区\", \"children\": [ { \"id\": 8, \"pid\": 6, \"name\": \"崔家店路\", \"children\": [ { \"id\": 10, \"pid\": 8, \"name\": \"A店铺\" }, { \"id\": 12, \"pid\": 8, \"name\": \"C店铺\" } ] } ] }, { \"id\": 7, \"pid\": 4, \"name\": \"龙泉驿区\", \"children\": [ { \"id\": 9, \"pid\": 7, \"name\": \"龙都南路\", \"children\": [ { \"id\": 11, \"pid\": 9, \"name\": \"B店铺\" } ] } ] } ] }, { \"id\": 5, \"pid\": 2, \"name\": \"乐山市\" } ] } ] ","date":"2020-12-16","objectID":"/posts/infinite-level-classification-tree-based-on-php-reference-feature.html:0:0","series":null,"tags":[],"title":"PHP基于引用特性实现的无限级分类树","uri":"/posts/infinite-level-classification-tree-based-on-php-reference-feature.html#输出"},{"categories":["软件"],"content":"ADB,即 Android Debug Bridge,是 Android 开发/测试人员不可替代的强大工具,其功能包括但不限于:安装卸载应用、拷贝推送文件、查看设备硬件信息、查看应用程序占用资源、在设备执行 shell 命令等。 在使用 python uiautomator2 这个库折腾安卓模拟器时,为了使用 adb 连接模拟机,我找到了此工具。 GitHub:https://github.com/koush/UniversalAdbDriver 官网:https://adb.clockworkmod.com 安装完成之后,将安装目录加入系统环境变量的 PATH 中即可。 C:\\Users\\Administrator\u003eadb -v Android Debug Bridge version 1.0.31 ","date":"2020-12-01","objectID":"/posts/adb.html:0:0","series":[],"tags":[],"title":"ADB - Android Debug Bridge","uri":"/posts/adb.html#"},{"categories":["Linux"],"content":"Systemd 服务是一种以 .service 结尾的单元(unit)配置文件,用于控制由 Systemd 控制或监视的进程。简单说,用于后台以守护精灵(daemon)的形式运行程序。Systemd 广泛应用于新版本的 RHEL、SUSE Linux Enterprise、CentOS、Fedora 和 openSUSE 中,用于替代旧有的服务管理器 service。 ","date":"2020-12-01","objectID":"/posts/how-to-create-a-linux-systemd-service.html:0:0","series":null,"tags":[],"title":"如何编写一个 Linux Systemd Service?","uri":"/posts/how-to-create-a-linux-systemd-service.html#"},{"categories":["Linux"],"content":" 一、如何创建一个服务?这里假设你已经自行编译安装好了 nginx,下面我们来创建一个 nginx.service 文件 vi /etc/systemd/system/nginx.service 内容如下: [Unit] Description=Nginx - high performance web server After=network.target [Service] Type=forking ExecStart=/usr/local/nginx/sbin/nginx ExecReload=/usr/local/nginx/sbin/nginx -s reload ExecStop=/usr/local/nginx/sbin/nginx -s stop [Install] WantedBy=multi-user.target 重新加载服务配置文件,使创建的 nginx 服务生效: systemctl daemon-reload 这样我们就可以用 Systemd 的方式来管理 nginx 了,命令如下: #启动nginx systemctl start nginx #重载nginx systemctl reload nginx #停止nginx systemctl stop nginx #重启nginx systemctl restart nginx #如果需要开机启动 systemctl enable nginx #如果需要取消开机启动 systemctl disable nginx ","date":"2020-12-01","objectID":"/posts/how-to-create-a-linux-systemd-service.html:0:0","series":null,"tags":[],"title":"如何编写一个 Linux Systemd Service?","uri":"/posts/how-to-create-a-linux-systemd-service.html#一如何创建一个服务"},{"categories":["Linux"],"content":" 二、关于 Systemd 服务Systemd 服务的内容主要分为三个部分,控制单元(unit)的定义、服务(service)的定义、以及安装(install)的定义。 ","date":"2020-12-01","objectID":"/posts/how-to-create-a-linux-systemd-service.html:0:0","series":null,"tags":[],"title":"如何编写一个 Linux Systemd Service?","uri":"/posts/how-to-create-a-linux-systemd-service.html#二关于-systemd-服务"},{"categories":["Linux"],"content":" 2.1、控制单元 unit从上面的例子中我们看到 Unit 内容如下: [Unit] Description=Nginx - high performance web server After=network.target Description:代表整个单元的描述,可根据需要任意填写。 Before/After:指定启动顺序。 network.target 代表有网路,network-online.target 代表一个连通着的网络。 ","date":"2020-12-01","objectID":"/posts/how-to-create-a-linux-systemd-service.html:1:0","series":null,"tags":[],"title":"如何编写一个 Linux Systemd Service?","uri":"/posts/how-to-create-a-linux-systemd-service.html#21控制单元-unit"},{"categories":["Linux"],"content":" 2.2、服务本体 service[Service] Type=forking ExecStart=/usr/local/nginx/sbin/nginx ExecReload=/usr/local/nginx/sbin/nginx -s reload ExecStop=/usr/local/nginx/sbin/nginx -s stop Type: 服务的类型,各种类型的区别如下所示 simple:默认,这是最简单的服务类型。意思就是说启动的程序就是主体程序,这个程序要是退出那么一切皆休。 forking:标准 Unix Daemon 使用的启动方式。启动程序后会调用 fork () 函数,把必要的通信频道都设置好之后父进程退出,留下守护精灵的子进程。 oneshot:适用于那些被一次性执行的任务或者命令,它运行完成后便了无痕迹。因为这类服务运行完就没有任何痕迹,我们经常会需要使用 RemainAfterExit=yes。意思是说,即使没有进程存在,Systemd 也认为该服务启动成功了。同时只有这种类型支持多条命令,命令之间用;分割,如需换行可以用 \\。 dbus:这个程序启动时需要获取一块 DBus 空间,所以需要和 BusName= 一起用。只有它成功获得了 DBus 空间,依赖它的程序才会被启动。 ExecStart:在输入的命令是 start 时候执行的命令,这里的命令启动的程序必须使用绝对路径,比如你必须用 /sbin/arp 而不能简单的以环境变量直接使用 arp。 ExecStop:在输入的命令是 stop 时候执行的命令,要求同上。 ExecReload:这个不是必需,如果不写则你的 service 就不支持 restart 命令。ExecStart 和 ExecStop 是必须要有的。 ","date":"2020-12-01","objectID":"/posts/how-to-create-a-linux-systemd-service.html:2:0","series":null,"tags":[],"title":"如何编写一个 Linux Systemd Service?","uri":"/posts/how-to-create-a-linux-systemd-service.html#22服务本体-service"},{"categories":["Linux"],"content":" 2.3、安装部分 install[Install] WantedBy=multi-user.target WantedBy:运行级别 / 设置服务被谁装载,一般设置为 multi-user.target(从 Centos7 以后 bai 采用 target 概念来定义运行级别,multi-user.target 是第三级别) ","date":"2020-12-01","objectID":"/posts/how-to-create-a-linux-systemd-service.html:3:0","series":null,"tags":[],"title":"如何编写一个 Linux Systemd Service?","uri":"/posts/how-to-create-a-linux-systemd-service.html#23安装部分-install"},{"categories":["Linux"],"content":" 2.4、存放的位置Systemd Service 位于 /etc/systemd/system(供系统管理员和用户使用),/usr/lib/systemd/system(供发行版打包者使用),我们一般使用前者即可。 ","date":"2020-12-01","objectID":"/posts/how-to-create-a-linux-systemd-service.html:4:0","series":null,"tags":[],"title":"如何编写一个 Linux Systemd Service?","uri":"/posts/how-to-create-a-linux-systemd-service.html#24存放的位置"},{"categories":["Linux"],"content":" 3、总结Systemd Service 是一种替代 /etc/init.d/ 下脚本的更好方式,它可以灵活的控制你什么时候要启动服务,一般情况下也不会造成系统无法启动进入紧急模式。所以如果想设置一些开机启动的东西,可以试着写 Systemd Service。当然了,前提是你使用的 Linux 发行版是支持它的才行。 ","date":"2020-12-01","objectID":"/posts/how-to-create-a-linux-systemd-service.html:0:0","series":null,"tags":[],"title":"如何编写一个 Linux Systemd Service?","uri":"/posts/how-to-create-a-linux-systemd-service.html#3总结"},{"categories":["Linux"],"content":" REFhttps://www.xiaoz.me/archives/14458 https://segmentfault.com/a/1190000014740871 https://zh.opensuse.org/openSUSE:How_to_write_a_systemd_service ","date":"2020-12-01","objectID":"/posts/how-to-create-a-linux-systemd-service.html:0:0","series":null,"tags":[],"title":"如何编写一个 Linux Systemd Service?","uri":"/posts/how-to-create-a-linux-systemd-service.html#ref"},{"categories":["Linux"],"content":"连接国外或内网 centos7 主机时发现会因为 DNS 的问题造成 SSH 连接速度慢。 SSH 登录太慢可能是 DNS 解析的问题,默认配置下 sshd 初次接受 ssh 客户端连接的时候会自动反向解析客户端 IP 以得到 ssh 客户端的域名或主机名。如果这个时候 DNS 的反向解析不正确,sshd 就会等到 DNS 解析超时后才提供 ssh 连接,这样就造成连接时间过长、ssh 客户端等待的情况,一般为 10-30 秒左右。有个简单的解决办法就是在 sshd 的配置文件(sshd_config)里取消 sshd 的反向 DNS 解析。 编辑 ssh 配置文件 vi /etc/ssh/sshd_config 找到 UseDNS 设置为 no 重启 ssh 服务即可 systemctl restart sshd ","date":"2020-11-28","objectID":"/posts/solution-for-ssh-login-too-slow.html:0:0","series":null,"tags":["ssh"],"title":"CentOS SSH 登录太慢的解决方法","uri":"/posts/solution-for-ssh-login-too-slow.html#"},{"categories":["Python"],"content":"读取本地文件内容,对文本内容进行中文分词,统计词频后,生成词云图。 ","date":"2020-11-18","objectID":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html:0:0","series":null,"tags":[],"title":"Python 中文分词,根据词频和背景图片生成词云","uri":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html#"},{"categories":["Python"],"content":" 一、生成矩形颜色随机的词云图读取本地文件内容,对文本内容进行中文分词,统计词频后,生成矩形随机颜色的词云图。 import logging import collections import re import math import jieba from wordcloud import WordCloud jieba.setLogLevel(logging.INFO) # 创建停用词列表 def stopwordslist(): # 按行读入 stopwords = [line.strip() for line in open('chinsesstop.txt', encoding='UTF-8').readlines()] # 分割为单个字符(列表解析) stopwords = [k for s in stopwords for k in s] return stopwords # # 指定字符串方式 # text = \"collections在python官方文档中的解释是High-performance container datatypes,直接的中文翻译解释高性能容量数据类型。它总共包含五种数据类型\" # 文件读取方式 f = open(\"./article.txt\", \"r\", encoding=\"utf-8\") text = f.read() f.close() # 生成词云的词频限制,选取前30% TopWordFrequencyPercentage = 30 TopWordFrequencyPercentage /= 100 # 词云图片生成路径(当前目录下的 wordcloud_rectangle.png 文件) PicSavePath = \"./wordcloud_rectangle.png\" # jieba分词 seg = jieba.cut(text) seg = \" \".join(seg) seg = seg.strip() # 去除标点符号 seg = re.sub(r\"[0-9\\s+\\.\\!\\/_,$%^*()?;;:-【】+\\\"\\']+|[+——!,;::。?、~@#¥%……\u0026*()]+\", \" \", seg) # 只取中文 # seg = re.sub(r'[^\\u4e00-\\u9fa5]', ' ', seg) # 转换为list seg = seg.split(\" \") # 过滤空字符和None seg = list(filter(None, seg)) # print(seg) # 创建一个停用词列表 stopwords = stopwordslist() # print(stopwords) # 过滤停用词(列表解析) seg = [v for v in seg if v not in stopwords] # print(seg) # 统计词频 word_counts = collections.Counter(seg) # print(word_counts) # print(math.ceil(len(word_counts) * 0.3)) # # 获取词频降序排列的前30% # word_counts_top_50_percent = word_counts.most_common(math.ceil(len(word_counts) * 0.3)) # print(word_counts_top_50_percent) # 生成词云 wc = WordCloud( # 限制词数(根据词频限制,计算个数,向上取整) max_words=math.ceil(len(word_counts) * TopWordFrequencyPercentage), # 设置背景宽 width=500, # 设置背景高 height=350, # 最大字体 max_font_size=50, # 最小字体 min_font_size=10, # 设置字体文件路径,不指定就会出现乱码。 font_path='./MSYH.TTC', # 设置背景色 background_color='white', ) # 根据词频产生词云 wc.generate_from_frequencies(word_counts) # 生成词云图片文件 wc.to_file(PicSavePath) 效果图: ","date":"2020-11-18","objectID":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html:0:0","series":null,"tags":[],"title":"Python 中文分词,根据词频和背景图片生成词云","uri":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html#一生成矩形颜色随机的词云图"},{"categories":["Python"],"content":" 二、根据图片生成规定形状的颜色相近的词云图读取本地文件内容,对文本内容进行中文分词,统计词频后,根据背景图片生成规定形状和颜色的词云图。 import logging import collections import re import math from random import randint import jieba from wordcloud import WordCloud from wordcloud import ImageColorGenerator from PIL import Image import numpy as np jieba.setLogLevel(logging.INFO) # 创建停用词列表 def stopwordslist(): # 按行读入 stopwords = [line.strip() for line in open('chinsesstop.txt', encoding='UTF-8').readlines()] # 分割为单个字符(列表解析) stopwords = [k for s in stopwords for k in s] return stopwords # 自定义颜色函数(在绘制词云图时发现有的字颜色为黄色导致看不清因此需要修改整个词云图的色调为冷色调 蓝绿色) def random_color_func(word=None, font_size=None, position=None, orientation=None, font_path=None, random_state=None): # what is HSL? https://baike.baidu.com/item/HSL/1443144?fr=aladdin H = randint(120, 250) S = int(100.0 * 255.0 / 255.0) L = int(100.0 * float(randint(60, 120)) / 255.0) return \"hsl({}, {}%, {}%)\".format(H, S, L) # # 指定字符串方式 # text = \"collections在python官方文档中的解释是High-performance container datatypes,直接的中文翻译解释高性能容量数据类型。它总共包含五种数据类型\" # 文件读取方式 f = open(\"./article.txt\", \"r\", encoding=\"utf-8\") text = f.read() f.close() # 生成词云的词频限制,选取前30% TopWordFrequencyPercentage = 30 TopWordFrequencyPercentage /= 100 # 词云图片生成路径(当前目录下的 wordcloud_arbitrary_shape.png 文件) PicSavePath = \"./wordcloud_arbitrary_shape.png\" # jieba分词 seg = jieba.cut(text) seg = \" \".join(seg) seg = seg.strip() # 去除标点符号 seg = re.sub(r\"[0-9\\s+\\.\\!\\/_,$%^*()?;;:-【】+\\\"\\']+|[+——!,;::。?、~@#¥%……\u0026*()]+\", \" \", seg) # 只取中文 # seg = re.sub(r'[^\\u4e00-\\u9fa5]', ' ', seg) # 转换为list seg = seg.split(\" \") # 过滤空字符和None seg = list(filter(None, seg)) # print(seg) # 创建一个停用词列表 stopwords = stopwordslist() # print(stopwords) # 过滤停用词(列表解析) seg = [v for v in seg if v not in stopwords] # print(seg) # 统计词频 word_counts = collections.Counter(seg) # print(word_counts) # print(math.ceil(len(word_counts) * 0.3)) # # 获取词频降序排列的前30% # word_counts_top_50_percent = word_counts.most_common(math.ceil(len(word_counts) * 0.3)) # print(word_counts_top_50_percent) # 词云形状 mask = np.array(Image.open(\"./background1.png\")) # 根据图片颜色设置词云颜色(如果背景图片是纯色背景会报 NotImplementedError: Gray-scale images TODO 错误) bg_color_func = ImageColorGenerator(mask) # 生成词云 wc = WordCloud( # 词云形状 mask=mask, # 限制词数(根据词频限制,计算个数,向上取整) max_words=math.ceil(len(word_counts) * TopWordFrequencyPercentage), # 设置图片宽度(单位:像素) width=500, # 设置图片高度(单位:像素) height=350, # 最大字体 max_font_size=50, # 最小字体 min_font_size=10, # 字体文件路径,不指定就会出现乱码。(windows系统字体文件路径:C:\\Windows\\Fonts,可以从这里拷贝出来) font_path='./MSYH.TTC', # 设置背景色 background_color='white', # 颜色模式(默认为RBG)当参数为“RGBA”并且background_color为None时,背景为黑色 mode='RGBA', # 词语水平方向排版出现的频率,默认 0.9 (所以词语垂直方向排版出现频率为 0.1 ) prefer_horizontal=0.9, # 按照比例进行放大画布,如设置为1.5,则长和宽都是原来画布的1.5倍 scale=1, # 词频和字体大小的关联性 relative_scaling=0.5, # 生成新颜色的函数(默认为None) # color_func=None, color_func=bg_color_func, # color_func=random_color_func, # 给每个单词随机分配颜色,若指定color_func,则忽略该方法 colormap=None, # colormap='pink', ) # 根据词频产生词云 wc.generate_from_frequencies(word_counts) # 生成词云图片文件 wc.to_file(PicSavePath) 效果图: 原背景图: ","date":"2020-11-18","objectID":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html:0:0","series":null,"tags":[],"title":"Python 中文分词,根据词频和背景图片生成词云","uri":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html#二根据图片生成规定形状的颜色相近的词云图"},{"categories":["Python"],"content":" 三、版本依赖 requirements.txtcertifi==2020.6.20 cycler==0.10.0 jieba==0.42.1 kiwisolver==1.3.1 matplotlib==3.3.3 numpy==1.19.3 Pillow==8.0.1 pyparsing==2.4.7 python-dateutil==2.8.1 six==1.15.0 wincertstore==0.2 wordcloud==1.8.1 ","date":"2020-11-18","objectID":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html:0:0","series":null,"tags":[],"title":"Python 中文分词,根据词频和背景图片生成词云","uri":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html#三版本依赖-requirementstxt"},{"categories":["Python"],"content":" 四、停止词 / 过滤词(chinsesstop.txt)这里其实我只对单字过滤了,没有对词组做过滤。 的地得 了就在与也为更而之由以把吧巴去过被搞或每及几 不让到等从人请叫和最当例本 干拿取说做给靠按受收记 但又及并另只则既即将 一二三四五六七八九 是非 大小 时分秒 前后左右 东西南北中 你我他她它们 年月日 进出 新旧 有无 对错 开关 里外 多少 上下 个头 ","date":"2020-11-18","objectID":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html:0:0","series":null,"tags":[],"title":"Python 中文分词,根据词频和背景图片生成词云","uri":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html#四停止词--过滤词chinsesstoptxt"},{"categories":["Python"],"content":" 五、字体文件(MSYH.TTC)windows 系统字体文件路径:C:\\Windows\\Fonts,可以从这里拷贝出来 ","date":"2020-11-18","objectID":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html:0:0","series":null,"tags":[],"title":"Python 中文分词,根据词频和背景图片生成词云","uri":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html#五字体文件msyhttc"},{"categories":["Python"],"content":" 六、测试文章戴着眼镜,文质彬彬,我们很难把刘聪和传统印象里的农人联系起来。但实际上,35岁的“新农人”刘聪,已经在重庆田间地头种了近十年的葡萄。 2009年毕业于西南农业大学,从事的销售工作需要经常出差,漂泊生活无法给刘聪带来安定感。一次偶然,刘聪看了电影《林中漫步》,被影片中祥和宁静的森林景色所打动,于是辞去了原有的工作,和朋友回乡种起了葡萄。 然而,刘聪理想中田园生活的“美轮美奂”,在现实里被各种鸡飞狗跳替代。因为资金链缺乏、设施落后,在对抗自然灾害、虫害方面抗风险能力较低,导致第一年出品的葡萄品质非常一般。 不过,刘聪并没有放弃,吸取教训后更认真地钻研葡萄栽培。同时,他着手申请政府的援助项目搭建钢棚,慢慢发展起来后加大投资,又顺利申请到平安普惠惠农贷,获得平安普惠三农服务的帮助,极大缓解了创业前期的资金压力。 如今,刘聪的“阳光玫瑰”种植面积已经有47亩,靠种葡萄改善了家庭状况。原本反对刘聪回农村搞农业的爸妈,也慢慢开始能够理解他了。 像刘聪这样“归园田居”的故事,正越来越多地在现实里上演。 一如国庆档黑马《一点就到家》把“三个合伙人”的创业故事从城市搬到了农村,如今,回家乡早已经不是退路,而是奋斗者的另一种选择。在农村脱贫主旋律下,当代农村的创业故事,远比想象中更精彩。 走出贫穷的循环, 辣椒地里开出幸福花 在《一点就到家》里,回到云南普洱的年轻人种起咖啡,圆了咖啡梦的同时助力家乡脱贫。电影里,咖啡是一开始不被接受的“新事物”,而实际上,云南的咖啡种植历史由来已久。 咖啡是云南的特色产品。同样,在贵州,也有一群人靠着家乡特色产品辣椒,闯出红火事业。 20年前,夏可福放弃国家公职人员身份,回到贵州湄潭开始创业。一开始,夏可福差价赚钱,但他意识到光靠转卖鲜辣椒终究不是长远之计,于是他用几年的积蓄租下了一个两万平方的工厂,开始转向辣椒加工。 如今,公司经过产业转型升级,已发展成为湄潭县辣椒加工及销售的龙头企业,夏可福也成为了黔北一带有名企业家“夏辣椒”。 夏可福不仅靠卖辣椒闯出红火事业,更带动了本地老百姓增收致富。 值得一提的是,夏可福的公司采取“公司+基地+农户”的模式,在提供技术指导和肥料补助的基础上,与基地农户签订收购合同,实行保护价、随行就市收购和公司二次分配返还利润的办法,直接带动农民增收,与基地农户建立了紧密合作伙伴关系。 2019年,通过遵义市农业信贷担保公司,夏可福了解到平安普惠三农服务,认为平安普惠惠农贷这个项目能够给企业和老百姓实实在在的实惠,遂与之展开合作。 90后单亲妈妈杨胜颖,正是通过收购合作方夏可福的介绍推荐,了解到平安普惠三农服务,获得了100万元贷款资金支持。 杨胜颖家祖祖辈辈以种辣椒为生,小规模种植只够一家人谋生,一年到最后,基本没剩什么存款。 直到杨胜颖用这笔钱扩大了辣椒地种植面积,有了帮工,提高了生产的效率,通过自己的努力增加收入,突破了小规模种植的拮据经济状况,让生活变得滋润起来,也为孩子创造了更好的成长环境。 带妇女走出家庭, 小镇独立女性的创业路 在过去,农村女性总是全身心为小家而活,一辈子没走出过厨房,缺少独立意识。而如今,越来越多的女性有了自己的事业,杨胜颖给出了一个不同的答案,张红中又给出了另一个。 2015年,张红中到甘肃靖远的农投公司上班,一直在党政综合办公室工作。今年疫情稍微有所好转的时候,张红中接到通知,说要让她搞妇女创业项目。 菌棒生产项目是靖远县人民政府为了解决全县香菇产业菌棒用量大、外购成本高、当地贫困户就业困难等问题而决定实施的重点扶贫项目。从一个“领导要求什么就做什么”的行政岗位,到千人千法的创业,临危受命,张红中一点点学,咬着牙干了起来。 繁忙的工作让张红中没有太多时间顾及家庭和孩子,但家人们的理解和支持让她能够放心去干。同时,一想到基地上的五六十名女同志,她们也有家庭、孩子,都在为这份事业不断奋斗,她作为领头人也更不能先退缩。 同样身为女性,张红中更能与妇女们产生共情。菌棒生产车间对技术要求比较高,车间也有挺多妇女的文化水平只是在初中,她们不懂,张红中就自己学来了之后给她们上课,教懂她们一项技能。 项目落地后,菌棒生产项目获得了平安普惠的80万惠农金,这份灵活的资金,对张红中的经营无疑是雪中送炭。资金被用于原料采购、销售技能培训等方面,妇女们获取劳务收入的同时,也提高了就业技能和致富能力。 不只是菌棒生产项目,2020年4月,由中国妇女发展基金会与平安普惠合作发起的惠农金项目落地甘肃靖远县,平安普惠为项目提供了400万元资金支持,以扶持靖远县香菇脱贫基地、千亩枸杞园、菌棒生产大棚、日光温室蔬菜大棚、爱心惠民超市等5个项目。每个项目各分配80万元,主要用于生产资料的购置、女性技术及营销技能的培训和工资发放。 这些项目的落地,实现了近300名女性就近就业。通过技能扶贫,让女性们手握脱贫的“金钥匙”,独立走出家庭,靠自己争取出了一片天。 不只如此,平安普惠扶贫的脚步一直没有停过,与传承非物质文化遗产相结合,创新设计扶贫也成为平安普惠创新扶贫的重点。 今年八月,由平安普惠联合平安集团、经济日报报业集团艺术与设计杂志社、中国服饰报社策划的“妈妈的针线活”项目启动仪式在四川凉山西昌举行。 彝族女孩子们自幼学习刺绣,彝族刺绣工艺独特,是具有悠久历史的非物质文化遗产。“妈妈的针线活” 妇女手工创业扶贫项目,通过旧衣物升级改造的“再生设计”,赋予旧废服饰新生命,在振兴非物质遗产同时,帮助和带动当地贫困妇女就地就业创业,并增加收益,起到精准扶贫的效果。 拔掉“穷根”, 平安普惠“造鱼塘”助农 2020年是精准扶贫、脱贫攻坚的关键之年,平安普惠积极响应国家“十四五”规划和2035年远景目标,在中国平安“三村工程”的指导下,始终怀有强烈的社会责任感和使命感。 助力扶贫攻坚与乡村振兴,平安普惠以“商业向善”为核心,围绕新农村创新产业发展、妇女创业、商业可持续、文化传承等方向,积极多角度探索创新三农扶贫模式: 与中国扶贫基金会共同成立“平安产业扶贫公益基金”,用于建设4个扶贫新农人产业发展基地; 与上海宋庆龄基金会、中国妇女发展基金会合作,为贫困地区农村合作社带头人经营生产,提供免息借款惠农金和创业能力建设服务; 凭借自身主营业务优势,积极探索解决农村农业发展中“融资难、融资贵、融资慢”的问题,联合多家省级农业担保公司及其他基层机构,为农村创业者提供商业可持续的惠农借贷服务; 结合凉山非遗传承文化,探索旧衣回收、非遗设计、手工加工、市场销售的全链条闭环扶贫长效机制。 截至目前,平安普惠累计向农村合作社带头人及农创者提供扶持资金超11880万元,直接扶持超过800户贫困户,覆盖地区包括甘肃、重庆、贵州、陕西、海南、黑龙江、云南等12个省份,覆盖种植业、畜牧业、林业等多种农业类型。 除了为农户提供资金扶持,平安普惠更充分发挥自身优势扶贫,依托平安的人工智能、云计算和区块链三大核心科技,积极构建多元的扶贫场景,以科技赋能扶贫。 平安普惠的精准扶贫不只是治标,从提供资金“授人以鱼”,到技术扶贫“授人以渔”,甚至从生产到销售打通全产业链助农,平安普惠更造了个“鱼塘”,给足了农民脱贫路上“放手去干”的安全感。 扶贫路上,平安普惠始终不忘普惠三农服务的社会责任初衷和决心:聚焦小微、助农惠农、帮助弱势群体,多角度、全方位探索助农模式,为贫困地区农村经济和产业发展注入新鲜动力,助力建档立卡贫困户和小农户脱贫致富。 ","date":"2020-11-18","objectID":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html:0:0","series":null,"tags":[],"title":"Python 中文分词,根据词频和背景图片生成词云","uri":"/posts/split-words-generate-word-clouds-based-on-word-frequency-and-background-images.html#六测试文章"},{"categories":["Python"],"content":"有时 pip 不指定源安装会比较慢,甚至会安装失败。 #阿里源 pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ #豆瓣 pip install -r requirements.txt -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com #清华大学 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/ ","date":"2020-11-17","objectID":"/posts/pip-source-specific-installation.html:0:0","series":null,"tags":[],"title":"Python pip 指定源安装","uri":"/posts/pip-source-specific-installation.html#"},{"categories":["Python"],"content":"在查看别人的 Python 项目时,经常会看到一个 requirements.txt 文件,里面记录了当前程序的所有依赖包及其精确版本号,与 npm 的 package.json 很像。其作用是用来在另一台 PC 上重新构建项目所需要的运行环境依赖。 # 导出依赖 pip freeze \u003e requirements.txt # 安装依赖 pip install -r requirements.txt ","date":"2020-11-17","objectID":"/posts/python-export-or-install-requirements.txt-dependencies.html:0:0","series":null,"tags":[],"title":"Python 导出 or 安装 requirements.txt 依赖","uri":"/posts/python-export-or-install-requirements.txt-dependencies.html#"},{"categories":["Python"],"content":"安装完 anaconda 后,打开 Anaconda Powershell Prompt (anaconda3) 执行命令。 ","date":"2020-11-13","objectID":"/posts/pycharm-using-anaconda-to-manage-multi-version-python-environments.html:0:0","series":null,"tags":[],"title":"PyCharm 使用 Anaconda 管理多版本 Python 环境","uri":"/posts/pycharm-using-anaconda-to-manage-multi-version-python-environments.html#"},{"categories":["Python"],"content":" 一、Anaconda# 查看 conda 版本号 conda --version # 查看系统当前已有的 Python 环境 conda info --envs # 添加一个名为 python27,Python 版本为 2.7 的环境 conda create --name python27 python=2.7 # 查看当前环境的 Python 版本 python --version # 切换 Python 环境到刚才新添加的 Python2.7 conda activate python27 # 切回原来的 Python 环境 conda deactivate python27 # 或 conda activate base # 删除 python27 这个环境 conda remove --name python27 --all # 添加清华源 conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config --set show_channel_urls yes # 查看镜像列表 conda config --show channels # 删除某个镜像 conda config --remove channels #如:https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ # 包管理 conda install \u003c包名\u003e 安装指定包 conda remove \u003c包名\u003e 移除指定包 conda update \u003c包名\u003e 更新指定包 ","date":"2020-11-13","objectID":"/posts/pycharm-using-anaconda-to-manage-multi-version-python-environments.html:0:0","series":null,"tags":[],"title":"PyCharm 使用 Anaconda 管理多版本 Python 环境","uri":"/posts/pycharm-using-anaconda-to-manage-multi-version-python-environments.html#一anaconda"},{"categories":["Python"],"content":" 二、PyCharm ","date":"2020-11-13","objectID":"/posts/pycharm-using-anaconda-to-manage-multi-version-python-environments.html:0:0","series":null,"tags":[],"title":"PyCharm 使用 Anaconda 管理多版本 Python 环境","uri":"/posts/pycharm-using-anaconda-to-manage-multi-version-python-environments.html#二pycharm"},{"categories":["Python"],"content":"先说结论:推荐使用 openyxl 库,而不是 xlwt 库。使用 xlwt 库创建并向 .xls 文件写入内容时,其单个 sheet 限制最大行数为 65535。而使用 openpyxl 库创建并向 .xlsx 文件写入内容时,其单个 sheet 限制最大行数为 1048576,最大列数为 16384。 ","date":"2020-11-11","objectID":"/posts/create-an-excel-file-and-write-to-it-using-python.html:0:0","series":null,"tags":[],"title":"Python 如何创建一个 Excel 文件并向其写入内容?","uri":"/posts/create-an-excel-file-and-write-to-it-using-python.html#"},{"categories":["Python"],"content":" 一、openpyxl 库openyxl 库文档地址:https://openpyxl.readthedocs.io/en/stable/usage.html / https://pypi.org/project/openpyxl import openpyxl ExeclFileSavePath = \"./test.xlsx\" wb = openpyxl.Workbook() rowInit = 1 rowLimit = 50 columnInit = 1 columnLimit = 5 # 默认Sheet ws = wb.active for row in range(rowInit, rowLimit + 1): for column in range(columnInit, columnLimit + 1): ws.cell(row, column).value = row # 创建一个sheet Pi wsA = wb.create_sheet(title=\"Pi\") for row in range(rowInit, rowLimit): wsA.append(range(columnInit, columnLimit)) # 保存文件 wb.save(ExeclFileSavePath) ","date":"2020-11-11","objectID":"/posts/create-an-excel-file-and-write-to-it-using-python.html:0:0","series":null,"tags":[],"title":"Python 如何创建一个 Excel 文件并向其写入内容?","uri":"/posts/create-an-excel-file-and-write-to-it-using-python.html#一openpyxl-库"},{"categories":["Python"],"content":" 二、xlwt 库xlwt 库文档地址:https://xlwt.readthedocs.io/en/latest / https://pypi.org/project/xlwt import xlwt ExeclFileSavePath = \"./test.xls\" workbook = xlwt.Workbook(encoding='utf-8') sheet1 = workbook.add_sheet('sheet1', cell_overwrite_ok=True) # 给excel文件添加sheet rowInit = 1 rowLimit = 50 columnInit = 1 columnLimit = 5 for row in range(rowInit - 1, rowLimit): for column in range(columnInit - 1, columnLimit): temp = row # 单元格内容 sheet1.write(row, column, temp) # sheet1.write_merge(0, 3, 1, 1, '合并') # 合并单元格 workbook.save(ExeclFileSavePath) ","date":"2020-11-11","objectID":"/posts/create-an-excel-file-and-write-to-it-using-python.html:0:0","series":null,"tags":[],"title":"Python 如何创建一个 Excel 文件并向其写入内容?","uri":"/posts/create-an-excel-file-and-write-to-it-using-python.html#二xlwt-库"},{"categories":["MySQL"],"content":"隐藏在各项配置文件之后的,是 MySQL 的思想和设计方案。 查看当前所有配置 show variables; ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:0:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#"},{"categories":["MySQL"],"content":" 一、表相关","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:0:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#一表相关"},{"categories":["MySQL"],"content":" 表数据独立存储表数据既可以存在共享表空间里,也可以是单独的文件。这个行为是由参数 innodb_file_per_table 控制的,从 MySQL 5.6.6 版本开始,它的默认值就是 ON 了。 这个参数设置为 OFF 表示的是,表的数据放在系统共享表空间,也就是跟数据字典放在一起; 这个参数设置为 ON 表示的是,每个 InnoDB 表数据存储在一个以 .ibd 为后缀的文件中。 我建议你不论使用 MySQL 的哪个版本,都将这个值设置为 ON。 因为,一个表单独存储为一个文件更容易管理,而且在你不需要这个表的时候,通过 drop/truncate table 命令,系统就会直接删除这个文件。而如果是放在共享表空间中,即使表删掉了,空间也是不会回收的。 innodb_file_per_table = ON ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:1:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#表数据独立存储"},{"categories":["MySQL"],"content":" 二、日志相关","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:0:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#二日志相关"},{"categories":["MySQL"],"content":" bin log 格式如果是使用 delete 语句误删了数据行,可以用 Flashback 工具通过闪回把数据恢复回来。Flashback 恢复数据的原理,是修改 binlog 的内容,拿回原库重放。而能够使用这个方案的前提是,需要确保以下两个配置。 binlog_format=row binlog_row_image=FULL ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:1:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#bin-log-格式"},{"categories":["MySQL"],"content":" bin log 文件体积限制 / 自动过期row 格式记录的数据更完整,在数据恢复方面有极大优势,但缺点是生成的日志量大。因此需要根据业务需求和服务器配置对 binlog 设置单个文件最大体积限制和保留时长的限制。 expire_logs_days = 30 max_binlog_size = 512M ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:2:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#bin-log-文件体积限制--自动过期"},{"categories":["MySQL"],"content":" 双 “1” 配置一个事务完整提交前,需要等待两次刷盘,一次是 redo log(prepare 阶段),一次是 binlog。 sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。 redo log 用于保证 crash-safe 能力,innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数我建议你设置成 1,这样可以保证 MySQL 异常重启之后 redo log 数据不丢失。 sync_binlog = 1 innodb_flush_log_at_trx_commit = 1 ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:3:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#双-1-配置"},{"categories":["MySQL"],"content":" redo log 文件体积 / 个数利用 WAL 技术,MySQL 将随机写转换成了顺序写,大大提升了数据库的性能。但是,由此也带来了内存脏页的问题。脏页会被后台线程自动 flush,也会由于数据页淘汰而触发 flush,而刷脏页的过程由于会占用资源,可能会让你的更新和查询语句的响应时间长一些,造成性能的间歇性下降。 “redo log 写满了,要 flush 脏页”,这种情况是 InnoDB 要尽量避免的。因为出现这种情况的时候,整个系统就不能再接受更新了,所有的更新都必须堵住。如果你从监控上看,这时候更新数会跌为 0。 一个内存配置为 128GB、innodb_io_capacity 设置为 20000 的大规格实例,正常会建议你将 redo log 设置成 4 个 1GB 的文件。 这两个属性是只读的,需要通过修改配置文件并重启 MySQL 生效。 innodb_log_file_size=512M innodb_log_files_in_group=2 ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:4:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#redo-log-文件体积--个数"},{"categories":["MySQL"],"content":" 三、buffer 相关","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:0:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#三buffer-相关"},{"categories":["MySQL"],"content":" 缓冲池大小缓冲池内存大小不够,意味着可用的数据页少,脏页比例容易接近 75%,造成性能间歇性抖动。 innodb_buffer_pool_size=1G ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:1:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#缓冲池大小"},{"categories":["MySQL"],"content":" join 联表查询缓冲池大小join_buffer 的大小是由参数 join_buffer_size 设定的,默认值是 256k。如果放不下表 t1 的所有数据话,策略很简单,就是分段放。如果 join 语句较慢,有可能是因为缓冲池过小。 join_buffer_size=32M ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:2:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#join-联表查询缓冲池大小"},{"categories":["MySQL"],"content":" 四、硬盘 IO 相关","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:0:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#四硬盘-io-相关"},{"categories":["MySQL"],"content":" IO 读写能力如果没能合理地设置 innodb_io_capacity 参数,会导致一些性能问题。比如 MySQL 的写入速度很慢,TPS 很低,但是数据库主机的 IO 压力并不大。 如果主机磁盘用的是 SSD,但是 innodb_io_capacity 的值设置的是 300。于是,InnoDB 认为这个系统的能力就这么差,所以刷脏页刷得特别慢,甚至比脏页生成的速度还慢,这样就造成了脏页累积,影响了查询和更新性能。 测试完成后可以得到服务器硬盘的 IOPS,根据这个值来设置:(200 是默认值) innodb_io_capacity = 200 centos 安装 fio,测试硬盘 io yum install -y libaio-devel fio cd /;mkdir test;cd /test;touch file;fio -filename=/test/file -direct=1 -iodepth 1 -thread -rw=randrw -ioengine=psync -bs=4k -size=500M -numjobs=10 -runtime=10 -group_reporting -name=mytest windows 可以使用 AS SSD 软件测试 IOPS ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:1:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#io-读写能力"},{"categories":["MySQL"],"content":" 脏页刷写连坐机制一旦一个查询请求需要在执行过程中先 flush 掉一个脏页时,这个查询就可能要比平时慢了。而 MySQL 中的一个机制,可能让你的查询会更慢:在准备刷一个脏页的时候,如果这个数据页旁边的数据页刚好是脏页,就会把这个 “邻居” 也带着一起刷掉;而且这个把 “邻居” 拖下水的逻辑还可以继续蔓延,也就是对于每个邻居数据页,如果跟它相邻的数据页也还是脏页的话,也会被放到一起刷。在 InnoDB 中,innodb_flush_neighbors 参数就是用来控制这个行为的,值为 1 的时候会有上述的 “连坐” 机制,值为 0 时表示不找邻居,自己刷自己的。 找 “邻居” 这个优化在机械硬盘时代是很有意义的,可以减少很多随机 IO。机械硬盘的随机 IOPS 一般只有几百,相同的逻辑操作减少随机 IO 就意味着系统性能的大幅度提升。 而如果使用的是 SSD 这类 IOPS 比较高的设备的话,建议把 innodb_flush_neighbors 的值设置成 0: 因为这时候 IOPS 往往不是瓶颈,而 “只刷自己”,就能更快地执行完必要的刷脏页操作,减少 SQL 语句响应时间。在 MySQL 8.0 中,innodb_flush_neighbors 参数的默认值已经是 0 了。 innodb_flush_neighbors = 0 ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:2:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#脏页刷写连坐机制"},{"categories":["MySQL"],"content":" 五、线程相关","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:0:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#五线程相关"},{"categories":["MySQL"],"content":" 并发线程上限数通常情况下,我们建议把 innodb_thread_concurrency 设置为 64~128 之间的值。并发连接和并发查询并不是同一个概念,你在 show processlist 的结果里,看到的几千个连接,指的就是并发连接。而 “当前正在执行” 的语句,才是我们所说的并发查询。 并发连接数达到几千个影响并不大,就是多占一些内存而已。我们应该关注的是并发查询,因为并发查询太高才是 CPU 杀手。这也是为什么我们需要设置 innodb_thread_concurrency 参数的原因。 在线程进入锁等待以后,并发线程的计数会减一,也就是说等行锁(也包括间隙锁)的线程是不算在 128 里面的。MySQL 这样设计是非常有意义的。因为,进入锁等待的线程已经不吃 CPU 了;更重要的是,必须这么设计,才能避免整个系统锁死。 innodb_thread_concurrency=128 ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:1:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#并发线程上限数"},{"categories":["MySQL"],"content":" 六、锁相关","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:0:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#六锁相关"},{"categories":["MySQL"],"content":" 自增锁模式MySQL 5.1.22 版本引入了一个新策略,新增参数 innodb_autoinc_lock_mode,默认值是 1。 这个参数的值被设置为 0 时,表示采用之前 MySQL 5.0 版本的策略,即语句执行结束后才释放锁; 这个参数的值被设置为 1 时:普通 insert 语句,自增锁在申请之后就马上释放;类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放; 这个参数的值被设置为 2 时,所有的申请自增主键的动作都是申请后就释放锁。 在生产上,尤其是有 insert … select 这种批量插入数据的场景时,从并发插入数据性能的角度考虑,我建议你这样设置: innodb_autoinc_lock_mode=2 ,并且 binlog_format=row. 这样做,既能提升并发性,又不会出现数据一致性问题。 innodb_autoinc_lock_mode=2 ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:1:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#自增锁模式"},{"categories":["MySQL"],"content":" 七、SQL 部分","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:0:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#七sql-部分"},{"categories":["MySQL"],"content":" 安全模式为了防止忘记在 delete 或者 update 语句中写 where 条件、where 条件里面没有包含索引字段、没有加入 limit 限制引起的非安全 SQL 语句造成数据误删等,写入该配置后,这些非安全语句执行就会报错。 sql_safe_updates = ON ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:1:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#安全模式"},{"categories":["MySQL"],"content":" 配置模板innodb_file_per_table = ON binlog_format=row binlog_row_image=FULL expire_logs_days = 30 max_binlog_size = 512M sync_binlog = 1 innodb_flush_log_at_trx_commit = 1 innodb_log_file_size=512M innodb_log_files_in_group=2 innodb_buffer_pool_size=1G join_buffer_size=32M innodb_io_capacity = 300 innodb_flush_neighbors = 0 innodb_thread_concurrency=128 innodb_autoinc_lock_mode=2 ","date":"2020-11-07","objectID":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html:0:0","series":null,"tags":[],"title":"MySQL5.7 生产环境推荐参数配置","uri":"/posts/mysql-5.7-recommended-parameter-configuration-for-production-environments.html#配置模板"},{"categories":["MySQL"],"content":"在日常开发中遇到的一些问题。 ","date":"2020-11-05","objectID":"/posts/common-maintenance-sql-for-mysql.html:0:0","series":null,"tags":[],"title":"MySQL 的常用维护语句","uri":"/posts/common-maintenance-sql-for-mysql.html#"},{"categories":["MySQL"],"content":" 一、查看数据库的各个表占用的文件大小以查看 test 数据库为例: SELECT table_schema AS '数据库', table_name AS '表名', engine AS '存储引擎', table_comment AS '备注', table_rows AS '记录数', TRUNCATE (data_length / 1024 / 1024, 2) AS '数据大小(MB)', TRUNCATE (index_length / 1024 / 1024, 2) AS '索引大小(MB)' FROM information_schema.TABLES WHERE table_schema = 'test' ORDER BY table_rows DESC; ","date":"2020-11-05","objectID":"/posts/common-maintenance-sql-for-mysql.html:0:0","series":null,"tags":[],"title":"MySQL 的常用维护语句","uri":"/posts/common-maintenance-sql-for-mysql.html#一查看数据库的各个表占用的文件大小"},{"categories":["MySQL"],"content":" 二、查找持续时间超过 60s 的事务select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))\u003e60 ","date":"2020-11-05","objectID":"/posts/common-maintenance-sql-for-mysql.html:0:0","series":null,"tags":[],"title":"MySQL 的常用维护语句","uri":"/posts/common-maintenance-sql-for-mysql.html#二查找持续时间超过-60s-的事务"},{"categories":["MySQL"],"content":" 三、查看当前线程处理情况配套 kill 语句可以处理突发事件 show full processlist; kill Id; ","date":"2020-11-05","objectID":"/posts/common-maintenance-sql-for-mysql.html:0:0","series":null,"tags":[],"title":"MySQL 的常用维护语句","uri":"/posts/common-maintenance-sql-for-mysql.html#三查看当前线程处理情况"},{"categories":["MySQL"],"content":" 四、优化表optimize table `table_name_A`; 重新组织表数据和相关索引数据的物理存储,以减少存储空间,提高访问表时的 I/O 效率。 但此操作会锁表,需要避开业务高峰期。 ","date":"2020-11-05","objectID":"/posts/common-maintenance-sql-for-mysql.html:0:0","series":null,"tags":[],"title":"MySQL 的常用维护语句","uri":"/posts/common-maintenance-sql-for-mysql.html#四优化表"},{"categories":["MySQL"],"content":" 五、重建表alter table A engine=InnoDB; 试想一下,如果你现在有一个表 A,需要做空间收缩,为了把表中存在的空洞去掉,你可以怎么做呢?你可以新建一个与表 A 结构相同的表 B,然后按照主键 ID 递增的顺序,把数据一行一行地从表 A 里读出来再插入到表 B 中。由于表 B 是新建的表,所以表 A 主键索引上的空洞,在表 B 中就都不存在了。显然地,表 B 的主键索引更紧凑,数据页的利用率也更高。如果我们把表 B 作为临时表,数据从表 A 导入表 B 的操作完成后,用表 B 替换 A,从效果上看,就起到了收缩表 A 空间的作用。这里,你可以使用 alter table A engine=InnoDB 命令来重建表。在 MySQL 5.5 版本之前,这个命令的执行流程跟我们前面描述的差不多,区别只是这个临时表 B 不需要你自己创建,MySQL 会自动完成转存数据、交换表名、删除旧表的操作。 显然,花时间最多的步骤是往临时表插入数据的过程,如果在这个过程中,有新的数据要写入到表 A 的话,就会造成数据丢失。因此,在整个 DDL 过程中,表 A 中不能有更新。也就是说,这个 DDL 不是 Online 的。 而在 MySQL 5.6 版本开始引入的 Online DDL,对这个操作流程做了优化。 我给你简单描述一下引入了 Online DDL 之后,重建表的流程: 建立一个临时文件,扫描表 A 主键的所有数据页; 用数据页中表 A 的记录生成 B+ 树,存储到临时文件中; 生成临时文件的过程中,将所有对 A 的操作记录在一个日志文件(row log)中; 临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表 A 相同的数据文件; 用临时文件替换表 A 的数据文件。 可以看到,不同之处在于,由于日志文件记录和重放操作这个功能的存在,这个方案在重建表的过程中,允许对表 A 做增删改操作。这也就是 Online DDL 名字的来源。 DDL 之前是要拿 MDL 写锁的,这样还能叫 Online DDL 吗? alter 语句在启动的时候需要获取 MDL 写锁,但是这个写锁在真正拷贝数据之前就退化成读锁了。为什么要退化呢?为了实现 Online,MDL 读锁不会阻塞增删改操作。那为什么不干脆直接解锁呢?为了保护自己,禁止其他线程对这个表同时做 DDL。而对于一个大表来说,Online DDL 最耗时的过程就是拷贝数据到临时表的过程,这个步骤的执行期间可以接受增删改操作。所以,相对于整个 DDL 过程来说,锁的时间非常短。对业务来说,就可以认为是 Online 的。 需要补充说明的是,上述的这些重建方法都会扫描原表数据和构建临时文件。对于很大的表来说,这个操作是很消耗 IO 和 CPU 资源的。因此,如果是线上服务,你要很小心地控制操作时间。如果想要比较安全的操作的话,我推荐你使用 GitHub 开源的 gh-ost 来做。 另外,使用 alter table t engine=InnoDB 有可能会让一个表占用的空间反而变大: 1、就是这个表本身就已经没有空洞,比如说刚刚做过一次重建表操作; 2、在 DDL 期间,如果刚好有外部的 DML 在执行,这期间可能会引入一些新的空洞; 3、在重建表的时候,InnoDB 不会把整张表占满,每个页留了 1/16 给后续的更新用。也就是说,其实重建表之后不是 “最” 紧凑的。 ","date":"2020-11-05","objectID":"/posts/common-maintenance-sql-for-mysql.html:0:0","series":null,"tags":[],"title":"MySQL 的常用维护语句","uri":"/posts/common-maintenance-sql-for-mysql.html#五重建表"},{"categories":["MySQL"],"content":"MySQL 的四种事务隔离级别 / 如何切换 MySQL 的默认全局事务隔离级别 / 了解 session 和 global 关键字 ","date":"2020-11-04","objectID":"/posts/modifying-the-mysql-default-transaction-isolation-level.html:0:0","series":null,"tags":[],"title":"MySQL 修改默认事务隔离级别","uri":"/posts/modifying-the-mysql-default-transaction-isolation-level.html#"},{"categories":["MySQL"],"content":" 一、查看当前 MySQL 版本号select version(); ","date":"2020-11-04","objectID":"/posts/modifying-the-mysql-default-transaction-isolation-level.html:0:0","series":null,"tags":[],"title":"MySQL 修改默认事务隔离级别","uri":"/posts/modifying-the-mysql-default-transaction-isolation-level.html#一查看当前-mysql-版本号"},{"categories":["MySQL"],"content":" 二、查看当前全局事务隔离级别","date":"2020-11-04","objectID":"/posts/modifying-the-mysql-default-transaction-isolation-level.html:0:0","series":null,"tags":[],"title":"MySQL 修改默认事务隔离级别","uri":"/posts/modifying-the-mysql-default-transaction-isolation-level.html#二查看当前全局事务隔离级别"},{"categories":["MySQL"],"content":" 2.1、MySQL5.6 及其更早的版本select @@global.tx_isolation; ","date":"2020-11-04","objectID":"/posts/modifying-the-mysql-default-transaction-isolation-level.html:1:0","series":null,"tags":[],"title":"MySQL 修改默认事务隔离级别","uri":"/posts/modifying-the-mysql-default-transaction-isolation-level.html#21mysql56-及其更早的版本"},{"categories":["MySQL"],"content":" 2.2、MySQL5.7 及更高版本select @@global.transaction_isolation; 1、MySQL5.7 引入了 transaction_isolation 用来代替 tx_isolation,并在 MySQL8.0.3 去掉了 tx_isolation,在 MySQL5.7 及更高版本中建议使用 transaction_isolation 2、若要查看当前会话的事务隔离级别,可以去掉 global. 使用 SELECT @@transaction_isolation。同理,若只想针对当前 session 设置事务隔离级别,可将 global 关键字替换为 session ","date":"2020-11-04","objectID":"/posts/modifying-the-mysql-default-transaction-isolation-level.html:2:0","series":null,"tags":[],"title":"MySQL 修改默认事务隔离级别","uri":"/posts/modifying-the-mysql-default-transaction-isolation-level.html#22mysql57-及更高版本"},{"categories":["MySQL"],"content":" 三、MySQL 的四个事务隔离级别| 事务隔离级别 | 脏读 | 不可重复读 | 幻读 | | —- | —- | | 读未提交(read-uncommitted) | 是 | 是 | 是 | | 读提交(read-committed) | 否 | 是 | 是 | | 可重复读(repeatable-read) | 否 | 否 | 是 | | 串行化(serializable) | 否 | 否 | 否 | ","date":"2020-11-04","objectID":"/posts/modifying-the-mysql-default-transaction-isolation-level.html:0:0","series":null,"tags":[],"title":"MySQL 修改默认事务隔离级别","uri":"/posts/modifying-the-mysql-default-transaction-isolation-level.html#三mysql-的四个事务隔离级别"},{"categories":["MySQL"],"content":" 四、修改 MySQL 全局默认事务隔离级别","date":"2020-11-04","objectID":"/posts/modifying-the-mysql-default-transaction-isolation-level.html:0:0","series":null,"tags":[],"title":"MySQL 修改默认事务隔离级别","uri":"/posts/modifying-the-mysql-default-transaction-isolation-level.html#四修改-mysql-全局默认事务隔离级别"},{"categories":["MySQL"],"content":" 4.1、MySQL5.6 及其更早的版本set global tx_isolation='read-uncommitted'; set global tx_isolation='read-committed'; set global tx_isolation='repeatable-read'; set global tx_isolation='serializable'; ","date":"2020-11-04","objectID":"/posts/modifying-the-mysql-default-transaction-isolation-level.html:1:0","series":null,"tags":[],"title":"MySQL 修改默认事务隔离级别","uri":"/posts/modifying-the-mysql-default-transaction-isolation-level.html#41mysql56-及其更早的版本"},{"categories":["MySQL"],"content":" 4.2、MySQL5.7 及更高版本set global transaction_isolation='read-uncommitted'; set global transaction_isolation='read-committed'; set global transaction_isolation='repeatable-read'; set global transaction_isolation='serializable'; ","date":"2020-11-04","objectID":"/posts/modifying-the-mysql-default-transaction-isolation-level.html:2:0","series":null,"tags":[],"title":"MySQL 修改默认事务隔离级别","uri":"/posts/modifying-the-mysql-default-transaction-isolation-level.html#42mysql57-及更高版本"},{"categories":["Nginx"],"content":"我们知道,在 nginx 的 if 中是不能写 limit_req 和 limit_conn 的。也就是说在 nginx 的配置文件中,我们无法通过 if 对请求参数做逻辑判断,从而实现对复杂请求参数的精准限流。 应用场景:针对某一个流量特别大的入口的某一个特定 GET 请求参数做限流,比如来自微信小程序的 API 请求入口(https://example.com/app/index.php?i=99\u0026v=9.9.9\u0026m=xxxx\u0026from=wxapp),我希望 from = wxapp 时进行限流,若 from 不等于 wxapp 则不进行限流。 ","date":"2020-09-17","objectID":"/posts/nginx-limits-flow-based-on-specific-request-parameters.html:0:0","series":null,"tags":["限流"],"title":"Nginx 根据特定请求参数做限流","uri":"/posts/nginx-limits-flow-based-on-specific-request-parameters.html#"},{"categories":["Nginx"],"content":" 二、使用 map 针对请求参数限流在 nginx 的 http 部分写入: limit_req_zone $paramFrom zone=from:10m rate=30r/s; map $arg_from $paramFrom { wxapp wxapp; } 这里的 rate 我设为了每秒处理 30 个请求,如果 limit_req 没有设置 burst,则默认为 0。 rate 支持 r/s 和 r/m 两种模式。 在 server 部分写入: location ^~ /app/index.php { limit_req zone=from; #limit_rate 512k; #宝塔PHP文件处理 try_files $uri =404; fastcgi_pass unix:/tmp/php-cgi-56.sock; fastcgi_index index.php; include fastcgi.conf; include pathinfo.conf; } ","date":"2020-09-17","objectID":"/posts/nginx-limits-flow-based-on-specific-request-parameters.html:0:0","series":null,"tags":["限流"],"title":"Nginx 根据特定请求参数做限流","uri":"/posts/nginx-limits-flow-based-on-specific-request-parameters.html#二使用-map-针对请求参数限流"},{"categories":["Nginx"],"content":" 三、关于 Nginx 的 map 模块使用 map 模块,可以根据一个或多个变量组合成一个新的变量。通过判断新的变量,我们可以处理非常复杂的业务逻辑。本文是以 map 模块用于限流为示例,简单展示一下 map 的使用方法。 ","date":"2020-09-17","objectID":"/posts/nginx-limits-flow-based-on-specific-request-parameters.html:0:0","series":null,"tags":["限流"],"title":"Nginx 根据特定请求参数做限流","uri":"/posts/nginx-limits-flow-based-on-specific-request-parameters.html#三关于-nginx-的-map-模块"},{"categories":["Nginx"],"content":" 四、原理http_limit_req_module 和 http_limit_conn_module 两个模块是在 nginx 的 preaccess 阶段对请求做拦截,也就是说使用 limit_req_zone 或是 limit_conn_zone 对请求都是可以的。 但两者有本质上的区别,request 和 connection 是不同的,在实际应用中应注意区别。 connection 是连接,即常说的 tcp 连接,通过三次握手而建立的。 request 是指请求,即 http 请求,(注意,tcp 连接是有状态的,而构建在 tcp 之上的 http 却是无状态的协议)。 limit_req_zone 或是 limit_conn_zone 对请求的限制有效性取决于 key 的设计,通常使用 realip 模块获取到的客户端 IP。但 IP 是针对全体用户做的限流,显然不能满足我们的需求。所以我们需要使用 map 模块来匹配特定的请求参数生成一个变量,将这个变量作为 limit_req_zone 或是 limit_conn_zone 的 key,这样就可以实现我们的需求了。 ","date":"2020-09-17","objectID":"/posts/nginx-limits-flow-based-on-specific-request-parameters.html:0:0","series":null,"tags":["限流"],"title":"Nginx 根据特定请求参数做限流","uri":"/posts/nginx-limits-flow-based-on-specific-request-parameters.html#四原理"},{"categories":["Nginx"],"content":"使用 nginx 的 http_limit_conn_module 模块可以在 nginx 的 preaccess 阶段对请求的并发做拦截。限制的有效性取决于 key 的设计,通常使用 realip 模块获取到的客户端 IP。 应用场景:假如你只希望限流用户的访问入口,但不希望管理后台也被纳入限流的范围内,因为在操作管理后台时,用户访问量激增,nginx 会频繁返回 503,那么管理后台将处于不可操作的状态。 ","date":"2020-09-16","objectID":"/posts/nginx-limits-flow-to-specific-paths-or-entry-files.html:0:0","series":null,"tags":["限流"],"title":"Nginx 针对特定路径或入口文件限流","uri":"/posts/nginx-limits-flow-to-specific-paths-or-entry-files.html#"},{"categories":["Nginx"],"content":" 一、编写 location 规则假设我需要对 /app/index.php 这个路径做并发量的控制(例如:https://example.com/app/index.php?i=99\u0026t=0\u0026v=5.5.5\u0026from=wxap),首先要确定的事情就是编写的 location 规则是否能被正确匹配到。下面是 nginx location 的写法: location ^~ /app/index.php { return 404; } 加上这条 location 规则后,如果访问(https://example.com/app/index.php?i=99\u0026t=0\u0026v=5.5.5\u0026from=wxap)这样的链接,nginx 会直接返回 404 错误。那么说明这段 location 是能够匹配到的。 ","date":"2020-09-16","objectID":"/posts/nginx-limits-flow-to-specific-paths-or-entry-files.html:0:0","series":null,"tags":["限流"],"title":"Nginx 针对特定路径或入口文件限流","uri":"/posts/nginx-limits-flow-to-specific-paths-or-entry-files.html#一编写-location-规则"},{"categories":["Nginx"],"content":" 二、限制并发量在 nginx 的 http 部分添加: limit_conn_zone $binary_remote_addr zone=perip:10m; limit_conn_zone $server_name zone=perserver:10m; $binary_remote_addr 是二进制格式的 IPv4 地址,这个 IP 是请求者的 IP。将远程客户端的 IP 地址作为 zone 的 key,目的是为了对单个客户端做并发限制。 $server_name 指的是当前站点的名称,将这个作为 zone 的 key,目的是为了对某个站点做总体的并发限制。 稍稍修改一下上面的 location 规则,设置为站点并发量为 300,单个 IP 并发量限制 25: location ^~ /app/index.php { limit_conn perserver 300; limit_conn perip 25; limit_rate 128k; #宝塔面板默认的 enable-php-56.conf 规则,对php文件的响应处理 try_files $uri =404; fastcgi_pass unix:/tmp/php-cgi-56.sock; fastcgi_index index.php; include fastcgi.conf; include pathinfo.conf; } limit_conn 的作用是,在某个 zone 下的并发限制为多少。 limit_rate 限制的是 nginx 向客户端传送响应的速率。 limit_conn 和 limit_req 不能设置在 if 指令中,所以如果针对不同的 URL 进行限流,只能通过不同的 location 实现。 limit_rate 可以在 if 指令中,可以使用 if 指令匹配 URL 实现不同 URL 的限流。 使用 jmeter 等相关并发测试工具可以测到,确实对 /app/index.php/ 请求做了并发量的限制,超出并发量的部分 nginx 会默认返回 503。 ","date":"2020-09-16","objectID":"/posts/nginx-limits-flow-to-specific-paths-or-entry-files.html:0:0","series":null,"tags":["限流"],"title":"Nginx 针对特定路径或入口文件限流","uri":"/posts/nginx-limits-flow-to-specific-paths-or-entry-files.html#二限制并发量"},{"categories":["PHP","Redis"],"content":"ThinkPHP 如何使用 Redis 实现悲观锁解决高并发情况下读写带来的脏读问题 / ThinkPHP5.1 / Redis Cache / File Cache 测试。 在用户量 / 客户端数量比较少的时候,只要系统的业务逻辑是正确的,一般都不会发现有什么问题。但随着用户量 / 客户端数量逐渐增多,高并发带来的问题就会逐渐出现,而脏读是众多问题的其中之一。 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:0:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#"},{"categories":["PHP","Redis"],"content":" 一、无并发控制,会带来什么问题?本文以 ThinkPHP5.1.39 的代码作为案例,下面是一个 File Cache 读写操作: public function fileCacheCase(){ $keyName = \"test\"; $keyValue = 996; //写入缓存 Cache::set($keyName, $keyValue, 3600); //从缓存中获取值 $data = Cache::get($keyName); //删除缓存 Cache::rm($keyName); echo \"OK! $data\"; } 访问这个 function,会输出 OK! 996 。无论你访问几次,结果都是如此,但仅限于单线程的情况(只有你自己一个人在访问这个 function),如果是多个人同时不停的访问这个 function,还会是这样吗?想一想 😛 使用 jmeter 测试一下,120 线程测试了十几秒,发现了 3 种不同的返回结果。 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:0:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#一无并发控制会带来什么问题"},{"categories":["PHP","Redis"],"content":" 1.1、返回了 OK! 996与单线程时的结果一致,是正常处理逻辑。 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:1:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#11返回了-ok-996"},{"categories":["PHP","Redis"],"content":" 1.2、只返回了 OK!而不是 OK! 996 说明缓存不存在,原因是:在 A 线程将 996 写入缓存后,B 线程将缓存删除了。此时 A 线程从缓存中读出来的数据为 null,所以 A 线程输出了 OK! ,而不是 OK! 996。 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:2:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#12只返回了-ok而不是-ok-996"},{"categories":["PHP","Redis"],"content":" 1.3、返回了一个 500 错误 报错的内容是: file_get_contents(…)No such file or directory。 显然是 cache 文件夹下的某个缓存文件不存在,所以引起了这个错误。原因是:A 线程在删除缓存后,B 线程也在执行删除缓存的操作。当缓存文件已被删除时,再执行删除缓存文件的操作,自然就报了文件不存在的错误。(实测 120 个线程并发,总计 500 个请求,异常率 0.20%) 尽管我修改了 File Cache 的 133 行,在删除前判断文件是否存在,虽然异常率降低了,但依然无法从根本上解决问题。可以看到的是,在高并发场景下,问题已经显现出来了。 下面我们用 redis 缓存试试看: public function fileCacheCase(){ $keyName = \"test\"; $keyValue = 996; //写入缓存 Cache::store('redis')-\u003eset($keyName, $keyValue, 3600); //从缓存中获取值 $data = Cache::store('redis')-\u003eget($keyName); //删除缓存 Cache::store('redis')-\u003erm($keyName); echo \"OK! $data\"; } 经过测试,与上面的 3 种情况一致。(根据 thinkphp5.1 的官方文档,我使用的是 store 来切换到 redis,但不知道为何,仍然会报 File Cache 驱动的 No such file or directory/unlink 错误,十分诡异)。 如何解决高并发场景下带来的脏读问题? 答案是:使用锁机制。 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:3:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#13返回了一个-500-错误"},{"categories":["PHP","Redis"],"content":" 二、关于锁机制根据锁的控制范围,可分为单机锁 / 分布式锁 2 种。根据锁的实现思想,可分为悲观锁 / 乐观锁 2 种。 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:0:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#二关于锁机制"},{"categories":["PHP","Redis"],"content":" 2.1、单机锁即为单机环境的锁,无分布式设计。 常用的实现工具: Redis Memcached ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:1:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#21单机锁"},{"categories":["PHP","Redis"],"content":" 2.2、分布式锁为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行 高可用的获取锁与释放锁 高性能的获取锁与释放锁 具备锁失效机制,防止死锁 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败 常用的实现工具: Zookeeper Redis Memcached Chubby ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:2:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#22分布式锁"},{"categories":["PHP","Redis"],"content":" 2.3、悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:3:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#23悲观锁"},{"categories":["PHP","Redis"],"content":" 2.4、乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和 CAS 算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:4:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#24乐观锁"},{"categories":["PHP","Redis"],"content":" 2.5、如何选择悲观 / 乐观锁?从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行 retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:5:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#25如何选择悲观--乐观锁"},{"categories":["PHP","Redis"],"content":" 三、Redis 实现悲观锁在商品秒杀活动活动中,流量峰值相对平常时的流量是高出非常多的。使用 Redis 实现悲观锁机制,可以解决商品库存脏读的问题。 初始化库存: public function stockInit() { $key = \"stock\"; $stockInit = 699; //清空所有缓存 Cache::clear(); Cache::store('redis')-\u003eclear(); //写入库存初始值 Cache::store('redis')-\u003eset($key, $stockInit); echo 'stock Init'; } ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:0:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#三redis-实现悲观锁"},{"categories":["PHP","Redis"],"content":" 3.1、悲观锁实现(一)非最佳实践看似符合逻辑的商品秒杀: public function flashSale() { $key = \"stock\"; $lockSuffix = \"_lock\"; //判断库存锁是否存在 while (Cache::get($key . $lockSuffix) == true) { // 存在锁定则等待 usleep(200000); } //库存上锁 Cache::store('redis')-\u003eset($key . $lockSuffix, 1, 30); //获取库存值 $stock = Cache::store('redis')-\u003eget($key); //减库存 if ($stock \u003e 0) { $temp = $stock; $stock -= 1; } else { //打开库存锁 Cache::store('redis')-\u003eset($key . $lockSuffix, false); return \"已售罄\"; } Cache::store('redis')-\u003eset($key, $stock); //打开库存锁 Cache::store('redis')-\u003eset($key . $lockSuffix, false); return \"恭喜,您抢到了第 {$temp}个库存!\"; } 实测 150 线程并发,异常率 0%,虽然引用了锁机制,看似符合逻辑的锁机制,但仍会有极低的概率脏读,原因无他,有 N 个线程同时抢到了锁。虽然概率低,但线程一多仍然会脏读。所以需要改用 redis 原生支持的 setnx 来保证只有一个线程抢到了锁。 如下,两个线程同时抢到了第 80 个库存: ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:1:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#31悲观锁实现一非最佳实践"},{"categories":["PHP","Redis"],"content":" 3.2、悲观锁实现(二)setnx 是 set if not exists 的简写,在 key 不存在时等价于 set,如果 key 存在,则不更新缓存内容,且返回 false。使用这个特性,可以保证锁只有一个线程抢到了。 使用 redis setnx 实现悲观锁的商品秒杀: public function flashSale() { $redisConifg = config('cache.redis'); //获取当前模块下的config文件夹中的cache文件的redis配置数组 $redis = Cache::connect($redisConifg); //获取thinkPHP官方封装的Redis Cache对象 $handler = Cache::connect($redisConifg)-\u003ehandler();//获取php redis扩展原生redis对象 https://github.com/phpredis/phpredis $key = \"stock\";//商品库存缓存名 $lockSuffix = \"_lock\";//商品库存锁后缀名 $timeOut = 10; //库存锁过期时间 //抢库存锁 while ($handler-\u003eset($key . $lockSuffix, 1, ['nx', 'ex' =\u003e $timeOut]) == false) { // 没有抢到则等待 usleep(20000); } //当前线程抢到库存锁了 //获取库存值 $stock = $redis-\u003eget($key); //减库存 if ($stock \u003e 0) { $temp = $stock; $stock -= 1; } else { //删除库存锁 $redis-\u003erm($key . $lockSuffix); return \"已售罄\"; } //更新库存值 $redis-\u003eset($key, $stock); //删除库存锁 $redis-\u003erm($key . $lockSuffix); return \"恭喜,您抢到了第 {$temp}个库存!\"; } 150 线程并发测试后,并没有发现有异常情况了。根据实际业务需求,可以增加等待超时机制。 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:2:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#32悲观锁实现二"},{"categories":["PHP","Redis"],"content":" 四、REFhttps://redis.io/commands/set http://www.redis.cn/commands/set.html https://github.com/phpredis/phpredis#set https://www.jianshu.com/p/a1ebab8ce78a https://blog.csdn.net/qq_34337272/article/details/81072874 ","date":"2020-08-30","objectID":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html:0:0","series":null,"tags":["悲观锁"],"title":"ThinkPHP5.1 如何使用 Redis 实现悲观锁","uri":"/posts/thinkphp-5.1-how-to-implement-pessimistic-locking-using-redis.html#四ref"},{"categories":["Nginx"],"content":"负载均衡是高可用网络基础架构的关键组件,通常用于将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能和可靠性。 如果手上没有足够数量的公网云服务器,本地使用虚拟机环境搭建集群做负载均衡测试是最省钱的做法,也方便做快照恢复。 ","date":"2020-08-24","objectID":"/posts/load-balancing-based-on-nginx.html:0:0","series":null,"tags":["负载均衡","高可用"],"title":"基于 Nginx 实现的负载均衡","uri":"/posts/load-balancing-based-on-nginx.html#"},{"categories":["Nginx"],"content":" 一、准备工作我使用 VMware 在本地部署了 3 台 CentOS 服务器作为测试环境,统一安装宝塔编译环境(LNMP)。虚拟机的网络配置不是本文的重点,就不再赘述了。 ","date":"2020-08-24","objectID":"/posts/load-balancing-based-on-nginx.html:0:0","series":null,"tags":["负载均衡","高可用"],"title":"基于 Nginx 实现的负载均衡","uri":"/posts/load-balancing-based-on-nginx.html#一准备工作"},{"categories":["Nginx"],"content":" 二、检查环境这 3 台 CentOS 服务器编译好环境之后分别添加一个网站,检查 web 服务是否正常,并修改其中 2 台的默认模板文件,方便在测试负载均衡效果时区分是哪台服务器提供的 web 服务。 本次测试的三台服务器 IP 地址分别为 192.168.150.10(负载均衡 主服务器) 192.168.150.20(业务服务器) 192.168.150.30(业务服务器) ","date":"2020-08-24","objectID":"/posts/load-balancing-based-on-nginx.html:0:0","series":null,"tags":["负载均衡","高可用"],"title":"基于 Nginx 实现的负载均衡","uri":"/posts/load-balancing-based-on-nginx.html#二检查环境"},{"categories":["Nginx"],"content":" 三、部署网站测试域名为 t10.com,因此需要在宿主机上将此域名解析到负载均衡主服务器(192.168.150.10)。 修改宿主机的 hosts 文件(路径:C:\\Windows\\System32\\drivers\\etc\\hosts),添加以下内容。 192.168.150.10 t10.com 分别在三台服务器上添加网站后,将 t10.com 这个域名解析到网站上。 如下图示例 在负载均衡主服务器的 server 语法块下,也就是网关的配置文件中加入一条转发规则 location / { # 转发至负载均衡服务器 proxy_pass http://fzjh; proxy_connect_timeout 2s; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } 在负载均衡主服务器的 nginx 配置文件的 http 语法块下(与 server 同级别)加入规则 upstream fzjh { # least_conn; # ip_hash; server 192.168.150.20:80 weight=1; server 192.168.150.30:80 weight=1; # server 192.168.150.30:80 backup; } 保存配置文件后重启 nginx,此时访问 t10.com 时会自动分发至两台业务服务器(192.168.150.20、192.168.150.30)。 ","date":"2020-08-24","objectID":"/posts/load-balancing-based-on-nginx.html:0:0","series":null,"tags":["负载均衡","高可用"],"title":"基于 Nginx 实现的负载均衡","uri":"/posts/load-balancing-based-on-nginx.html#三部署网站"},{"categories":["Nginx"],"content":" 四、常用的 nginx 负载均衡算法","date":"2020-08-24","objectID":"/posts/load-balancing-based-on-nginx.html:0:0","series":null,"tags":["负载均衡","高可用"],"title":"基于 Nginx 实现的负载均衡","uri":"/posts/load-balancing-based-on-nginx.html#四常用的-nginx-负载均衡算法"},{"categories":["Nginx"],"content":" 1、轮询(默认)轮询即 Round Robin,根据 Nginx 配置文件中的顺序,依次把客户端的 Web 请求分发到不同的后端服务器。 http{ upstream sampleapp { server ...; server ...; } .... server{ listen 80; ... location / { proxy_pass http://sampleapp; } } ","date":"2020-08-24","objectID":"/posts/load-balancing-based-on-nginx.html:1:0","series":null,"tags":["负载均衡","高可用"],"title":"基于 Nginx 实现的负载均衡","uri":"/posts/load-balancing-based-on-nginx.html#1轮询默认"},{"categories":["Nginx"],"content":" 2、least_conn(最少连接)Web 请求会被转发到连接数最少的服务器上。 http{ upstream sampleapp { least_conn; server ...; server ...; } .... server{ listen 80; ... location / { proxy_pass http://sampleapp; } } ","date":"2020-08-24","objectID":"/posts/load-balancing-based-on-nginx.html:2:0","series":null,"tags":["负载均衡","高可用"],"title":"基于 Nginx 实现的负载均衡","uri":"/posts/load-balancing-based-on-nginx.html#2least_conn最少连接"},{"categories":["Nginx"],"content":" 3、ip_hash(IP 地址哈希)前述的两种负载均衡方案中,同一客户端连续的 Web 请求可能会被分发到不同的后端服务器进行处理,因此如果涉及到会话 Session,那么会话会比较复杂。常见的是基于数据库的会话持久化。要克服上面的难题,可以使用基于 IP 地址哈希的负载均衡方案。这样的话,同一客户端连续的 Web 请求都会被分发到同一服务器进行处理。 http{ upstream sampleapp { ip_hash; server ...; server ...; } .... server{ listen 80; ... location / { proxy_pass http://sampleapp; } } ","date":"2020-08-24","objectID":"/posts/load-balancing-based-on-nginx.html:3:0","series":null,"tags":["负载均衡","高可用"],"title":"基于 Nginx 实现的负载均衡","uri":"/posts/load-balancing-based-on-nginx.html#3ip_haship-地址哈希"},{"categories":["Nginx"],"content":" 4、基于权重的负载均衡算法基于权重的负载均衡即 Weighted Load Balancing,这种方式下,我们可以配置 Nginx 把请求更多地分发到高配置的后端服务器上,把相对较少的请求分发到低配服务器。 http{ upstream sampleapp { server ... weight=1; server ... weight=1; } .... server{ listen 80; ... location / { proxy_pass http://sampleapp; } } backup 即为备机,当所有业务服务器都宕机时,请求会自动转发至备机; 多台业务服务器的代码同步可以使用 rsync 解决。 ","date":"2020-08-24","objectID":"/posts/load-balancing-based-on-nginx.html:4:0","series":null,"tags":["负载均衡","高可用"],"title":"基于 Nginx 实现的负载均衡","uri":"/posts/load-balancing-based-on-nginx.html#4基于权重的负载均衡算法"},{"categories":["软件"],"content":"内存盘就是将一部分硬盘当硬盘使用,由于内存速度很快,利用了这个特性设置虚拟内存硬盘,在一些特定场景和需求下是非常有帮助的。 界面简洁,原生支持中文,支持在创建时自动创建目录,能够满足需求。 官网:https://www.softperfect.com/products/ramdisk 度盘:https://pan.baidu.com/s/1z9elPiX103hloTwXj-NW5w 提取码:kcik ","date":"2020-08-07","objectID":"/posts/ram-disk-tool.html:0:0","series":null,"tags":["内存盘"],"title":"SoftPerfect RAM Disk 内存盘工具","uri":"/posts/ram-disk-tool.html#"},{"categories":["How-to-use"],"content":"在开发 API 完成后,测试的环节中免不了要进行压力测试。postman 的 runner 只支持串行化测试,不支持并发。jmeter 需要在本机上安装 JDK,且需要配置好 JAVA 环境变量,安装略显复杂,软件界面对小白并不友好,非开箱即用的工具。权衡之后,我个人推荐任何对压力测试毫无经验的人优先选择使用 Linux 环境下的 ab(apache benchmark)压测工具进行压力测试。 # 安装 ab yum -y install httpd-tools # 显示可选的参数列表及说明 ab -help # 测试 # c:并发数量 # n:总计请求数量 ab -c 100 -n 10000 https://yourdomain.com/getUserInfo?id=36\u0026token=6vPgUT0gG1RyzBRKxYsSNBgwFwM1mQLz 耐心等待测试完毕,以下是测试报告 Server Software: nginx Server Hostname: yourdomain.com Server Port: 443 SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128 Document Path: getUserInfo?id=36\u0026token=6vPgUT0gG1RyzBRKxYsSNBgwFwM1mQLz Document Length: 41 bytes Concurrency Level: 200 Time taken for tests: 187.546 seconds Complete requests: 30000 Failed requests: 53 (Connect: 0, Receive: 0, Length: 53, Exceptions: 0) Write errors: 0 Non-2xx responses: 47 Total transferred: 11695535 bytes HTML transferred: 1235123 bytes Requests per second: 159.96 [#/sec] (mean) Time per request: 1250.304 [ms] (mean) Time per request: 6.252 [ms] (mean, across all concurrent requests) Transfer rate: 60.90 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 420 1078.0 286 63684 Processing: 50 640 996.0 609 69848 Waiting: 50 625 248.5 608 1796 Total: 217 1060 1452.9 951 69848 Percentage of the requests served within a certain time (ms) 50% 951 # 50%的请求在951ms内返回 66% 1074 75% 1152 80% 1205 90% 1391 # 90%的请求在1391ms内返回 95% 1660 98% 2043 99% 2928 # 99%的请求在2928ms内返回 100% 69848 (longest request) 比较重要的也就是最后这部分的 percent line 了,jmeter 只有 90%、95%、99% 这三个量级,ab 从 50% 到 100% 都有。 ","date":"2020-06-13","objectID":"/posts/how-to-stress-test-the-api-using-apache-benchmark.html:0:0","series":null,"tags":["压力测试","apache","API"],"title":"如何使用 apache benchmark 对 API 进行压力测试","uri":"/posts/how-to-stress-test-the-api-using-apache-benchmark.html#"},{"categories":["Nginx"],"content":"自动切割目标目录下的所有 .log 文件,即使新建站点,产生新的日志需要切割,也无需修改脚本。 #!/bin/bash #自动版日志切割 无需设置 #存放 .log 日志文件的路径 log_files_path=\"/home/wwwlogs\" #存放切割后日志的文件夹路径 log_files_dir=${log_files_path}/$(date -d \"yesterday\" +\"%Y\")/$(date -d \"yesterday\" +\"%m\") #nginx的路径 nginx_sbin=\"/usr/local/nginx/sbin/nginx\" #保存多少天内的日志 save_days=30 ############################################ # 请勿修改此脚本下面的任何内容!!!!!!!!! # ############################################ mkdir -p $log_files_dir cd $log_files_path #cut nginx log files for d in `ls -f *.log`;do mv ${log_files_path}${d} ${log_files_dir}/$(date -d \"yesterday\" +\"%Y%m%d\")_$d done #delete 30 days ago nginx log files find $log_files_path -mtime +$save_days -exec rm -rf {} \\; $nginx_sbin -s reload ","date":"2020-03-28","objectID":"/posts/nginx-log-auto-chunk-script.html:0:0","series":null,"tags":["日志"],"title":"Nginx 日志全自动切割脚本","uri":"/posts/nginx-log-auto-chunk-script.html#"},{"categories":["Linux"],"content":"刚接触 Linux 时最怕的就是 SSH 远程登录 Linux VPS 编译安装程序时(比如安装 lnmp)网络突然断开,或者其他情况导致不得不与远程 SSH 服务器链接断开,远程执行的命令也被迫停止,只能重新连接,重新运行。使用 screen 可以解决这个问题。 ","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:0:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#"},{"categories":["Linux"],"content":" 一、screen 是什么?Screen 是一个可以在多个进程之间多路复用一个物理终端的全屏窗口管理器。Screen 中有会话的概念,用户可以在一个 screen 会话中创建多个 screen 窗口,在每一个 screen 窗口中就像操作一个真实的 telnet/SSH 连接窗口那样。 ","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:0:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#一screen-是什么"},{"categories":["Linux"],"content":" 二、如何安装 screen 命令?除部分精简的系统或者定制的系统大部分都安装了 screen 命令,如果没有安装,CentOS 系统可以执行:yum install screen ; CentOS 8 上移除了 screen,需要安装 epel 后安装 screen 执行:yum install screen # 安装 epel yum install epel-release # 如果无法安装,尝试以下命令 # CentOS/RHEL 5 : rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-5.noarch.rpm # CentOS/RHEL 6 : rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm # CentOS/RHEL 7 : rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm Debian/Ubuntu 系统执行:apt-get install screen ","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:0:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#二如何安装-screen-命令"},{"categories":["Linux"],"content":" 三、screen 命令使用方法?","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:0:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#三screen-命令使用方法"},{"categories":["Linux"],"content":" 3.1 创建 screen 会话可以先执行:screen -S lnmp ,screen 就会创建一个名字为 lnmp 的会话。 ","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:1:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#31-创建-screen-会话"},{"categories":["Linux"],"content":" 3.2 暂时离开,保留 screen 会话中的任务或程序当需要临时离开时(会话中的程序不会关闭,仍在运行)可以用快捷键 Ctrl+a d (即按住 Ctrl,依次再按 a,d) ","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:2:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#32-暂时离开保留-screen-会话中的任务或程序"},{"categories":["Linux"],"content":" 3.3 恢复 screen 会话当回来时可以再执行执行:screen -r lnmp 即可恢复到离开前创建的 lnmp 会话的工作界面。如果忘记了,或者当时没有指定会话名,可以执行:screen -ls screen 会列出当前存在的会话列表。 如果因为某种异常情况,但你断开了 SSH 连接,但 screen 状态为 Attached 时,可以使用 screen -D -r lnmp ,先踢掉前一用户,再登陆。 ","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:3:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#33-恢复-screen-会话"},{"categories":["Linux"],"content":" 3.4 关闭 screen 的会话执行:exit ,会提示:[screen is terminating],表示已经成功退出 screen 会话。 ","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:4:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#34-关闭-screen-的会话"},{"categories":["Linux"],"content":" 四、远程演示首先演示者先在服务器上执行 screen -S test 创建一个 screen 会话,观众可以链接到远程服务器上执行 screen -x test 观众屏幕上就会出现和演示者同步。 ","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:0:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#四远程演示"},{"categories":["Linux"],"content":" 五、常用快捷键Ctrl+a c :在当前 screen 会话中创建窗口 Ctrl+a w :窗口列表 Ctrl+a n :下一个窗口 Ctrl+a p :上一个窗口 Ctrl+a 0-9 :在第 0 个窗口和第 9 个窗口之间切换 ","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:0:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#五常用快捷键"},{"categories":["Linux"],"content":" REFhttps://www.vpser.net/manage/screen.html ","date":"2020-03-28","objectID":"/posts/ssh-remote-session-management-tool-screen-tutorial.html:0:0","series":null,"tags":["screen"],"title":"SSH 远程会话管理工具 – screen 使用教程","uri":"/posts/ssh-remote-session-management-tool-screen-tutorial.html#ref"},{"categories":["Mark"],"content":"你还在发起 ajax 请求时在 data 里写上一大堆参数,或是手动拼接参数字符串吗? var url = \"http://example.com/...\"; $.ajax({ url: url, type: 'POST',//使用post方式提交 data:$(\"form\").serialize(),//使用jQuery选择器获取form标签中的数据,并序列化 success: function (data) { if (data['code'] == 1) { // ... } else if(data['code'] == 0) { // ... } else { // ... } } }); jQuery Yes! ","date":"2020-02-27","objectID":"/posts/how-to-gracefully-launch-an-ajax-request-with-jquery.html:0:0","series":null,"tags":["JQuery","Ajax"],"title":"JQuery 如何优雅的发起一个 ajax 请求","uri":"/posts/how-to-gracefully-launch-an-ajax-request-with-jquery.html#"},{"categories":["news"],"content":"Microsoft 将于 2020 年 1 月 14 日对 Windows 7 终止支持。 一个时代落幕了。 官网截图 ","date":"2020-01-14","objectID":"/posts/news/goodbye-windows-7.html:0:0","series":null,"tags":[],"title":"再见, Windows7","uri":"/posts/news/goodbye-windows-7.html#"},{"categories":["news"],"content":" REF微软声明原文:http://www.microsoft.com/zh-cn/windows/windows-7-end-of-life-support-information ","date":"2020-01-14","objectID":"/posts/news/goodbye-windows-7.html:0:0","series":null,"tags":[],"title":"再见, Windows7","uri":"/posts/news/goodbye-windows-7.html#ref"},{"categories":["脚本"],"content":"在调试设备的时候需要来回切换笔记本电脑无线网卡的静态 IP 地址,非常麻烦。所以写了两个 .bat 的批处理脚本,希望对你有帮助。 ","date":"2019-11-22","objectID":"/posts/setting-up-windows-ip-dns-using-bat-scripts.html:0:0","series":null,"tags":["Windows","批处理","IP","DNS"],"title":"Windows 使用 .bat 批处理脚本设置 IP、DNS ","uri":"/posts/setting-up-windows-ip-dns-using-bat-scripts.html#"},{"categories":["脚本"],"content":" 一、DHCP将如下内容保存为 set_WLAN_DHCP.bat 文件 设置为自动获取IP/DNS @echo off % 设置UTF-8 防止中文乱码 % chcp 65001 echo 设置IP信息自动获取 netsh interface ip set address \"WLAN\" source=dhcp echo 设置DNS自动获取 netsh interface ip set dnsservers \"WLAN\" source=dhcp echo 完成 pause ","date":"2019-11-22","objectID":"/posts/setting-up-windows-ip-dns-using-bat-scripts.html:0:0","series":null,"tags":["Windows","批处理","IP","DNS"],"title":"Windows 使用 .bat 批处理脚本设置 IP、DNS ","uri":"/posts/setting-up-windows-ip-dns-using-bat-scripts.html#一dhcp"},{"categories":["脚本"],"content":" 二、静态 IP将如下内容保存为 set_WLAN_StaticIP.bat 文件 设置为静态IP/DNS @echo off % 设置UTF-8 防止中文乱码 % chcp 65001 echo 设置IP、子网掩码及网关 netsh interface ip set address name=\"WLAN\" static 192.168.11.119 255.255.255.0 192.168.11.1 1 \u003enul echo 设置主DNS netsh interface ip set dns name=\"WLAN\" static 192.168.11.1 \u003enul echo 设置副DNS netsh interface ip add dns name=\"WLAN\" 114.114.114.114 2 \u003enul echo 完成 pause PS:两个脚本中的 “WLAN” 是因为我的笔记本无线网卡名称为 WLAN ","date":"2019-11-22","objectID":"/posts/setting-up-windows-ip-dns-using-bat-scripts.html:0:0","series":null,"tags":["Windows","批处理","IP","DNS"],"title":"Windows 使用 .bat 批处理脚本设置 IP、DNS ","uri":"/posts/setting-up-windows-ip-dns-using-bat-scripts.html#二静态-ip"},{"categories":["软件"],"content":"Snipaste 是一个简单但强大的截图工具,同时可以让你将截图贴到屏幕上!下载并打开 Snipaste,按下 F1 来开始截图,再按 F3,截图就在桌面置顶显示了。就这么简单!而且它提供的截图操作功能也相当全面,足够当成一款截图工具使用。当前支持 windows / mac 平台。 官网:https://www.snipaste.com 中文官网:https://zh.snipaste.com 你还可以将剪贴板里的文字或者颜色信息转化为图片窗口,并且将它们进行缩放、旋转、翻转、设为半透明,甚至让鼠标能穿透它们!如果你是程序员、设计师,或者是大部分工作时间都在电脑前,贴图功能将改变你的工作方式、提升工作效率。 Snipaste 使用很简单,但同时也有一些较高级的用法可以进一步提升你的工作效率。感兴趣的话,请抽空读一读用户手册。 Snipaste 是免费软件,它也很安全,没有广告、不会扫描你的硬盘、更不会上传用户数据,它只做它应该做的事。 ","date":"2019-10-18","objectID":"/posts/snipaste-simple-but-powerful-snipping-tool.html:0:0","series":null,"tags":[],"title":"Snipaste - 简单但强大的截图工具","uri":"/posts/snipaste-simple-but-powerful-snipping-tool.html#"},{"categories":["Python"],"content":"没有多进程,没有任何黑科技的裸爬虫。练手用,爬虫获取到的数据皆为非敏感且公开的用户信息。 ","date":"2019-09-12","objectID":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html:0:0","series":null,"tags":[],"title":"我用一天时间“偷了”网易云音乐50W+用户信息","uri":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html#"},{"categories":["Python"],"content":" 一、思路在 GitHub 上已经有网易云音乐的 node.js API(GitHub:https://github.com/Binaryify/NeteaseCloudMusicApi)。根据这个库提供的信息,可以很轻易的获取到网易云音乐获取某个用户的粉丝信息接口的参数(接口限制只能获取 100 个),进而继续获取这 100 个粉丝的粉丝… 简单的几层循环嵌套就能很轻易的拿到十万级到百万级的用户数据(非敏感用户信息)。 ","date":"2019-09-12","objectID":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html:0:0","series":null,"tags":[],"title":"我用一天时间“偷了”网易云音乐50W+用户信息","uri":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html#一思路"},{"categories":["Python"],"content":" 二、参数加密流程分析__getFormData(data, __get_random_str()) 参数1:data是dict数据,包含了表单的各个字段和数据 参数2:16位的随机字符串 最终return的是一个dict,包含了params和encSecKey两个参数 params __get_encText(args1, random16str) 参数1:args1是__getFormData函数的参数1,是dict数据,包含了表单的各个字段和数据 参数2:random16str是__getFormData函数的参数2,是一个16位的随机字符串 最终返回的是将参数加密后产生的params __AES_encrypt(args1, args4) 参数1:args1是__get_encText函数的参数1,是dict数据,包含了表单的各个字段和数据 参数2:arg4是一个固定参数 最终返回的是将参数使用AES CBC加密后再进行一次base64加密产生的字符串 使用__AES_encrypt函数首先加密一次参数是(args1,args4)得到一个加密的字符串 在使用加密过一次的字符串作为参数1,和__get_encText函数传入的参数2 random16str 这个随机16位的字符串作为参数2继续加密1次 最终得到params encSecKey __get_encSecKey(random16str) 参数1:random16str是__getFormData函数的参数2,是一个16位的随机字符串 最终返回的是通过随机字符串产生的encSecKey 固定参数arg2 固定参数arg3 通过固定算法,使用随机16位的字符串random16str与这两个固定参数产生encSecKey ","date":"2019-09-12","objectID":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html:0:0","series":null,"tags":[],"title":"我用一天时间“偷了”网易云音乐50W+用户信息","uri":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html#二参数加密流程分析"},{"categories":["Python"],"content":" 三、源码","date":"2019-09-12","objectID":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html:0:0","series":null,"tags":[],"title":"我用一天时间“偷了”网易云音乐50W+用户信息","uri":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html#三源码"},{"categories":["Python"],"content":" common.py (需要用到的函数)import base64 from Crypto.Cipher import AES import random import codecs import requests from fake_useragent import UserAgent def __AES_encrypt(text, key): ''' 获取到加密后的数据 :param text: 首先CBC加密方法,text必须位16位数据 :param key: 加密的key :return: 加密后的字符串 ''' iv = \"0102030405060708\" pad = 16 - len(text) % 16 if isinstance(text, str): text = text + pad * chr(pad) else: text = text.deocde(\"utf-8\") + pad * chr(pad) aes = AES.new(key=bytes(key, encoding=\"utf-8\"), mode=2, IV=bytes(iv, encoding=\"utf-8\")) res = aes.encrypt(bytes(text, encoding=\"utf-8\")) res = base64.b64encode(res).decode(\"utf-8\") return res def __get_encText(args1, random16str): args4 = \"0CoJUm6Qyw8W8jud\" encText = __AES_encrypt(args1, args4) encText = __AES_encrypt(encText, random16str) return encText def __get_encSecKey(random16str): '''通过查看js代码,获取encSecKey''' arg2 = \"010001\" arg3 = \"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7\" text = random16str[::-1] rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(arg2, 16) % int(arg3, 16) return format(rs, 'x').zfill(256) def __get_random_str(): '''这是16位的随机字符串''' str_set = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\" random_str = \"\" for i in range(16): index = random.randint(0, len(str_set) - 1) random_str += str_set[index] return random_str def __getFormData(args1, random16str): '''获取到提交的数据''' data = {\"params\": __get_encText(args1, random16str), \"encSecKey\": __get_encSecKey(random16str)} return data def __getFans(userID): userDict = {} userID = str(userID) # userID=\"6177307\" data = '{\"userId\":\"' + userID + '\",\"time\":\"-1\",\"limit\":\"104334\",\"csrf_token\": \"\"}' formdata = __getFormData(data, __get_random_str()) ua = UserAgent() session = requests.Session() headers = {} headers[\"content-type\"] = \"application/x-www-form-urlencoded\" headers[\"user-agent\"] = ua.random headers[\"referer\"] = \"https://music.163.com/\" response = session.post(url='https://music.163.com/weapi/user/getfolloweds', headers=headers, data=formdata) results = response.json() # print(response.status_code) results = results.get('followeds') for one in results: userDict[one.get('userId')] = one # print(one.get('userId')) # print(str(one)) return userDict ","date":"2019-09-12","objectID":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html:1:0","series":null,"tags":[],"title":"我用一天时间“偷了”网易云音乐50W+用户信息","uri":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html#commonpy-需要用到的函数"},{"categories":["Python"],"content":" main.py (主程序)# -*- coding: utf-8 -*- import common AllData = {} data = common.__getFans(6177307) AllData.update(data) sum = 0 for item in data: temp = common.__getFans(item) AllData.update(temp) for item1 in temp: temp2 = common.__getFans(item1) AllData.update(temp2) for item2 in temp2: sum += 1 print(sum) temp3 = common.__getFans(item2) AllData.update(temp3) with open('fans.txt', 'a', encoding='utf-8') as f: for one in AllData.items(): f.write(str(one) + '\\n') print(str(one)) ","date":"2019-09-12","objectID":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html:2:0","series":null,"tags":[],"title":"我用一天时间“偷了”网易云音乐50W+用户信息","uri":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html#mainpy-主程序"},{"categories":["Python"],"content":" 四、数据我把 main.py 放到了服务器上运行,跑到程序结束大概用了 24 小时左右后看了一下存储的文本有 50W 左右的用户数据(不含敏感信息),如下。 (83543823, {'py': 'mjdtjst', 'time': 1510758264852, 'userId': 83543823, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': '名劍動天倦收天', 'avatarUrl': 'http://p1.music.126.net/uocXBF145t-_V0pLWDwv0w==/3272146604393759.jpg', 'gender': 1, 'expertTags': None, 'experts': None, 'followeds': 13, 'remarkName': None, 'follows': 19, 'authStatus': 0, 'userType': 0, 'vipType': 0, 'signature': '天下若倾,尚有儒门一手擎天!', 'vipRights': None, 'eventCount': 1, 'playlistCount': 5}) (305937375, {'py': 'ttqsunny', 'time': 1510751111003, 'userId': 305937375, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': '甜甜圈sunny', 'avatarUrl': 'http://p1.music.126.net/v9iyq-6I1WC96R7SlbKvXQ==/3420580709664324.jpg', 'gender': 1, 'expertTags': None, 'experts': None, 'followeds': 4, 'remarkName': None, 'follows': 30, 'authStatus': 0, 'userType': 0, 'vipType': 11, 'signature': None, 'vipRights': {'associator': {'vipCode': 100, 'rights': True}, 'musicPackage': None, 'redVipAnnualCount': -1}, 'eventCount': 0, 'playlistCount': 3}) (359743222, {'py': 'xxwmhjl-_', 'time': 1509411979309, 'userId': 359743222, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': '醒醒我们回家了-_', 'avatarUrl': 'http://p1.music.126.net/KcAVTPDSC8MrKaFB9_Vd9g==/109951163985306640.jpg', 'gender': 1, 'expertTags': None, 'experts': None, 'followeds': 4, 'remarkName': None, 'follows': 9, 'authStatus': 0, 'userType': 0, 'vipType': 0, 'signature': '身邪不怕影子正', 'vipRights': None, 'eventCount': 0, 'playlistCount': 7}) (285736292, {'py': 'gzydyzj-', 'time': 1509016136464, 'userId': 285736292, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': '孤舟夜灯一掌剑-', 'avatarUrl': 'http://p1.music.126.net/SsOPGfkTUM0dSEkOuvHfHQ==/109951163351011922.jpg', 'gender': 1, 'expertTags': None, 'experts': None, 'followeds': 13, 'remarkName': None, 'follows': 33, 'authStatus': 0, 'userType': 0, 'vipType': 0, 'signature': '寂寞候忘了向先生说告辞!', 'vipRights': None, 'eventCount': 39, 'playlistCount': 23}) (79969440, {'py': 'cj', 'time': 1508992729596, 'userId': 79969440, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': '尺疾', 'avatarUrl': 'http://p1.music.126.net/SLdf4abndYLV4Gq8eXbK8w==/109951163788127476.jpg', 'gender': 1, 'expertTags': None, 'experts': None, 'followeds': 9, 'remarkName': None, 'follows': 17, 'authStatus': 0, 'userType': 0, 'vipType': 11, 'signature': '绝情莫过绝音讯,断爱无非断感情', 'vipRights': {'associator': {'vipCode': 100, 'rights': True}, 'musicPackage': None, 'redVipAnnualCount': -1}, 'eventCount': 0, 'playlistCount': 6}) (413801433, {'py': 'Aresfx', 'time': 1508811460203, 'userId': 413801433, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': 'Ares风雪', 'avatarUrl': 'http://p1.music.126.net/B1qPhuJvDGuAkQxGvNkxoA==/18828037115798229.jpg', 'gender': 1, 'expertTags': None, 'experts': None, 'followeds': 5, 'remarkName': None, 'follows': 32, 'authStatus': 0, 'userType': 0, 'vipType': 0, 'signature': None, 'vipRights': None, 'eventCount': 6, 'playlistCount': 11}) (385103142, {'py': 'gyw', 'time': 1508734902599, 'userId': 385103142, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': '归于无', 'avatarUrl': 'http://p1.music.126.net/HjaQMktAYh5dDuf009Fv5A==/18588343580852886.jpg', 'gender': 1, 'expertTags': None, 'experts': None, 'followeds': 1, 'remarkName': None, 'follows': 8, 'authStatus': 0, 'userType': 0, 'vipType': 0, 'signature': None, 'vipRights': None, 'eventCount': 0, 'playlistCount': 2}) (83142991, {'py': 'persana', 'time': 1508582619577, 'userId': 83142991, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': 'persana', 'avatarUrl': 'http://p1.music.126.net/psdQcsE9EWpawBXFDEbBag==/109951163289688334.jpg', 'gender': 1, 'expertTags': None, 'experts': None, 'followeds': 2, 'remarkName': None, 'follows': 11, 'authStatus': 0, 'userType': 0, 'vipType': 0, 'signature': '所有悲欢离合最后都不过付与说书人', 'vipRights': None, 'eventCount': 0, 'playlistCount': 4}) (259674694, {'py': 'hcxdn', 'time': 1508483033261, 'userId': 259674694, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': '会嘲笑的鸟', 'avatarUrl': 'http://p1.music.126.net/Z6hvgL__sAOKLe_X30ePUg==/1424967073916267.jpg', 'gender': 1, 'expertTags': None, 'experts': None, 'followeds': 1, 'remarkName': None, 'follows': 7, 'authStatus': 0, 'userType': 0, 'vipType': 0, 'signature': '', 'vipRights': None, 'eventCount': 0, 'playlistCount': 5}) (277358463, {'py': 'yqj', 'time': 1507095713513, 'userId': 277358463, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': '御清觉', 'avatarUrl': 'http://p1.music.126.net/VCxi7mae0G7lu1auRDiRQg==/1406275381794388.jpg', 'gender': 0, 'expertTags': None, 'experts': None, 'followeds': 1, 'remarkName': None, 'follows': 8, 'authStatus': 0, 'userType': 0, 'vipType': 0, 'signature': None, 'vipRights': None, 'eventCount': 7, 'playlistCount': 6}) (411934208, {'py': 'lybl10', 'time': 1498717923059, 'followed': False, 'accountStatus': 0, 'nickname': 'lybl10', 'follows': 50, 'followeds': 31, 'userId': 411934208, 'vipType': 0, 'remarkName': None, 'avatarUrl': 'http://p1.music.126.net/BMFrIsI8jj8R-ths5hR4QQ==/109951163450374697.jpg', 'authStatus': 0, 'userType': 0, 'mutual': False, 'gender': 1, 'expertTags': None, 'experts': None, 'signature': '旁友,你听说过布袋戏吗?( ´・ᴗ・` )', 'vipRights': None, 'eventCount': 18, 'playlistCount': 18}) (380749891, {'py': '18579086180', 'time': 1563184649570, 'followed': False, 'remarkName': None, 'userId': 380749891, 'nickname': '18579086180', 'mutual': False, 'follows': 3, 'followeds': 1, 'vipType': 0, 'accountStatus': 0, 'avatarUrl': 'http://p2.music.126.net/Eu7gRGq_brlja7CqeHaMsA==/18646617697102174.jpg', 'authStatus': 0, 'userType': 0, 'gender': 1, 'expertTags': None, 'experts': None, 'signature': None, 'vipRights': None, 'eventCount': 0, 'playlistCount': 2}) (367993248, {'py': 'fymxrys', 'time': 1561220920584, 'followed': False, 'remarkName': None, 'userId': 367993248, 'nickname': '风摇满袖人与事', 'mutual': False, 'follows': 4, 'followeds': 4, 'vipType': 0, 'accountStatus': 0, 'avatarUrl': 'http://p1.music.126.net/mU8t1BFAz-KKdOz_9vFbJQ==/18807146394528801.jpg', 'authStatus': 0, 'userType': 0, 'gender': 2, 'expertTags': None, 'experts': None, 'signature': '好霹雳,萌天下,无cp观,重口皆吃,大锅炖肉,讨厌角色性格走形╮( ̄▽ ̄)╭', 'vipRights': None, 'eventCount': 0, 'playlistCount': 8}) (307861774, {'py': 'yj', 'time': 1561120492944, 'followed': False, 'remarkName': None, 'userId': 307861774, 'nickname': '予玦', 'mutual': False, 'follows': 17, 'followeds': 5, 'vipType': 0, 'accountStatus': 0, 'avatarUrl': 'http://p1.music.126.net/d_GZI27ulOSrLoUtH7ohcw==/1398578804087193.jpg', 'authStatus': 0, 'userType': 0, 'gender': 2, 'expertTags': None, 'experts': None, 'signature': '曾绕林中潇湘索,鸿雁去,倚风听竹山河曲;无奈百尺叹高楼,此间不胜寒。', 'vipRights': None, 'eventCount': 0, 'playlistCount': 7}) (554030934, {'py': 'rqezcp', 'time': 1556848452508, 'followed': False, 'remarkName': None, 'userId': 554030934, 'nickname': '如期而至cp', 'mutual': False, 'follows': 31, 'followeds': 4, 'vipType': 0, 'accountStatus': 0, 'avatarUrl': 'http://p1.music.126.net/ut1M-6fLqv6q97Eh8WGXPw==/109951163993855389.jpg', 'authStatus': 0, 'userType': 0, 'gender': 2, 'expertTags': None, 'experts': None, 'signature': '', 'vipRights': None, 'eventCount': 0, 'playlistCount': 4}) (249943751, {'py': 'jjjjaaaay', 'time': 1542523045166, 'followed': False, 'remarkName': None, 'userId': 249943751, 'nickname': 'jjjjaaaay', 'mutual': False, 'follows': 7, 'followeds': 9, 'vipType': 0, 'accountStatus': 0, 'avatarUrl': 'http://p1.music.126.net/otp0fgk7yTala8lxZ6E5YQ==/109951163653627471.jpg', 'authStatus': 0, 'userType': 0, 'gender': 1, 'expertTags': None, 'experts': None, 'signature': '', 'vipRights': None, 'eventCount': 5, 'playlistCount': 3}) (69678664, {'py': 'syfsb', 'time': 1512553262435, 'followed': False, 'userId': 69678664, 'nickname': '疏影浮生灬', 'mutual': False, 'vipType': 0, 'followeds': 9, 'remarkName': None, 'accountStatus': 0, 'follows': 90, 'avatarUrl': 'http://p2.music.126.net/StbUovFF2Fe4OM8BXfn7Sw==/109951164172211056.jpg', 'authStatus': 0, 'userType': 0, 'gender': 1, 'expertTags': None, 'experts': None, 'signature': '星雪凄天银河垂,狂艳夜徊铸楚辞。萧瑟悲声秋风起,杀忆寒蝉未鸣时。', 'vipRights': None, 'eventCount': 8, 'playlistCount': 2}) (393632402, {'py': 'lyh', 'time': 1528925134497, 'followed': False, 'remarkName': None, 'userId': 393632402, 'nickname': '樂悦鹤', 'mutual': False, 'follows': 2, 'followeds': 7, 'vipType': 0, 'accountStatus': 0, 'avatarUrl': 'http://p1.music.126.net/w7yJivlRLfnXALj62LLPBw==/109951163959519476.jpg', 'authStatus': 0, 'userType': 0, 'gender': 2, 'expertTags': None, 'experts': None, 'signature': '“你要爱荒野上的风声 ,胜过爱贫穷和思考,暮冬时烤雪 ,迟夏写长信,早春不过,一颗树。”', 'vipRights': None, 'eventCount': 1, 'playlistCount': 13}) (363727100, {'py': 'qqz', 'time': 1521783497886, 'followed': False, 'remarkName': None, 'userId': 363727100, 'nickname': '乾秋子', 'mutual': False, 'follows': 35, 'followeds': 3, 'vipType': 0, 'accountStatus': 0, 'avatarUrl': 'http://p1.music.126.net/tLbQkYiAvnWUISXkgsqlAQ==/109951163731450756.jpg', 'authStatus': 0, 'userType': 0, 'gender': 1, 'expertTags': None, 'experts': None, 'signature': '', 'vipRights': None, 'eventCount': 3, 'playlistCount': 5}) (281619418, {'py': 'hrwsltx', 'time': 1515770911364, 'followed': False, 'vipType': 11, 'nickname': '皇儒无上蔺天邢', 'mutual': False, 'accountStatus': 0, 'avatarUrl': 'http://p1.music.126.net/UEyFyd1lC50-TP8OFdYujw==/109951164003473228.jpg', 'authStatus': 0, 'userType': 0, 'gender': 1, 'expertTags': None, 'experts': None, 'remarkName': None, 'userId': 281619418, 'follows': 41, 'followeds': 19, 'signature': '师太,放我一马!!!!!', 'vipRights': {'associator': {'vipCode': 100, 'rights': True}, 'musicPackage': None, 'redVipAnnualCount': 1}, 'eventCount': 2, 'playlistCount': 11}) (102754093, {'py': 'sts', 'time': 1510381589001, 'followed': False, 'remarkName': None, 'userId': 102754093, 'nickname': '説太岁', 'mutual': False, 'follows': 7, 'followeds': 6, 'vipType': 0, 'accountStatus': 0, 'avatarUrl': 'http://p1.music.126.net/vZ3I_7VzVxF3iqTrXIh6eQ==/3272146607712737.jpg', 'authStatus': 0, 'userType': 0, 'gender': 1, 'expertTags': None, 'experts': None, 'signature': '愤怒是对自己无能的表现', 'vipRights': None, 'eventCount': 0, 'playlistCount': 6}) (255086049, {'py': 'rwyjths', 'time': 1546144733907, 'avatarUrl': 'http://p1.music.126.net/s0lEIISQLCiCvcjfuI5wUw==/109951163864346086.jpg', 'authStatus': 0, 'userType': 0, 'gender': 2, 'expertTags': None, 'experts': None, 'follows': 26, 'followeds': 9, 'remarkName': None, 'mutual': False, 'accountStatus': 0, 'followed': False, 'userId': 255086049, 'nickname': '人舞一剑探花殇', 'vipType': 10, 'signature': '', 'vipRights': {'associator': None, 'musicPackage': {'vipCode': 220, 'rights': True}, 'redVipAnnualCount': -1}, 'eventCount': 0, 'playlistCount': 6}) (125556054, {'py': '95-58165', 'time': 1506253400100, 'followed': False, 'remarkName': None, 'userId': 125556054, 'nickname': '95-58165', 'mutual': False, 'follows': 14, 'followeds': 6, 'vipType': 0, 'accountStatus': 0, 'avatarUrl': 'http://p1.music.126.net/odk0UIRLjxtl9BFZyU_7qw==/109951164198663765.jpg', 'authStatus': 0, 'userType': 0, 'gender': 1, 'expertTags': None, 'experts': None, 'signature': '知进退,明得失', 'vipRights': None, 'eventCount': 4, 'playlistCount': 11}) (79927936, {'py': '_nfbj_', 'time': 1499765008933, 'followed': False, 'remarkName': None, 'userId': 79927936, 'nickname': '_南风不竞_', 'mutual': False, 'follows': 16, 'followeds': 12, 'vipType': 11, 'accountStatus': 0, 'avatarUrl': 'http://p1.music.126.net/_HXCTTkMUS4uCbDv8_ECuQ==/109951163041830201.jpg', 'authStatus': 0, 'userType': 0, 'gender': 1, 'expertTags': None, 'experts': None, 'signature': '驰来北马多骄气,歌到南风尽死声。', 'vipRights': {'associator': {'vipCode': 100, 'rights': True}, 'musicPackage': None, 'redVipAnnualCount': -1}, 'eventCount': 4, 'playlistCount': 14}) 下面是一个格式化的 json,信息的维度从 UID 到用户注册时间,还是比较丰富的。(非敏感用户信息) { 'py': 'mjdtjst', 'time': 1510758264852, 'userId': 83543823, 'mutual': False, 'followed': False, 'accountStatus': 0, 'nickname': '名劍動天倦收天', 'avatarUrl': 'http://p1.music.126.net/uocXBF145t-_V0pLWDwv0w==/3272146604393759.jpg', 'gender': 1, 'expertTags': None, 'experts': None, 'followeds': 13, 'remarkName': None, 'follows': 19, 'authStatus': 0, 'userType': 0, 'vipType': 0, 'signature': '天下若倾,尚有儒门一手擎天!', 'vipRights': None, 'eventCount': 1, 'playlistCount': 5 } 人对技术要持有敬畏之心,慎用之。 ","date":"2019-09-12","objectID":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html:0:0","series":null,"tags":[],"title":"我用一天时间“偷了”网易云音乐50W+用户信息","uri":"/posts/i-spent-a-day-stealing-50w-user-information-from-netease-cloud-music.html#四数据"},{"categories":["软件"],"content":"软件体积不大,底层由 VC++ 编写,用起来也是十分流畅。功能也是十分多样,我也一直在用这款播放器。安利一下 ","date":"2019-08-31","objectID":"/posts/potplayer-small-and-powerful-video-player.html:0:0","series":null,"tags":[],"title":"potplayer - 小巧、功能强大的视频播放器","uri":"/posts/potplayer-small-and-powerful-video-player.html#"},{"categories":["软件"],"content":" 下载地址https://daumpotplayer.com/download https://potplayer.daum.net (这个貌似已经打不开了) 度盘:https://pan.baidu.com/s/1CO5Xq51aAi5b7_9Y-9xKYQ 提取码:i018 ","date":"2019-08-31","objectID":"/posts/potplayer-small-and-powerful-video-player.html:0:0","series":null,"tags":[],"title":"potplayer - 小巧、功能强大的视频播放器","uri":"/posts/potplayer-small-and-powerful-video-player.html#下载地址"},{"categories":["Linux"],"content":"Secure Shell(安全外壳协议,简称 SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。SSH 通过在网络中创建安全隧道来实现 SSH 客户端与服务器之间的连接。虽然任何网络服务都可以通过 SSH 实现安全传输,SSH 最常见的用途是远程登录系统,通常利用 SSH 来传输命令行界面和远程执行命令。 在设计上,SSH 是 Telnet 和非安全 shell 的替代品。Telnet 和 Berkeley rlogin、rsh、rexec 等协议采用明文传输,使用不可靠的密码,容易遭到监听、嗅探和中间人攻击 [5]。SSH 旨在保证非安全网络环境(例如互联网)中信息加密完整可靠。 ","date":"2019-08-25","objectID":"/posts/how-to-use-ssh-to-remotely-log-in-to-a-linux-server.html:0:0","series":null,"tags":["ssh"],"title":"如何使用 SSH 远程登录 Linux 服务器","uri":"/posts/how-to-use-ssh-to-remotely-log-in-to-a-linux-server.html#"},{"categories":["Linux"],"content":" 一、Windows 远程登录 Linux下载 git bash ,方便执行 ssh 命令。 官网:https://git-scm.com ","date":"2019-08-25","objectID":"/posts/how-to-use-ssh-to-remotely-log-in-to-a-linux-server.html:0:0","series":null,"tags":["ssh"],"title":"如何使用 SSH 远程登录 Linux 服务器","uri":"/posts/how-to-use-ssh-to-remotely-log-in-to-a-linux-server.html#一windows-远程登录-linux"},{"categories":["Linux"],"content":" 1.1、生成 SSH 密钥对在 windows 中的 cmd 中输入(使用 git bash 客户端输入也可以) ssh-keygen 连摁 3 个回车即可。 生成的秘钥文件在 C:\\Users\\Administrator.ssh 文件夹下 ","date":"2019-08-25","objectID":"/posts/how-to-use-ssh-to-remotely-log-in-to-a-linux-server.html:1:0","series":null,"tags":["ssh"],"title":"如何使用 SSH 远程登录 Linux 服务器","uri":"/posts/how-to-use-ssh-to-remotely-log-in-to-a-linux-server.html#11生成-ssh-密钥对"},{"categories":["Linux"],"content":" 1.2、添加公钥文件至 Linux 服务器打开 git bash,输入以下命令 # 将192.168.1.1替换为Linux服务器的真实IP地址 # root是最高权限用户,可以替换为你想要远程登录的某个用户 # ssh端口默认是22 ssh-copy-id [email protected] -p 22 之后的提示输入 yes ,再输入 Linux 的用户密码就可以了。(输入密码时客户端不会显示任何输入提示) ","date":"2019-08-25","objectID":"/posts/how-to-use-ssh-to-remotely-log-in-to-a-linux-server.html:2:0","series":null,"tags":["ssh"],"title":"如何使用 SSH 远程登录 Linux 服务器","uri":"/posts/how-to-use-ssh-to-remotely-log-in-to-a-linux-server.html#12添加公钥文件至-linux-服务器"},{"categories":["Linux"],"content":" 1.3、登陆打开 git bash,输入以下命令就可以直接进入远程 Linux 的 Shell 了。 # 将192.168.1.1替换为Linux服务器的真实IP地址 # root是最高权限用户,可以替换为你想要远程登录的某个用户 # ssh端口默认是22 ssh [email protected] -p 22 ","date":"2019-08-25","objectID":"/posts/how-to-use-ssh-to-remotely-log-in-to-a-linux-server.html:3:0","series":null,"tags":["ssh"],"title":"如何使用 SSH 远程登录 Linux 服务器","uri":"/posts/how-to-use-ssh-to-remotely-log-in-to-a-linux-server.html#13登陆"},{"categories":["软件"],"content":"HashCheck 全称 HashCheck Shell Extension,安装后添加到属性窗口中,是一款非常小巧快速的文件校验工具,而且 HashCheck 在 Github 上开放了源代码。软件体积小,文件 hash 值计算速度快。安装后迁入资源管理器的属性中,查看文件 hash 值十分方便。 ","date":"2019-08-21","objectID":"/posts/hashcheck-a-small-and-useful-file-verification-tool.html:0:0","series":null,"tags":[],"title":"HashCheck - 一款小巧、实用的文件校验工具","uri":"/posts/hashcheck-a-small-and-useful-file-verification-tool.html#"},{"categories":["软件"],"content":" 下载地址官网:http://code.kliu.org/hashcheck Github:https://github.com/gurnec/HashCheck 度盘:https://pan.baidu.com/s/11yLvdMRYHhFncVMvyumO9Q 提取码:1k4a ","date":"2019-08-21","objectID":"/posts/hashcheck-a-small-and-useful-file-verification-tool.html:0:0","series":null,"tags":[],"title":"HashCheck - 一款小巧、实用的文件校验工具","uri":"/posts/hashcheck-a-small-and-useful-file-verification-tool.html#下载地址"},{"categories":["Python"],"content":"在写脚本的过程中无法避免的需要一些配置性的信息,但是又不想写死在 python 文件中,刚好 python 的 configparser 模块实现了以上的功能,可以用来操作 .ini 配置文件。 ","date":"2019-08-19","objectID":"/posts/manipulating-configuration-files-with-the-python-configparser-library.html:0:0","series":null,"tags":[],"title":"Python 使用 configparser 库操作配置文件","uri":"/posts/manipulating-configuration-files-with-the-python-configparser-library.html#"},{"categories":["Python"],"content":" 一、新建配置文件在 python 项目中新建一个 conf 文件夹,并在此文件夹下创建一个 conf.ini 的文件用于存储配置信息。 文件路径:./conf/conf.ini 文件内容: [EditionInfo] version = 1.0.0 ","date":"2019-08-19","objectID":"/posts/manipulating-configuration-files-with-the-python-configparser-library.html:0:0","series":null,"tags":[],"title":"Python 使用 configparser 库操作配置文件","uri":"/posts/manipulating-configuration-files-with-the-python-configparser-library.html#一新建配置文件"},{"categories":["Python"],"content":" 二、源码import configparser import os import sys def get_path(config_name): \"\"\"获取路径 返回运行本函数的文件绝对路径+config_name determine if application is a script file or frozen exe \"\"\" if getattr(sys, 'frozen', False): application_path = os.path.dirname(sys.executable) elif __file__: application_path = os.path.dirname(__file__) config_path = os.path.join(application_path, config_name) return config_path # 读取conf.ini configPath = \"conf\\\\conf.ini\" config = configparser.ConfigParser() config.read_file(open(get_path(configPath), encoding='UTF-8')) # 获取conf中的section sections = config.sections() # 获取第一个section中的所有option paramList = config.options(sections[0]) # 修改version的值 config.set(sections[0], paramList[0], \"1.0.1\") config.write(open(configPath, \"w\")) # 获取version的值 方法1 version = str(config.get(sections[0], paramList[0])) # 获取version的值 方法2 # version = str(config.get(\"EditionInfo\", \"version\")) print(version) 可能有心人会注意到 get_path 的方法有点怪异,那是因为在写 python 程序中,有可能需要获取当前运行脚本的路径。打包成 exe 的脚本和直接运行地脚本在获取路径上稍微有点不同。 get_path 的方法参考了这篇博客:https://www.cnblogs.com/pzxbc/archive/2012/03/18/2404695.html ","date":"2019-08-19","objectID":"/posts/manipulating-configuration-files-with-the-python-configparser-library.html:0:0","series":null,"tags":[],"title":"Python 使用 configparser 库操作配置文件","uri":"/posts/manipulating-configuration-files-with-the-python-configparser-library.html#二源码"},{"categories":["Python"],"content":" REFpython中获取打包成执行文件(exe)和脚本运行文件的路径 :https://www.cnblogs.com/pzxbc/archive/2012/03/18/2404695.html ","date":"2019-08-19","objectID":"/posts/manipulating-configuration-files-with-the-python-configparser-library.html:0:0","series":null,"tags":[],"title":"Python 使用 configparser 库操作配置文件","uri":"/posts/manipulating-configuration-files-with-the-python-configparser-library.html#ref"},{"categories":["Linux"],"content":"一直学下 Shell 脚本的,借鉴了很多大佬的脚本,靠着 Google 写「Ctrl+C / V」了个 Demo 出来。 直接放脚本,Mark 一下防止以后忘记… 复制脚本内容,保存为 *.sh 文件,运行 bash *.sh 结合查看脚本的输出情况来理解命令更佳。 #!/bin/bash :\u003c\u003cEOF 这是多行注释区块 $0 当前脚本的文件名 $n 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。 $# 传递给脚本或函数的参数个数。 $* 传递给脚本或函数的所有参数。 $@ 传递给脚本或函数的所有参数。被双引号(\" \")包含时,与 $* 稍有不同,下面将会讲到。 $? 上个命令的退出状态,或函数的返回值。 $$ 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。 EOF str=\"frist shell script\" #字符串拼接 echo \"1:do u know this is my $str\" #字符串长度 echo \"2:${#str}\" #提取子字符串 echo \"3:${str:1:4}\" #查找子字符串 查找字符 i 或 s 的位置(哪个字母先出现就计算哪个) echo \"4:`expr index \"$str\" is`\" #数组定义 array_test=(1 'A' 2 4) #获取数组元素值 echo \"5:${array_test[0]}\" echo \"6:${array_test[1]}\" echo \"7:${array_test[*]}\" echo \"8:${array_test[@]}\" #获取数组的长度 echo \"9:${#array_test[@]}\" #加法运算 val=`expr 2 + 2` echo \"10:两数之和为 : $val\" #在当前目录下生成log文件 log_path=\"test.log\" echo \"11:往当前目录下的log文件写入内容,如果文件不存在则自动创建。\" #如果不希望文件内容被覆盖,可以使用 \u003e\u003e 追加到文件末尾 echo \"It is a test\" \u003e $log_path #显示命令执行结果 echo \"12:显示命令执行结果 ls\" echo `ls` :\u003c\u003cEOF %s %c %d %f都是格式替代符 %-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内 如果不足则自动以空格填充,超过也会将内容全部显示出来。 %-4.2f 指格式化为小数,其中.2指保留2位小数。 EOF #printf显示表格 echo -e \"13:printf显示表格\" printf \"%-10s %-8s %-4s\\n\" 姓名 性别 体重kg printf \"%-10s %-8s %-4.2f\\n\" 郭靖 男 66.1234 printf \"%-10s %-8s %-4.2f\\n\" 杨过 男 48.6543 printf \"%-10s %-8s %-4.2f\\n\" 郭芙 女 47.9876 # 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用 echo -e \"14:format-string 重用\" printf \"%s\\n\" abc df printf \"%s %s %s\\n\" a b c d e f g h i j #if echo \"14:if语句\" a=102 b=101 if [ $a -eq $b ] then echo '两个数相等!' elif [ $a -gt $b ] then echo 'a大于b!' else echo 'a小于b!' fi a=101 b=101 if [[ $a \u003e $b || $a == $b ]] then echo 'c大等于d' else echo 'c小于d' fi #字符串比较 c='sss' d='sss' if [[ $c == $d ]] then echo 'c和d相等!' else echo 'c和d不相等!' fi #for循环 echo -e \"15:for循环输出数组内的所有元素\" for vo in ${array_test[*]} do echo $vo done #while循环 echo -e \"15:while循环输出数组内的所有元素\" array_length=${#array_test[*]} temp=1 while [ $temp -lt $array_length ] do echo $temp let \"temp++\" done #until循环 #until 循环执行一系列命令直至条件为 true 时停止。 temp=1 echo \"16:until循环输出数组内的所有元素\" until [ $temp -eq $array_length ] do echo $temp let \"temp++\" done #读取用户输入 echo \"17:读取用户输入\" echo \u0026\u0026 read -e -p \"请输入数字 [1-4]:\" aNum case $aNum in 1) echo '你选择了 1' ;; 2) echo '你选择了 2' ;; 3) echo '你选择了 3' ;; 4) echo '你选择了 4' ;; *) echo '你没有输入 1 到 4 之间的数字' ;; esac #函数定义 调用 echo \"18:函数定义 调用\" demoFun(){ echo \"这是我的第一个 shell 函数!\" } echo \"-----函数开始执行-----\" demoFun echo \"-----函数执行完毕-----\" ","date":"2019-05-31","objectID":"/posts/getting-started-with-linux-shell-scripting.html:0:0","series":null,"tags":["shell"],"title":"Linux Shell 脚本入门","uri":"/posts/getting-started-with-linux-shell-scripting.html#"},{"categories":["Linux"],"content":"在 Linux 服务器 IP 可能发生变动的情况下,使用 DDNS(Dynamic Domain Name Server / 动态域名服务)可以随时将本地服务器的 IP 更新至域名解析。DDNS 的作用和适用范围不再赘述,下面说明如何配置。 ","date":"2019-05-25","objectID":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html:0:0","series":null,"tags":["cloudflare","ddns"],"title":"Linux 服务器使用 Cloudflare 搭建 DDNS","uri":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html#"},{"categories":["Linux"],"content":" 一、准备工作注册一个 cloudflare 账号后,按照 cloudflare 给出的提示,将域名接入 cloudflare,让 cloudflare 接管你的域名。 ","date":"2019-05-25","objectID":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html:0:0","series":null,"tags":["cloudflare","ddns"],"title":"Linux 服务器使用 Cloudflare 搭建 DDNS","uri":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html#一准备工作"},{"categories":["Linux"],"content":" 二、获取账号 global key打开网页:https://dash.cloudflare.com/profile 在页面下方找到【Global API Key】,点击右侧的 View 查看 Key,并保存下来 ","date":"2019-05-25","objectID":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html:0:0","series":null,"tags":["cloudflare","ddns"],"title":"Linux 服务器使用 Cloudflare 搭建 DDNS","uri":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html#二获取账号-global-key"},{"categories":["Linux"],"content":" 三、设置用于 DDNS 解析的二级域名在 Cloudflare 中新建一个 A 记录,如:ddns.yourdomain.com,指向 1.1.1.1(可随意指定,如 123.123.123.123 等等,主要用于后续查看 DDNS 是否生效) ","date":"2019-05-25","objectID":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html:0:0","series":null,"tags":["cloudflare","ddns"],"title":"Linux 服务器使用 Cloudflare 搭建 DDNS","uri":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html#三设置用于-ddns-解析的二级域名"},{"categories":["Linux"],"content":" 四、下载 DDNS 脚本,修改配置wget -N --no-check-certificate https://raw.githubusercontent.com/yulewang/cloudflare-api-v4-ddns/master/cf-v4-ddns.sh vim cf-v4-ddns.sh 1.CFKEY 就是第一步获取的 global key 2.CFUSER 是登录 cloudflare 的邮箱 3.CFZONE_NAME 是你的一级域名 4.CFRECORD_NAME 则是用于 DDNS 的二级域名 5.CFTTL 是域名生效的 ttl,默认 120 即可 ","date":"2019-05-25","objectID":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html:0:0","series":null,"tags":["cloudflare","ddns"],"title":"Linux 服务器使用 Cloudflare 搭建 DDNS","uri":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html#四下载-ddns-脚本修改配置"},{"categories":["Linux"],"content":" 五、脚本授权并执行chmod +x cf-v4-ddns.sh ./cf-v4-ddns.sh 如果脚本相关信息填写正确,输出内容会显示服务器当前 IP,登录 Cloudflare DNS 选项 查看之前设置的 1.1.1.1 已变为当前服务器的 IP。 ","date":"2019-05-25","objectID":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html:0:0","series":null,"tags":["cloudflare","ddns"],"title":"Linux 服务器使用 Cloudflare 搭建 DDNS","uri":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html#五脚本授权并执行"},{"categories":["Linux"],"content":" 六、设置 crontab 定时任务# 编辑定时任务 crontab -e # 将以下内容添加到crontab中 # 无日志 */2 * * * * /root/cf-v4-ddns.sh \u003e/dev/null 2\u003e\u00261 如果需要日志文件,可将上述代码请替换成下述代码 */2 * * * * /root/cf-v4-ddns.sh \u003e\u003e /var/log/cf-ddns.log 2\u003e\u00261 ","date":"2019-05-25","objectID":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html:0:0","series":null,"tags":["cloudflare","ddns"],"title":"Linux 服务器使用 Cloudflare 搭建 DDNS","uri":"/posts/linux-server-using-cloudflare-to-build-ddns-service.html#六设置-crontab-定时任务"},{"categories":["Linux"],"content":"crontab 命令常见于 Unix 和类 Unix 的操作系统之中,用于设置周期性被执行的指令。 ","date":"2019-05-20","objectID":"/posts/linux-using-crontab-to-execute-timed-tasks.html:0:0","series":null,"tags":["crontab"],"title":"Linux 使用 crontab 执行定时任务","uri":"/posts/linux-using-crontab-to-execute-timed-tasks.html#"},{"categories":["Linux"],"content":" 一、命令格式及其含义#语义 * * * * * command ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗━需要执行的命令或脚本的路径 ┃ ┃ ┃ ┃ ┗━━━星期 取值范围 0-6 ┃ ┃ ┃ ┗━━━━月份 取值范围 1-12 ┃ ┃ ┗━━━━━ 日 取值范围 1-31 ┃ ┗━━━━━━ 小时 取值范围 0-23 ┗━━━━━━━━ 分钟 取值范围 0-59 #编辑crontab任务,写入后保存退出生效 crontab -e #列出已经存在的crontab任务 crontab -l ","date":"2019-05-20","objectID":"/posts/linux-using-crontab-to-execute-timed-tasks.html:0:0","series":null,"tags":["crontab"],"title":"Linux 使用 crontab 执行定时任务","uri":"/posts/linux-using-crontab-to-execute-timed-tasks.html#一命令格式及其含义"},{"categories":["Linux"],"content":" 二、Demo#每天7:00重启 0 7 * * * reboot #每周六凌晨4:00重启 0 4 * * 6 reboot #每周六凌晨4:05执行脚本 5 4 * * 6 /root/clearLog.sh #每周六凌晨4:15执行 15 4 * * 6 /root/clearLog.sh #每天8:40执行 40 8 * * * /root/clearLog.sh #每周一到周五的11:41开始,每隔10分钟执行一次 41,51 11 * * 1-5 /root/clearLog.sh 1-59/10 12-23 * * 1-5 /root/clearLog.sh #在每天的10:31开始,每隔2小时重复一次 31 10-23/2 * * * /root/clearLog.sh #每天23:50执行 50 23 * * * /root/clearLog.sh #每天10:00、16:00执行 0 10,16 * * * /root/clearLog.sh ","date":"2019-05-20","objectID":"/posts/linux-using-crontab-to-execute-timed-tasks.html:0:0","series":null,"tags":["crontab"],"title":"Linux 使用 crontab 执行定时任务","uri":"/posts/linux-using-crontab-to-execute-timed-tasks.html#二demo"},{"categories":["Linux"],"content":" REFLinux 计划任务 Crontab 实例详解:https://www.osyunwei.com/archives/5039.html ","date":"2019-05-20","objectID":"/posts/linux-using-crontab-to-execute-timed-tasks.html:0:0","series":null,"tags":["crontab"],"title":"Linux 使用 crontab 执行定时任务","uri":"/posts/linux-using-crontab-to-execute-timed-tasks.html#ref"},{"categories":["Linux"],"content":"目前端口扫描可以通过 web 版本的端口扫描进行简单的测试,一次性扫描的端口数量一般也会有限制。通常情况下,如果仅仅是扫描一些常用端口,如上图所示的网页版端口扫描可以满足。如果想折腾一下,或者是对自己的主机进行安全检查之类的,还是使用 Linux 系统下的 nmap 工具进行扫描会比较方便一点。 ","date":"2019-05-19","objectID":"/posts/using-nmap-for-port-scanning-on-linux-systems.html:0:0","series":null,"tags":["nmap","端口扫描"],"title":"Linux 使用 nmap 进行端口扫描","uri":"/posts/using-nmap-for-port-scanning-on-linux-systems.html#"},{"categories":["Linux"],"content":" 一、安装 nmap使用 yum 安装 nmap #更新一下源 yum update -y #安装 yum install nmap -y ","date":"2019-05-19","objectID":"/posts/using-nmap-for-port-scanning-on-linux-systems.html:0:0","series":null,"tags":["nmap","端口扫描"],"title":"Linux 使用 nmap 进行端口扫描","uri":"/posts/using-nmap-for-port-scanning-on-linux-systems.html#一安装-nmap"},{"categories":["Linux"],"content":" 二、nmap 命令#扫描本机开放的端口「127.0.0.1 一般指代本机」 nmap 127.0.0.1 -p 1-65535 先咕咕咕… 日后再补批量扫 IP 段并将结果写入日志 ","date":"2019-05-19","objectID":"/posts/using-nmap-for-port-scanning-on-linux-systems.html:0:0","series":null,"tags":["nmap","端口扫描"],"title":"Linux 使用 nmap 进行端口扫描","uri":"/posts/using-nmap-for-port-scanning-on-linux-systems.html#二nmap-命令"},{"categories":["抽屉"],"content":"About Content and Tools I’ve been trying to use some note taking software for about half a year now, and I’ve gradually fallen into the swamp of searching and experiencing all kinds of note taking software, which seems to be a strange circle that shouldn’t exist: looking for a note taking app, experiencing it, and looking for the next note taking app, but we shouldn’t be like this, originally wanting to find a good weapon to fight with, but finally becoming a shopkeeper of a weapon store. Recently gradually realized, should gradually return to the original heart, think of what content, find the right place to start writing immediately, the content is far more important than the carrier. No longer go to spend a lot of time looking for good tools, focus on content output, improve their own, obviously more important. ","date":"2019-05-18","objectID":"/posts/en/about.html:0:0","series":null,"tags":[],"title":"About blog","uri":"/posts/en/about.html#"},{"categories":["抽屉"],"content":"关于内容和工具。 大概在半年前,尝试并且使用了一些笔记软件,于是逐渐陷入寻找的沼泽里,一发不可收拾,开始体验各种笔记软件。 这似乎是陷入了一个不应该存在的怪圈:找笔记APP,体验,找下一个笔记APP。而我们本心不该是这样的,本来是想找个称手的兵器打仗,最后却变成了武器商店的店长。 我觉得应该逐渐回归初心,想到什么内容,就找合适的地方马上开始写,内容远比载体重要的多。 内容产出才是核心。 ","date":"2019-05-18","objectID":"/posts/about.html:0:0","series":[],"tags":[],"title":"关于博客","uri":"/posts/about.html#"}]