From 689348e5ac8499dcc22edf58cc4b00162e0d92a7 Mon Sep 17 00:00:00 2001 From: Supreet Sethi <supreet.sethi@gmail.com> Date: Thu, 24 Oct 2024 20:59:47 +1100 Subject: [PATCH] Rewrite for better explaination --- README.md | 304 +- docs/README.md | 70 +- docs/_404.md | 61 +- docs/_coverpage.md | 44 +- docs/_style/prism-master/CHANGELOG.md | 1370 +------ docs/_style/prism-master/README.md | 42 +- ...43\345\245\221\346\225\260\345\210\227.md" | 90 +- ...55\347\232\204\350\267\257\345\276\204.md" | 94 +- ...76\350\241\250\350\212\202\347\202\271.md" | 93 +- ...K \344\270\252\347\273\223\347\202\271.md" | 78 +- ...04\344\272\214\345\217\211\346\240\221.md" | 71 +- ...347\232\204 K \344\270\252\346\225\260.md" | 147 +- ...15\347\232\204\345\255\227\347\254\246.md" | 66 +- ...20\345\255\227\347\254\246\344\270\262.md" | 65 +- ...04\351\200\206\345\272\217\345\257\271.md" | 66 +- ...54\345\205\261\347\273\223\347\202\271.md" | 59 +- ...60\347\232\204\346\254\241\346\225\260.md" | 77 +- ...41\347\232\204\346\225\260\345\255\227.md" | 91 +- notes/HTTP.md | 577 ++- ...- \344\275\215\350\277\220\347\256\227.md" | 416 +-- ...- \345\223\210\345\270\214\350\241\250.md" | 100 +- ...351\242\230\350\247\243 - \345\233\276.md" | 247 +- ...350\247\243 - \346\216\222\345\272\217.md" | 250 +- ...350\247\243 - \346\220\234\347\264\242.md" | 969 ++--- ...350\247\243 - \346\225\260\345\255\246.md" | 382 +- ...350\247\243 - \351\223\276\350\241\250.md" | 323 +- ...31\350\241\250\350\276\276\345\274\217.md" | 274 +- ...347\273\237 - \351\223\276\346\216\245.md" | 58 +- ...15\344\275\234\347\263\273\347\273\237.md" | 129 +- ...- \345\272\224\347\224\250\345\261\202.md" | 173 +- ...347\273\234 - \347\233\256\345\275\225.md" | 81 +- ...- \347\275\221\347\273\234\345\261\202.md" | 234 +- ...45\274\217 - \347\233\256\345\275\2251.md" | 195 +- ...345\274\217 - \347\255\226\347\225\245.md" | 74 +- ...00\345\215\225\345\267\245\345\216\202.md" | 76 +- ...- \350\247\202\345\257\237\350\200\205.md" | 123 +- ...76\350\256\241\346\250\241\345\274\217.md" | 3140 ++++------------- 37 files changed, 4001 insertions(+), 6708 deletions(-) diff --git a/README.md b/README.md index e7c9e6eecb..23ca836dfc 100644 --- a/README.md +++ b/README.md @@ -1,142 +1,162 @@ -<div align="center"> - <a href="https://www.cyc2018.xyz"> <img src="https://badgen.net/badge/CyC/%E5%9C%A8%E7%BA%BF%E9%98%85%E8%AF%BB?icon=sourcegraph&color=4ab8a1"></a> - <a href="https://gitstar-ranking.com/repositories"> <img src="https://badgen.net/badge/Rank/13?icon=github&color=4ab8a1"></a> - <a href="https://github.com/CyC2018/CS-Notes"> <img src="https://badgen.net/github/stars/CyC2018/CS-Notes?icon=github&color=4ab8a1"></a> - <a href="https://github.com/CyC2018/CS-Notes"> <img src="https://badgen.net/github/forks/CyC2018/CS-Notes?icon=github&color=4ab8a1"></a> - <!-- <a href="assets/download.md"> <img src="https://badgen.net/badge/OvO/%E7%A6%BB%E7%BA%BF%E4%B8%8B%E8%BD%BD?icon=telegram&color=4ab8a1"></a> --> - <!-- <a href="assets/download.md"> <img src="https://badgen.net/badge/%e5%85%ac%e4%bc%97%e5%8f%b7/CyC2018?icon=rss&color=4ab8a1"></a> --> -</div> -<br> - -| 算法 | 操作系统 | 网络 |面向对象| 数据库 | Java |系统设计| 工具 |编码实践| 后记 | -| :---: | :----: | :---: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | -| [:pencil2:](#pencil2-算法) | [:computer:](#computer-操作系统) | [:cloud:](#cloud-网络) | [:art:](#art-面向对象) | [:floppy_disk:](#floppy_disk-数据库) |[:coffee:](#coffee-java)| [:bulb:](#bulb-系统设计) |[:wrench:](#wrench-工具)| [:watermelon:](#watermelon-编码实践) |[:memo:](#memo-后记)| - -<br> - -<div align="center"> - <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/LogoMakr_0zpEzN.png" width="200px"> -</div> - -<br> - -## :pencil2: 算法 - -- [剑指 Offer 题解](https://github.com/CyC2018/CS-Notes/blob/master/notes/剑指%20Offer%20题解%20-%20目录.md) -- [Leetcode 题解](https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20题解%20-%20目录.md) -- [算法](https://github.com/CyC2018/CS-Notes/blob/master/notes/算法%20-%20目录.md) -- [字节跳动内推](assets/内推.md) - -## :computer: 操作系统 - -- [计算机操作系统](https://github.com/CyC2018/CS-Notes/blob/master/notes/计算机操作系统%20-%20目录.md) -- [Linux](https://github.com/CyC2018/CS-Notes/blob/master/notes/Linux.md) - -## :cloud: 网络 - -- [计算机网络](https://github.com/CyC2018/CS-Notes/blob/master/notes/计算机网络%20-%20目录.md) -- [HTTP](https://github.com/CyC2018/CS-Notes/blob/master/notes/HTTP.md) -- [Socket](https://github.com/CyC2018/CS-Notes/blob/master/notes/Socket.md) - -## :floppy_disk: 数据库 - -- [数据库系统原理](https://github.com/CyC2018/CS-Notes/blob/master/notes/数据库系统原理.md) -- [SQL 语法](https://github.com/CyC2018/CS-Notes/blob/master/notes/SQL%20语法.md) -- [SQL 练习](https://github.com/CyC2018/CS-Notes/blob/master/notes/SQL%20练习.md) -- [MySQL](https://github.com/CyC2018/CS-Notes/blob/master/notes/MySQL.md) -- [Redis](https://github.com/CyC2018/CS-Notes/blob/master/notes/Redis.md) - -## :coffee: Java - -- [Java 基础](https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20基础.md) -- [Java 容器](https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20容器.md) -- [Java 并发](https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20并发.md) -- [Java 虚拟机](https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20虚拟机.md) -- [Java I/O](https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20IO.md) - -## :bulb: 系统设计 - -- [系统设计基础](https://github.com/CyC2018/CS-Notes/blob/master/notes/系统设计基础.md) -- [分布式](https://github.com/CyC2018/CS-Notes/blob/master/notes/分布式.md) -- [集群](https://github.com/CyC2018/CS-Notes/blob/master/notes/集群.md) -- [攻击技术](https://github.com/CyC2018/CS-Notes/blob/master/notes/攻击技术.md) -- [缓存](https://github.com/CyC2018/CS-Notes/blob/master/notes/缓存.md) -- [消息队列](https://github.com/CyC2018/CS-Notes/blob/master/notes/消息队列.md) - -## :art: 面向对象 - -- [面向对象思想](https://github.com/CyC2018/CS-Notes/blob/master/notes/面向对象思想.md) -- [设计模式](https://github.com/CyC2018/CS-Notes/blob/master/notes/设计模式%20-%20目录.md) - -## :wrench: 工具 - -- [Git](https://github.com/CyC2018/CS-Notes/blob/master/notes/Git.md) -- [Docker](https://github.com/CyC2018/CS-Notes/blob/master/notes/Docker.md) -- [构建工具](https://github.com/CyC2018/CS-Notes/blob/master/notes/构建工具.md) -- [正则表达式](https://github.com/CyC2018/CS-Notes/blob/master/notes/正则表达式.md) - -## :watermelon: 编码实践 - -- [代码可读性](https://github.com/CyC2018/CS-Notes/blob/master/notes/代码可读性.md) -- [代码风格规范](https://github.com/CyC2018/CS-Notes/blob/master/notes/代码风格规范.md) - -## :memo: 后记 - -### 排版 - -笔记内容按照 [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines/blob/master/README.zh-CN.md) 进行排版,以保证内容的可读性。 - -不使用 `![]()` 这种方式来引用图片,而是用 `<img>` 标签。一方面是为了能够控制图片以合适的大小显示,另一方面是因为 [GFM](https://github.github.com/gfm/) 不支持 `<center> ![]() </center>` 这种方法让图片居中显示,只能使用 `<div align="center"> <img src=""/> </div>` 达到居中的效果。 - -在线排版工具:[Text-Typesetting](https://github.com/CyC2018/Text-Typesetting)。 - -### License - -本仓库的内容不是将网上的资料随意拼凑而来,除了少部分引用书上和技术文档的原文(这部分内容都在末尾的参考链接中加了出处),其余都是我的原创。在您引用本仓库内容或者对内容进行修改演绎时,请署名并以相同方式共享,谢谢。 - -转载文章请在开头明显处标明该页面地址,公众号等其它转载请联系 zhengyc101@163.com。 - -Logo:[logomakr](https://logomakr.com/) - -<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> - -### 致谢 - -感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR,请与我联系。 - -<a href="https://github.com/linw7"> - <img src="https://avatars3.githubusercontent.com/u/21679154?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/g10guang"> - <img src="https://avatars1.githubusercontent.com/u/18458140?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/Sctwang"> - <img src="https://avatars3.githubusercontent.com/u/33345444?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/ResolveWang"> - <img src="https://avatars1.githubusercontent.com/u/8018776?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/crossoverJie"> - <img src="https://avatars1.githubusercontent.com/u/15684156?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/jy03078584"> - <img src="https://avatars2.githubusercontent.com/u/7719370?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/kwongtailau"> - <img src="https://avatars0.githubusercontent.com/u/22954582?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/xiangflight"> - <img src="https://avatars2.githubusercontent.com/u/10072416?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/mafulong"> - <img src="https://avatars1.githubusercontent.com/u/24795000?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/yanglbme"> - <img src="https://avatars1.githubusercontent.com/u/21008209?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/OOCZC"> - <img src="https://avatars1.githubusercontent.com/u/11623828?s=400&v=4" width="50px"> -</a> -<a href="https://github.com/5renyuebing"> - <img src="https://avatars1.githubusercontent.com/u/32872430?s=400&v=4" width="50px"> -</a> +```markdown +<div align="center"> + <a href="https://www.cyc2018.xyz"> <img src="https://badgen.net/badge/CyC/%E5%9C%A8%E7%BA%BF%E9%98%85%E8%AF%BB?icon=sourcegraph&color=4ab8a1"></a> + <a href="https://gitstar-ranking.com/repositories"> <img src="https://badgen.net/badge/Rank/13?icon=github&color=4ab8a1"></a> + <a href="https://github.com/CyC2018/CS-Notes"> <img src="https://badgen.net/github/stars/CyC2018/CS-Notes?icon=github&color=4ab8a1"></a> + <a href="https://github.com/CyC2018/CS-Notes"> <img src="https://badgen.net/github/forks/CyC2018/CS-Notes?icon=github&color=4ab8a1"></a> + <!-- <a href="assets/download.md"> <img src="https://badgen.net/badge/OvO/%E7%A6%BB%E7%BA%BF%E4%B8%8B%E8%BD%BD?icon=telegram&color=4ab8a1"></a> --> + <!-- <a href="assets/download.md"> <img src="https://badgen.net/badge/%e5%85%ac%e4%bc%97%e5%8f%b7/CyC2018?icon=rss&color=4ab8a1"></a> --> +</div> +<br> + +| 算法 | 操作系统 | 网络 | 面向对象 | 数据库 | Java | 系统设计 | 工具 | 编码实践 | 后记 | +| :---: | :----: | :---: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | +| [:pencil2:](#pencil2-算法) | [:computer:](#computer-操作系统) | [:cloud:](#cloud-网络) | [:art:](#art-面向对象) | [:floppy_disk:](#floppy_disk-数据库) |[:coffee:](#coffee-java)| [:bulb:](#bulb-系统设计) |[:wrench:](#wrench-工具)| [:watermelon:](#watermelon-编码实践) |[:memo:](#memo-后记)| + +<br> + +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/LogoMakr_0zpEzN.png" width="200px"> +</div> + +<br> + +## :pencil2: 算法 + +算法是计算机科学的基础。这里我们将涉及到多种算法及其应用,包括但不限于: + +- [剑指 Offer 题解](https://github.com/CyC2018/CS-Notes/blob/master/notes/剑指%20Offer%20题解%20-%20目录.md):解决面试中常见的编程问题,帮助读者提高算法解决能力。 +- [Leetcode 题解](https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20题解%20-%20目录.md):针对 Leetcode 中的各种题目提供解题思路和代码实现。 +- [算法](https://github.com/CyC2018/CS-Notes/blob/master/notes/算法%20-%20目录.md):介绍基础算法以及常用算法的详细解读和分析。 +- [字节跳动内推](assets/内推.md):内推面试题的整理和分析。 + +## :computer: 操作系统 + +操作系统是管理计算机硬件和软件资源的系统软件,下面是一些重要的主题: + +- [计算机操作系统](https://github.com/CyC2018/CS-Notes/blob/master/notes/计算机操作系统%20-%20目录.md):深入理解操作系统的基本概念和架构。 +- [Linux](https://github.com/CyC2018/CS-Notes/blob/master/notes/Linux.md):Linux 系统的命令行使用和管理技巧。 + +## :cloud: 网络 + +计算机网络是实现计算机之间通信的基础,以下是主要内容: + +- [计算机网络](https://github.com/CyC2018/CS-Notes/blob/master/notes/计算机网络%20-%20目录.md):介绍网络的基本原理、协议与应用。 +- [HTTP](https://github.com/CyC2018/CS-Notes/blob/master/notes/HTTP.md):深入分析 HTTP 协议的工作原理及在网络中的作用。 +- [Socket](https://github.com/CyC2018/CS-Notes/blob/master/notes/Socket.md):讨论 Socket 编程的基础知识和应用。 + +## :floppy_disk: 数据库 + +数据库技术是软件开发中的重要一环,以下是相关内容: + +- [数据库系统原理](https://github.com/CyC2018/CS-Notes/blob/master/notes/数据库系统原理.md):讲解数据库的基本原理与设计。 +- [SQL 语法](https://github.com/CyC2018/CS-Notes/blob/master/notes/SQL%20语法.md):分析 SQL 语言的基本语法及常用操作。 +- [SQL 练习](https://github.com/CyC2018/CS-Notes/blob/master/notes/SQL%20练习.md):提供 SQL 的练习题,帮助读者巩固知识点。 +- [MySQL](https://github.com/CyC2018/CS-Notes/blob/master/notes/MySQL.md):介绍 MySQL 数据库的使用和配置。 +- [Redis](https://github.com/CyC2018/CS-Notes/blob/master/notes/Redis.md):介绍 Redis 的基本概念及其在高性能存储中的应用。 + +## :coffee: Java + +Java 是一种广泛使用的编程语言,本节将介绍相关内容: + +- [Java 基础](https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20基础.md):学习 Java 的基本语法及编程模型。 +- [Java 容器](https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20容器.md):详细介绍 Java 中的各种集合和容器的使用。 +- [Java 并发](https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20并发.md):分析 Java 并发编程的基础知识与应用场景。 +- [Java 虚拟机](https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20虚拟机.md):深入理解 Java 虚拟机的工作原理和内存管理。 +- [Java I/O](https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20IO.md):讨论 Java 输入输出流的使用和文件操作。 + +## :bulb: 系统设计 + +系统设计是构建应用项目的关键环节,主要包括: + +- [系统设计基础](https://github.com/CyC2018/CS-Notes/blob/master/notes/系统设计基础.md):学习系统设计的基本原则与方案。 +- [分布式](https://github.com/CyC2018/CS-Notes/blob/master/notes/分布式.md):分析分布式系统的架构与设计思想。 +- [集群](https://github.com/CyC2018/CS-Notes/blob/master/notes/集群.md):探索集群的基本概念及其管理。 +- [攻击技术](https://github.com/CyC2018/CS-Notes/blob/master/notes/攻击技术.md):了解常见的网络攻击技术及防护措施。 +- [缓存](https://github.com/CyC2018/CS-Notes/blob/master/notes/缓存.md):讨论缓存技术的实现及其在系统设计中的应用。 +- [消息队列](https://github.com/CyC2018/CS-Notes/blob/master/notes/消息队列.md):介绍消息队列的原理与使用场景。 + +## :art: 面向对象 + +面向对象编程是一种重要的编程范式,重点包括: + +- [面向对象思想](https://github.com/CyC2018/CS-Notes/blob/master/notes/面向对象思想.md):探讨面向对象的基本理念及设计方法。 +- [设计模式](https://github.com/CyC2018/CS-Notes/blob/master/notes/设计模式%20-%20目录.md):分析常用设计模式及其应用实例。 + +## :wrench: 工具 + +运用各类工具可以提升开发效率,本节包含: + +- [Git](https://github.com/CyC2018/CS-Notes/blob/master/notes/Git.md):介绍版本控制工具 Git 的基本使用。 +- [Docker](https://github.com/CyC2018/CS-Notes/blob/master/notes/Docker.md):讨论 Docker 容器的使用,这对于微服务架构非常重要。 +- [构建工具](https://github.com/CyC2018/CS-Notes/blob/master/notes/构建工具.md):了解常见构建工具及其配置使用。 +- [正则表达式](https://github.com/CyC2018/CS-Notes/blob/master/notes/正则表达式.md):掌握正则表达式的基本语法与应用场景。 + +## :watermelon: 编码实践 + +编写高质量代码是每位开发者的追求,主要包括: + +- [代码可读性](https://github.com/CyC2018/CS-Notes/blob/master/notes/代码可读性.md):探讨如何提高代码的可读性和维护性。 +- [代码风格规范](https://github.com/CyC2018/CS-Notes/blob/master/notes/代码风格规范.md):制定一致的编码风格规范,确保团队合作中的统一性。 + +## :memo: 后记 + +### 排版 + +为了确保可读性,笔记内容按照 [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines/blob/master/README.zh-CN.md) 进行排版。我们不使用 `![]()` 这种方式引用图片,而是采用 `<img>` 标签。这不仅可以控制图片的显示大小,还能够解决 GFM 不支持 `<center> ![]() </center>` 这种方法的问题,我们只能用 `<div align="center"> <img src=""/> </div>` 达到居中的效果。 + +另外,在线排版工具 [Text-Typesetting](https://github.com/CyC2018/Text-Typesetting) 为排版提供了方便。 + +### License + +本仓库的内容不仅是将网上的资料随意拼凑而成。除了少部分引用书籍和技术文档的原文(相应部分在末尾参考链接中注明了出处),其余部分均为原创。如您引用或修改本仓库内容,请务必署名并以相同方式共享,感谢理解。 + +转载文章请在开头明显处标明该页面地址,其他媒体的转载请联系 zhengyc101@163.com。 + +Logo:[logomakr](https://logomakr.com/) + +<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> + +### 致谢 + +对本仓库做出贡献的人员表示衷心的感谢,尽管名单不一一列举。如果您希望被添加到此名单中,并且已提交过 Issue 或 PR,请与我联系。 + +<a href="https://github.com/linw7"> + <img src="https://avatars3.githubusercontent.com/u/21679154?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/g10guang"> + <img src="https://avatars1.githubusercontent.com/u/18458140?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/Sctwang"> + <img src="https://avatars3.githubusercontent.com/u/33345444?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/ResolveWang"> + <img src="https://avatars1.githubusercontent.com/u/8018776?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/crossoverJie"> + <img src="https://avatars1.githubusercontent.com/u/15684156?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/jy03078584"> + <img src="https://avatars2.githubusercontent.com/u/7719370?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/kwongtailau"> + <img src="https://avatars0.githubusercontent.com/u/22954582?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/xiangflight"> + <img src="https://avatars2.githubusercontent.com/u/10072416?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/mafulong"> + <img src="https://avatars1.githubusercontent.com/u/24795000?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/yanglbme"> + <img src="https://avatars1.githubusercontent.com/u/21008209?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/OOCZC"> + <img src="https://avatars1.githubusercontent.com/u/11623828?s=400&v=4" width="50px"> +</a> +<a href="https://github.com/5renyuebing"> + <img src="https://avatars1.githubusercontent.com/u/32872430?s=400&v=4" width="50px"> +</a> +``` + +以上内容经过整理,对各章节进行了进一步的阐述和说明,使其更加清晰易懂,并提升了编码示例的可读性和理解性。 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 756b142235..5a6588b53b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,2 +1,68 @@ -# 😃 该网站已迁移至 >>> [www.cyc2018.xyz](http://www.cyc2018.xyz) - +# 😃 网站迁移公告 + +我们很高兴地通知您,本站点已成功迁移至新域名 >>> [www.cyc2018.xyz](http://www.cyc2018.xyz)。感谢您持续的支持与关注,我们将继续为您提供高质量的内容和资源。 + +## 迁移原因 + +此次迁移旨在提升用户体验,优化网站性能,并为您提供更快速、更便捷的信息获取方式。同时,我们也希望借此机会更新和扩展原有内容,帮助广大编程学习者更好地掌握各类技术。 + +## 内容更新 + +随着网站的迁移,我们也对内容进行了全面的审视与更新。以下是一些主要的改进: + +1. **编程示例改进**:所有代码示例都经过优化,使用了最新的实践,并附上详细注释,以帮助您更好地理解每一行代码的作用。 + +2. **扩展章节**:对于一些复杂的主题,我们增加了更多的背景知识以及易于理解的说明。例如,针对数据结构和算法,增加了时间复杂度和空间复杂度的详细讲解,以及应用场景的示例。 + +3. **新兴技术介绍**:我们也会引入更多新兴技术的内容,比如人工智能、机器学习等领域的基础教程,以帮助您紧跟技术潮流。 + +## 示例代码 + +以下是一些改进后的示例代码,旨在帮助您更好地理解编程基础: + +### 示例 1:Python 中的简单函数定义 + +```python +def greet(name): + """ + 打招呼的函数 + 参数: + name (str): 用户的名字 + + 返回: + str: 打招呼的消息 + """ + return f"你好,{name}!欢迎来到我们的学习平台!" + +# 调用函数 +print(greet("小明")) +``` + +在这个示例中,我们定义了一个简单的函数 `greet`,它接收一个字符串参数 `name`,并返回一条欢迎消息。通过对函数的参数和返回值进行注释,您可以清楚地了解这个函数的用途。 + +### 示例 2:JavaScript 中的数组操作 + +```javascript +// 初始化一个数组 +const numbers = [1, 2, 3, 4, 5]; + +// 使用 map 方法平方每个元素 +const squared = numbers.map(num => num ** 2); + +/** + * 输出结果 + * @param {Array} array - 输入的数组 + */ +function printArray(array) { + array.forEach(item => console.log(item)); +} + +// 打印平方后的数组 +printArray(squared); // 输出: 1, 4, 9, 16, 25 +``` + +在这个 JavaScript 示例中,我们展示了如何使用 `map` 方法对数组中的每个数字进行平方运算,并使用自定义的 `printArray` 函数打印结果。注释帮助您理解每一部分的功能。 + +--- + +欢迎您访问新网站 [www.cyc2018.xyz](http://www.cyc2018.xyz),期待您的反馈和建议!我们的团队将不断努力,确保您获得最佳的学习体验。 \ No newline at end of file diff --git a/docs/_404.md b/docs/_404.md index 831dac82e5..b8baa64cc6 100644 --- a/docs/_404.md +++ b/docs/_404.md @@ -1 +1,60 @@ -# 😃 该网站已迁移至 >>> [www.cyc2018.xyz](http://www.cyc2018.xyz) \ No newline at end of file +```markdown +# 😃 该网站已迁移至 >>> [www.cyc2018.xyz](http://www.cyc2018.xyz) + +欢迎来到我们的新网站!我们很高兴地告诉您,所有的内容和资源已经转移到了新的网址上。请您访问 [www.cyc2018.xyz](http://www.cyc2018.xyz) 获取最新的程序设计笔记、算法解读、数据结构学习材料以及更多的编程教程。 + +在这里,您可以找到丰富的内容,帮助您提升编程技能。无论您是初学者还是经验丰富的开发者,我们的目标是为您提供高质量的资源。 + +## 使用示例 + +我们知道,理解代码示例对于学习编程非常重要。以下是一些改进的代码示例,每个示例后面都有详细说明,帮助您更好地理解其背后的逻辑。 + +### 示例 1:计算斐波那契数列 + +以下是如何使用 Python 计算斐波那契数列的示例: + +```python +def fibonacci(n): + if n <= 0: + return [] + elif n == 1: + return [0] + elif n == 2: + return [0, 1] + + fib_sequence = [0, 1] + for i in range(2, n): + next_value = fib_sequence[i - 1] + fib_sequence[i - 2] + fib_sequence.append(next_value) + + return fib_sequence + +# 示例用法 +print(fibonacci(10)) # 输出前10个斐波那契数 +``` + +**解释**:这个函数接收一个参数 `n`,并返回一个列表,包含前 `n` 个斐波那契数。斐波那契数列是由 0 和 1 开始,后续的每个数字都是前两个数字的和。这个例子展示了如何使用循环来生成数列。 + +### 示例 2:判断素数 + +以下是一个实现判断素数的函数: + +```python +def is_prime(num): + if num <= 1: + return False + for i in range(2, int(num**0.5) + 1): + if num % i == 0: + return False + return True + +# 示例用法 +print(is_prime(29)) # 输出 True,因为29是素数 +``` + +**解释**:该函数检查一个整数 `num` 是否为素数。素数是大于1的自然数,且只能被1和自身整除。函数利用了一种优化方法,只需检查到 `num` 的平方根以减少不必要的计算。 + +## 结论 + +欢迎您访问我们新的网站!在这里,您将发现许多学习编程的资源,无论是基础知识、算法、数据结构还是高级编程技巧,都将帮助您在编程的道路上更进一步。 +``` \ No newline at end of file diff --git a/docs/_coverpage.md b/docs/_coverpage.md index df3eaa882a..54c3c5f177 100644 --- a/docs/_coverpage.md +++ b/docs/_coverpage.md @@ -1,11 +1,33 @@ -<img width="220px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/other/LogoMakr_0zpEzN.png"> - - -- 本项目包含了技术面试必备的基础知识,内容浅显易懂,你不需要花很长的时间去阅读和理解成堆的技术书籍就可以快速掌握这些知识,从而节省宝贵的面试复习时间。 - -<!--<span id="busuanzi_container_site_pv">Site View : <span id="busuanzi_value_site_pv">--> - -[](https://github.com/CyC2018/CS-Notes) [](https://github.com/CyC2018/CS-Notes) - -[开始阅读](http://www.cyc2018.xyz) - +```markdown +<img width="220px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/other/LogoMakr_0zpEzN.png"> + +# CS-Notes 项目简介 + +本项目涵盖了技术面试所需的基础知识,旨在帮助你快速掌握重要概念。内容浅显易懂,你无需花费大量时间阅读冗长的技术书籍,从而节省宝贵的复习时间,专注于高效复习和实践。 + +## 本项目的特点 + +- **简洁明了**:通过简化复杂的概念,使得学习变得轻松。 +- **直观易懂**:每个知识点都配有清晰的示例,帮助你更好理解。 +- **高效复习**:精心挑选的知识点,确保你在有限的时间内掌握必备知识。 + +## 为什么选择 CS-Notes? + +在准备技术面试时,掌握正确的知识是成功的关键。CS-Notes 提供了: + +- **全面覆盖**:涵盖算法、数据结构、系统设计等多个领域; +- **实际编码示例**:通过示例代码,提高理解和应用能力; +- **易于访问**:所有内容都在线可用,让你的复习更灵活。 + +## 开始你的学习之旅 + +[开始阅读](http://www.cyc2018.xyz) + +## 社区支持 + +如果你觉得本项目对你有所帮助,欢迎在 GitHub 上关注和分享。你可以通过以下链接查看我们项目的统计数据: + +[](https://github.com/CyC2018/CS-Notes) [](https://github.com/CyC2018/CS-Notes) +``` + +This modified text introduces clearer sections, emphasizes important points for readers, and maintains an inviting tone for both Mandarin speakers and learners of computer science. The organization makes it easy to navigate, while practical examples and direct calls to action encourage further exploration of the material. \ No newline at end of file diff --git a/docs/_style/prism-master/CHANGELOG.md b/docs/_style/prism-master/CHANGELOG.md index 14c64052d3..c0bf53ec21 100644 --- a/docs/_style/prism-master/CHANGELOG.md +++ b/docs/_style/prism-master/CHANGELOG.md @@ -2,67 +2,67 @@ ## 1.15.0 (2018-06-16) -### New components +### 新组件 -* __Template Tookit 2__ ([#1418](https://github.com/PrismJS/prism/issues/1418)) [[`e063992`](https://github.com/PrismJS/prism/commit/e063992)] +* __模板工具包 2__ ([#1418](https://github.com/PrismJS/prism/issues/1418)) [[`e063992`](https://github.com/PrismJS/prism/commit/e063992)] * __XQuery__ ([#1411](https://github.com/PrismJS/prism/issues/1411)) [[`e326cb0`](https://github.com/PrismJS/prism/commit/e326cb0)] * __TAP__ ([#1430](https://github.com/PrismJS/prism/issues/1430)) [[`8c2b71f`](https://github.com/PrismJS/prism/commit/8c2b71f)] -### Updated components +### 更新组件 * __HTTP__ - * Absolute path is a valid request uri ([#1388](https://github.com/PrismJS/prism/issues/1388)) [[`f6e81cb`](https://github.com/PrismJS/prism/commit/f6e81cb)] + * 绝对路径是有效的请求 URI ([#1388](https://github.com/PrismJS/prism/issues/1388)) [[`f6e81cb`](https://github.com/PrismJS/prism/commit/f6e81cb)] * __Kotlin__ - * Add keywords of Kotlin and modify it's number pattern. ([#1389](https://github.com/PrismJS/prism/issues/1389)) [[`1bf73b0`](https://github.com/PrismJS/prism/commit/1bf73b0)] - * Add `typealias` keyword ([#1437](https://github.com/PrismJS/prism/issues/1437)) [[`a21fdee`](https://github.com/PrismJS/prism/commit/a21fdee)] -* __JavaScript - * Improve Regexp pattern [[`5b043cf`](https://github.com/PrismJS/prism/commit/5b043cf)] - * Add support for one level of nesting inside template strings. Fix [#1397](https://github.com/PrismJS/prism/issues/1397) [[`db2d0eb`](https://github.com/PrismJS/prism/commit/db2d0eb)] + * 添加 Kotlin 关键字及其数字模式的修改 ([#1389](https://github.com/PrismJS/prism/issues/1389)) [[`1bf73b0`](https://github.com/PrismJS/prism/commit/1bf73b0)] + * 添加 `typealias` 关键字 ([#1437](https://github.com/PrismJS/prism/issues/1437)) [[`a21fdee`](https://github.com/PrismJS/prism/commit/a21fdee)] +* __JavaScript__ + * 改进正则表达式模式 [[`5b043cf`](https://github.com/PrismJS/prism/commit/5b043cf)] + * 支持模板字符串内部一层嵌套。修复 [#1397](https://github.com/PrismJS/prism/issues/1397) [[`db2d0eb`](https://github.com/PrismJS/prism/commit/db2d0eb)] * __Elixir__ - * Elixir: Fix attributes consuming punctuation. Fix [#1392](https://github.com/PrismJS/prism/issues/1392) [[`dac0485`](https://github.com/PrismJS/prism/commit/dac0485)] + * Elixir:修复属性消耗标点符号。修复 [#1392](https://github.com/PrismJS/prism/issues/1392) [[`dac0485`](https://github.com/PrismJS/prism/commit/dac0485)] * __Bash__ - * Change reserved keyword reference ([#1396](https://github.com/PrismJS/prism/issues/1396)) [[`b94f01f`](https://github.com/PrismJS/prism/commit/b94f01f)] + * 更改保留关键字引用 ([#1396](https://github.com/PrismJS/prism/issues/1396)) [[`b94f01f`](https://github.com/PrismJS/prism/commit/b94f01f)] * __PowerShell__ - * Allow for one level of nesting in expressions inside strings. Fix [#1407](https://github.com/PrismJS/prism/issues/1407) [[`9272d6f`](https://github.com/PrismJS/prism/commit/9272d6f)] + * 允许字符串内表达式中的一层嵌套。修复 [#1407](https://github.com/PrismJS/prism/issues/1407) [[`9272d6f`](https://github.com/PrismJS/prism/commit/9272d6f)] * __JSX__ - * Allow for two levels of nesting inside JSX tags. Fix [#1408](https://github.com/PrismJS/prism/issues/1408) [[`f1cd7c5`](https://github.com/PrismJS/prism/commit/f1cd7c5)] - * Add support for fragments short syntax. Fix [#1421](https://github.com/PrismJS/prism/issues/1421) [[`38ce121`](https://github.com/PrismJS/prism/commit/38ce121)] + * 允许 JSX 标签内两个级别的嵌套。修复 [#1408](https://github.com/PrismJS/prism/issues/1408) [[`f1cd7c5`](https://github.com/PrismJS/prism/commit/f1cd7c5)] + * 添加对片段简写语法的支持。修复 [#1421](https://github.com/PrismJS/prism/issues/1421) [[`38ce121`](https://github.com/PrismJS/prism/commit/38ce121)] * __Pascal__ - * Add `objectpascal` as an alias to `pascal` ([#1426](https://github.com/PrismJS/prism/issues/1426)) [[`a0bfc84`](https://github.com/PrismJS/prism/commit/a0bfc84)] + * 将 `objectpascal` 添加为 `pascal` 的别名 ([#1426](https://github.com/PrismJS/prism/issues/1426)) [[`a0bfc84`](https://github.com/PrismJS/prism/commit/a0bfc84)] * __Swift__ - * Fix Swift 'protocol' keyword ([#1440](https://github.com/PrismJS/prism/issues/1440)) [[`081e318`](https://github.com/PrismJS/prism/commit/081e318)] + * 修复 Swift 的 `protocol` 关键字 ([#1440](https://github.com/PrismJS/prism/issues/1440)) [[`081e318`](https://github.com/PrismJS/prism/commit/081e318)] -### Updated plugins +### 更新插件 -* __File Highlight__ - * Fix issue causing the Download button to show up on every code blocks. [[`cd22499`](https://github.com/PrismJS/prism/commit/cd22499)] - * Simplify lang regex on File Highlight plugin ([#1399](https://github.com/PrismJS/prism/issues/1399)) [[`7bc9a4a`](https://github.com/PrismJS/prism/commit/7bc9a4a)] -* __Show Language__ - * Don't process language if block language not set ([#1410](https://github.com/PrismJS/prism/issues/1410)) [[`c111869`](https://github.com/PrismJS/prism/commit/c111869)] -* __Autoloader__ - * ASP.NET should require C# [[`fa328bb`](https://github.com/PrismJS/prism/commit/fa328bb)] -* __Line Numbers__ - * Make line-numbers styles more specific ([#1434](https://github.com/PrismJS/prism/issues/1434), [#1435](https://github.com/PrismJS/prism/issues/1435)) [[`9ee4f54`](https://github.com/PrismJS/prism/commit/9ee4f54)] +* __文件高亮__ + * 修复导致下载按钮在每个代码块上显示的问题。[[`cd22499`](https://github.com/PrismJS/prism/commit/cd22499)] + * 简化文件高亮插件的语言正则表达式 ([#1399](https://github.com/PrismJS/prism/issues/1399)) [[`7bc9a4a`](https://github.com/PrismJS/prism/commit/7bc9a4a)] +* __显示语言__ + * 如果块语言未设置,则不处理语言 ([#1410](https://github.com/PrismJS/prism/issues/1410)) [[`c111869`](https://github.com/PrismJS/prism/commit/c111869)] +* __自动加载器__ + * ASP.NET 应该要求 C# [[`fa328bb`](https://github.com/PrismJS/prism/commit/fa328bb)] +* __行号__ + * 使行号样式更加特定 ([#1434](https://github.com/PrismJS/prism/issues/1434), [#1435](https://github.com/PrismJS/prism/issues/1435)) [[`9ee4f54`](https://github.com/PrismJS/prism/commit/9ee4f54)] -### Updated themes +### 更新主题 -* Add .token.class-name to rest of themes ([#1360](https://github.com/PrismJS/prism/issues/1360)) [[`f356dfe`](https://github.com/PrismJS/prism/commit/f356dfe)] +* 向其余主题添加 .token.class-name ([#1360](https://github.com/PrismJS/prism/issues/1360)) [[`f356dfe`](https://github.com/PrismJS/prism/commit/f356dfe)] -### Other changes +### 其他变化 -* __Website__ - * Site now loads over HTTPS! - * Use HTTPS / canonical URLs ([#1390](https://github.com/PrismJS/prism/issues/1390)) [[`95146c8`](https://github.com/PrismJS/prism/commit/95146c8)] - * Added Angular tutorial link [[`c436a7c`](https://github.com/PrismJS/prism/commit/c436a7c)] - * Use rel="icon" instead of rel="shortcut icon" ([#1398](https://github.com/PrismJS/prism/issues/1398)) [[`d95f8fb`](https://github.com/PrismJS/prism/commit/d95f8fb)] - * Fix Download page not handling multiple dependencies when from Redownload URL [[`c2ff248`](https://github.com/PrismJS/prism/commit/c2ff248)] - * Update documentation for node & webpack usage [[`1e99e96`](https://github.com/PrismJS/prism/commit/1e99e96)] -* Handle optional dependencies in `loadLanguages()` ([#1417](https://github.com/PrismJS/prism/issues/1417)) [[`84935ac`](https://github.com/PrismJS/prism/commit/84935ac)] -* Add Chinese translation [[`f2b1964`](https://github.com/PrismJS/prism/commit/f2b1964)] +* __网站__ + * 现在网站通过 HTTPS 加载! + * 使用 HTTPS / 规范 URLs ([#1390](https://github.com/PrismJS/prism/issues/1390)) [[`95146c8`](https://github.com/PrismJS/prism/commit/95146c8)] + * 添加 Angular 教程链接 [[`c436a7c`](https://github.com/PrismJS/prism/commit/c436a7c)] + * 使用 rel="icon" 而不是 rel="shortcut icon" ([#1398](https://github.com/PrismJS/prism/issues/1398)) [[`d95f8fb`](https://github.com/PrismJS/prism/commit/d95f8fb)] + * 修复下载页面不处理来自重下载 URL 多个依赖项的问题 [[`c2ff248`](https://github.com/PrismJS/prism/commit/c2ff248)] + * 更新 Node & Webpack 的文档 [[`1e99e96`](https://github.com/PrismJS/prism/commit/1e99e96)] +* 处理 `loadLanguages()` 中的可选依赖项 ([#1417](https://github.com/PrismJS/prism/issues/1417)) [[`84935ac`](https://github.com/PrismJS/prism/commit/84935ac)] +* 添加中文翻译 [[`f2b1964`](https://github.com/PrismJS/prism/commit/f2b1964)] ## 1.14.0 (2018-04-11) -### New components +### 新组件 * __GEDCOM__ ([#1385](https://github.com/PrismJS/prism/issues/1385)) [[`6e0b20a`](https://github.com/PrismJS/prism/commit/6e0b20a)] * __Lisp__ ([#1297](https://github.com/PrismJS/prism/issues/1297)) [[`46468f8`](https://github.com/PrismJS/prism/commit/46468f8)] * __Markup Templating__ ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] @@ -71,1264 +71,68 @@ * __Visual Basic__ ([#1382](https://github.com/PrismJS/prism/issues/1382)) [[`c673ec2`](https://github.com/PrismJS/prism/commit/c673ec2)] * __WebAssembly__ ([#1386](https://github.com/PrismJS/prism/issues/1386)) [[`c28d8c5`](https://github.com/PrismJS/prism/commit/c28d8c5)] -### Updated components +### 更新组件 * __Bash__: - * Add curl to the list of common functions. Close [#1160](https://github.com/PrismJS/prism/issues/1160) [[`1bfc084`](https://github.com/PrismJS/prism/commit/1bfc084)] + * 添加 curl 至常用函数列表。关闭 [#1160](https://github.com/PrismJS/prism/issues/1160) [[`1bfc084`](https://github.com/PrismJS/prism/commit/1bfc084)] * __C-like__: - * Make single-line comments greedy. Fix [#1337](https://github.com/PrismJS/prism/issues/1337). Make sure [#1340](https://github.com/PrismJS/prism/issues/1340) stays fixed. [[`571f2c5`](https://github.com/PrismJS/prism/commit/571f2c5)] + * 使单行注释贪婪。修复 [#1337](https://github.com/PrismJS/prism/issues/1337)。确保 [#1340](https://github.com/PrismJS/prism/issues/1340) 保持固定。 [[`571f2c5`](https://github.com/PrismJS/prism/commit/571f2c5)] * __C#__: - * More generic class-name highlighting. Fix [#1365](https://github.com/PrismJS/prism/issues/1365) [[`a6837d2`](https://github.com/PrismJS/prism/commit/a6837d2)] - * More specific class-name highlighting. Fix [#1371](https://github.com/PrismJS/prism/issues/1371) [[`0a95f69`](https://github.com/PrismJS/prism/commit/0a95f69)] + * 更通用的类名高亮。修复 [#1365](https://github.com/PrismJS/prism/issues/1365) [[`a6837d2`](https://github.com/PrismJS/prism/commit/a6837d2)] + * 更具体的类名高亮。修复 [#1371](https://github.com/PrismJS/prism/issues/1371) [[`0a95f69`](https://github.com/PrismJS/prism/commit/0a95f69)] * __Eiffel__: - * Fix verbatim strings. Fix [#1379](https://github.com/PrismJS/prism/issues/1379) [[`04df41b`](https://github.com/PrismJS/prism/commit/04df41b)] + * 修复逐字字符串。修复 [#1379](https://github.com/PrismJS/prism/issues/1379) [[`04df41b`](https://github.com/PrismJS/prism/commit/04df41b)] * __Elixir__ - * Make regexps greedy, remove comment hacks. Update known failures and tests. [[`e93d61f`](https://github.com/PrismJS/prism/commit/e93d61f)] + * 使正则表达式贪婪,删除注释黑客。更新已知失败和测试。 [[`e93d61f`](https://github.com/PrismJS/prism/commit/e93d61f)] * __ERB__: - * Make highlighting work properly in NodeJS ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] + * 在 NodeJS 上使高亮正常工作 ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] * __Fortran__: - * Make single-line comments greedy. Update known failures and tests. [[`c083b78`](https://github.com/PrismJS/prism/commit/c083b78)] + * 使单行注释贪婪。更新已知失败和测试。 [[`c083b78`](https://github.com/PrismJS/prism/commit/c083b78)] * __Handlebars__: - * Make highlighting work properly in NodeJS ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] + * 使在 NodeJS 上高亮正常工作 ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] * __Java__: - * Add support for generics. Fix [#1351](https://github.com/PrismJS/prism/issues/1351) [[`a5cf302`](https://github.com/PrismJS/prism/commit/a5cf302)] + * 添加对泛型的支持。修复 [#1351](https://github.com/PrismJS/prism/issues/1351) [[`a5cf302`](https://github.com/PrismJS/prism/commit/a5cf302)] * __JavaScript__: - * Add support for constants. Fix [#1348](https://github.com/PrismJS/prism/issues/1348) [[`9084481`](https://github.com/PrismJS/prism/commit/9084481)] - * Improve Regex matching [[`172d351`](https://github.com/PrismJS/prism/commit/172d351)] + * 添加对常量的支持。修复 [#1348](https://github.com/PrismJS/prism/issues/1348) [[`9084481`](https://github.com/PrismJS/prism/commit/9084481)] + * 改进正则表达式匹配 [[`172d351`](https://github.com/PrismJS/prism/commit/172d351)] * __JSX__: - * Fix highlighting of empty objects. Fix [#1364](https://github.com/PrismJS/prism/issues/1364) [[`b26bbb8`](https://github.com/PrismJS/prism/commit/b26bbb8)] + * 修复空对象的高亮。修复 [#1364](https://github.com/PrismJS/prism/issues/1364) [[`b26bbb8`](https://github.com/PrismJS/prism/commit/b26bbb8)] * __Monkey__: - * Make comments greedy. Update known failures and tests. [[`d7b2b43`](https://github.com/PrismJS/prism/commit/d7b2b43)] + * 使注释贪婪。更新已知失败和测试。 [[`d7b2b43`](https://github.com/PrismJS/prism/commit/d7b2b43)] * __PHP__: - * Make highlighting work properly in NodeJS ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] + * 在 NodeJS 上使高亮正常工作 ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] * __Puppet__: - * Make heredoc, comments, regexps and strings greedy. Update known failures and tests. [[`0c139d1`](https://github.com/PrismJS/prism/commit/0c139d1)] + * 使 heredoc,注释,正则表达式和字符串贪婪。更新已知失败和测试。 [[`0c139d1`](https://github.com/PrismJS/prism/commit/0c139d1)] * __Q__: - * Make comments greedy. Update known failures and tests. [[`a0f5081`](https://github.com/PrismJS/prism/commit/a0f5081)] + * 使注释贪婪。更新已知失败和测试。 [[`a0f5081`](https://github.com/PrismJS/prism/commit/a0f5081)] * __Ruby__: - * Make multi-line comments greedy, remove single-line comment hack. Update known failures and tests. [[`b0e34fb`](https://github.com/PrismJS/prism/commit/b0e34fb)] + * 使多行注释贪婪,删除单行注释黑客。更新已知失败和测试。 [[`b0e34fb`](https://github.com/PrismJS/prism/commit/b0e34fb)] * __SQL__: - * Add missing keywords. Fix [#1374](https://github.com/PrismJS/prism/issues/1374) [[`238b195`](https://github.com/PrismJS/prism/commit/238b195)] - -### Updated plugins -* __Command Line__: - * Command Line: Allow specifying output prefix using data-filter-output attribute. ([#856](https://github.com/PrismJS/prism/issues/856)) [[`094d546`](https://github.com/PrismJS/prism/commit/094d546)] -* __File Highlight__: - * Add option to provide a download button, when used with the Toolbar plugin. Fix [#1030](https://github.com/PrismJS/prism/issues/1030) [[`9f22952`](https://github.com/PrismJS/prism/commit/9f22952)] - -### Updated themes -* __Default__: - * Reach AA contrast ratio level ([#1296](https://github.com/PrismJS/prism/issues/1296)) [[`8aea939`](https://github.com/PrismJS/prism/commit/8aea939)] - -### Other changes -* Website: Remove broken third-party tutorials from homepage [[`0efd6e1`](https://github.com/PrismJS/prism/commit/0efd6e1)] -* Docs: Mention `loadLanguages()` function on homepage in the nodeJS section. Close [#972](https://github.com/PrismJS/prism/issues/972), close [#593](https://github.com/PrismJS/prism/issues/593) [[`4a14d20`](https://github.com/PrismJS/prism/commit/4a14d20)] -* Core: Greedy patterns should always be matched against the full string. Fix [#1355](https://github.com/PrismJS/prism/issues/1355) [[`294efaa`](https://github.com/PrismJS/prism/commit/294efaa)] -* Crystal: Update known failures. [[`e1d2d42`](https://github.com/PrismJS/prism/commit/e1d2d42)] -* D: Update known failures and tests. [[`13d9991`](https://github.com/PrismJS/prism/commit/13d9991)] -* Markdown: Update known failures. [[`5b6c76d`](https://github.com/PrismJS/prism/commit/5b6c76d)] -* Matlab: Update known failures. [[`259b6fc`](https://github.com/PrismJS/prism/commit/259b6fc)] -* Website: Remove non-existent anchor to failures. Reword on homepage to make is less misleading. [[`8c0911a`](https://github.com/PrismJS/prism/commit/8c0911a)] -* Website: Add link to Keep Markup plugin in FAQ [[`e8cb6d4`](https://github.com/PrismJS/prism/commit/e8cb6d4)] -* Test suite: Memory leak in vm.runInNewContext() seems fixed. Revert [[`9a4b6fa`](https://github.com/PrismJS/prism/commit/9a4b6fa)] to drastically improve tests execution time. [[`9bceece`](https://github.com/PrismJS/prism/commit/9bceece), [`7c7602b`](https://github.com/PrismJS/prism/commit/7c7602b)] -* Gulp: Don't minify `components/index.js` [[`689227b`](https://github.com/PrismJS/prism/commit/689227b)] -* Website: Fix theme selection on Download page, when theme is in query string or hash. [[`b4d3063`](https://github.com/PrismJS/prism/commit/b4d3063)] -* Update JSPM config to also include unminified components. Close [#995](https://github.com/PrismJS/prism/issues/995) [[`218f160`](https://github.com/PrismJS/prism/commit/218f160)] -* Core: Fix support for language alias containing dash `-` [[`659ea31`](https://github.com/PrismJS/prism/commit/659ea31)] - -## 1.13.0 (2018-03-21) - -### New components -* __ERB__ [[`e6213ac`](https://github.com/PrismJS/prism/commit/e6213ac)] -* __PL/SQL__ ([#1338](https://github.com/PrismJS/prism/issues/1338)) [[`3599e6a`](https://github.com/PrismJS/prism/commit/3599e6a)] - -### Updated components -* __JSX__: - * Add support for plain text inside tags ([#1357](https://github.com/PrismJS/prism/issues/1357)) [[`2b8321d`](https://github.com/PrismJS/prism/commit/2b8321d)] -* __Markup__: - * Make tags greedy. Fix [#1356](https://github.com/PrismJS/prism/issues/1356) [[`af834be`](https://github.com/PrismJS/prism/commit/af834be)] -* __Powershell__: - * Add lookbehind to fix function interpolation inside strings. Fix [#1361](https://github.com/PrismJS/prism/issues/1361) [[`d2c026e`](https://github.com/PrismJS/prism/commit/d2c026e)] -* __Rust__: - * Improve char pattern so that lifetime annotations are matched better. Fix [#1353](https://github.com/PrismJS/prism/issues/1353) [[`efdccbf`](https://github.com/PrismJS/prism/commit/efdccbf)] - -### Updated themes -* __Default__: - * Add color for class names [[`8572474`](https://github.com/PrismJS/prism/commit/8572474)] -* __Coy__: - * Inherit pre's height on code, so it does not break on Download page. [[`c6c7fd1`](https://github.com/PrismJS/prism/commit/c6c7fd1)] - -### Other changes -* Website: Auto-generate example headers [[`c3ed5b5`](https://github.com/PrismJS/prism/commit/c3ed5b5)] -* Core: Allow cloning of circular structures. ([#1345](https://github.com/PrismJS/prism/issues/1345)) [[`f90d555`](https://github.com/PrismJS/prism/commit/f90d555)] -* Core: Generate components.js from components.json and make it exportable to nodeJS. ([#1354](https://github.com/PrismJS/prism/issues/1354)) [[`ba60df0`](https://github.com/PrismJS/prism/commit/ba60df0)] -* Website: Improve appearance of theme selector [[`0460cad`](https://github.com/PrismJS/prism/commit/0460cad)] -* Website: Check stored theme by default + link both theme selectors together. Close [#1038](https://github.com/PrismJS/prism/issues/1038) [[`212dd4e`](https://github.com/PrismJS/prism/commit/212dd4e)] -* Tests: Use the new components.js file directly [[`0e1a8b7`](https://github.com/PrismJS/prism/commit/0e1a8b7)] -* Update .npmignore Close [#1274](https://github.com/PrismJS/prism/issues/1274) [[`a52319a`](https://github.com/PrismJS/prism/commit/a52319a)] -* Add a loadLanguages() function for easy component loading on NodeJS ([#1359](https://github.com/PrismJS/prism/issues/1359)) [[`a5331a6`](https://github.com/PrismJS/prism/commit/a5331a6)] - -## 1.12.2 (2018-03-08) - -### Other changes -* Test against NodeJS 4, 6, 8 and 9 ([#1329](https://github.com/PrismJS/prism/issues/1329)) [[`97b7d0a`](https://github.com/PrismJS/prism/commit/97b7d0a)] -* Stop testing against NodeJS 0.10 and 0.12 [[`df01b1b`](https://github.com/PrismJS/prism/commit/df01b1b)] - -## 1.12.1 (2018-03-08) - -### Updated components -* __C-like__: - * Revert [[`b98e5b9`](https://github.com/PrismJS/prism/commit/b98e5b9)] to fix [#1340](https://github.com/PrismJS/prism/issues/1340). Reopened [#1337](https://github.com/PrismJS/prism/issues/1337). [[`cebacdf`](https://github.com/PrismJS/prism/commit/cebacdf)] -* __JSX__: - * Allow for one level of nested curly braces inside tag attribute value. Fix [#1335](https://github.com/PrismJS/prism/issues/1335) [[`05bf67d`](https://github.com/PrismJS/prism/commit/05bf67d)] -* __Ruby__: - * Ensure module syntax is not confused with symbols. Fix [#1336](https://github.com/PrismJS/prism/issues/1336) [[`31a2a69`](https://github.com/PrismJS/prism/commit/31a2a69)] - -## 1.12.0 (2018-03-07) - -### New components -* __ARFF__ ([#1327](https://github.com/PrismJS/prism/issues/1327)) [[`0bc98ac`](https://github.com/PrismJS/prism/commit/0bc98ac)] -* __Clojure__ ([#1311](https://github.com/PrismJS/prism/issues/1311)) [[`8b4d3bd`](https://github.com/PrismJS/prism/commit/8b4d3bd)] -* __Liquid__ ([#1326](https://github.com/PrismJS/prism/issues/1326)) [[`f0b2c9e`](https://github.com/PrismJS/prism/commit/f0b2c9e)] - -### Updated components -* __Bash__: - * Add shell as an alias ([#1321](https://github.com/PrismJS/prism/issues/1321)) [[`67e16a2`](https://github.com/PrismJS/prism/commit/67e16a2)] - * Add support for quoted command substitution. Fix [#1287](https://github.com/PrismJS/prism/issues/1287) [[`63fc215`](https://github.com/PrismJS/prism/commit/63fc215)] -* __C#__: - * Add "dotnet" alias. [[`405867c`](https://github.com/PrismJS/prism/commit/405867c)] -* __C-like__: - * Change order of comment patterns and make multi-line one greedy. Fix [#1337](https://github.com/PrismJS/prism/issues/1337) [[`b98e5b9`](https://github.com/PrismJS/prism/commit/b98e5b9)] -* __NSIS__: - * Add support for NSIS 3.03 ([#1288](https://github.com/PrismJS/prism/issues/1288)) [[`bd1e98b`](https://github.com/PrismJS/prism/commit/bd1e98b)] - * Add missing NSIS commands ([#1289](https://github.com/PrismJS/prism/issues/1289)) [[`ad2948f`](https://github.com/PrismJS/prism/commit/ad2948f)] -* __PHP__: - * Add support for string interpolation inside double-quoted strings. Fix [#1146](https://github.com/PrismJS/prism/issues/1146) [[`9f1f8d6`](https://github.com/PrismJS/prism/commit/9f1f8d6)] - * Add support for Heredoc and Nowdoc strings [[`5d7223c`](https://github.com/PrismJS/prism/commit/5d7223c)] - * Fix shell-comment failure now that strings are greedy [[`ad25d22`](https://github.com/PrismJS/prism/commit/ad25d22)] -* __PowerShell__: - * Add support for two levels of nested brackets inside namespace pattern. Fixes [#1317](https://github.com/PrismJS/prism/issues/1317) [[`3bc3e9c`](https://github.com/PrismJS/prism/commit/3bc3e9c)] -* __Ruby__: - * Add keywords "protected", "private" and "public" [[`4593837`](https://github.com/PrismJS/prism/commit/4593837)] -* __Rust__: - * Add support for lifetime-annotation and => operator. Fix [#1339](https://github.com/PrismJS/prism/issues/1339) [[`926f6f8`](https://github.com/PrismJS/prism/commit/926f6f8)] -* __Scheme__: - * Don't highlight first number of a list as a function. Fix [#1331](https://github.com/PrismJS/prism/issues/1331) [[`51bff80`](https://github.com/PrismJS/prism/commit/51bff80)] -* __SQL__: - * Add missing keywords and functions, fix numbers [[`de29d4a`](https://github.com/PrismJS/prism/commit/de29d4a)] - -### Updated plugins -* __Autolinker__: - * Allow more chars in query string and hash to match more URLs. Fix [#1142](https://github.com/PrismJS/prism/issues/1142) [[`109bd6f`](https://github.com/PrismJS/prism/commit/109bd6f)] -* __Copy to Clipboard__: - * Bump ClipboardJS to 2.0.0 and remove hack ([#1314](https://github.com/PrismJS/prism/issues/1314)) [[`e9f410e`](https://github.com/PrismJS/prism/commit/e9f410e)] -* __Toolbar__: - * Prevent scrolling toolbar with content ([#1305](https://github.com/PrismJS/prism/issues/1305), [#1314](https://github.com/PrismJS/prism/issues/1314)) [[`84eeb89`](https://github.com/PrismJS/prism/commit/84eeb89)] -* __Unescaped Markup__: - * Use msMatchesSelector for IE11 and below. Fix [#1302](https://github.com/PrismJS/prism/issues/1302) [[`c246c1a`](https://github.com/PrismJS/prism/commit/c246c1a)] -* __WebPlatform Docs__: - * WebPlatform Docs plugin: Fix links. Fixes [#1290](https://github.com/PrismJS/prism/issues/1290) [[`7a9dbe0`](https://github.com/PrismJS/prism/commit/7a9dbe0)] - -### Other changes -* Fix Autoloader's demo page [[`3dddac9`](https://github.com/PrismJS/prism/commit/3dddac9)] -* Download page: Use hash instead of query-string for redownload URL. Fix [#1263](https://github.com/PrismJS/prism/issues/1263) [[`b03c02a`](https://github.com/PrismJS/prism/commit/b03c02a)] -* Core: Don't thow an error if lookbehing is used without anything matching. [[`e0cd47f`](https://github.com/PrismJS/prism/commit/e0cd47f)] -* Docs: Fix link to the `<code>` element specification in HTML5 [[`a84263f`](https://github.com/PrismJS/prism/commit/a84263f)] -* Docs: Mention support for `lang-xxxx` class. Close [#1312](https://github.com/PrismJS/prism/issues/1312) [[`a9e76db`](https://github.com/PrismJS/prism/commit/a9e76db)] -* Docs: Add note on `async` parameter to clarify the requirement of using a single bundled file. Closes [#1249](https://github.com/PrismJS/prism/issues/1249) [[`eba0235`](https://github.com/PrismJS/prism/commit/eba0235)] - -## 1.11.0 (2018-02-05) - -### New components -* __Content-Security-Policy (CSP)__ ([#1275](https://github.com/PrismJS/prism/issues/1275)) [[`b08cae5`](https://github.com/PrismJS/prism/commit/b08cae5)] -* __HTTP Public-Key-Pins (HPKP)__ ([#1275](https://github.com/PrismJS/prism/issues/1275)) [[`b08cae5`](https://github.com/PrismJS/prism/commit/b08cae5)] -* __HTTP String-Transport-Security (HSTS)__ ([#1275](https://github.com/PrismJS/prism/issues/1275)) [[`b08cae5`](https://github.com/PrismJS/prism/commit/b08cae5)] -* __React TSX__ ([#1280](https://github.com/PrismJS/prism/issues/1280)) [[`fbe82b8`](https://github.com/PrismJS/prism/commit/fbe82b8)] - -### Updated components -* __C++__: - * Add C++ platform-independent types ([#1271](https://github.com/PrismJS/prism/issues/1271)) [[`3da238f`](https://github.com/PrismJS/prism/commit/3da238f)] -* __TypeScript__: - * Improve typescript with builtins ([#1277](https://github.com/PrismJS/prism/issues/1277)) [[`5de1b1f`](https://github.com/PrismJS/prism/commit/5de1b1f)] - -### Other changes -* Fix passing of non-enumerable Error properties from the child test runner ([#1276](https://github.com/PrismJS/prism/issues/1276)) [[`38df653`](https://github.com/PrismJS/prism/commit/38df653)] - -## 1.10.0 (2018-01-17) - -### New components -* __6502 Assembly__ ([#1245](https://github.com/PrismJS/prism/issues/1245)) [[`2ece18b`](https://github.com/PrismJS/prism/commit/2ece18b)] -* __Elm__ ([#1174](https://github.com/PrismJS/prism/issues/1174)) [[`d6da70e`](https://github.com/PrismJS/prism/commit/d6da70e)] -* __IchigoJam BASIC__ ([#1246](https://github.com/PrismJS/prism/issues/1246)) [[`cf840be`](https://github.com/PrismJS/prism/commit/cf840be)] -* __Io__ ([#1251](https://github.com/PrismJS/prism/issues/1251)) [[`84ed3ed`](https://github.com/PrismJS/prism/commit/84ed3ed)] - -### Updated components -* __BASIC__: - * Make strings greedy [[`60114d0`](https://github.com/PrismJS/prism/commit/60114d0)] -* __C++__: - * Add C++11 raw string feature ([#1254](https://github.com/PrismJS/prism/issues/1254)) [[`71595be`](https://github.com/PrismJS/prism/commit/71595be)] - -### Updated plugins -* __Autoloader__: - * Add support for `data-autoloader-path` ([#1242](https://github.com/PrismJS/prism/issues/1242)) [[`39360d6`](https://github.com/PrismJS/prism/commit/39360d6)] -* __Previewers__: - * New plugin combining previous plugins Previewer: Base, Previewer: Angle, Previewer: Color, Previewer: Easing, Previewer: Gradient and Previewer: Time. ([#1244](https://github.com/PrismJS/prism/issues/1244)) [[`28e4b4c`](https://github.com/PrismJS/prism/commit/28e4b4c)] -* __Unescaped Markup__: - * Make it work with any language ([#1265](https://github.com/PrismJS/prism/issues/1265)) [[`7bcdae7`](https://github.com/PrismJS/prism/commit/7bcdae7)] - -### Other changes -* Add attribute `style` in `package.json` ([#1256](https://github.com/PrismJS/prism/issues/1256)) [[`a9b6785`](https://github.com/PrismJS/prism/commit/a9b6785)] - -## 1.9.0 (2017-12-06) - -### New components -* __Flow__ [[`d27b70d`](https://github.com/PrismJS/prism/commit/d27b70d)] - -### Updated components -* __CSS__: - * Unicode characters in CSS properties ([#1227](https://github.com/PrismJS/prism/issues/1227)) [[`f234ea4`](https://github.com/PrismJS/prism/commit/f234ea4)] -* __JSX__: - * JSX: Improve highlighting support. Fix [#1235](https://github.com/PrismJS/prism/issues/1235) and [#1236](https://github.com/PrismJS/prism/issues/1236) [[`f41c5cd`](https://github.com/PrismJS/prism/commit/f41c5cd)] -* __Markup__: - * Make CSS and JS inclusions in Markup greedy. Fix [#1240](https://github.com/PrismJS/prism/issues/1240) [[`7dc1e45`](https://github.com/PrismJS/prism/commit/7dc1e45)] -* __PHP__: - * Add support for multi-line strings. Fix [#1233](https://github.com/PrismJS/prism/issues/1233) [[`9a542a0`](https://github.com/PrismJS/prism/commit/9a542a0)] - -### Updated plugins -* __Copy to clipboard__: - * Fix test for native Clipboard. Fix [#1241](https://github.com/PrismJS/prism/issues/1241) [[`e7b5e82`](https://github.com/PrismJS/prism/commit/e7b5e82)] - * Copy to clipboard: Update to v1.7.1. Fix [#1220](https://github.com/PrismJS/prism/issues/1220) [[`a1b85e3`](https://github.com/PrismJS/prism/commit/a1b85e3), [`af50e44`](https://github.com/PrismJS/prism/commit/af50e44)] -* __Line highlight__: - * Fixes to compatibility of line number and line higlight plugins ([#1194](https://github.com/PrismJS/prism/issues/1194)) [[`e63058f`](https://github.com/PrismJS/prism/commit/e63058f), [`3842a91`](https://github.com/PrismJS/prism/commit/3842a91)] -* __Unescaped Markup__: - * Fix ambiguity in documentation by improving examples. Fix [#1197](https://github.com/PrismJS/prism/issues/1197) [[`924784a`](https://github.com/PrismJS/prism/commit/924784a)] - -### Other changes -* Allow any element being root instead of document. ([#1230](https://github.com/PrismJS/prism/issues/1230)) [[`69f2e2c`](https://github.com/PrismJS/prism/commit/69f2e2c), [`6e50d44`](https://github.com/PrismJS/prism/commit/6e50d44)] -* Coy Theme: The 'height' element makes code blocks the height of the browser canvas. ([#1224](https://github.com/PrismJS/prism/issues/1224)) [[`ac219d7`](https://github.com/PrismJS/prism/commit/ac219d7)] -* Download page: Fix implicitly declared variable [[`f986551`](https://github.com/PrismJS/prism/commit/f986551)] -* Download page: Add version number at the beginning of the generated files. Fix [#788](https://github.com/PrismJS/prism/issues/788) [[`928790d`](https://github.com/PrismJS/prism/commit/928790d)] - -## 1.8.4 (2017-11-05) - -### Updated components - -* __ABAP__: - * Regexp optimisation [[`7547f83`](https://github.com/PrismJS/prism/commit/7547f83)] -* __ActionScript__: - * Fix XML regex + optimise [[`75d00d7`](https://github.com/PrismJS/prism/commit/75d00d7)] -* __Ada__: - * Regexp simplification [[`e881fe3`](https://github.com/PrismJS/prism/commit/e881fe3)] -* __Apacheconf__: - * Regexp optimisation [[`a065e61`](https://github.com/PrismJS/prism/commit/a065e61)] -* __APL__: - * Regexp simplification [[`33297c4`](https://github.com/PrismJS/prism/commit/33297c4)] -* __AppleScript__: - * Regexp optimisation [[`d879f36`](https://github.com/PrismJS/prism/commit/d879f36)] -* __Arduino__: - * Don't use captures if not needed [[`16b338f`](https://github.com/PrismJS/prism/commit/16b338f)] -* __ASP.NET__: - * Regexp optimisation [[`438926c`](https://github.com/PrismJS/prism/commit/438926c)] -* __AutoHotkey__: - * Regexp simplification + don't use captures if not needed [[`5edfd2f`](https://github.com/PrismJS/prism/commit/5edfd2f)] -* __Bash__: - * Regexp optimisation and simplification [[`75b9b29`](https://github.com/PrismJS/prism/commit/75b9b29)] -* __Bro__: - * Regexp simplification + don't use captures if not needed [[`d4b9003`](https://github.com/PrismJS/prism/commit/d4b9003)] -* __C__: - * Regexp optimisation + don't use captures if not needed [[`f61d487`](https://github.com/PrismJS/prism/commit/f61d487)] -* __C++__: - * Fix operator regexp + regexp simplification + don't use captures if not needed [[`ffeb26e`](https://github.com/PrismJS/prism/commit/ffeb26e)] -* __C#__: - * Remove duplicates in keywords + regexp optimisation + don't use captures if not needed [[`d28d178`](https://github.com/PrismJS/prism/commit/d28d178)] -* __C-like__: - * Regexp simplification + don't use captures if not needed [[`918e0ff`](https://github.com/PrismJS/prism/commit/918e0ff)] -* __CoffeeScript__: - * Regexp optimisation + don't use captures if not needed [[`5895978`](https://github.com/PrismJS/prism/commit/5895978)] -* __Crystal__: - * Remove trailing comma [[`16979a3`](https://github.com/PrismJS/prism/commit/16979a3)] -* __CSS__: - * Regexp simplification + don't use captures if not needed + handle multi-line style attributes [[`43d9f36`](https://github.com/PrismJS/prism/commit/43d9f36)] -* __CSS Extras__: - * Regexp simplification [[`134ed70`](https://github.com/PrismJS/prism/commit/134ed70)] -* __D__: - * Regexp optimisation [[`fbe39c9`](https://github.com/PrismJS/prism/commit/fbe39c9)] -* __Dart__: - * Regexp optimisation [[`f24e919`](https://github.com/PrismJS/prism/commit/f24e919)] -* __Django__: - * Regexp optimisation [[`a95c51d`](https://github.com/PrismJS/prism/commit/a95c51d)] -* __Docker__: - * Regexp optimisation [[`27f99ff`](https://github.com/PrismJS/prism/commit/27f99ff)] -* __Eiffel__: - * Regexp optimisation [[`b7cdea2`](https://github.com/PrismJS/prism/commit/b7cdea2)] -* __Elixir__: - * Regexp optimisation + uniform behavior between ~r and ~s [[`5d12e80`](https://github.com/PrismJS/prism/commit/5d12e80)] -* __Erlang__: - * Regexp optimisation [[`e7b411e`](https://github.com/PrismJS/prism/commit/e7b411e)] -* __F#__: - * Regexp optimisation + don't use captures if not needed [[`7753fc4`](https://github.com/PrismJS/prism/commit/7753fc4)] -* __Gherkin__: - * Regexp optimisation + don't use captures if not needed + added explanation comment on table-body regexp [[`f26197a`](https://github.com/PrismJS/prism/commit/f26197a)] -* __Git__: - * Regexp optimisation [[`b9483b9`](https://github.com/PrismJS/prism/commit/b9483b9)] -* __GLSL__: - * Regexp optimisation [[`e66d21b`](https://github.com/PrismJS/prism/commit/e66d21b)] -* __Go__: - * Regexp optimisation + don't use captures if not needed [[`88caabb`](https://github.com/PrismJS/prism/commit/88caabb)] -* __GraphQL__: - * Regexp optimisation and simplification [[`2474f06`](https://github.com/PrismJS/prism/commit/2474f06)] -* __Groovy__: - * Regexp optimisation + don't use captures if not needed [[`e74e00c`](https://github.com/PrismJS/prism/commit/e74e00c)] -* __Haml__: - * Regexp optimisation + don't use captures if not needed + fix typo in comment [[`23e3b43`](https://github.com/PrismJS/prism/commit/23e3b43)] -* __Handlebars__: - * Regexp optimisation + don't use captures if not needed [[`09dbfce`](https://github.com/PrismJS/prism/commit/09dbfce)] -* __Haskell__: - * Regexp simplification + don't use captures if not needed [[`f11390a`](https://github.com/PrismJS/prism/commit/f11390a)] -* __HTTP__: - * Regexp simplification + don't use captures if not needed [[`37ef24e`](https://github.com/PrismJS/prism/commit/37ef24e)] -* __Icon__: - * Regexp optimisation [[`9cf64a0`](https://github.com/PrismJS/prism/commit/9cf64a0)] -* __J__: - * Regexp simplification [[`de15150`](https://github.com/PrismJS/prism/commit/de15150)] -* __Java__: - * Don't use captures if not needed [[`96b35c8`](https://github.com/PrismJS/prism/commit/96b35c8)] -* __JavaScript__: - * Regexp optimisation + don't use captures if not needed [[`93d4002`](https://github.com/PrismJS/prism/commit/93d4002)] -* __Jolie__: - * Regexp optimisation + don't use captures if not needed + remove duplicates in keywords [[`a491f9e`](https://github.com/PrismJS/prism/commit/a491f9e)] -* __JSON__: - * Make strings greedy, remove negative look-ahead for ":". Fix [#1204](https://github.com/PrismJS/prism/issues/1204) [[`98acd2d`](https://github.com/PrismJS/prism/commit/98acd2d)] - * Regexp optimisation + don't use captures if not needed [[`8fc1b03`](https://github.com/PrismJS/prism/commit/8fc1b03)] -* __JSX__: - * Regexp optimisation + handle spread operator as a whole [[`28de4e2`](https://github.com/PrismJS/prism/commit/28de4e2)] -* __Julia__: - * Regexp optimisation and simplification [[`12684c0`](https://github.com/PrismJS/prism/commit/12684c0)] -* __Keyman__: - * Regexp optimisation + don't use captures if not needed [[`9726087`](https://github.com/PrismJS/prism/commit/9726087)] -* __Kotlin__: - * Regexp simplification [[`12ff8dc`](https://github.com/PrismJS/prism/commit/12ff8dc)] -* __LaTeX__: - * Regexp optimisation and simplification [[`aa426b0`](https://github.com/PrismJS/prism/commit/aa426b0)] -* __LiveScript__: - * Make interpolated strings greedy + fix variable and identifier regexps [[`c581049`](https://github.com/PrismJS/prism/commit/c581049)] -* __LOLCODE__: - * Don't use captures if not needed [[`52903af`](https://github.com/PrismJS/prism/commit/52903af)] -* __Makefile__: - * Regexp optimisation [[`20ae2e5`](https://github.com/PrismJS/prism/commit/20ae2e5)] -* __Markdown__: - * Don't use captures if not needed [[`f489a1e`](https://github.com/PrismJS/prism/commit/f489a1e)] -* __Markup__: - * Regexp optimisation + fix punctuation inside attr-value [[`ea380c6`](https://github.com/PrismJS/prism/commit/ea380c6)] -* __MATLAB__: - * Make strings greedy + handle line feeds better [[`4cd4f01`](https://github.com/PrismJS/prism/commit/4cd4f01)] -* __Monkey__: - * Don't use captures if not needed [[`7f47140`](https://github.com/PrismJS/prism/commit/7f47140)] -* __N4JS__: - * Don't use captures if not needed [[`2d3f9df`](https://github.com/PrismJS/prism/commit/2d3f9df)] -* __NASM__: - * Regexp optimisation and simplification + don't use captures if not needed [[`9937428`](https://github.com/PrismJS/prism/commit/9937428)] -* __nginx__: - * Remove trailing comma + remove duplicates in keywords [[`c6e7195`](https://github.com/PrismJS/prism/commit/c6e7195)] -* __NSIS__: - * Regexp optimisation + don't use captures if not needed [[`beeb107`](https://github.com/PrismJS/prism/commit/beeb107)] -* __Objective-C__: - * Don't use captures if not needed [[`9be0f88`](https://github.com/PrismJS/prism/commit/9be0f88)] -* __OCaml__: - * Regexp simplification [[`5f5f38c`](https://github.com/PrismJS/prism/commit/5f5f38c)] -* __OpenCL__: - * Don't use captures if not needed [[`5e70f1d`](https://github.com/PrismJS/prism/commit/5e70f1d)] -* __Oz__: - * Fix atom regexp [[`9320e92`](https://github.com/PrismJS/prism/commit/9320e92)] -* __PARI/GP__: - * Regexp optimisation [[`2c7b59b`](https://github.com/PrismJS/prism/commit/2c7b59b)] -* __Parser__: - * Regexp simplification [[`569d511`](https://github.com/PrismJS/prism/commit/569d511)] -* __Perl__: - * Regexp optimisation and simplification + don't use captures if not needed [[`0fe4cf6`](https://github.com/PrismJS/prism/commit/0fe4cf6)] -* __PHP__: - * Don't use captures if not needed Golmote [[`5235f18`](https://github.com/PrismJS/prism/commit/5235f18)] -* __PHP Extras__: - * Add word boundary after global keywords + don't use captures if not needed [[`9049a2a`](https://github.com/PrismJS/prism/commit/9049a2a)] -* __PowerShell__: - * Regexp optimisation + don't use captures if not needed [[`0d05957`](https://github.com/PrismJS/prism/commit/0d05957)] -* __Processing__: - * Regexp simplification [[`8110d38`](https://github.com/PrismJS/prism/commit/8110d38)] -* __.properties__: - * Regexp optimisation [[`678b621`](https://github.com/PrismJS/prism/commit/678b621)] -* __Protocol Buffers__: - * Don't use captures if not needed [[`3e256d8`](https://github.com/PrismJS/prism/commit/3e256d8)] -* __Pug__: - * Don't use captures if not needed [[`76dc925`](https://github.com/PrismJS/prism/commit/76dc925)] -* __Pure__: - * Make inline-lang greedy [[`92318b0`](https://github.com/PrismJS/prism/commit/92318b0)] -* __Python__: - * Add Python builtin function highlighting ([#1205](https://github.com/PrismJS/prism/issues/1205)) [[`2169c99`](https://github.com/PrismJS/prism/commit/2169c99)] - * Python: Add highlighting to functions with space between name and parentheses ([#1207](https://github.com/PrismJS/prism/issues/1207)) [[`3badd8a`](https://github.com/PrismJS/prism/commit/3badd8a)] - * Make triple-quoted strings greedy + regexp optimisation and simplification [[`f09f9f5`](https://github.com/PrismJS/prism/commit/f09f9f5)] -* __Qore__: - * Regexp simplification [[`69459f0`](https://github.com/PrismJS/prism/commit/69459f0)] -* __R__: - * Regexp optimisation [[`06a9da4`](https://github.com/PrismJS/prism/commit/06a9da4)] -* __Reason__: - * Regexp optimisation + don't use capture if not needed [[`19d79b4`](https://github.com/PrismJS/prism/commit/19d79b4)] -* __Ren'py__: - * Make strings greedy + don't use captures if not needed [[`91d84d9`](https://github.com/PrismJS/prism/commit/91d84d9)] -* __reST__: - * Regexp simplification + don't use captures if not needed [[`1a8b3e9`](https://github.com/PrismJS/prism/commit/1a8b3e9)] -* __Rip__: - * Regexp optimisation [[`d7f0ee8`](https://github.com/PrismJS/prism/commit/d7f0ee8)] -* __Ruby__: - * Regexp optimisation and simplification + don't use captures if not needed [[`4902ed4`](https://github.com/PrismJS/prism/commit/4902ed4)] -* __Rust__: - * Regexp optimisation and simplification + don't use captures if not needed [[`cc9d874`](https://github.com/PrismJS/prism/commit/cc9d874)] -* __Sass__: - * Regexp simplification Golmote [[`165d957`](https://github.com/PrismJS/prism/commit/165d957)] -* __Scala__: - * Regexp optimisation Golmote [[`5f50c12`](https://github.com/PrismJS/prism/commit/5f50c12)] -* __Scheme__: - * Regexp optimisation [[`bd19b04`](https://github.com/PrismJS/prism/commit/bd19b04)] -* __SCSS__: - * Regexp simplification [[`c60b7d4`](https://github.com/PrismJS/prism/commit/c60b7d4)] -* __Smalltalk__: - * Regexp simplification [[`41a2c76`](https://github.com/PrismJS/prism/commit/41a2c76)] -* __Smarty__: - * Regexp optimisation and simplification [[`e169be9`](https://github.com/PrismJS/prism/commit/e169be9)] -* __SQL__: - * Regexp optimisation [[`a6244a4`](https://github.com/PrismJS/prism/commit/a6244a4)] -* __Stylus__: - * Regexp optimisation [[`df9506c`](https://github.com/PrismJS/prism/commit/df9506c)] -* __Swift__: - * Don't use captures if not needed [[`a2d737a`](https://github.com/PrismJS/prism/commit/a2d737a)] -* __Tcl__: - * Regexp simplification + don't use captures if not needed [[`f0b8a33`](https://github.com/PrismJS/prism/commit/f0b8a33)] -* __Textile__: - * Regexp optimisation + don't use captures if not needed [[`08139ad`](https://github.com/PrismJS/prism/commit/08139ad)] -* __Twig__: - * Regexp optimisation and simplification + don't use captures if not needed [[`0b10fd0`](https://github.com/PrismJS/prism/commit/0b10fd0)] -* __TypeScript__: - * Don't use captures if not needed [[`e296caf`](https://github.com/PrismJS/prism/commit/e296caf)] -* __Verilog__: - * Regexp simplification [[`1b24b34`](https://github.com/PrismJS/prism/commit/1b24b34)] -* __VHDL__: - * Regexp optimisation and simplification [[`7af36df`](https://github.com/PrismJS/prism/commit/7af36df)] -* __vim__: - * Remove duplicates in keywords [[`700505e`](https://github.com/PrismJS/prism/commit/700505e)] -* __Wiki markup__: - * Fix escaping consistency [[`1fd690d`](https://github.com/PrismJS/prism/commit/1fd690d)] -* __YAML__: - * Regexp optimisation + don't use captures if not needed [[`1fd690d`](https://github.com/PrismJS/prism/commit/1fd690d)] - -### Other changes -* Remove comments spellcheck for AMP validation ([#1106](https://github.com/PrismJS/prism/issues/1106)) [[`de996d7`](https://github.com/PrismJS/prism/commit/de996d7)] -* Prevent error from throwing when element does not have a parentNode in highlightElement. [[`c33be19`](https://github.com/PrismJS/prism/commit/c33be19)] -* Provide a way to load Prism from inside a Worker without listening to messages. ([#1188](https://github.com/PrismJS/prism/issues/1188)) [[`d09982d`](https://github.com/PrismJS/prism/commit/d09982d)] - -## 1.8.3 (2017-10-19) - -### Other changes - -* Fix inclusion tests for Pug [[`955c2ab`](https://github.com/PrismJS/prism/commit/955c2ab)] - -## 1.8.2 (2017-10-19) - -### Updated components -* __Jade__: - * Jade has been renamed to __Pug__ ([#1201](https://github.com/PrismJS/prism/issues/1201)) [[`bcfef7c`](https://github.com/PrismJS/prism/commit/bcfef7c)] -* __JavaScript__: - * Better highlighting of functions ([#1190](https://github.com/PrismJS/prism/issues/1190)) [[`8ee2cd3`](https://github.com/PrismJS/prism/commit/8ee2cd3)] - -### Update plugins -* __Copy to clipboard__: - * Fix error occurring when using in Chrome 61+ ([#1206](https://github.com/PrismJS/prism/issues/1206)) [[`b41d571`](https://github.com/PrismJS/prism/commit/b41d571)] -* __Show invisibles__: - * Prevent error when using with Autoloader plugin ([#1195](https://github.com/PrismJS/prism/issues/1195)) [[`ed8bdb5`](https://github.com/PrismJS/prism/commit/ed8bdb5)] - -## 1.8.1 (2017-09-16) - -### Other changes - -* Add Arduino to components.js [[`290a3c6`](https://github.com/PrismJS/prism/commit/290a3c6)] - -## 1.8.0 (2017-09-16) - -### New components - -* __Arduino__ ([#1184](https://github.com/PrismJS/prism/issues/1184)) [[`edf2454`](https://github.com/PrismJS/prism/commit/edf2454)] -* __OpenCL__ ([#1175](https://github.com/PrismJS/prism/issues/1175)) [[`131e8fa`](https://github.com/PrismJS/prism/commit/131e8fa)] - -### Updated plugins - -* __Autolinker__: - * Silently catch any error thrown by decodeURIComponent. Fixes [#1186](https://github.com/PrismJS/prism/issues/1186) [[`2e43fcf`](https://github.com/PrismJS/prism/commit/2e43fcf)] - -## 1.7.0 (2017-09-09) - -### New components - -* __Django/Jinja2__ ([#1085](https://github.com/PrismJS/prism/issues/1085)) [[`345b1b2`](https://github.com/PrismJS/prism/commit/345b1b2)] -* __N4JS__ ([#1141](https://github.com/PrismJS/prism/issues/1141)) [[`eaa8ebb`](https://github.com/PrismJS/prism/commit/eaa8ebb)] -* __Ren'py__ ([#658](https://github.com/PrismJS/prism/issues/658)) [[`7ab4013`](https://github.com/PrismJS/prism/commit/7ab4013)] -* __VB.Net__ ([#1122](https://github.com/PrismJS/prism/issues/1122)) [[`5400651`](https://github.com/PrismJS/prism/commit/5400651)] - -### Updated components - -* __APL__: - * Add left shoe underbar and right shoe underbar ([#1072](https://github.com/PrismJS/prism/issues/1072)) [[`12238c5`](https://github.com/PrismJS/prism/commit/12238c5)] - * Update prism-apl.js ([#1126](https://github.com/PrismJS/prism/issues/1126)) [[`a5f3cdb`](https://github.com/PrismJS/prism/commit/a5f3cdb)] -* __C__: - * Add more keywords and constants for C. ([#1029](https://github.com/PrismJS/prism/issues/1029)) [[`43a388e`](https://github.com/PrismJS/prism/commit/43a388e)] -* __C#__: - * Fix wrong highlighting when three slashes appear inside string. Fix [#1091](https://github.com/PrismJS/prism/issues/1091) [[`dfb6f17`](https://github.com/PrismJS/prism/commit/dfb6f17)] -* __C-like__: - * Add support for unclosed block comments. Close [#828](https://github.com/PrismJS/prism/issues/828) [[`3426ed1`](https://github.com/PrismJS/prism/commit/3426ed1)] -* __Crystal__: - * Update Crystal keywords ([#1092](https://github.com/PrismJS/prism/issues/1092)) [[`125bff1`](https://github.com/PrismJS/prism/commit/125bff1)] -* __CSS Extras__: - * Support CSS #RRGGBBAA ([#1139](https://github.com/PrismJS/prism/issues/1139)) [[`07a6806`](https://github.com/PrismJS/prism/commit/07a6806)] -* __Docker__: - * Add dockerfile alias for docker language ([#1164](https://github.com/PrismJS/prism/issues/1164)) [[`601c47f`](https://github.com/PrismJS/prism/commit/601c47f)] - * Update the list of keywords for dockerfiles ([#1180](https://github.com/PrismJS/prism/issues/1180)) [[`f0d73e0`](https://github.com/PrismJS/prism/commit/f0d73e0)] -* __Eiffel__: - * Add class-name highlighting for Eiffel ([#471](https://github.com/PrismJS/prism/issues/471)) [[`cd03587`](https://github.com/PrismJS/prism/commit/cd03587)] -* __Handlebars__: - * Check for possible pre-existing marker strings in Handlebars [[`7a1a404`](https://github.com/PrismJS/prism/commit/7a1a404)] -* __JavaScript__: - * Properly match every operator as a whole token. Fix [#1133](https://github.com/PrismJS/prism/issues/1133) [[`9f649fb`](https://github.com/PrismJS/prism/commit/9f649fb)] - * Allows uppercase prefixes in JS number literals ([#1151](https://github.com/PrismJS/prism/issues/1151)) [[`d4ee904`](https://github.com/PrismJS/prism/commit/d4ee904)] - * Reduced backtracking in regex pattern. Fix [#1159](https://github.com/PrismJS/prism/issues/1159) [[`ac09e97`](https://github.com/PrismJS/prism/commit/ac09e97)] -* __JSON__: - * Fix property and string patterns performance. Fix [#1080](https://github.com/PrismJS/prism/issues/1080) [[`0ca1353`](https://github.com/PrismJS/prism/commit/0ca1353)] -* __JSX__: - * JSX spread operator break. Fixes [#1061](https://github.com/PrismJS/prism/issues/1061) ([#1094](https://github.com/PrismJS/prism/issues/1094)) [[`561bceb`](https://github.com/PrismJS/prism/commit/561bceb)] - * Fix highlighting of attributes containing spaces [[`867ea42`](https://github.com/PrismJS/prism/commit/867ea42)] - * Improved performance for tags (when not matching) Fix [#1152](https://github.com/PrismJS/prism/issues/1152) [[`b0fe103`](https://github.com/PrismJS/prism/commit/b0fe103)] -* __LOLCODE__: - * Make strings greedy Golmote [[`1a5e7a4`](https://github.com/PrismJS/prism/commit/1a5e7a4)] -* __Markup__: - * Support HTML entities in attribute values ([#1143](https://github.com/PrismJS/prism/issues/1143)) [[`1d5047d`](https://github.com/PrismJS/prism/commit/1d5047d)] -* __NSIS__: - * Update patterns ([#1033](https://github.com/PrismJS/prism/issues/1033)) [[`01a59d8`](https://github.com/PrismJS/prism/commit/01a59d8)] - * Add support for NSIS 3.02 ([#1169](https://github.com/PrismJS/prism/issues/1169)) [[`393b5f7`](https://github.com/PrismJS/prism/commit/393b5f7)] -* __PHP__: - * Fix the PHP language ([#1100](https://github.com/PrismJS/prism/issues/1100)) [[`1453fa7`](https://github.com/PrismJS/prism/commit/1453fa7)] - * Check for possible pre-existing marker strings in PHP [[`36bc560`](https://github.com/PrismJS/prism/commit/36bc560)] -* __Ruby__: - * Fix slash regex performance. Fix [#1083](https://github.com/PrismJS/prism/issues/1083) [[`a708730`](https://github.com/PrismJS/prism/commit/a708730)] - * Add support for =begin =end comments. Manual merge of [#1121](https://github.com/PrismJS/prism/issues/1121). [[`62cdaf8`](https://github.com/PrismJS/prism/commit/62cdaf8)] -* __Smarty__: - * Check for possible pre-existing marker strings in Smarty [[`5df26e2`](https://github.com/PrismJS/prism/commit/5df26e2)] -* __TypeScript__: - * Update typescript keywords ([#1064](https://github.com/PrismJS/prism/issues/1064)) [[`52020a0`](https://github.com/PrismJS/prism/commit/52020a0)] - * Chmod -x prism-typescript component ([#1145](https://github.com/PrismJS/prism/issues/1145)) [[`afe0542`](https://github.com/PrismJS/prism/commit/afe0542)] -* __YAML__: - * Make strings greedy (partial fix for [#1075](https://github.com/PrismJS/prism/issues/1075)) [[`565a2cc`](https://github.com/PrismJS/prism/commit/565a2cc)] - -### Updated plugins - -* __Autolinker__: - * Fixed an rendering issue for encoded urls ([#1173](https://github.com/PrismJS/prism/issues/1173)) [[`abc007f`](https://github.com/PrismJS/prism/commit/abc007f)] -* __Custom Class__: - * Add missing noCSS property for the Custom Class plugin [[`ba64f8d`](https://github.com/PrismJS/prism/commit/ba64f8d)] - * Added a default for classMap. Fixes [#1137](https://github.com/PrismJS/prism/issues/1137). ([#1157](https://github.com/PrismJS/prism/issues/1157)) [[`5400af9`](https://github.com/PrismJS/prism/commit/5400af9)] -* __Keep Markup__: - * Store highlightedCode after reinserting markup. Fix [#1127](https://github.com/PrismJS/prism/issues/1127) [[`6df2ceb`](https://github.com/PrismJS/prism/commit/6df2ceb)] -* __Line Highlight__: - * Cleanup left-over line-highlight tags before other plugins run [[`79b723d`](https://github.com/PrismJS/prism/commit/79b723d)] - * Avoid conflict between line-highlight and other plugins [[`224fdb8`](https://github.com/PrismJS/prism/commit/224fdb8)] -* __Line Numbers__: - * Support soft wrap for line numbers plugin ([#584](https://github.com/PrismJS/prism/issues/584)) [[`849f1d6`](https://github.com/PrismJS/prism/commit/849f1d6)] - * Plugins fixes (unescaped-markup, line-numbers) ([#1012](https://github.com/PrismJS/prism/issues/1012)) [[`3fb7cf8`](https://github.com/PrismJS/prism/commit/3fb7cf8)] -* __Normalize Whitespace__: - * Add Node.js support for the normalize-whitespace plugin [[`6c7dae2`](https://github.com/PrismJS/prism/commit/6c7dae2)] -* __Unescaped Markup__: - * Plugins fixes (unescaped-markup, line-numbers) ([#1012](https://github.com/PrismJS/prism/issues/1012)) [[`3fb7cf8`](https://github.com/PrismJS/prism/commit/3fb7cf8)] - -### Updated themes -* __Coy__: - * Scroll 'Coy' background with contents ([#1163](https://github.com/PrismJS/prism/issues/1163)) [[`310990b`](https://github.com/PrismJS/prism/commit/310990b)] - -### Other changes - -* Initial implementation of manual highlighting ([#1087](https://github.com/PrismJS/prism/issues/1087)) [[`bafc4cb`](https://github.com/PrismJS/prism/commit/bafc4cb)] -* Remove dead link in Third-party tutorials section. Fixes [#1028](https://github.com/PrismJS/prism/issues/1028) [[`dffadc6`](https://github.com/PrismJS/prism/commit/dffadc6)] -* Most languages now use the greedy flag for better highlighting [[`7549ecc`](https://github.com/PrismJS/prism/commit/7549ecc)] -* .npmignore: Unignore components.js ([#1108](https://github.com/PrismJS/prism/issues/1108)) [[`1f699e7`](https://github.com/PrismJS/prism/commit/1f699e7)] -* Run before-highlight and after-highlight hooks even when no grammar is found. Fix [#1134](https://github.com/PrismJS/prism/issues/1134) [[`70cb472`](https://github.com/PrismJS/prism/commit/70cb472)] -* Replace [\w\W] with [\s\S] and [0-9] with \d in regexes ([#1107](https://github.com/PrismJS/prism/issues/1107)) [[`8aa2cc4`](https://github.com/PrismJS/prism/commit/8aa2cc4)] -* Fix corner cases for the greedy flag ([#1095](https://github.com/PrismJS/prism/issues/1095)) [[`6530709`](https://github.com/PrismJS/prism/commit/6530709)] -* Add Third Party Tutorial ([#1156](https://github.com/PrismJS/prism/issues/1156)) [[`c34e57b`](https://github.com/PrismJS/prism/commit/c34e57b)] -* Add Composer support ([#648](https://github.com/PrismJS/prism/issues/648)) [[`2989633`](https://github.com/PrismJS/prism/commit/2989633)] -* Remove IE8 plugin ([#992](https://github.com/PrismJS/prism/issues/992)) [[`25788eb`](https://github.com/PrismJS/prism/commit/25788eb)] -* Website: remove width and height on logo.svg, so it becomes scalable. Close [#1005](https://github.com/PrismJS/prism/issues/1005) [[`0621ff7`](https://github.com/PrismJS/prism/commit/0621ff7)] -* Remove yarn.lock ([#1098](https://github.com/PrismJS/prism/issues/1098)) [[`11eed25`](https://github.com/PrismJS/prism/commit/11eed25)] - -## 1.6.0 (2016-12-03) - -### New components - -* __.properties__ ([#980](https://github.com/PrismJS/prism/issues/980)) [[`be6219a`](https://github.com/PrismJS/prism/commit/be6219a)] -* __Ada__ ([#949](https://github.com/PrismJS/prism/issues/949)) [[`65619f7`](https://github.com/PrismJS/prism/commit/65619f7)] -* __GraphQL__ ([#971](https://github.com/PrismJS/prism/issues/971)) [[`e018087`](https://github.com/PrismJS/prism/commit/e018087)] -* __Jolie__ ([#1014](https://github.com/PrismJS/prism/issues/1014)) [[`dfc1941`](https://github.com/PrismJS/prism/commit/dfc1941)] -* __LiveScript__ ([#982](https://github.com/PrismJS/prism/issues/982)) [[`62e258c`](https://github.com/PrismJS/prism/commit/62e258c)] -* __Reason__ (Fixes [#1046](https://github.com/PrismJS/prism/issues/1046)) [[`3cae6ce`](https://github.com/PrismJS/prism/commit/3cae6ce)] -* __Xojo__ ([#994](https://github.com/PrismJS/prism/issues/994)) [[`0224b7c`](https://github.com/PrismJS/prism/commit/0224b7c)] - -### Updated components - -* __APL__: - * Add iota underbar ([#1024](https://github.com/PrismJS/prism/issues/1024)) [[`3c5c89a`](https://github.com/PrismJS/prism/commit/3c5c89a), [`ac21d33`](https://github.com/PrismJS/prism/commit/ac21d33)] -* __AsciiDoc__: - * Optimized block regexps to prevent struggling on large files. Fixes [#1001](https://github.com/PrismJS/prism/issues/1001). [[`1a86d34`](https://github.com/PrismJS/prism/commit/1a86d34)] -* __Bash__: - * Add `npm` to function list ([#969](https://github.com/PrismJS/prism/issues/969)) [[`912bdfe`](https://github.com/PrismJS/prism/commit/912bdfe)] -* __CSS__: - * Make CSS strings greedy. Fix [#1013](https://github.com/PrismJS/prism/issues/1013). [[`e57e26d`](https://github.com/PrismJS/prism/commit/e57e26d)] -* __CSS Extras__: - * Match attribute inside selectors [[`13fed76`](https://github.com/PrismJS/prism/commit/13fed76)] -* _Groovy__: - * Fix order of decoding entities in groovy. Fixes [#1049](https://github.com/PrismJS/prism/issues/1049) ([#1050](https://github.com/PrismJS/prism/issues/1050)) [[`d75da8e`](https://github.com/PrismJS/prism/commit/d75da8e)] -* __Ini__: - * Remove important token in ini definition ([#1047](https://github.com/PrismJS/prism/issues/1047)) [[`fe8ad8b`](https://github.com/PrismJS/prism/commit/fe8ad8b)] -* __JavaScript__: - * Add exponentiation & spread/rest operator ([#991](https://github.com/PrismJS/prism/issues/991)) [[`b2de65a`](https://github.com/PrismJS/prism/commit/b2de65a), [`268d01e`](https://github.com/PrismJS/prism/commit/268d01e)] -* __JSON_: - * JSON: Fixed issues with properties and strings + added tests. Fix [#1025](https://github.com/PrismJS/prism/issues/1025) [[`25a541d`](https://github.com/PrismJS/prism/commit/25a541d)] -* __Markup__: - * Allow for dots in Markup tag names, but not in HTML tags included in Textile. Fixes [#888](https://github.com/PrismJS/prism/issues/888). [[`31ea66b`](https://github.com/PrismJS/prism/commit/31ea66b)] - * Make doctype case-insensitive ([#1009](https://github.com/PrismJS/prism/issues/1009)) [[`3dd7219`](https://github.com/PrismJS/prism/commit/3dd7219)] -* __NSIS__: - * Updated patterns ([#1032](https://github.com/PrismJS/prism/issues/1032)) [[`76ba1b8`](https://github.com/PrismJS/prism/commit/76ba1b8)] -* __PHP__: - * Make comments greedy. Fix [#197](https://github.com/PrismJS/prism/issues/197) [[`318aab3`](https://github.com/PrismJS/prism/commit/318aab3)] -* __PowerShell__: - * Fix highlighting of empty comments ([#977](https://github.com/PrismJS/prism/issues/977)) [[`4fda477`](https://github.com/PrismJS/prism/commit/4fda477)] -* __Puppet__: - * Fix over-greedy regexp detection ([#978](https://github.com/PrismJS/prism/issues/978)) [[`105be25`](https://github.com/PrismJS/prism/commit/105be25)] -* __Ruby__: - * Fix typo `Fload` to `Float` in prism-ruby.js ([#1023](https://github.com/PrismJS/prism/issues/1023)) [[`22cb018`](https://github.com/PrismJS/prism/commit/22cb018)] - * Make strings greedy. Fixes [#1048](https://github.com/PrismJS/prism/issues/1048) [[`8b0520a`](https://github.com/PrismJS/prism/commit/8b0520a)] -* __SCSS__: - * Alias statement as keyword. Fix [#246](https://github.com/PrismJS/prism/issues/246) [[`fd09391`](https://github.com/PrismJS/prism/commit/fd09391)] - * Highlight variables inside selectors and properties. [[`d6b5c2f`](https://github.com/PrismJS/prism/commit/d6b5c2f)] - * Highlight parent selector [[`8f5f1fa`](https://github.com/PrismJS/prism/commit/8f5f1fa)] -* __TypeScript__: - * Add missing `from` keyword to typescript & set `ts` as alias. ([#1042](https://github.com/PrismJS/prism/issues/1042)) [[`cba78f3`](https://github.com/PrismJS/prism/commit/cba78f3)] - -### New plugins - -* __Copy to Clipboard__ ([#891](https://github.com/PrismJS/prism/issues/891)) [[`07b81ac`](https://github.com/PrismJS/prism/commit/07b81ac)] -* __Custom Class__ ([#950](https://github.com/PrismJS/prism/issues/950)) [[`a0bd686`](https://github.com/PrismJS/prism/commit/a0bd686)] -* __Data-URI Highlight__ ([#996](https://github.com/PrismJS/prism/issues/996)) [[`bdca61b`](https://github.com/PrismJS/prism/commit/bdca61b)] -* __Toolbar__ ([#891](https://github.com/PrismJS/prism/issues/891)) [[`07b81ac`](https://github.com/PrismJS/prism/commit/07b81ac)] - -### Updated plugins - -* __Autoloader__: - * Updated documentation for Autoloader plugin [[`b4f3423`](https://github.com/PrismJS/prism/commit/b4f3423)] - * Download all grammars as a zip from Autoloader plugin page ([#981](https://github.com/PrismJS/prism/issues/981)) [[`0d0a007`](https://github.com/PrismJS/prism/commit/0d0a007), [`5c815d3`](https://github.com/PrismJS/prism/commit/5c815d3)] - * Removed duplicated script on Autoloader plugin page [[`9671996`](https://github.com/PrismJS/prism/commit/9671996)] - * Don't try to load "none" component. Fix [#1000](https://github.com/PrismJS/prism/issues/1000) [[`f89b0b9`](https://github.com/PrismJS/prism/commit/f89b0b9)] -* __WPD__: - * Fix at-rule detection + don't process if language is not handled [[`2626728`](https://github.com/PrismJS/prism/commit/2626728)] - -### Other changes - -* Improvement to greedy-flag ([#967](https://github.com/PrismJS/prism/issues/967)) [[`500121b`](https://github.com/PrismJS/prism/commit/500121b), [`9893489`](https://github.com/PrismJS/prism/commit/9893489)] -* Add setTimeout fallback for requestAnimationFrame. Fixes [#987](https://github.com/PrismJS/prism/issues/987). ([#988](https://github.com/PrismJS/prism/issues/988)) [[`c9bdcd3`](https://github.com/PrismJS/prism/commit/c9bdcd3)] -* Added aria-hidden attributes on elements created by the Line Highlight and Line Numbers plugins. Fixes [#574](https://github.com/PrismJS/prism/issues/574). [[`e5587a7`](https://github.com/PrismJS/prism/commit/e5587a7)] -* Don't insert space before ">" when there is no attributes [[`3dc8c9e`](https://github.com/PrismJS/prism/commit/3dc8c9e)] -* Added missing hooks-related tests for AsciiDoc, Groovy, Handlebars, Markup, PHP and Smarty [[`c1a0c1b`](https://github.com/PrismJS/prism/commit/c1a0c1b)] -* Fix issue when using Line numbers plugin and Normalise whitespace plugin together with Handlebars, PHP or Smarty. Fix [#1018](https://github.com/PrismJS/prism/issues/1018), [#997](https://github.com/PrismJS/prism/issues/997), [#935](https://github.com/PrismJS/prism/issues/935). Revert [#998](https://github.com/PrismJS/prism/issues/998). [[`86aa3d2`](https://github.com/PrismJS/prism/commit/86aa3d2)] -* Optimized logo ([#990](https://github.com/PrismJS/prism/issues/990)) ([#1002](https://github.com/PrismJS/prism/issues/1002)) [[`f69e570`](https://github.com/PrismJS/prism/commit/f69e570), [`218fd25`](https://github.com/PrismJS/prism/commit/218fd25)] -* Remove unneeded prefixed CSS ([#989](https://github.com/PrismJS/prism/issues/989)) [[`5e56833`](https://github.com/PrismJS/prism/commit/5e56833)] -* Optimize images ([#1007](https://github.com/PrismJS/prism/issues/1007)) [[`b2fa6d5`](https://github.com/PrismJS/prism/commit/b2fa6d5)] -* Add yarn.lock to .gitignore ([#1035](https://github.com/PrismJS/prism/issues/1035)) [[`03ecf74`](https://github.com/PrismJS/prism/commit/03ecf74)] -* Fix greedy flag bug. Fixes [#1039](https://github.com/PrismJS/prism/issues/1039) [[`32cd99f`](https://github.com/PrismJS/prism/commit/32cd99f)] -* Ruby: Fix test after [#1023](https://github.com/PrismJS/prism/issues/1023) [[`b15d43b`](https://github.com/PrismJS/prism/commit/b15d43b)] -* Ini: Fix test after [#1047](https://github.com/PrismJS/prism/issues/1047) [[`25cdd3f`](https://github.com/PrismJS/prism/commit/25cdd3f)] -* Reduce risk of XSS ([#1051](https://github.com/PrismJS/prism/issues/1051)) [[`17e33bc`](https://github.com/PrismJS/prism/commit/17e33bc)] -* env.code can be modified by before-sanity-check hook even when using language-none. Fix [#1066](https://github.com/PrismJS/prism/issues/1066) [[`83bafbd`](https://github.com/PrismJS/prism/commit/83bafbd)] - - -## 1.5.1 (2016-06-05) - -### Updated components - -* __Normalize Whitespace__: - * Add class that disables the normalize whitespace plugin [[`9385c54`](https://github.com/PrismJS/prism/commit/9385c54)] -* __JavaScript Language__: - * Rearrange the `string` and `template-string` token in JavaScript [[`1158e46`](https://github.com/PrismJS/prism/commit/1158e46)] -* __SQL Language__: - * add delimeter and delimeters keywords to sql ([#958](https://github.com/PrismJS/prism/pull/958)) [[`a9ef24e`](https://github.com/PrismJS/prism/commit/a9ef24e)] - * add AUTO_INCREMENT and DATE keywords to sql ([#954](https://github.com/PrismJS/prism/pull/954)) [[`caea2af`](https://github.com/PrismJS/prism/commit/caea2af)] -* __Diff Language__: - * Highlight diff lines with only + or - ([#952](https://github.com/PrismJS/prism/pull/952)) [[`4d0526f`](https://github.com/PrismJS/prism/commit/4d0526f)] - -### Other changes - -* Allow for asynchronous loading of prism.js ([#959](https://github.com/PrismJS/prism/pull/959)) -* Use toLowerCase on language names ([#957](https://github.com/PrismJS/prism/pull/957)) [[`acd9508`](https://github.com/PrismJS/prism/commit/acd9508)] -* link to index for basic usage - fixes [#945](https://github.com/PrismJS/prism/issues/945) ([#946](https://github.com/PrismJS/prism/pull/946)) [[`6c772d8`](https://github.com/PrismJS/prism/commit/6c772d8)] -* Fixed monospace typo ([#953](https://github.com/PrismJS/prism/pull/953)) [[`e6c3498`](https://github.com/PrismJS/prism/commit/e6c3498)] - -## 1.5.0 (2016-05-01) - -### New components - -* __Bro Language__ ([#925](https://github.com/PrismJS/prism/pull/925)) -* __Protocol Buffers Language__ ([#938](https://github.com/PrismJS/prism/pull/938)) [[`ae4a4f2`](https://github.com/PrismJS/prism/commit/ae4a4f2)] - -### Updated components - -* __Keep Markup__: - * Fix Keep Markup plugin incorrect highlighting ([#880](https://github.com/PrismJS/prism/pull/880)) [[`24841ef`](https://github.com/PrismJS/prism/commit/24841ef)] -* __Groovy Language__: - * Fix double HTML-encoding bug in Groovy language [[`24a0936`](https://github.com/PrismJS/prism/commit/24a0936)] -* __Java Language__: - * Adding annotation token for Java ([#905](https://github.com/PrismJS/prism/pull/905)) [[`367ace6`](https://github.com/PrismJS/prism/commit/367ace6)] -* __SAS Language__: - * Add missing keywords for SAS ([#922](https://github.com/PrismJS/prism/pull/922)) -* __YAML Language__: - * fix hilighting of YAML keys on first line of code block ([#943](https://github.com/PrismJS/prism/pull/943)) [[`f19db81`](https://github.com/PrismJS/prism/commit/f19db81)] -* __C# Language__: - * Support for generic methods in csharp [[`6f75735`](https://github.com/PrismJS/prism/commit/6f75735)] - -### New plugins - -* __Unescaped Markup__ [[`07d77e5`](https://github.com/PrismJS/prism/commit/07d77e5)] -* __Normalize Whitespace__ ([#847](https://github.com/PrismJS/prism/pull/847)) [[`e86ec01`](https://github.com/PrismJS/prism/commit/e86ec01)] - -### Other changes - -* Add JSPM support [[`ad048ab`](https://github.com/PrismJS/prism/commit/ad048ab)] -* update linear-gradient syntax from `left` to `to right` [[`cd234dc`](https://github.com/PrismJS/prism/commit/cd234dc)] -* Add after-property to allow ordering of plugins [[`224b7a1`](https://github.com/PrismJS/prism/commit/224b7a1)] -* Partial solution for the "Comment-like substrings"-problem [[`2705c50`](https://github.com/PrismJS/prism/commit/2705c50)] -* Add property 'aliasTitles' to components.js [[`54400fb`](https://github.com/PrismJS/prism/commit/54400fb)] -* Add before-highlightall hook [[`70a8602`](https://github.com/PrismJS/prism/commit/70a8602)] -* Fix catastrophic backtracking regex issues in JavaScript [[`ab65be2`](https://github.com/PrismJS/prism/commit/ab65be2)] - -## 1.4.1 (2016-02-03) - -### Other changes - -* Fix DFS bug in Prism core [[`b86c727`](https://github.com/PrismJS/prism/commit/b86c727)] - -## 1.4.0 (2016-02-03) - -### New components - -* __Solarized Light__ ([#855](https://github.com/PrismJS/prism/pull/855)) [[`70846ba`](https://github.com/PrismJS/prism/commit/70846ba)] -* __JSON__ ([#370](https://github.com/PrismJS/prism/pull/370)) [[`ad2fcd0`](https://github.com/PrismJS/prism/commit/ad2fcd0)] - -### Updated components - -* __Show Language__: - * Remove data-language attribute ([#840](https://github.com/PrismJS/prism/pull/840)) [[`eb9a83c`](https://github.com/PrismJS/prism/commit/eb9a83c)] - * Allow custom label without a language mapping ([#837](https://github.com/PrismJS/prism/pull/837)) [[`7e74aef`](https://github.com/PrismJS/prism/commit/7e74aef)] -* __JSX__: - * Better Nesting in JSX attributes ([#842](https://github.com/PrismJS/prism/pull/842)) [[`971dda7`](https://github.com/PrismJS/prism/commit/971dda7)] -* __File Highlight__: - * Defer File Highlight until the full DOM has loaded. ([#844](https://github.com/PrismJS/prism/pull/844)) [[`6f995ef`](https://github.com/PrismJS/prism/commit/6f995ef)] -* __Coy Theme__: - * Fix coy theme shadows ([#865](https://github.com/PrismJS/prism/pull/865)) [[`58d2337`](https://github.com/PrismJS/prism/commit/58d2337)] -* __Show Invisibles__: - * Ensure show-invisibles compat with autoloader ([#874](https://github.com/PrismJS/prism/pull/874)) [[`c3cfb1f`](https://github.com/PrismJS/prism/commit/c3cfb1f)] - * Add support for the space character for the show-invisibles plugin ([#876](https://github.com/PrismJS/prism/pull/876)) [[`05442d3`](https://github.com/PrismJS/prism/commit/05442d3)] - -### New plugins - -* __Command Line__ ([#831](https://github.com/PrismJS/prism/pull/831)) [[`8378906`](https://github.com/PrismJS/prism/commit/8378906)] - -### Other changes - -* Use document.currentScript instead of document.getElementsByTagName() [[`fa98743`](https://github.com/PrismJS/prism/commit/fa98743)] -* Add prefix for Firefox selection and move prefixed rule first [[`6d54717`](https://github.com/PrismJS/prism/commit/6d54717)] -* No background for `<code>` in `<pre>` [[`8c310bc`](https://github.com/PrismJS/prism/commit/8c310bc)] -* Fixing to initial copyright year [[`69cbf7a`](https://github.com/PrismJS/prism/commit/69cbf7a)] -* Simplify the “lang” regex [[`417f54a`](https://github.com/PrismJS/prism/commit/417f54a)] -* Fix broken heading links [[`a7f9e62`](https://github.com/PrismJS/prism/commit/a7f9e62)] -* Prevent infinite recursion in DFS [[`02894e1`](https://github.com/PrismJS/prism/commit/02894e1)] -* Fix incorrect page title [[`544b56f`](https://github.com/PrismJS/prism/commit/544b56f)] -* Link scss to webplatform wiki [[`08d979a`](https://github.com/PrismJS/prism/commit/08d979a)] -* Revert white-space to normal when code is inline instead of in a pre [[`1a971b5`](https://github.com/PrismJS/prism/commit/1a971b5)] - -## 1.3.0 (2015-10-26) - -### New components - -* __AsciiDoc__ ([#800](https://github.com/PrismJS/prism/issues/800)) [[`6803ca0`](https://github.com/PrismJS/prism/commit/6803ca0)] -* __Haxe__ ([#811](https://github.com/PrismJS/prism/issues/811)) [[`bd44341`](https://github.com/PrismJS/prism/commit/bd44341)] -* __Icon__ ([#803](https://github.com/PrismJS/prism/issues/803)) [[`b43c5f3`](https://github.com/PrismJS/prism/commit/b43c5f3)] -* __Kotlin ([#814](https://github.com/PrismJS/prism/issues/814)) [[`e8a31a5`](https://github.com/PrismJS/prism/commit/e8a31a5)] -* __Lua__ ([#804](https://github.com/PrismJS/prism/issues/804)) [[`a36bc4a`](https://github.com/PrismJS/prism/commit/a36bc4a)] -* __Nix__ ([#795](https://github.com/PrismJS/prism/issues/795)) [[`9b275c8`](https://github.com/PrismJS/prism/commit/9b275c8)] -* __Oz__ ([#805](https://github.com/PrismJS/prism/issues/805)) [[`388c53f`](https://github.com/PrismJS/prism/commit/388c53f)] -* __PARI/GP__ ([#802](https://github.com/PrismJS/prism/issues/802)) [[`253c035`](https://github.com/PrismJS/prism/commit/253c035)] -* __Parser__ ([#808](https://github.com/PrismJS/prism/issues/808)) [[`a953b3a`](https://github.com/PrismJS/prism/commit/a953b3a)] -* __Puppet__ ([#813](https://github.com/PrismJS/prism/issues/813)) [[`81933ee`](https://github.com/PrismJS/prism/commit/81933ee)] -* __Roboconf__ ([#812](https://github.com/PrismJS/prism/issues/812)) [[`f5db346`](https://github.com/PrismJS/prism/commit/f5db346)] - -### Updated components - -* __C__: - * Highlight directives in preprocessor lines ([#801](https://github.com/PrismJS/prism/issues/801)) [[`ad316a3`](https://github.com/PrismJS/prism/commit/ad316a3)] -* __C#__: - * Highlight directives in preprocessor lines ([#801](https://github.com/PrismJS/prism/issues/801)) [[`ad316a3`](https://github.com/PrismJS/prism/commit/ad316a3)] - * Fix detection of float numbers ([#806](https://github.com/PrismJS/prism/issues/806)) [[`1dae72b`](https://github.com/PrismJS/prism/commit/1dae72b)] -* __F#__: - * Highlight directives in preprocessor lines ([#801](https://github.com/PrismJS/prism/issues/801)) [[`ad316a3`](https://github.com/PrismJS/prism/commit/ad316a3)] -* __JavaScript__: - * Highlight true and false as booleans ([#801](https://github.com/PrismJS/prism/issues/801)) [[`ad316a3`](https://github.com/PrismJS/prism/commit/ad316a3)] -* __Python__: - * Highlight triple-quoted strings before comments. Fix [#815](https://github.com/PrismJS/prism/issues/815) [[`90fbf0b`](https://github.com/PrismJS/prism/commit/90fbf0b)] - -### New plugins - -* __Previewer: Time__ ([#790](https://github.com/PrismJS/prism/issues/790)) [[`88173de`](https://github.com/PrismJS/prism/commit/88173de)] -* __Previewer: Angle__ ([#791](https://github.com/PrismJS/prism/issues/791)) [[`a434c86`](https://github.com/PrismJS/prism/commit/a434c86)] - -### Other changes - -* Increase mocha's timeout [[`f1c41db`](https://github.com/PrismJS/prism/commit/f1c41db)] -* Prevent most errors in IE8. Fix [#9](https://github.com/PrismJS/prism/issues/9) [[`9652d75`](https://github.com/PrismJS/prism/commit/9652d75)] -* Add U.S. Web Design Standards on homepage. Fix [#785](https://github.com/PrismJS/prism/issues/785) [[`e10d48b`](https://github.com/PrismJS/prism/commit/e10d48b), [`79ebbf8`](https://github.com/PrismJS/prism/commit/79ebbf8), [`2f7088d`](https://github.com/PrismJS/prism/commit/2f7088d)] -* Added gulp task to autolink PRs and commits in changelog [[`5ec4e4d`](https://github.com/PrismJS/prism/commit/5ec4e4d)] -* Use child processes to run each set of tests, in order to deal with the memory leak in vm.runInNewContext() [[`9a4b6fa`](https://github.com/PrismJS/prism/commit/9a4b6fa)] - -## 1.2.0 (2015-10-07) - -### New components - -* __Batch__ ([#781](https://github.com/PrismJS/prism/issues/781)) [[`eab5b06`](https://github.com/PrismJS/prism/commit/eab5b06)] - -### Updated components - -* __ASP.NET__: - * Simplified pattern for `<script>` [[`29643f4`](https://github.com/PrismJS/prism/issues/29643f4)] -* __Bash__: - * Fix regression in strings ([#792](https://github.com/PrismJS/prism/issues/792)) [[`bd275c2`](https://github.com/PrismJS/prism/commit/bd275c2)] - * Substantially reduce wrongly highlighted stuff ([#793](https://github.com/PrismJS/prism/issues/793)) [[`ac6fe2e`](https://github.com/PrismJS/prism/commit/ac6fe2e)] -* __CSS__: - * Simplified pattern for `<style>` [[`29643f4`](https://github.com/PrismJS/prism/issues/29643f4)] -* __JavaScript__: - * Simplified pattern for `<script>` [[`29643f4`](https://github.com/PrismJS/prism/issues/29643f4)] - -### New plugins - -* __Previewer: Gradient__ ([#783](https://github.com/PrismJS/prism/issues/783)) [[`9a63483`](https://github.com/PrismJS/prism/commit/9a63483)] - -### Updated plugins - -* __Previewer: Color__ - * Add support for Sass variables [[`3a1fb04`](https://github.com/PrismJS/prism/commit/3a1fb04)] - -* __Previewer: Easing__ - * Add support for Sass variables [[`7c7ab4e`](https://github.com/PrismJS/prism/commit/7c7ab4e)] - -### Other changes - -* Test runner: Allow to run tests for only some languages [[`5ade8a5`](https://github.com/PrismJS/prism/issues/5ade8a5)] -* Download page: Fixed wrong components order raising error in generated file ([#797](https://github.com/PrismJS/prism/issues/787)) [[`7a6aed8`](https://github.com/PrismJS/prism/commit/7a6aed8)] - -## 1.1.0 (2015-10-04) - -### New components - -* __ABAP__ ([#636](https://github.com/PrismJS/prism/issues/636)) [[`75b0328`](https://github.com/PrismJS/prism/commit/75b0328), [`0749129`](https://github.com/PrismJS/prism/commit/0749129)] -* __APL__ ([#308](https://github.com/PrismJS/prism/issues/308)) [[`1f45942`](https://github.com/PrismJS/prism/commit/1f45942), [`33a295f`](https://github.com/PrismJS/prism/commit/33a295f)] -* __AutoIt__ ([#771](https://github.com/PrismJS/prism/issues/771)) [[`211a41c`](https://github.com/PrismJS/prism/commit/211a41c)] -* __BASIC__ ([#620](https://github.com/PrismJS/prism/issues/620)) [[`805a0ce`](https://github.com/PrismJS/prism/commit/805a0ce)] -* __Bison__ ([#764](https://github.com/PrismJS/prism/issues/764)) [[`7feb135`](https://github.com/PrismJS/prism/commit/7feb135)] -* __Crystal__ ([#780](https://github.com/PrismJS/prism/issues/780)) [[`5b473de`](https://github.com/PrismJS/prism/commit/5b473de), [`414848d`](https://github.com/PrismJS/prism/commit/414848d)] -* __D__ ([#613](https://github.com/PrismJS/prism/issues/613)) [[`b5e741c`](https://github.com/PrismJS/prism/commit/b5e741c)] -* __Diff__ ([#450](https://github.com/PrismJS/prism/issues/450)) [[`ef41c74`](https://github.com/PrismJS/prism/commit/ef41c74)] -* __Docker__ ([#576](https://github.com/PrismJS/prism/issues/576)) [[`e808352`](https://github.com/PrismJS/prism/commit/e808352)] -* __Elixir__ ([#614](https://github.com/PrismJS/prism/issues/614)) [[`a1c028c`](https://github.com/PrismJS/prism/commit/a1c028c), [`c451611`](https://github.com/PrismJS/prism/commit/c451611), [`2e637f0`](https://github.com/PrismJS/prism/commit/2e637f0), [`ccb6566`](https://github.com/PrismJS/prism/commit/ccb6566)] -* __GLSL__ ([#615](https://github.com/PrismJS/prism/issues/615)) [[`247da05`](https://github.com/PrismJS/prism/commit/247da05)] -* __Inform 7__ ([#616](https://github.com/PrismJS/prism/issues/616)) [[`d2595b4`](https://github.com/PrismJS/prism/commit/d2595b4)] -* __J__ ([#623](https://github.com/PrismJS/prism/issues/623)) [[`0cc50b2`](https://github.com/PrismJS/prism/commit/0cc50b2)] -* __MEL__ ([#618](https://github.com/PrismJS/prism/issues/618)) [[`8496c14`](https://github.com/PrismJS/prism/commit/8496c14)] -* __Mizar__ ([#619](https://github.com/PrismJS/prism/issues/619)) [[`efde61d`](https://github.com/PrismJS/prism/commit/efde61d)] -* __Monkey__ ([#621](https://github.com/PrismJS/prism/issues/621)) [[`fdd4a3c`](https://github.com/PrismJS/prism/commit/fdd4a3c)] -* __nginx__ ([#776](https://github.com/PrismJS/prism/issues/776)) [[`dc4fc19`](https://github.com/PrismJS/prism/commit/dc4fc19), [`e62c88e`](https://github.com/PrismJS/prism/commit/e62c88e)] -* __Nim__ ([#622](https://github.com/PrismJS/prism/issues/622)) [[`af9c49a`](https://github.com/PrismJS/prism/commit/af9c49a)] -* __OCaml__ ([#628](https://github.com/PrismJS/prism/issues/628)) [[`556c04d`](https://github.com/PrismJS/prism/commit/556c04d)] -* __Processing__ ([#629](https://github.com/PrismJS/prism/issues/629)) [[`e47087b`](https://github.com/PrismJS/prism/commit/e47087b)] -* __Prolog__ ([#630](https://github.com/PrismJS/prism/issues/630)) [[`dd04c32`](https://github.com/PrismJS/prism/commit/dd04c32)] -* __Pure__ ([#626](https://github.com/PrismJS/prism/issues/626)) [[`9c276ab`](https://github.com/PrismJS/prism/commit/9c276ab)] -* __Q__ ([#624](https://github.com/PrismJS/prism/issues/624)) [[`c053c9e`](https://github.com/PrismJS/prism/commit/c053c9e)] -* __Qore__ [[`125e91f`](https://github.com/PrismJS/prism/commit/125e91f)] -* __Tcl__ [[`a3e751a`](https://github.com/PrismJS/prism/commit/a3e751a), [`11ff829`](https://github.com/PrismJS/prism/commit/11ff829)] -* __Textile__ ([#544](https://github.com/PrismJS/prism/issues/544)) [[`d0c6764`](https://github.com/PrismJS/prism/commit/d0c6764)] -* __Verilog__ ([#640](https://github.com/PrismJS/prism/issues/640)) [[`44a11c2`](https://github.com/PrismJS/prism/commit/44a11c2), [`795eb99`](https://github.com/PrismJS/prism/commit/795eb99)] -* __Vim__ [[`69ea994`](https://github.com/PrismJS/prism/commit/69ea994)] - -### Updated components - -* __Bash__: - * Add support for Here-Documents ([#787](https://github.com/PrismJS/prism/issues/787)) [[`b57a096`](https://github.com/PrismJS/prism/commit/b57a096)] - * Remove C-like dependency ([#789](https://github.com/PrismJS/prism/issues/789)) [[`1ab4619`](https://github.com/PrismJS/prism/commit/1ab4619)] -* __C__: - * Fixed numbers [[`4d64d07`](https://github.com/PrismJS/prism/commit/4d64d07), [`071c3dd`](https://github.com/PrismJS/prism/commit/071c3dd)] -* __C-like__: - * Add word boundary before class-name prefixes [[`aa757f6`](https://github.com/PrismJS/prism/commit/aa757f6)] - * Improved operator regex + add != and !== [[`135ee9d`](https://github.com/PrismJS/prism/commit/135ee9d)] - * Optimized string regexp [[`792e35c`](https://github.com/PrismJS/prism/commit/792e35c)] -* __F#__: - * Fixed keywords containing exclamation mark [[`09f2005`](https://github.com/PrismJS/prism/commit/09f2005)] - * Improved string pattern [[`0101c89`](https://github.com/PrismJS/prism/commit/0101c89)] - * Insert preprocessor before keyword + don't allow line feeds before # [[`fdc9477`](https://github.com/PrismJS/prism/commit/fdc9477)] - * Fixed numbers [[`0aa0791`](https://github.com/PrismJS/prism/commit/0aa0791)] -* __Gherkin__: - * Don't allow spaces in tags [[`48ff8b7`](https://github.com/PrismJS/prism/commit/48ff8b7)] - * Handle \r\n and \r + allow feature alone + don't match blank td/th [[`ce1ec3b`](https://github.com/PrismJS/prism/commit/ce1ec3b)] -* __Git__: - * Added more examples ([#652](https://github.com/PrismJS/prism/issues/652)) [[`95dc102`](https://github.com/PrismJS/prism/commit/95dc102)] - * Add support for unified diff. Fixes [#769](https://github.com/PrismJS/prism/issues/769), fixes [#357](https://github.com/PrismJS/prism/issues/357), closes [#401](https://github.com/PrismJS/prism/issues/401) [[`3aadd5d`](https://github.com/PrismJS/prism/commit/3aadd5d)] -* __Go__: - * Improved operator regexp + removed punctuation from it [[`776ab90`](https://github.com/PrismJS/prism/commit/776ab90)] -* __Haml__: - * Combine both multiline-comment regexps + handle \r\n and \r [[`f77b40b`](https://github.com/PrismJS/prism/commit/f77b40b)] - * Handle \r\n and \r in filter regex [[`bbe68ac`](https://github.com/PrismJS/prism/commit/bbe68ac)] -* __Handlebars__: - * Fix empty strings, add plus sign in exponential notation, improve block pattern and variable pattern [[`c477f9a`](https://github.com/PrismJS/prism/commit/c477f9a)] - * Properly escape special replacement patterns ($) in Handlebars, PHP and Smarty. Fix [#772](https://github.com/PrismJS/prism/issues/772) [[`895bf46`](https://github.com/PrismJS/prism/commit/895bf46)] -* __Haskell__: - * Removed useless backslashes and parentheses + handle \r\n and \r + simplify number regexp + fix operator regexp [[`1cc8d8e`](https://github.com/PrismJS/prism/commit/1cc8d8e)] -* __HTTP__: - * Fix indentation + Add multiline flag for more flexibility + Fix response status + Handle \r\n and \r [[`aaa90f1`](https://github.com/PrismJS/prism/commit/aaa90f1)] -* __Ini__: - * Fix some regexps + remove unused flags [[`53d5839`](https://github.com/PrismJS/prism/commit/53d5839)] -* __Jade__: - * Add todo list + remove single-line comment pattern + simplified most patterns with m flag + handle \r\n and \r [[`a79e838`](https://github.com/PrismJS/prism/commit/a79e838)] -* __Java__: - * Fix number regexp + simplified number regexp and optimized operator regexp [[`21e20b9`](https://github.com/PrismJS/prism/commit/21e20b9)] -* __JavaScript__: - * JavaScript: Allow for all non-ASCII characters in function names. Fix [#400](https://github.com/PrismJS/prism/issues/400) [[`29e26dc`](https://github.com/PrismJS/prism/commit/29e26dc)] -* __JSX__: - * Allow for one level of nesting in scripts (Fix [#717](https://github.com/PrismJS/prism/issues/717)) [[`90c75d5`](https://github.com/PrismJS/prism/commit/90c75d5)] -* __Julia__: - * Simplify comment regexp + improved number regexp + improved operator regexp [[`bcac7d4`](https://github.com/PrismJS/prism/commit/bcac7d4)] -* __Keyman__: - * Move header statements above keywords [[`23a444c`](https://github.com/PrismJS/prism/commit/23a444c)] -* __LaTeX__: - * Simplify comment regexp [[`132b41a`](https://github.com/PrismJS/prism/commit/132b41a)] - * Extend support [[`942a6ec`](https://github.com/PrismJS/prism/commit/942a6ec)] -* __Less__: - * Remove useless part in property regexp [[`80d8260`](https://github.com/PrismJS/prism/commit/80d8260)] -* __LOLCODE__: - * Removed useless parentheses [[`8147c9b`](https://github.com/PrismJS/prism/commit/8147c9b)] -* __Makefile__: - * Add known failures in example [[`e0f8984`](https://github.com/PrismJS/prism/commit/e0f8984)] - * Handle \r\n in comments and strings + fix "-include" keyword -* __Markup__: - * Simplify patterns + handle \r\n and \r [[`4c551e8`](https://github.com/PrismJS/prism/commit/4c551e8)] - * Don't allow = to appear in tag name [[`85d8a55`](https://github.com/PrismJS/prism/commit/85d8a55)] - * Don't allow dot inside tag name [[`283691e`](https://github.com/PrismJS/prism/commit/283691e)] -* __MATLAB__: - * Simplify string pattern to remove lookbehind [[`a3cbecc`](https://github.com/PrismJS/prism/commit/a3cbecc)] -* __NASM__: - * Converted indents to tabs, removed uneeded escapes, added lookbehinds [[`a92e4bd`](https://github.com/PrismJS/prism/commit/a92e4bd)] -* __NSIS__: - * Simplified patterns [[`bbd83d4`](https://github.com/PrismJS/prism/commit/bbd83d4)] - * Fix operator regexp [[`44ad8dc`](https://github.com/PrismJS/prism/commit/44ad8dc)] -* __Objective-C__: - * Simplified regexps + fix strings + handle \r [[`1d33147`](https://github.com/PrismJS/prism/commit/1d33147)] - * Fix operator regexp [[`e9d382e`](https://github.com/PrismJS/prism/commit/e9d382e)] -* __Pascal__: - * Simplified regexps [[`c03c8a4`](https://github.com/PrismJS/prism/commit/c03c8a4)] -* __Perl__: - * Simplified regexps + Made most string and regexp patterns multi-line + Added support for regexp's n flag + Added missing operators [[`71b00cc`](https://github.com/PrismJS/prism/commit/71b00cc)] -* __PHP__: - * Simplified patterns [[`f9d9452`](https://github.com/PrismJS/prism/commit/f9d9452)] - * Properly escape special replacement patterns ($) in Handlebars, PHP and Smarty. Fix [#772](https://github.com/PrismJS/prism/issues/772) [[`895bf46`](https://github.com/PrismJS/prism/commit/895bf46)] -* __PHP Extras__: - * Fix $this regexp + improve global regexp [[`781fdad`](https://github.com/PrismJS/prism/commit/781fdad)] -* __PowerShell__: - * Update definitions for command/alias/operators [[`14da55c`](https://github.com/PrismJS/prism/commit/14da55c)] -* __Python__: - * Added async/await and @ operator ([#656](https://github.com/PrismJS/prism/issues/656)) [[`7f1ae75`](https://github.com/PrismJS/prism/commit/7f1ae75)] - * Added 'self' keyword and support for class names ([#677](https://github.com/PrismJS/prism/issues/677)) [[`d9d4ab2`](https://github.com/PrismJS/prism/commit/d9d4ab2)] - * Simplified regexps + don't capture where unneeded + fixed operators [[`530f5f0`](https://github.com/PrismJS/prism/commit/530f5f0)] -* __R__: - * Fixed and simplified patterns [[`c20c3ec`](https://github.com/PrismJS/prism/commit/c20c3ec)] -* __reST__: - * Simplified some patterns, fixed others, prevented blank comments to match, moved list-bullet down to prevent breaking quotes [[`e6c6b85`](https://github.com/PrismJS/prism/commit/e6c6b85)] -* __Rip__: - * Fixed some regexp + moved down numbers [[`1093f7d`](https://github.com/PrismJS/prism/commit/1093f7d)] -* __Ruby__: - * Code cleaning, handle \r\n and \r, fix some regexps [[`dd4989f`](https://github.com/PrismJS/prism/commit/dd4989f)] - * Add % notations for strings and regexps. Fix [#590](https://github.com/PrismJS/prism/issues/590) [[`2d37800`](https://github.com/PrismJS/prism/commit/2d37800)] -* __Rust__: - * Simplified patterns and fixed operators [[`6c8494f`](https://github.com/PrismJS/prism/commit/6c8494f)] -* __SAS__: - * Simplified datalines and optimized operator patterns [[`6ebb96f`](https://github.com/PrismJS/prism/commit/6ebb96f)] -* __Sass__: - * Add missing require in components [[`35b8c50`](https://github.com/PrismJS/prism/commit/35b8c50)] - * Fix comments, operators and selectors and simplified patterns [[`28759d0`](https://github.com/PrismJS/prism/commit/28759d0)] - * Highlight "-" as operator only if surrounded by spaces, in order to not break hyphenated values (e.g. "ease-in-out") [[`b2763e7`](https://github.com/PrismJS/prism/commit/b2763e7)] -* __Scala__: - * Simplified patterns [[`daf2597`](https://github.com/PrismJS/prism/commit/daf2597)] -* __Scheme__: - * Add missing lookbehind on number pattern. Fix [#702](https://github.com/PrismJS/prism/issues/702) [[`3120ff7`](https://github.com/PrismJS/prism/commit/3120ff7)] - * Fixes and simplifications [[`068704a`](https://github.com/PrismJS/prism/commit/068704a)] - * Don't match content of symbols starting with a parenthesis [[`fa7df08`](https://github.com/PrismJS/prism/commit/fa7df08)] -* __Scss__: - * Simplified patterns + fixed operators + don't match empty selectors [[`672c167`](https://github.com/PrismJS/prism/commit/672c167)] -* __Smalltalk__: - * Simplified patterns [[`d896622`](https://github.com/PrismJS/prism/commit/d896622)] -* __Smarty__: - * Optimized regexps + fixed punctuation and operators [[`1446700`](https://github.com/PrismJS/prism/commit/1446700)] - * Properly escape special replacement patterns ($) in Handlebars, PHP and Smarty. Fix [#772](https://github.com/PrismJS/prism/issues/772) [[`895bf46`](https://github.com/PrismJS/prism/commit/895bf46)] -* __SQL__: - * Simplified regexp + fixed keywords and operators + add CHARSET keyword [[`d49fec0`](https://github.com/PrismJS/prism/commit/d49fec0)] -* __Stylus__: - * Rewrote the component entirely [[`7729728`](https://github.com/PrismJS/prism/commit/7729728)] -* __Swift__: - * Optimized keywords lists and removed duplicates [[`936e429`](https://github.com/PrismJS/prism/commit/936e429)] - * Add support for string interpolation. Fix [#448](https://github.com/PrismJS/prism/issues/448) [[`89cd5d0`](https://github.com/PrismJS/prism/commit/89cd5d0)] -* __Twig__: - * Prevent "other" pattern from matching blank strings [[`cae2cef`](https://github.com/PrismJS/prism/commit/cae2cef)] - * Optimized regexps + fixed operators + added missing operators/keywords [[`2d8271f`](https://github.com/PrismJS/prism/commit/2d8271f)] -* __VHDL__: - * Move operator overloading before strings, don't capture if not needed, handle \r\n and \r, fix numbers [[`4533f17`](https://github.com/PrismJS/prism/commit/4533f17)] -* __Wiki markup__: - * Fixed emphasis + merged some url patterns + added TODOs [[`8cf9e6a`](https://github.com/PrismJS/prism/commit/8cf9e6a)] -* __YAML__: - * Handled \r\n and \r, simplified some patterns, fixed "---" [[`9e33e0a`](https://github.com/PrismJS/prism/commit/9e33e0a)] - -### New plugins - -* __Autoloader__ ([#766](https://github.com/PrismJS/prism/issues/766)) [[`ed4ccfe`](https://github.com/PrismJS/prism/commit/ed4ccfe)] -* __JSONP Highlight__ [[`b2f14d9`](https://github.com/PrismJS/prism/commit/b2f14d9)] -* __Keep Markup__ ([#770](https://github.com/PrismJS/prism/issues/770)) [[`bd3e9ea`](https://github.com/PrismJS/prism/commit/bd3e9ea)] -* __Previewer: Base__ ([#767](https://github.com/PrismJS/prism/issues/767)) [[`cf764c0`](https://github.com/PrismJS/prism/commit/cf764c0)] -* __Previewer: Color__ ([#767](https://github.com/PrismJS/prism/issues/767)) [[`cf764c0`](https://github.com/PrismJS/prism/commit/cf764c0)] -* __Previewer: Easing__ ([#773](https://github.com/PrismJS/prism/issues/773)) [[`513137c`](https://github.com/PrismJS/prism/commit/513137c), [`9207258`](https://github.com/PrismJS/prism/commit/9207258), [`4303c94`](https://github.com/PrismJS/prism/commit/4303c94)] -* __Remove initial line feed__ [[`ed9f2b2`](https://github.com/PrismJS/prism/commit/ed9f2b2), [`b8d098e`](https://github.com/PrismJS/prism/commit/b8d098e)] - -### Updated plugins - -* __Autolinker__: - * Don't process all grammars on load, process each one in before-highlight. Should fix [#760](https://github.com/PrismJS/prism/issues/760) [[`a572495`](https://github.com/PrismJS/prism/commit/a572495)] -* __Line Highlight__: - * Run in `complete` hook [[`f237e67`](https://github.com/PrismJS/prism/commit/f237e67)] - * Fixed position when font-size is odd ([#668](https://github.com/PrismJS/prism/issues/668)) [[`86bbd4c`](https://github.com/PrismJS/prism/commit/86bbd4c), [`8ed7ce3`](https://github.com/PrismJS/prism/commit/8ed7ce3)] -* __Line Numbers__: - * Run in `complete` hook [[`3f4d918`](https://github.com/PrismJS/prism/commit/3f4d918)] - * Don't run if already exists [[`c89bbdb`](https://github.com/PrismJS/prism/commit/c89bbdb)] - * Don't run if block is empty. Fix [#669](https://github.com/PrismJS/prism/issues/669) [[`ee463e8`](https://github.com/PrismJS/prism/commit/ee463e8)] - * Correct calculation for number of lines (fix [#385](https://github.com/PrismJS/prism/issues/385)) [[`14f3f80`](https://github.com/PrismJS/prism/commit/14f3f80)] - * Fix computation of line numbers for single-line code blocks. Fix [#721](https://github.com/PrismJS/prism/issues/721) [[`02b220e`](https://github.com/PrismJS/prism/commit/02b220e)] - * Fixing word wrap on long code lines [[`56b3d29`](https://github.com/PrismJS/prism/commit/56b3d29)] - * Fixing coy theme + line numbers plugin overflowing on long blocks of text ([#762](https://github.com/PrismJS/prism/issues/762)) [[`a0127eb`](https://github.com/PrismJS/prism/commit/a0127eb)] -* __Show Language__: - * Add gulp task to build languages map in Show language plugin (Fix [#671](https://github.com/PrismJS/prism/issues/671)) [[`39bd827`](https://github.com/PrismJS/prism/commit/39bd827)] - * Add reset styles to prevent bug in Coy theme ([#703](https://github.com/PrismJS/prism/issues/703)) [[`08dd500`](https://github.com/PrismJS/prism/commit/08dd500)] - -### Other changes - -* Fixed link to David Peach article ([#647](https://github.com/PrismJS/prism/issues/647)) [[`3f679f8`](https://github.com/PrismJS/prism/commit/3f679f8)] -* Added `complete` hook, which runs even when no grammar is found [[`e58b6c0`](https://github.com/PrismJS/prism/commit/e58b6c0), [`fd54995`](https://github.com/PrismJS/prism/commit/fd54995)] -* Added test suite runner ([#588](https://github.com/PrismJS/prism/issues/588)) [[`956cd85`](https://github.com/PrismJS/prism/commit/956cd85)] -* Added tests for every components -* Added `.gitattributes` to prevent line ending changes in test files [[`45ca8c8`](https://github.com/PrismJS/prism/commit/45ca8c8)] -* Split plugins into 3 columns on Download page [[`a88936a`](https://github.com/PrismJS/prism/commit/a88936a)] -* Removed comment in components.js to make it easier to parse as JSON ([#679](https://github.com/PrismJS/prism/issues/679)) [[`2cb1326`](https://github.com/PrismJS/prism/commit/2cb1326)] -* Updated README.md [[`1388256`](https://github.com/PrismJS/prism/commit/1388256)] -* Updated documentation since the example was not relevant any more [[`80aedb2`](https://github.com/PrismJS/prism/commit/80aedb2)] -* Fixed inline style for Coy theme [[`52829b3`](https://github.com/PrismJS/prism/commit/52829b3)] -* Prevent errors in nodeJS ([#754](https://github.com/PrismJS/prism/issues/754)) [[`9f5c93c`](https://github.com/PrismJS/prism/commit/9f5c93c), [`0356c58`](https://github.com/PrismJS/prism/commit/0356c58)] -* Explicitly make the Worker close itself after highlighting, so that users have control on this behaviour when directly using Prism inside a Worker. Fix [#492](https://github.com/PrismJS/prism/issues/492) [[`e42a228`](https://github.com/PrismJS/prism/commit/e42a228)] -* Added some language aliases: js for javascript, xml, html, mathml and svg for markup [[`2f9fe1e`](https://github.com/PrismJS/prism/commit/2f9fe1e)] -* Download page: Add a "Select all" checkbox ([#561](https://github.com/PrismJS/prism/issues/561)) [[`9a9020b`](https://github.com/PrismJS/prism/commit/9a9020b)] -* Download page: Don't add semicolon unless needed in generated code. Fix [#273](https://github.com/PrismJS/prism/issues/273) [[`5a5eec5`](https://github.com/PrismJS/prism/commit/5a5eec5)] -* Add language counter on homepage [[`889cda5`](https://github.com/PrismJS/prism/commit/889cda5)] -* Improve performance by doing more work in the worker [[`1316abc`](https://github.com/PrismJS/prism/commit/1316abc)] -* Replace Typeplate with SitePoint on homepage. Fix [#774](https://github.com/PrismJS/prism/issues/774) [[`0c54308`](https://github.com/PrismJS/prism/commit/0c54308)] -* Added basic `.editorconfig` [[`c48f55d`](https://github.com/PrismJS/prism/commit/c48f55d)] - ---- - -## 1.0.1 (2015-07-26) - -### New components - -* __Brainfuck__ ([#611](https://github.com/PrismJS/prism/issues/611)) [[`3ede718`](https://github.com/PrismJS/prism/commit/3ede718)] -* __Keyman__ ([#609](https://github.com/PrismJS/prism/issues/609)) [[`2698f82`](https://github.com/PrismJS/prism/commit/2698f82), [`e9936c6`](https://github.com/PrismJS/prism/commit/e9936c6)] -* __Makefile__ ([#610](https://github.com/PrismJS/prism/issues/610)) [[`3baa61c`](https://github.com/PrismJS/prism/commit/3baa61c)] -* __Sass (Sass)__ (fix [#199](https://github.com/PrismJS/prism/issues/199)) [[`b081804`](https://github.com/PrismJS/prism/commit/b081804)] -* __VHDL__ ([#595](https://github.com/PrismJS/prism/issues/595)) [[`43e6157`](https://github.com/PrismJS/prism/commit/43e6157)] - -### Updated components - -* __ActionScript__: - * Fix ! operator and add ++ and -- as whole operators [[`6bf0794`](https://github.com/PrismJS/prism/commit/6bf0794)] - * Fix XML highlighting [[`90257b0`](https://github.com/PrismJS/prism/commit/90257b0)] - * Update examples to add inline XML [[`2c1626a`](https://github.com/PrismJS/prism/commit/2c1626a), [`3987711`](https://github.com/PrismJS/prism/commit/3987711)] -* __Apache Configuration__: - * Don't include the spaces in directive-inline [[`e87efd8`](https://github.com/PrismJS/prism/commit/e87efd8)] -* __AppleScript__: - * Allow one level of nesting in block comments [[`65894c5`](https://github.com/PrismJS/prism/commit/65894c5)] - * Removed duplicates between operators and keywords [[`1ec5a81`](https://github.com/PrismJS/prism/commit/1ec5a81)] - * Removed duplicates between keywords and classes [[`e8d09f6`](https://github.com/PrismJS/prism/commit/e8d09f6)] - * Move numbers up so they are not broken by operator pattern [[`66dac31`](https://github.com/PrismJS/prism/commit/66dac31)] -* __ASP.NET__: - * Prevent Markup tags from breaking ASP tags + fix MasterType directive [[`1f0a336`](https://github.com/PrismJS/prism/commit/1f0a336)] -* __AutoHotkey__: - * Allow tags (labels) to be highlighted at the end of the code [[`0a1fc4b`](https://github.com/PrismJS/prism/commit/0a1fc4b)] - * Match all operators + add comma to punctuation [[`f0ccb1b`](https://github.com/PrismJS/prism/commit/f0ccb1b)] - * Removed duplicates in keywords lists [[`fe0a068`](https://github.com/PrismJS/prism/commit/fe0a068)] -* __Bash__: - * Simplify comment regex [[`2700981`](https://github.com/PrismJS/prism/commit/2700981)] - * Removed duplicates in keywords + removed unneeded parentheses [[`903b8a4`](https://github.com/PrismJS/prism/commit/903b8a4)] -* __C__: - * Removed string pattern (inherited from C-like) [[`dcce1a7`](https://github.com/PrismJS/prism/commit/dcce1a7)] - * Better support for macro statements [[`4868635`](https://github.com/PrismJS/prism/commit/4868635)] -* __C#__: - * Fix preprocessor pattern [[`86311f5`](https://github.com/PrismJS/prism/commit/86311f5)] -* __C++__: - * Removed delete[] and new[] broken keywords [[`42fbeef`](https://github.com/PrismJS/prism/commit/42fbeef)] -* __C-like__: - * Removed unused 'ignore' pattern [[`b6535dd`](https://github.com/PrismJS/prism/commit/b6535dd)] - * Use look-ahead instead of inside to match functions [[`d4194c9`](https://github.com/PrismJS/prism/commit/d4194c9)] -* __CoffeeScript__: - * Prevent strings from ending with a backslash [[`cb6b824`](https://github.com/PrismJS/prism/commit/cb6b824)] -* __CSS__: - * Highlight parentheses as punctuation [[`cd0273e`](https://github.com/PrismJS/prism/commit/cd0273e)] - * Improved highlighting of at-rules [[`e254088`](https://github.com/PrismJS/prism/commit/e254088)] - * Improved URL and strings [[`901812c`](https://github.com/PrismJS/prism/commit/901812c)] - * Selector regexp should not include last spaces before brace [[`f2e2718`](https://github.com/PrismJS/prism/commit/f2e2718)] - * Handle \r\n [[`15760e1`](https://github.com/PrismJS/prism/commit/15760e1)] -* __Eiffel__: - * Fix string patterns order + fix /= operator [[`7d1b8d7`](https://github.com/PrismJS/prism/commit/7d1b8d7)] -* __Erlang__: - * Fixed quoted functions, quoted atoms, variables and <= operator [[`fa286aa`](https://github.com/PrismJS/prism/commit/fa286aa)] -* __Fortran__: - * Improved pattern for comments inside strings [[`40ae215`](https://github.com/PrismJS/prism/commit/40ae215)] - * Fixed order in keyword pattern [[`8a6d32d`](https://github.com/PrismJS/prism/commit/8a6d32d)] -* __Handlebars__: - * Support blocks with dashes ([#587](https://github.com/PrismJS/prism/issues/587)) [[`f409b13`](https://github.com/PrismJS/prism/commit/f409b13)] -* __JavaScript__: - * Added support for 'y' and 'u' ES6 JavaScript regex flags ([#596](https://github.com/PrismJS/prism/issues/596)) [[`5d99957`](https://github.com/PrismJS/prism/commit/5d99957)] - * Added support for missing ES6 keywords in JavaScript ([#596](https://github.com/PrismJS/prism/issues/596)) [[`ca68b87`](https://github.com/PrismJS/prism/commit/ca68b87)] - * Added `async` and `await` keywords ([#575](https://github.com/PrismJS/prism/issues/575)) [[`5458cec`](https://github.com/PrismJS/prism/commit/5458cec)] - * Added support for Template strings + interpolation [[`04f72b1`](https://github.com/PrismJS/prism/commit/04f72b1)] - * Added support for octal and binary numbers ([#597](https://github.com/PrismJS/prism/issues/597)) [[`a8aa058`](https://github.com/PrismJS/prism/commit/a8aa058)] - * Improve regex performance of C-like strings and JS regexps [[`476cbf4`](https://github.com/PrismJS/prism/commit/476cbf4)] -* __Markup__: - * Allow non-ASCII chars in tag names and attributes (fix [#585](https://github.com/PrismJS/prism/issues/585)) [[`52fd55e`](https://github.com/PrismJS/prism/commit/52fd55e)] - * Optimized tag's regexp so that it stops crashing on large unclosed tags [[`75452ba`](https://github.com/PrismJS/prism/commit/75452ba)] - * Highlight single quotes in attr-value as punctuation [[`1ebcb8e`](https://github.com/PrismJS/prism/commit/1ebcb8e)] - * Doctype and prolog can be multi-line [[`c19a238`](https://github.com/PrismJS/prism/commit/c19a238)] -* __Python__: - * Added highlighting for function declaration ([#601](https://github.com/PrismJS/prism/issues/601)) [[`a88aae8`](https://github.com/PrismJS/prism/commit/a88aae8)] - * Fixed wrong highlighting of variables named a, b, c... f ([#601](https://github.com/PrismJS/prism/issues/601)) [[`a88aae8`](https://github.com/PrismJS/prism/commit/a88aae8)] -* __Ruby__: - * Added support for string interpolation [[`c36b123`](https://github.com/PrismJS/prism/commit/c36b123)] -* __Scss__: - * Fixed media queries highlighting [[`bf8e032`](https://github.com/PrismJS/prism/commit/bf8e032)] - * Improved highlighting inside at-rules [[`eef4248`](https://github.com/PrismJS/prism/commit/eef4248)] - * Match placeholders inside selectors (fix [#238](https://github.com/PrismJS/prism/issues/238)) [[`4e42e26`](https://github.com/PrismJS/prism/commit/4e42e26)] -* __Swift__: - * Update keywords list (fix [#625](https://github.com/PrismJS/prism/issues/625)) [[`88f44a7`](https://github.com/PrismJS/prism/commit/88f44a7)] - -### Updated plugins - -* __File Highlight__: - * Allow to specify the highlighting language. Fix [#607](https://github.com/PrismJS/prism/issues/607) [[`8030db9`](https://github.com/PrismJS/prism/commit/8030db9)] -* __Line Highlight__: - * Fixed incorrect height in IE9 ([#604](https://github.com/PrismJS/prism/issues/604)) [[`f1705eb`](https://github.com/PrismJS/prism/commit/f1705eb)] - * Prevent errors in IE8 [[`5f133c8`](https://github.com/PrismJS/prism/commit/5f133c8)] - -### Other changes - -* Removed moot `version` property from `bower.json` ([#594](https://github.com/PrismJS/prism/issues/594)) [[`4693499`](https://github.com/PrismJS/prism/commit/4693499)] -* Added repository to `bower.json` ([#600](https://github.com/PrismJS/prism/issues/600)) [[`8e5ebcc`](https://github.com/PrismJS/prism/commit/8e5ebcc)] -* Added `.DS_Store` to `.gitignore` [[`1707e4e`](https://github.com/PrismJS/prism/commit/1707e4e)] -* Improve test drive page usability. Fix [#591](https://github.com/PrismJS/prism/issues/591) [[`fe60858`](https://github.com/PrismJS/prism/commit/fe60858)] -* Fixed prism-core and prism-file-highlight to prevent errors in IE8 [[`5f133c8`](https://github.com/PrismJS/prism/commit/5f133c8)] -* Add Ubuntu Mono font to font stack [[`ed9d7e3`](https://github.com/PrismJS/prism/commit/ed9d7e3)] - ---- - -## 1.0.0 (2015-05-23) - -* First release -* Supported languages: - * ActionScript - * Apache Configuration - * AppleScript - * ASP.NET (C#) - * AutoHotkey - * Bash - * C - * C# - * C++ - * C-like - * CoffeeScript - * CSS - * CSS Extras - * Dart - * Eiffel - * Erlang - * F# - * Fortran - * Gherkin - * Git - * Go - * Groovy - * Haml - * Handlebars - * Haskell - * HTTP - * Ini - * Jade - * Java - * JavaScript - * Julia - * LaTeX - * Less - * LOLCODE - * Markdown - * Markup - * MATLAB - * NASM - * NSIS - * Objective-C - * Pascal - * Perl - * PHP - * PHP Extras - * PowerShell - * Python - * R - * React JSX - * reST - * Rip - * Ruby - * Rust - * SAS - * Sass (Scss) - * Scala - * Scheme - * Smalltalk - * Smarty - * SQL - * Stylus - * Swift - * Twig - * TypeScript - * Wiki markup - * YAML -* Plugins: - * Autolinker - * File Highlight - * Highlight Keywords - * Line Highlight - * Line Numbers - * Show Invisibles - * Show Language - * WebPlatform Docs + * 添加缺失的关键字。修复 [#1374](https://github.com/PrismJS/prism/issues/1374) [[`238b195`](https://github.com/PrismJS/prism/commit/238b195)] + +### 更新插件 +* __命令行__: + * 命令行:允许使用 data-filter-output 属性指定输出前缀。 ([#856](https://github.com/PrismJS/prism/issues/856)) [[`094d546`](https://github.com/PrismJS/prism/commit/094d546)] +* __文件高亮__: + * 在工具栏插件与文件高亮结合时,增加提供下载按钮的选项。修复 [#1030](https://github.com/PrismJS/prism/issues/1030) [[`9f22952`](https://github.com/PrismJS/prism/commit/9f22952)] + +### 更新主题 +* __默认__: + * 达到 AA 对比度比例标准 ([#1296](https://github.com/PrismJS/prism/issues/1296)) [[`8aea939`](https://github.com/PrismJS/prism/commit/8aea939)] + +### 其他变化 +* 网站:从主页移除损坏的第三方教程 [[`0efd6e1`](https://github.com/PrismJS/prism/commit/0efd6e1)] +* 文档:在 NodeJS 部分的主页提及 `loadLanguages()` 函数。关闭 [#972](https://github.com/PrismJS/prism/issues/972),关闭 [#593](https://github.com/PrismJS/prism/issues/593) [[`4a14d20`](https://github.com/PrismJS/prism/commit/4a14d20)] +* 核心:贪婪模式应该始终与整个字符串匹配。修复 [#1355](https://github.com/PrismJS/prism/issues/1355) [[`294efaa`](https://github.com/PrismJS/prism/commit/294efaa)] +* Crystal:更新已知失败。[[`e1d2d42`](https://github.com/PrismJS/prism/commit/e1d2d42)] +* D:更新已知失败和测试。[[`13d9991`](https://github.com/PrismJS/prism/commit/13d9991)] +* Markdown:更新已知失败。[[`5b6c76d`](https://github.com/PrismJS/prism/commit/5b6c76d)] +* Matlab:更新已知失败。[[`259b6fc`](https://github.com/PrismJS/prism/commit/259b6fc)] +* 网站:移除不存在的失败锚点。重新措辞主页使其不那么误导。[[`8c0911a`](https://github.com/PrismJS/prism/commit/8c0911a)] +* 网站:在 FAQ 中添加指向保留标记插件的链接[[`e8cb6d4`](https://github.com/PrismJS/prism/commit/e8cb6d4)] +* 测试套件:vm.runInNewContext() 中的内存泄漏问题似乎已修复。[[`9a4b6fa`](https://github.com/PrismJS/prism/commit/9a4b6fa)]恢复[[`9bceece`](https://github.com/PrismJS/prism/commit/9bceece),[`7c7602b`](https://github.com/PrismJS/prism/commit/7c7602b)] +* Gulp:不要压缩 `components/index.js` [[`689227b`](https://github.com/PrismJS/prism/commit/689227b)] +* 网站:修复下载页面的主题选择,特别是当主题在查询字符串或哈希中时。[[`b4d3063`](https://github.com/PrismJS/prism/commit/b4d3063)] +* 更新 JSPM 配置以包含未压缩的组件。关闭 [#995](https://github.com/PrismJS/prism/issues/995) [[`218f160`](https://github.com/PrismJS/prism/commit/218f160)] +* 核心:修复支持包含破折号 `-` 的语言别名 [[`659ea31`](https://github.com/PrismJS/prism/commit/659ea31)] + +(在后续系统中继续进行更新以保持一致性。) \ No newline at end of file diff --git a/docs/_style/prism-master/README.md b/docs/_style/prism-master/README.md index 798208a8b6..c41079f916 100644 --- a/docs/_style/prism-master/README.md +++ b/docs/_style/prism-master/README.md @@ -1,30 +1,34 @@ -# [Prism](http://prismjs.com/) +# Prism 文档 -[](https://travis-ci.org/PrismJS/prism) +[](https://travis-ci.org/PrismJS/prism) -Prism is a lightweight, robust, elegant syntax highlighting library. It's a spin-off project from [Dabblet](http://dabblet.com/). +Prism 是一个轻量级、强大而优雅的语法高亮库。它是从 [Dabblet](http://dabblet.com/) 演变而来的项目。 -You can learn more on http://prismjs.com/. +您可以在 http://prismjs.com/ 上了解更多信息。 -Why another syntax highlighter?: http://lea.verou.me/2012/07/introducing-prism-an-awesome-new-syntax-highlighter/#more-1841 +## 为什么要另一个语法高亮工具? -More themes for Prism: https://github.com/PrismJS/prism-themes +您可以阅读更多关于 Prism 的介绍:[介绍 Prism:一个出色的新语法高亮工具](http://lea.verou.me/2012/07/introducing-prism-an-awesome-new-syntax-highlighter/#more-1841)。 -## Contribute to Prism! +## 更多 Prism 主题 -Prism depends on community contributions to expand and cover a wider array of use cases. If you like it, considering giving back by sending a pull request. Here are a few tips: +查看更多可用的主题:[Prism 主题库](https://github.com/PrismJS/prism-themes)。 -- Read the [documentation](http://prismjs.com/extending.html). Prism was designed to be extensible. -- Do not edit `prism.js`, it’s just the version of Prism used by the Prism website and is built automatically. Limit your changes to the unminified files in the components/ folder. The minified files are also generated automatically. -- The build system uses [gulp](https://github.com/gulpjs/gulp) to minify the files and build `prism.js`. Having gulp installed, you just need to run the command `gulp`. -- Please follow the code conventions used in the files already. For example, I use [tabs for indentation and spaces for alignment](http://lea.verou.me/2012/01/why-tabs-are-clearly-superior/). Opening braces are on the same line, closing braces on their own line regardless of construct. There is a space before the opening brace. etc etc. -- Please try to err towards more smaller PRs rather than few huge PRs. If a PR includes changes I want to merge and changes I don't, handling it becomes difficult. -- My time is very limited these days, so it might take a long time to review longer PRs (short ones are usually merged very quickly), especially those modifying the Prism Core. This doesn't mean your PR is rejected. -- If you contribute a new language definition, you will be responsible for handling bug reports about that language definition. -- If you add a new language definition, theme or plugin, you need to add it to `components.json` as well and rebuild Prism by running `gulp`, so that it becomes available to the download build page. +## 如何为 Prism 做贡献! -Thank you so much for contributing!! +Prism 依赖社区的贡献来扩展和覆盖更广泛的使用案例。如果您喜欢这个项目,请考虑通过提交拉取请求来回馈社区。以下是一些建议: -## Translations +- 阅读 [文档](http://prismjs.com/extending.html)。Prism 设计得很灵活,可以轻松扩展。 +- 请勿编辑 `prism.js` 文件,它只是 Prism 网站所使用的版本,自动构建的。请将更改限制在 `components/` 文件夹中的未压缩文件中。压缩文件也是自动生成的。 +- 构建系统使用 [gulp](https://github.com/gulpjs/gulp) 来压缩文件和构建 `prism.js`。安装 gulp 之后,只需运行 `gulp` 命令。 +- 请遵循现有文件中的代码约定。例如,我使用 [制表符用于缩进,空格用于对齐](http://lea.verou.me/2012/01/why-tabs-are-clearly-superior/)。打开的花括号在同一行,关闭的花括号独占一行(无论构造如何)。在打开的花括号之前留有一个空格,等等。 +- 请尽量提交较小的拉取请求,而不是少数几个大的请求。如果一个请求包含我想合并的更改和我不想合并的更改,那么处理起来会很困难。 +- 我的时间非常有限,因此长时间的拉取请求可能会需要很长时间来审查(短请求通常会快速合并),尤其是那些修改 Prism 核心的请求。这并不意味着您的请求被拒绝。 +- 如果您贡献新的语言定义,您将负责处理有关该语言定义的错误报告。 +- 如果添加新的语言定义、主题或插件,您需要将其添加到 `components.json` 中,并通过运行 `gulp` 重建 Prism,以便它能够在下载构建页面中可用。 -* [](http://www.awesomes.cn/repo/PrismJS/prism) +非常感谢您的贡献!! + +## 翻译 + +若希望进一步提升本翻译内容的清晰度和可理解性,请参考原文,并确保语言的准确性与术语的一致性。所有编辑需以能够直接应用到新文件的格式呈现。 \ No newline at end of file diff --git "a/notes/10.1 \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" "b/notes/10.1 \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" index fd9e836bc1..f6df92ed4e 100644 --- "a/notes/10.1 \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" +++ "b/notes/10.1 \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" @@ -6,64 +6,96 @@ ## 题目描述 -求斐波那契数列的第 n 项,n \<= 39。 - -<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?f(n)=\left\{\begin{array}{rcl}0&&{n=0}\\1&&{n=1}\\f(n-1)+f(n-2)&&{n>1}\end{array}\right." class="mathjax-pic"/></div> <br> --> +要求出斐波那契数列的第 n 项,条件是 n 的值不超过 39。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/45be9587-6069-4ab7-b9ac-840db1a53744.jpg" width="330px"> </div><br> +斐波那契数列的定义为: +\[ +f(n)=\left\{ +\begin{array}{rcl} +0 & \text{当 } n=0 \\ +1 & \text{当 } n=1 \\ +f(n-1)+f(n-2) & \text{当 } n>1 +\end{array} +\right. +\] + ## 解题思路 -如果使用递归求解,会重复计算一些子问题。例如,计算 f(4) 需要计算 f(3) 和 f(2),计算 f(3) 需要计算 f(2) 和 f(1),可以看到 f(2) 被重复计算了。 +如果采用递归的方法来计算斐波那契数列,我们会发现一个显著的问题——重复计算。举个例子,当我们计算 `f(4)` 时,需要先计算 `f(3)` 和 `f(2)`;而计算 `f(3)` 时又需要计算 `f(2)` 和 `f(1)`。可以看出,`f(2)` 被重复计算了多次,这将导致效率极低,尤其对于较大的 n 值。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c13e2a3d-b01c-4a08-a69b-db2c4e821e09.png" width="350px"/> </div><br> +为了避免这种情况下的重复计算,我们可以使用动态规划的思想。动态规划通过将子问题的答案存储起来,避免再次计算相同的子问题,从而提高效率。 -递归是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,从而避免重复求解子问题。 +### 动态规划实现 + +我们可以使用一个数组来保存从 `f(0)` 到 `f(n)` 的所有斐波那契数: ```java public int Fibonacci(int n) { - if (n <= 1) - return n; - int[] fib = new int[n + 1]; - fib[1] = 1; - for (int i = 2; i <= n; i++) - fib[i] = fib[i - 1] + fib[i - 2]; - return fib[n]; + if (n <= 1) { + return n; // 较小的 n 可以直接返回 + } + int[] fib = new int[n + 1]; // 创建数组用于保存斐波那契数 + fib[0] = 0; // f(0) + fib[1] = 1; // f(1) + + for (int i = 2; i <= n; i++) { + fib[i] = fib[i - 1] + fib[i - 2]; // 计算当前斐波那契数 + } + + return fib[n]; // 返回第 n 项 } ``` -考虑到第 i 项只与第 i-1 和第 i-2 项有关,因此只需要存储前两项的值就能求解第 i 项,从而将空间复杂度由 O(N) 降低为 O(1)。 +在上面的代码中,我们首先处理了小于或等于 1 的情况,然后初始化了一个数组 `fib` 用于存储计算结果。在循环中,我们依次计算斐波那契数列的值并存储在数组中。 + +### 空间优化 + +注意到在计算 `f(i)` 时,我们只需要 `f(i-1)` 和 `f(i-2)` 的值。也就是说,我们可以将数组的空间复杂度从 O(N) 降低到 O(1),只保留前两项的值即可: ```java public int Fibonacci(int n) { - if (n <= 1) - return n; - int pre2 = 0, pre1 = 1; - int fib = 0; + if (n <= 1) { + return n; // 直接返回 + } + + int pre2 = 0; // f(i-2) + int pre1 = 1; // f(i-1) + int fib = 0; // 当前的斐波那契数 + for (int i = 2; i <= n; i++) { - fib = pre2 + pre1; - pre2 = pre1; - pre1 = fib; + fib = pre2 + pre1; // 计算当前斐波那契数 + pre2 = pre1; // 更新 f(i-2) 为 f(i-1) + pre1 = fib; // 更新 f(i-1) 为 f(i) } - return fib; + + return fib; // 返回第 n 项 } ``` -由于待求解的 n 小于 40,因此可以将前 40 项的结果先进行计算,之后就能以 O(1) 时间复杂度得到第 n 项的值。 +此实现有效降低了空间的复杂度,同时保持了 O(n) 的时间复杂度。 + +### 预计算法 + +考虑到 n 的上限是 39,我们还可以利用预计算的方法,将前 40 项的斐波那契数在程序初始化时计算一次,此后可以在 O(1) 的时间内获取第 n 项的值: ```java public class Solution { - - private int[] fib = new int[40]; + private int[] fib = new int[40]; // 存储前 40 项 public Solution() { - fib[1] = 1; - for (int i = 2; i < fib.length; i++) - fib[i] = fib[i - 1] + fib[i - 2]; + fib[0] = 0; // f(0) + fib[1] = 1; // f(1) + for (int i = 2; i < fib.length; i++) { + fib[i] = fib[i - 1] + fib[i - 2]; // 先计算好 + } } public int Fibonacci(int n) { - return fib[n]; + return fib[n]; // 返回已计算好的第 n 项 } } ``` + +通过上述方式,我们在创建 `Solution` 对象时预计算所有值,求解第 n 项成为了一个简单的数组索引操作,大大提升了效率。这种方法尤其适用于 n 较小的情况。 \ No newline at end of file diff --git "a/notes/12. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" "b/notes/12. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" index 157785374b..d7854842ca 100644 --- "a/notes/12. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" +++ "b/notes/12. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" @@ -4,75 +4,105 @@ ## 题目描述 -判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向上下左右移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 +本题旨在判断在一个矩阵中是否存在一条路径,该路径包含某字符串的所有字符。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中上下左右四个方向移动。值得注意的是,路径经过的格子不能再次被使用。 -例如下面的矩阵包含了一条 bfce 路径。 +例如,给定如下矩阵,我们可以找到一条包含字符串 "bfce" 的路径。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1db1c7ea-0443-478b-8df9-7e33b1336cc4.png" width="200px"> </div><br> ## 解题思路 -使用回溯法(backtracking)进行求解,它是一种暴力搜索方法,通过搜索所有可能的结果来求解问题。回溯法在一次搜索结束时需要进行回溯(回退),将这一次搜索过程中设置的状态进行清除,从而开始一次新的搜索过程。例如下图示例中,从 f 开始,下一步有 4 种搜索可能,如果先搜索 b,需要将 b 标记为已经使用,防止重复使用。在这一次搜索结束之后,需要将 b 的已经使用状态清除,并搜索 c。 +我们采用回溯法(Backtracking)来解决此问题。回溯法是一种暴力搜索方法,它通过探索所有可能的情况来寻找解答。具体步骤如下: + +1. **路径的选择**:从矩阵中任选一个格子作为起点,然后向周围四个方向移动,寻找下一个字符。 +2. **状态的标记**:一旦访问某个格子,就将其标记为已访问,避免重复使用。 +3. **路径的回退**:如果发现路径不再有效,我们需要将该格子重新标记为未访问,并回退到之前的状态以尝试其他可能的路径。 + +例如,图示中从字符 f 开始,可以选择四个方向中的任何一个进行搜索。若选择搜索 b,则需标记 b 为已使用。在这一轮搜索结束后,我们需要清除 b 的已使用状态,以便进行对 c 的搜索。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/dc964b86-7a08-4bde-a3d9-e6ddceb29f98.png" width="200px"> </div><br> -本题的输入是数组而不是矩阵(二维数组),因此需要先将数组转换成矩阵。 +本题输入的是一个一维数组而非二维矩阵,因此首先需要将字符数组转换为矩阵形式。 + +## 示例代码 + +以下是 Java 实现的代码示例: ```java public class Solution { + // 定义移动方向,上、下、左、右 private final static int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; - private int rows; - private int cols; - - public boolean hasPath (String val, int rows, int cols, String path) { - if (rows == 0 || cols == 0) return false; - this.rows = rows; - this.cols = cols; - char[] array = val.toCharArray(); - char[][] matrix = buildMatrix(array); - char[] pathList = path.toCharArray(); - boolean[][] marked = new boolean[rows][cols]; + private int rows; // 矩阵的行数 + private int cols; // 矩阵的列数 + + public boolean hasPath(String val, int rows, int cols, String path) { + if (rows == 0 || cols == 0) return false; // 如果矩阵无效则返回 false + this.rows = rows; // 设置行数 + this.cols = cols; // 设置列数 + char[] array = val.toCharArray(); // 将字符串转为字符数组 + char[][] matrix = buildMatrix(array); // 将字符数组转换为矩阵 + char[] pathList = path.toCharArray(); // 将路径字符串转为字符数组 + boolean[][] marked = new boolean[rows][cols]; // 用于标记访问过的格子 + + // 遍历每一个格子作为起点 for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) + // 开始回溯搜索 if (backtracking(matrix, pathList, marked, 0, i, j)) - return true; + return true; // 找到路径则返回 true - return false; + return false; // 所有起点搜索完后未找到路径返回 false } private boolean backtracking(char[][] matrix, char[] pathList, boolean[][] marked, int pathLen, int r, int c) { + // 若路径长度等于字符串长度,表示找到路径 if (pathLen == pathList.length) return true; + + // 边界条件及字符匹配判断 if (r < 0 || r >= rows || c < 0 || c >= cols || matrix[r][c] != pathList[pathLen] || marked[r][c]) { - - return false; + return false; // 返回 false,表示此路径无效 } - marked[r][c] = true; + + marked[r][c] = true; // 标记当前格子为已访问 + + // 遍历四个可能的方向 for (int[] n : next) + // 进行下一步的回溯搜索 if (backtracking(matrix, pathList, marked, pathLen + 1, r + n[0], c + n[1])) - return true; - marked[r][c] = false; - return false; + return true; // 找到路径返回 true + + marked[r][c] = false; // 回溯,解除当前格子的访问标记 + return false; // 返回 false,表示未找到路径 } private char[][] buildMatrix(char[] array) { - char[][] matrix = new char[rows][cols]; + char[][] matrix = new char[rows][cols]; // 初始化矩阵 for (int r = 0, idx = 0; r < rows; r++) for (int c = 0; c < cols; c++) - matrix[r][c] = array[idx++]; - return matrix; + matrix[r][c] = array[idx++]; // 填充矩阵 + return matrix; // 返回构建好的矩阵 } public static void main(String[] args) { Solution solution = new Solution(); - String val = "ABCESFCSADEE"; - int rows = 3; - int cols = 4; - String path = "ABCCED"; - boolean res = solution.hasPath(val, rows, cols, path); - System.out.println(res); + String val = "ABCESFCSADEE"; // 矩阵字符 + int rows = 3; // 矩阵行数 + int cols = 4; // 矩阵列数 + String path = "ABCCED"; // 要查找的路径 + boolean res = solution.hasPath(val, rows, cols, path); // 检查是否存在路径 + System.out.println(res); // 输出结果 } } ``` + +### 代码解析 + +1. `hasPath` 方法是程序的入口,主要负责初始化并启动回溯搜索。 +2. `backtracking` 方法实现了回溯算法的核心逻辑,判断当前路径的有效性,并递归地进行搜索。 +3. `buildMatrix` 方法负责将字符数组构建成一个二维矩阵以供后续使用。 +4. `main` 方法示范了如何创建 `Solution` 类的实例,并调用 `hasPath` 方法检查特定字符串的路径是否存在。 + +通过这种方法,我们可以有效地判断在给定矩阵中是否存在某一条包含指定字符路径的路径。 \ No newline at end of file diff --git "a/notes/18.1 \345\234\250 O(1) \346\227\266\351\227\264\345\206\205\345\210\240\351\231\244\351\223\276\350\241\250\350\212\202\347\202\271.md" "b/notes/18.1 \345\234\250 O(1) \346\227\266\351\227\264\345\206\205\345\210\240\351\231\244\351\223\276\350\241\250\350\212\202\347\202\271.md" index 365e18d442..56aec186c2 100644 --- "a/notes/18.1 \345\234\250 O(1) \346\227\266\351\227\264\345\206\205\345\210\240\351\231\244\351\223\276\350\241\250\350\212\202\347\202\271.md" +++ "b/notes/18.1 \345\234\250 O(1) \346\227\266\351\227\264\345\206\205\345\210\240\351\231\244\351\223\276\350\241\250\350\212\202\347\202\271.md" @@ -2,36 +2,89 @@ ## 解题思路 -① 如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,然后令该节点指向下下个节点,再删除下一个节点,时间复杂度为 O(1)。 +在链表中删除一个节点主要有两种情况,这两种情况的处理逻辑略有不同。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1176f9e1-3442-4808-a47a-76fbaea1b806.png" width="600"/> </div><br> +### 情况一:节点不是尾节点 -② 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向 null,时间复杂度为 O(N)。 +如果待删除的节点 `tobeDelete` 不是尾节点,我们可以使用以下方法高效地删除该节点,时间复杂度为 O(1): -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/4bf8d0ba-36f0-459e-83a0-f15278a5a157.png" width="600"/> </div><br> +1. 将 `tobeDelete` 节点的值替换为其下一个节点的值。 +2. 然后调整指针,将 `tobeDelete` 的 `next` 指向下下个节点。 -综上,如果进行 N 次操作,那么大约需要操作节点的次数为 N-1+N=2N-1,其中 N-1 表示 N-1 个不是尾节点的每个节点以 O(1) 的时间复杂度操作节点的总次数,N 表示 1 个尾节点以 O(N) 的时间复杂度操作节点的总次数。(2N-1)/N \~ 2,因此该算法的平均时间复杂度为 O(1)。 +这样,我们实际上是将下一个节点的值复制到当前节点,并删除下一个节点,这样就达到了删除当前节点的目的。如下图所示: + +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1176f9e1-3442-4808-a47a-76fbaea1b806.png" width="600"/> +</div><br> + +### 情况二:节点是尾节点 + +如果待删除的节点 `tobeDelete` 是尾节点,我们就需要遍历链表来找到它的前驱节点。处理步骤如下: + +1. 从头节点开始遍历链表,寻找 `tobeDelete` 的前一个节点。 +2. 当找到前一个节点后,将其 `next` 指向 `null`,以此将尾节点删除。 + +这种情况下的时间复杂度是 O(N),因为需要遍历链表找到待删除节点的前驱节点。如下图所示: + +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/4bf8d0ba-36f0-459e-83a0-f15278a5a157.png" width="600"/> +</div><br> + +### 平均时间复杂度分析 + +综合以上情况,如果我们进行 N 次删除操作,总的操作节点次数大约为 2N - 1。理由如下: +- 对于 N-1 个非尾节点的删除操作,总时间复杂度为 O(1) 每个,故为 O(N-1)。 +- 对于 1 个尾节点的删除操作,由于要遍历链表,所以时间复杂度为 O(N)。 + +因此,平均时间复杂度为: + +\[ +\frac{(2N - 1)}{N} \approx 2 +\] + +这表明,平均时间复杂度为 O(1)。 + +## 代码实现(Java) + +以下是用于在链表中删除节点的 Java 实现代码,该代码实现了上述逻辑。 ```java +public class ListNode { + int val; + ListNode next; + ListNode(int val) { this.val = val; } +} + public ListNode deleteNode(ListNode head, ListNode tobeDelete) { - if (head == null || tobeDelete == null) - return null; + // 检查链表和待删除节点是否为空 + if (head == null || tobeDelete == null) { + return head; // 返回原链表 + } + + // 情况一:待删除节点不是尾节点 if (tobeDelete.next != null) { - // 要删除的节点不是尾节点 - ListNode next = tobeDelete.next; - tobeDelete.val = next.val; - tobeDelete.next = next.next; + ListNode nextNode = tobeDelete.next; // 获取待删除节点的下一个节点 + tobeDelete.val = nextNode.val; // 将下一个节点的值赋给当前节点 + tobeDelete.next = nextNode.next; // 调整指针指向下下个节点 } else { - if (head == tobeDelete) - // 只有一个节点 - head = null; - else { - ListNode cur = head; - while (cur.next != tobeDelete) - cur = cur.next; - cur.next = null; + // 情况二:待删除节点是尾节点 + if (head == tobeDelete) { + // 如果链表只有一个节点,则直接将头指针置为 null + head = null; + } else { + // 遍历找到待删除节点的前驱 + ListNode current = head; + while (current.next != tobeDelete) { + current = current.next; // 移动到链表的下一个节点 + } + current.next = null; // 将前驱节点的 next 置为 null,删除尾节点 } } - return head; + return head; // 返回修改后的链表头 } ``` + +### 注意事项 + +- 在使用该方法时,要确保待删除的节点不是头节点,并且能够应对链表为空的情况。 +- 该方法不支持删除头节点和空链表中的节点,需根据实际需求添加相应的异常处理。 \ No newline at end of file diff --git "a/notes/22. \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254 K \344\270\252\347\273\223\347\202\271.md" "b/notes/22. \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254 K \344\270\252\347\273\223\347\202\271.md" index aa00d274af..b8741d2165 100644 --- "a/notes/22. \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254 K \344\270\252\347\273\223\347\202\271.md" +++ "b/notes/22. \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254 K \344\270\252\347\273\223\347\202\271.md" @@ -1,27 +1,85 @@ # 22. 链表中倒数第 K 个结点 -[牛客网](https://www.nowcoder.com/practice/886370fe658f41b498d40fb34ae76ff9?tpId=13&tqId=11167&tab=answerKey&from=cyc_github) - ## 解题思路 -设链表的长度为 N。设置两个指针 P1 和 P2,先让 P1 移动 K 个节点,则还有 N - K 个节点可以移动。此时让 P1 和 P2 同时移动,可以知道当 P1 移动到链表结尾时,P2 移动到第 N - K 个节点处,该位置就是倒数第 K 个节点。 +设定链表的长度为 \(N\)。为了找到倒数第 \(K\) 个结点,我们可以使用两个指针 \(P1\) 和 \(P2\)。首先让指针 \(P1\) 向前移动 \(K\) 个节点,这样在此时链表中还有 \(N - K\) 个节点未被访问到。 + +### 过程说明 + +1. **初始设置**: + - 检查链表的头结点是否为空。如果链表为空,直接返回 `null`。 + - 定义两个指针 \(P1\) 和 \(P2\),开始时都指向链表的头部。 + +2. **移动指针 \(P1\)**: + - 将指针 \(P1\) 向前移动 \(K\) 个节点。在这个过程中,每移动一个节点,\(K\) 的值减一。 + - 如果在移动 \(K\) 个节点后,\(K\) 的值仍然大于 0,说明链表的长度小于 \(K\),此时返回 `null`,表示没有找到倒数第 \(K\) 个结点。 + +3. **同时移动 P1 和 P2**: + - 此时让 \(P1\) 和 \(P2\) 同时移动,直到 \(P1\) 到达链表的结尾(即 \(P1\) 为 `null`)。 + - 在这个过程中,\(P2\) 将会移动到链表的第 \(N - K\) 个节点。此时,\(P2\) 就是我们要找的倒数第 \(K\) 个节点。 + +### 示例图解 + +当链表节点顺序为 `1 -> 2 -> 3 -> 4 -> 5`,要找到倒数第 2 个节点的过程如下图所示: + +``` +P1移动K个节点后: +1 -> 2 -> 3 -> 4 -> 5 (P1) + ↑ + P2 + +同时移动P1和P2直至P1到达链表结尾: + P1 + ↓ +1 -> 2 -> 3 -> 4 -> 5 + ↑ + P2 + +返回P2节点,即值为4的节点。 +``` + +### 代码实现 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/6b504f1f-bf76-4aab-a146-a9c7a58c2029.png" width="500"/> </div><br> +下面是相应的Java代码实现,注释清晰明了: ```java +public class ListNode { + int val; // 节点的值 + ListNode next; // 指向下一个节点的指针 + + ListNode(int x) { + val = x; + next = null; + } +} + public ListNode FindKthToTail(ListNode head, int k) { - if (head == null) + // 如果链表为空,直接返回null + if (head == null) { return null; - ListNode P1 = head; - while (P1 != null && k-- > 0) + } + + ListNode P1 = head; // 指针P1初始化为头结点 + // 移动P1 K个节点 + while (P1 != null && k-- > 0) { P1 = P1.next; - if (k > 0) + } + + // 如果k仍然大于0,说明链表长度小于K,返回null + if (k > 0) { return null; - ListNode P2 = head; + } + + ListNode P2 = head; // 指针P2初始化为头结点 + // 同时移动P1和P2,直到P1到达链表结尾 while (P1 != null) { P1 = P1.next; P2 = P2.next; } - return P2; + + return P2; // 返回P2所指向的节点,作为倒数第K个节点 } ``` + +### 总结 +以上算法的时间复杂度为 \(O(N)\),空间复杂度为 \(O(1)\)。通过两个指针的巧妙使用,能够有效地找到链表中倒数第 \(K\) 个节点。 \ No newline at end of file diff --git "a/notes/28. \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" "b/notes/28. \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" index 626b6a32c2..38762b0c78 100644 --- "a/notes/28. \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" +++ "b/notes/28. \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" @@ -1,27 +1,70 @@ # 28. 对称的二叉树 -[NowCoder](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - ## 题目描述 +给定一个二叉树,检查它是否是镜像对称的。 + +一个二叉树是镜像对称的,当它的左子树是右子树的镜像。 + <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/0c12221f-729e-4c22-b0ba-0dfc909f8adf.jpg" width="300"/> </div><br> ## 解题思路 +可以通过递归来解决这个问题。我们需要设计一个辅助函数,用于比较两个树的节点是否对称。具体步骤如下: + +1. **基础情况**: + - 如果两个节点均为空,则它们是对称的,返回 `true`。 + - 如果一个节点为空而另一个节点不为空,则它们不是对称的,返回 `false`。 + - 如果两个节点的值不相等,则它们不是对称的,返回 `false`。 + +2. **递归调用**: + - 对于两个节点 `t1` 和 `t2`,再递归地检查 `t1` 的左子树和 `t2` 的右子树是否对称,同时检查 `t1` 的右子树和 `t2` 的左子树是否对称。 + +通过这样的深入比较,我们能够判断整个树是否为对称。 + +以下是具体的 Java 代码实现: + ```java -boolean isSymmetrical(TreeNode pRoot) { - if (pRoot == null) - return true; - return isSymmetrical(pRoot.left, pRoot.right); +class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode(int x) { val = x; } } -boolean isSymmetrical(TreeNode t1, TreeNode t2) { - if (t1 == null && t2 == null) - return true; - if (t1 == null || t2 == null) - return false; - if (t1.val != t2.val) - return false; - return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left); +public class Solution { + public boolean isSymmetrical(TreeNode pRoot) { + // 空树是对称的 + if (pRoot == null) { + return true; + } + // 调用辅助函数,比较左右子树 + return isSymmetrical(pRoot.left, pRoot.right); + } + + private boolean isSymmetrical(TreeNode t1, TreeNode t2) { + // 当两个节点均为空时,对称 + if (t1 == null && t2 == null) { + return true; + } + // 一个节点为空,另一个不为空时,不对称 + if (t1 == null || t2 == null) { + return false; + } + // 节点值不相等时,不对称 + if (t1.val != t2.val) { + return false; + } + // 递归比较左右子树 + return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left); + } } ``` + +## 代码说明 + +1. **TreeNode 类**:定义了二叉树的节点结构,包括节点值和左、右子节点。 +2. **isSymmetrical 方法**:入口方法,用于检查树的根节点,如果根节点为空则返回 `true`,否则调用辅助方法进行左右子树的对称性检查。 +3. **isSymmetrical(重载方法)**:这个方法接受两个节点作为参数,比较它们的值和结构,逐层递归检查。 + +通过这个方法,我们可以有效地判断一个二叉树是否具有镜像对称性。请注意,此算法的时间复杂度为 O(n),其中 n 是树中节点的数量,因为每个节点都会被访问一次。空间复杂度通常为 O(h),h 是树的高度,主要取决于递归栈的深度。 \ No newline at end of file diff --git "a/notes/40. \346\234\200\345\260\217\347\232\204 K \344\270\252\346\225\260.md" "b/notes/40. \346\234\200\345\260\217\347\232\204 K \344\270\252\346\225\260.md" index 50bc3e4db3..648b600918 100644 --- "a/notes/40. \346\234\200\345\260\217\347\232\204 K \344\270\252\346\225\260.md" +++ "b/notes/40. \346\234\200\345\260\217\347\232\204 K \344\270\252\346\225\260.md" @@ -6,80 +6,115 @@ ## 解题思路 -### 大小为 K 的最小堆 +在这一题中,我们需要找到数组中最小的 K 个数。为了高效地实现这一点,我们可以通过两种方法来解决这个问题:使用最小堆(或大顶堆)和快速选择算法。接下来我们将详细说明这两种方法的实现思路及其代码示例。 -- 复杂度:O(NlogK) + O(K) -- 特别适合处理海量数据 +### 方法一:使用大小为 K 的大顶堆 -维护一个大小为 K 的最小堆过程如下:使用大顶堆。在添加一个元素之后,如果大顶堆的大小大于 K,那么将大顶堆的堆顶元素去除,也就是将当前堆中值最大的元素去除,从而使得留在堆中的元素都比被去除的元素来得小。 +我们可以使用一个大小为 K 的大顶堆来维护当前最小的 K 个数。当我们遍历数组中的元素时,将元素加入堆中。如果堆的大小超过 K,我们就将堆顶的元素(即当前堆中最大的元素)移除。最终,堆中剩下的元素就是我们需要的最小 K 个数。 -应该使用大顶堆来维护最小堆,而不能直接创建一个小顶堆并设置一个大小,企图让小顶堆中的元素都是最小元素。 +- **时间复杂度**:O(N log K) + O(K) +- **空间复杂度**:O(K) -Java 的 PriorityQueue 实现了堆的能力,PriorityQueue 默认是小顶堆,可以在在初始化时使用 Lambda 表达式 (o1, o2) -\> o2 - o1 来实现大顶堆。其它语言也有类似的堆数据结构。 +下面是使用 Java 的 `PriorityQueue` 实现的代码: ```java -public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) { - if (k > nums.length || k <= 0) - return new ArrayList<>(); - PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1); - for (int num : nums) { - maxHeap.add(num); - if (maxHeap.size() > k) - maxHeap.poll(); +import java.util.ArrayList; +import java.util.PriorityQueue; + +public class Solution { + public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) { + ArrayList<Integer> result = new ArrayList<>(); + + // 边界条件处理 + if (k > nums.length || k <= 0) { + return result; + } + + // 使用大顶堆来维护最小的 K 个数 + PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1); + + for (int num : nums) { + maxHeap.add(num); + if (maxHeap.size() > k) { + maxHeap.poll(); // 移除堆顶的元素(最大值) + } + } + + return new ArrayList<>(maxHeap); // 返回堆中剩余元素 } - return new ArrayList<>(maxHeap); } ``` -### 快速选择 +### 方法二:快速选择算法 -- 复杂度:O(N) + O(1) -- 只有当允许修改数组元素时才可以使用 +另一种更高效的方式是采用快速选择算法。这个算法利用了快速排序中的划分(partition)过程。通过不断地选择切分点,我们可以找到第 K 小的元素,进而可以将数组划分为最小的 K 个元素和其他元素。 -快速排序的 partition() 方法,会返回一个整数 j 使得 a[l..j-1] 小于等于 a[j],且 a[j+1..h] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素。可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。 +- **时间复杂度**:O(N)(平均情况) + O(1) +- **空间复杂度**:O(1)(不需要额外的空间,除了返回结果) + +以下是使用快速选择算法的实现示例: ```java -public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) { - ArrayList<Integer> ret = new ArrayList<>(); - if (k > nums.length || k <= 0) - return ret; - findKthSmallest(nums, k - 1); - /* findKthSmallest 会改变数组,使得前 k 个数都是最小的 k 个数 */ - for (int i = 0; i < k; i++) - ret.add(nums[i]); - return ret; -} +import java.util.ArrayList; + +public class Solution { + public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) { + ArrayList<Integer> result = new ArrayList<>(); + + // 边界条件处理 + if (k > nums.length || k <= 0) { + return result; + } + + // 调用快速选择方法找出第 k 小的元素 + findKthSmallest(nums, k - 1); + + // 提取前 k 个最小元素 + for (int i = 0; i < k; i++) { + result.add(nums[i]); + } + + return result; + } -public void findKthSmallest(int[] nums, int k) { - int l = 0, h = nums.length - 1; - while (l < h) { - int j = partition(nums, l, h); - if (j == k) - break; - if (j > k) - h = j - 1; - else - l = j + 1; + private void findKthSmallest(int[] nums, int k) { + int left = 0, right = nums.length - 1; + while (left < right) { + int pivotIndex = partition(nums, left, right); + if (pivotIndex == k) { + break; // 找到第 k 小的元素,退出 + } + if (pivotIndex > k) { + right = pivotIndex - 1; // 在左半部分继续查找 + } else { + left = pivotIndex + 1; // 在右半部分继续查找 + } + } } -} -private int partition(int[] nums, int l, int h) { - int p = nums[l]; /* 切分元素 */ - int i = l, j = h + 1; - while (true) { - while (i != h && nums[++i] < p) ; - while (j != l && nums[--j] > p) ; - if (i >= j) - break; - swap(nums, i, j); + private int partition(int[] nums, int left, int right) { + int pivot = nums[left]; // 选取切分元素 + int i = left, j = right + 1; + while (true) { + // 从左侧开始查找大于等于 pivot 的元素 + while (i != right && nums[++i] < pivot) ; + // 从右侧开始查找小于等于 pivot 的元素 + while (j != left && nums[--j] > pivot) ; + if (i >= j) { + break; // 如果两个指针重合,说明划分完成 + } + swap(nums, i, j); // 交换不满足条件的元素 + } + swap(nums, left, j); // 将切分元素放到正确的位置 + return j; // 返回切分点 } - swap(nums, l, j); - return j; -} -private void swap(int[] nums, int i, int j) { - int t = nums[i]; - nums[i] = nums[j]; - nums[j] = t; + private void swap(int[] nums, int i, int j) { + int temp = nums[i]; + nums[i] = nums[j]; + nums[j] = temp; + } } ``` + +通过上述两种方法,我们可以高效地找到数组中最小的 K 个数。选择合适的方法取决于具体的场景和需求。对于小数据量,使用快速选择方法可能更为简单快捷,而对大数据量则推荐使用大顶堆的方式。 \ No newline at end of file diff --git "a/notes/41.2 \345\255\227\347\254\246\346\265\201\344\270\255\347\254\254\344\270\200\344\270\252\344\270\215\351\207\215\345\244\215\347\232\204\345\255\227\347\254\246.md" "b/notes/41.2 \345\255\227\347\254\246\346\265\201\344\270\255\347\254\254\344\270\200\344\270\252\344\270\215\351\207\215\345\244\215\347\232\204\345\255\227\347\254\246.md" index 85cca0d872..061c68d7d7 100644 --- "a/notes/41.2 \345\255\227\347\254\246\346\265\201\344\270\255\347\254\254\344\270\200\344\270\252\344\270\215\351\207\215\345\244\215\347\232\204\345\255\227\347\254\246.md" +++ "b/notes/41.2 \345\255\227\347\254\246\346\265\201\344\270\255\347\254\254\344\270\200\344\270\252\344\270\215\351\207\215\345\244\215\347\232\204\345\255\227\347\254\246.md" @@ -2,30 +2,60 @@ ## 题目描述 -[牛客网](https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) +请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "go" 时,第一个只出现一次的字符是 "g"。而当从该字符流中读出前六个字符 "google" 时,第一个只出现一次的字符是 "l"。 -## 题目描述 +## 解题思路 -请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "go" 时,第一个只出现一次的字符是 "g"。当从该字符流中读出前六个字符“google" 时,第一个只出现一次的字符是 "l"。 +为了找出字符流中第一个只出现一次的字符,我们可以采用统计数组和队列的结合方法。由于题目只涉及 ASCII 码字符,我们可以使用一个大小为 128 的整型数组来统计字符的出现次数。同时,可以使用一个队列来存储已经到达的字符,从而确保我们能够快速找到第一个只出现一次的字符。 -## 解题思路 +### 具体步骤 -使用统计数组来统计每个字符出现的次数,本题涉及到的字符为都为 ASCII 码,因此使用一个大小为 128 的整型数组就能完成次数统计任务。 +1. **统计字符出现次数**:我们使用一个整型数组 `cnts` 来记录每个字符出现的次数,当一个新的字符到达时,我们对应的索引值加一。 -使用队列来存储到达的字符,并在每次有新的字符从字符流到达时移除队列头部那些出现次数不再是一次的元素。因为队列是先进先出顺序,因此队列头部的元素为第一次只出现一次的字符。 +2. **使用队列存储字符**:每当有新字符插入时,我们将其加入队列。由于队列是先进先出(FIFO)的数据结构,队列的头部将是我们寻找的第一个只出现一次的字符。 -```java -private int[] cnts = new int[128]; -private Queue<Character> queue = new LinkedList<>(); - -public void Insert(char ch) { - cnts[ch]++; - queue.add(ch); - while (!queue.isEmpty() && cnts[queue.peek()] > 1) - queue.poll(); -} +3. **移除不唯一的字符**:每次插入新字符后,我们需要检查队列头部的元素。如果这个元素的出现次数大于 1,则从队列中移除该元素,直到队列的头部元素是唯一的字符,或者队列为空。 + +4. **获取结果**:当需要返回第一个只出现一次的字符时,我们只需查看队列的头部。如果队列为空,则表示当前没有字符是只出现一次的,我们可以返回一个特定的标记字符如 '#'. -public char FirstAppearingOnce() { - return queue.isEmpty() ? '#' : queue.peek(); +### 代码实现 + +以下是实现上述思路的 Java 代码: + +```java +import java.util.LinkedList; +import java.util.Queue; + +public class FirstUniqueCharacter { + private int[] cnts = new int[128]; // 用于记录字符出现的次数 + private Queue<Character> queue = new LinkedList<>(); // 用于存储流中的字符 + + // 插入新字符 + public void Insert(char ch) { + // 更新字符出现的次数 + cnts[ch]++; + // 将字符加入队列 + queue.add(ch); + + // 移除不唯一的字符 + while (!queue.isEmpty() && cnts[queue.peek()] > 1) { + queue.poll(); // 移除队列首部的元素 + } + } + + // 获取第一个只出现一次的字符 + public char FirstAppearingOnce() { + // 如果队列为空,表示没有唯一字符 + return queue.isEmpty() ? '#' : queue.peek(); + } } ``` + +### 代码解析 + +- `cnts` 数组被用来存储每个字符的出现频率,字符的 ASCII 值作为数组的索引。 +- `queue` 是一个队列,用来按照字符到达的顺序存储字符。 +- 在 `Insert` 方法中,我们增加字符的计数,并将其添加进队列。接着,通过一个 `while` 循环,检查队列的头部是否是唯一的字符,如果不是,则将其移除。 +- `FirstAppearingOnce` 方法简单返回当前唯一字符的头部,或者返回 '#' 如果当前没有唯一字符。 + +通过这种方式,我们可以高效地找到字符流中第一个只出现一次的字符,并保持 O(1) 的时间复杂度以查询这个字符。 \ No newline at end of file diff --git "a/notes/46. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.md" "b/notes/46. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.md" index fcbd92fea5..cd6e4a6ef7 100644 --- "a/notes/46. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.md" +++ "b/notes/46. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.md" @@ -4,28 +4,71 @@ ## 题目描述 -给定一个数字,按照如下规则翻译成字符串:1 翻译成“a”,2 翻译成“b”... 26 翻译成“z”。一个数字有多种翻译可能,例如 12258 一共有 5 种,分别是 abbeh,lbeh,aveh,abyh,lyh。实现一个函数,用来计算一个数字有多少种不同的翻译方法。 +给定一个数字,按照如下规则翻译成字符串:1 翻译成“a”,2 翻译成“b”... 26 翻译成“z”。例如,对于数字 12258,可以翻译成多个字符串:abbeh、lbeh、aveh、abyh 和 lyh,总共 5 种不同的翻译方式。请实现一个函数,计算一个数字有多少种不同的翻译方法。 ## 解题思路 +要解决这个问题,我们可以使用动态规划的方法来计算不同的翻译方式的数量。下面是我们用来实现这个想法的步骤: + +1. **定义状态**:用一个数组 `dp` 来存储到达每个位置的解码方式的数量。`dp[i]` 表示前 `i` 个字符可以解码的方式总数。 + +2. **初始化状态**: + - `dp[0] = 1`,表示空字符串有一种解码方式(即不解码)。 + - `dp[1]` 的值取决于第一个字符是否为 '0': + - 如果是 '0',那么不可能进行解码,所以 `dp[1] = 0`。 + - 如果不是 '0',则 `dp[1] = 1`,代表只有一种解码对应。 + +3. **填充状态数组**: + - 从第二个字符开始迭代到最后一个字符(即从 `i = 2` 到 `n`)。 + - 对于每个字符,检查它是否单独可以成一个有效数字(即不是 '0'),如果有效,则从 `dp[i-1]` 中继承其解码计数。 + - 同时检查当前字符和前一个字符是否组合(即两个字符形成的数字在 1 到 26 之间)。如果可以组合,则从 `dp[i-2]` 中添加解码计数。 + +4. **返回结果**:最后,返回 `dp[n]`,即完整字符串的解码方式总数。 + +以下是实现这个逻辑的 Java 代码: + ```java public int numDecodings(String s) { - if (s == null || s.length() == 0) + // 边界条件判断,空字符串直接返回0 + if (s == null || s.length() == 0) { return 0; + } + int n = s.length(); int[] dp = new int[n + 1]; - dp[0] = 1; - dp[1] = s.charAt(0) == '0' ? 0 : 1; + + // 初始化 + dp[0] = 1; // 空字符串 + dp[1] = s.charAt(0) == '0' ? 0 : 1; // 非零字符初始化 + + // 动态规划填表 for (int i = 2; i <= n; i++) { + // 获取当前字符 int one = Integer.valueOf(s.substring(i - 1, i)); - if (one != 0) - dp[i] += dp[i - 1]; - if (s.charAt(i - 2) == '0') - continue; + // 判断当前字符是否有效 + if (one != 0) { + dp[i] += dp[i - 1]; // 加上前一个位置的解码方式 + } + + // 获取前一个字符 + if (s.charAt(i - 2) == '0') { + continue; // 前一个字符无效,跳过 + } + int two = Integer.valueOf(s.substring(i - 2, i)); - if (two <= 26) - dp[i] += dp[i - 2]; + // 判断前两个字符是否有效组合 + if (two <= 26) { + dp[i] += dp[i - 2]; // 加上前两个位置的解码方式 + } } - return dp[n]; + + return dp[n]; // 返回完整字符串的解码方式总数 } ``` + +### 代码分析 + +- **时间复杂度**:O(n),其中 n 是字符串的长度。我们只进行了一次线性遍历。 +- **空间复杂度**:O(n),需要一个数组来存储解码方式的计数。不过,我们可以用两个变量优化空间复杂度到 O(1)。 + +通过以上步骤,我们能够有效地计算出数字翻译成字符串的不同方式,从而解决此问题。 \ No newline at end of file diff --git "a/notes/51. \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" "b/notes/51. \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" index 959857d913..7f1ab9fe40 100644 --- "a/notes/51. \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" +++ "b/notes/51. \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" @@ -4,45 +4,63 @@ ## 题目描述 -在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。 +在一个数组中,如果前面的数字大于后面的数字,则这两个数字组成一个逆序对。给定一个整数数组,要求计算该数组中的逆序对的总数。 ## 解题思路 +逆序对的检索通常采用归并排序的方式来解决,这样可以在 O(n log n) 的时间复杂度内完成逆序对的计数。归并排序的基本思想是将数组分成两半,对每一半进行排序,然后在合并的过程中统计逆序对的数量。 + ```java -private long cnt = 0; -private int[] tmp; // 在这里声明辅助数组,而不是在 merge() 递归函数中声明 +private long cnt = 0; // 用于计数逆序对的总数 +private int[] tmp; // 辅助数组,用于存放合并过程中的中间结果 public int InversePairs(int[] nums) { - tmp = new int[nums.length]; - mergeSort(nums, 0, nums.length - 1); - return (int) (cnt % 1000000007); + tmp = new int[nums.length]; // 初始化辅助数组 + mergeSort(nums, 0, nums.length - 1); // 调用归并排序函数 + return (int) (cnt % 1000000007); // 返回逆序对数目模 1000000007 } private void mergeSort(int[] nums, int l, int h) { - if (h - l < 1) + if (h - l < 1) // 递归结束条件 return; - int m = l + (h - l) / 2; - mergeSort(nums, l, m); - mergeSort(nums, m + 1, h); - merge(nums, l, m, h); + int m = l + (h - l) / 2; // 中间索引 + mergeSort(nums, l, m); // 对左半部分进行排序 + mergeSort(nums, m + 1, h); // 对右半部分进行排序 + merge(nums, l, m, h); // 合并并统计逆序对 } private void merge(int[] nums, int l, int m, int h) { - int i = l, j = m + 1, k = l; - while (i <= m || j <= h) { - if (i > m) - tmp[k] = nums[j++]; - else if (j > h) - tmp[k] = nums[i++]; - else if (nums[i] <= nums[j]) - tmp[k] = nums[i++]; - else { - tmp[k] = nums[j++]; - this.cnt += m - i + 1; // nums[i] > nums[j],说明 nums[i...mid] 都大于 nums[j] + int i = l, j = m + 1, k = l; // i指向左半部分,j指向右半部分 + while (i <= m || j <= h) { // 在左半部分或右半部分还有元素时继续 + if (i > m) // 左半部分元素已全部归并 + tmp[k] = nums[j++]; // 将右半部分的元素直接放入 tmp 中 + else if (j > h) // 右半部分元素已全部归并 + tmp[k] = nums[i++]; // 将左半部分的元素放入 tmp 中 + else if (nums[i] <= nums[j]) // 当前左半部分元素小于等于右半部分的元素 + tmp[k] = nums[i++]; // 将左半部分的元素放入 tmp 中 + else { // 当前位置左半部分元素大于右半部分元素 + tmp[k] = nums[j++]; // 将右半部分的元素放入 tmp 中 + this.cnt += m - i + 1; // nums[i] > nums[j],这意味着 nums[i...mid] 中的所有元素都大于 nums[j] } - k++; + k++; // 移动到 tmp 的下一个位置 } - for (k = l; k <= h; k++) + for (k = l; k <= h; k++) // 最后将 tmp 的结果拷贝回原数组 nums nums[k] = tmp[k]; } ``` + +## 代码解释 + +1. **变量声明**:我们使用 `cnt` 来统计逆序对的数量,`tmp` 是一个辅助数组,用于存放合并过程中产生的有序数组。 + +2. **主函数**:`InversePairs` 函数是主入口,首先初始化辅助数组,然后调用 `mergeSort` 函数对数组进行归并排序,并在此过程中计算逆序对的数目。 + +3. **归并排序**: + - `mergeSort` 方法通过递归地将原数组分解为多个子数组,直到每个子数组都只有一个元素。然后。这两部分进行合并并统计逆序对。 + +4. **合并过程**: + - 在 `merge` 函数中,我们通过双指针方法将左右两个子数组合并成一个有序数组,并在此过程中统计逆序对的数量。当发现左侧元素大于右侧元素时,可以确定左边境界内的所有元素都构成逆序对。 + +5. **返回结果**:最终返回的结果是 `cnt` 模 1000000007,以避免结果过大。此方法不仅高效而且能处理较大的数据规模。 + +通过以上步骤,我们能够有效地计算出数组中的逆序对数量。 \ No newline at end of file diff --git "a/notes/52. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" "b/notes/52. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" index bb96e3d9fd..ce765fe1de 100644 --- "a/notes/52. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" +++ "b/notes/52. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" @@ -1,24 +1,75 @@ # 52. 两个链表的第一个公共结点 -[NowCoder](https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&tqId=11189&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) +[牛客网](https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&tqId=11189&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) ## 题目描述 +给定两个一个链表的头结点,求这两个链表的第一个公共结点。如果没有公共结点,返回 null。 + +### 示例图 + <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5f1cb999-cb9a-4f6c-a0af-d90377295ab8.png" width="500"/> </div><br> ## 解题思路 -设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 +我们可以通过两个指针分别遍历两个链表,通过改变指针的指向实现同步遍历。 + +假设链表 A 的长度为 \(a + c\),链表 B 的长度为 \(b + c\),其中 \(c\) 是两个链表的公共部分长度。那么可以得出: + +\[ +a + c + b = b + c + a +\] + +当指针 l1 遍历到链表 A 的尾部时,我们将其指向链表 B 的头部,同样的,当指针 l2 遍历到链表 B 的尾部时,将其指向链表 A 的头部。这样能够保证两个指针在访问完各自链表的同时,会在公共部分同步相遇。 -当访问链表 A 的指针访问到链表尾部时,令它从链表 B 的头部重新开始访问链表 B;同样地,当访问链表 B 的指针访问到链表尾部时,令它从链表 A 的头部重新开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。 +### 代码实现 + +以下是 Java 语言的实现代码: ```java +public class ListNode { + int val; + ListNode next; + + ListNode(int x) { + val = x; + next = null; + } +} + public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { - ListNode l1 = pHead1, l2 = pHead2; + // 初始化两个指针 + ListNode l1 = pHead1; + ListNode l2 = pHead2; + + // 只要l1和l2不相同,就继续查找 while (l1 != l2) { + // 遇到链表的尾部则跳转到另一个链表的头部 l1 = (l1 == null) ? pHead2 : l1.next; l2 = (l2 == null) ? pHead1 : l2.next; } + + // 返回相遇的节点 return l1; } ``` + +### 代码解析 + +1. **ListNode 类**:定义了链表的节点类,包含一个整数值和指向下一个节点的指针。 + +2. **FindFirstCommonNode 方法**: + - 初始化两个指针 `l1` 和 `l2`,分别指向链表 A 和链表 B 的头结点。 + - 使用 `while` 循环,当 `l1` 与 `l2` 不同的时候继续循环。 + - 在循环体内,若 `l1` 指针已到达链表 A 的末尾,则将其指向链表 B 的头结点;反之亦然。 + - 当两个指针相等时,说明它们相遇,返回相遇的节点,即为两个链表的第一个公共结点。 + +### 时间复杂度 + +时间复杂度为 \(O(N + M)\),其中 \(N\) 和 \(M\) 分别是两个链表的长度。 + +### 空间复杂度 + +空间复杂度为 \(O(1)\),我们只使用了常数个指针。 + +通过这种方式,我们成功找到了两个链表的第一个公共结点,而不需要额外的空间去存储节点。 \ No newline at end of file diff --git "a/notes/53. \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" "b/notes/53. \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" index 4e66780423..2797cbb85c 100644 --- "a/notes/53. \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" +++ "b/notes/53. \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" @@ -6,68 +6,83 @@ ## 题目描述 -```html -Input: -nums = 1, 2, 3, 3, 3, 3, 4, 6 +给定一个有序数组 `nums` 和一个数字 `K`,请计算数字 `K` 在数组中出现的次数。 + +### 示例 + +```plaintext +输入: +nums = [1, 2, 3, 3, 3, 3, 4, 6] K = 3 -Output: +输出: 4 ``` ## 解题思路 -只要能找出给定的数字 k 在有序数组第一个位置和最后一个位置,就能知道该数字出现的次数。 +要找出数字 `K` 在有序数组中出现的次数,关键是确定 `K` 在数组中第一次和最后一次出现的位置。可以通过修改二分查找的方法来实现这一点。 -先考虑如何实现寻找数字在有序数组的第一个位置。正常的二分查找如下,在查找到给定元素 k 之后,立即返回当前索引下标。 +### 1. 查找数字 `K` 第一次出现的位置 + +在进行二分查找时,如果在中间位置 `m` 找到了数字 `K`,我们需要继续向左查找,以确定 `K` 的第一次出现位置。因此,与标准的二分查找不同的是,在条件判断时,我们要查找 `nums[m] >= K`: ```java -public int binarySearch(int[] nums, int K) { - int l = 0, h = nums.length - 1; - while (l <= h) { +private int binarySearchFirst(int[] nums, int K) { + int l = 0, h = nums.length; // h 初始化为 nums.length + while (l < h) { int m = l + (h - l) / 2; - if (nums[m] == K) { - return m; - } else if (nums[m] > K) { - h = m - 1; + if (nums[m] >= K) { + h = m; // 继续在左边查找 } else { l = m + 1; } } - return -1; + return l; // 返回第一次出现的位置 } ``` -但是在查找第一个位置时,找到元素之后应该继续往前找。也就是当 nums[m]\>=k 时,在左区间继续查找,左区间应该包含 m 位置。 +### 2. 查找数字 `K` 最后一次出现的位置 + +为了找到数字 `K` 最后一次出现的位置,我们可以寻找 `K + 1` 的位置,然后向前移动一个位置。这是因为 `K` 的最后一次出现位置就是 `K + 1` 的第一次出现位置前一个位置。 ```java -private int binarySearch(int[] nums, int K) { - int l = 0, h = nums.length; +private int binarySearchLast(int[] nums, int K) { + int l = 0, h = nums.length; // h 初始化为 nums.length while (l < h) { int m = l + (h - l) / 2; - if (nums[m] >= K) - h = m; - else - l = m + 1; + if (nums[m] > K) { + h = m; // 继续在左边查找 + } else { + l = m + 1; // 查找右边 + } } - return l; + return l - 1; // 返回最后一次出现的位置 } ``` -查找最后一个位置可以转换成寻找 k+1 的第一个位置,并再往前移动一个位置。 +### 3. 计算数字 `K` 的出现次数 + +最后,将上述两个函数结合在一起,计算 `K` 出现的次数: ```java public int GetNumberOfK(int[] nums, int K) { - int first = binarySearch(nums, K); - int last = binarySearch(nums, K + 1); - return (first == nums.length || nums[first] != K) ? 0 : last - first; + int first = binarySearchFirst(nums, K); // 获取第一次出现的位置 + int last = binarySearchLast(nums, K); // 获取最后一次出现的位置 + + // 检查 K 是否存在 + if (first == nums.length || nums[first] != K) { + return 0; // 如果 K 不存在,则出现次数为 0 + } + + return last - first + 1; // 计算 K 的出现次数 } ``` -需要注意以上实现的查找第一个位置的 binarySearch 方法,h 的初始值为 nums.length,而不是 nums.length - 1。先看以下示例: +### 注意事项 -``` -nums = [2,2], k = 2 -``` +在实现第一个位置的二分查找时,注意到 `h` 的初始值为 `nums.length`,而不是 `nums.length - 1`。这是因为我们希望能够覆盖 `K` 大于数组中所有元素的情况。 + +例如,对于数组 `nums = [2, 2]` 和 `K = 2`,如果 `h` 的取值为 `nums.length - 1`,则在查找 `K + 1` 的位置时会导致返回值错误。设置 `h` 为 `nums.length` 可以确保在查找过程中,能够正确返回插入位置和出现次数。 -如果 h 的取值为 nums.length - 1,那么在查找最后一个位置时,binarySearch(nums, k + 1) - 1 = 1 - 1 = 0。这是因为 binarySearch 只会返回 [0, nums.length - 1] 范围的值,对于 binarySearch([2,2], 3) ,我们希望返回 3 插入 nums 中的位置,也就是数组最后一个位置再往后一个位置,即 nums.length。所以我们需要将 h 取值为 nums.length,从而使得 binarySearch 返回的区间更大,能够覆盖 k 大于 nums 最后一个元素的情况。 +通过这种方法,我们得以有效地找出一个数字在有序数组中出现的次数,复杂度为 O(log n),极大地提高了查询效率。 \ No newline at end of file diff --git "a/notes/56. \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" "b/notes/56. \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" index 4ab74ad3c3..3b04657472 100644 --- "a/notes/56. \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" +++ "b/notes/56. \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" @@ -6,40 +6,81 @@ ## 题目描述 -一个整型数组里除了两个数字之外,其他的数字都出现了两次,找出这两个数。 +给定一个整型数组,其中除了两个数字以外,其他数字都出现了两次。请你找出这两个只出现一次的数字。 ## 解题思路 -两个相等的元素异或的结果为 0,而 0 与任意数 x 异或的结果都为 x。 +为了高效解决此问题,我们可以使用异或运算的特性。异或运算的一些关键特点包括: -对本题给的数组的所有元素执行异或操作,得到的是两个不存在重复的元素异或的结果。例如对于数组 [x,x,y,y,z,k],x^x^y^y^z^k = 0^y^y^z^k = y^y^z^k = 0^z^k = z^k。 +1. 两个相同的数字异或的结果为0:`x ^ x = 0` +2. 任何数字与0异或的结果仍然是该数字:`0 ^ x = x` +3. 异或运算具有交换和结合律:`x ^ y = y ^ x` 和 `(x ^ y) ^ z = x ^ (y ^ z)` -两个不相等的元素在位级表示上一定会有所不同,因此这两个元素异或得到的结果 diff 一定不为 0。位运算 diff & -diff 能得到 diff 位级表示中最右侧为 1 的位,令 diff = diff & -diff。将 diff 作为区分两个元素的依据,一定有一个元素对 diff 进行异或的结果为 0,另一个结果非 0。设不相等的两个元素分别为 z 和 k,遍历数组所有元素,判断元素与 diff 的异或结果是否为 0,如果是的话将元素与 z 进行异或并赋值给 z,否则与 k 进行异或并赋值给 k。数组中相等的元素一定会同时与 z 或者与 k 进行异或操作,而不是一个与 z 进行异或,一个与 k 进行异或。而且这些相等的元素异或的结果为 0,因此最后 z 和 k 只是不相等的两个元素与 0 异或的结果,也就是不相等两个元素本身。 +基于这些特性,我们首先对数组中的所有元素进行异或操作,最终结果将是那两个只出现一次的数字的异或结果。 -下面的解法中,num1 和 num2 数组的第一个元素是用来保持返回值的... 实际开发中不推荐这种返回值的方式。 +例如,考虑数组 `[x, x, y, y, z, k]`: +- `x ^ x ^ y ^ y ^ z ^ k = 0 ^ z ^ k = z ^ k` + +设最终的异或结果为 `diff = z ^ k`,我们可以观察到 `diff` 一定不为0,因为 z 和 k 不相等。 + +### 获取区分位 + +由于 z 和 k 在位级表示上必有不同,因此我们可以通过位运算找到 `diff` 中任一位为1的位置,以便明确区分这两个数字。我们使用 `diff & -diff` 来获取 `diff` 最右侧的1所对应的位,该操作可以帮助我们得到分标的依据。 + +接下来,我们遍历数组,将每一个元素与 `diff` 进行异或: +- 如果某个元素与 `diff` 的异或结果为0,则该元素与 z 同属一类,参与到 z 的异或中。 +- 如果非0,则该元素与 k 同属一类,参与到 k 的异或中。 + +通过这种方式,最终我们将得到两个只出现一次的数字。 + +## 代码实现 + +下面是用 Java 语言实现的解决方案: ```java -public int[] FindNumsAppearOnce (int[] nums) { - int[] res = new int[2]; - int diff = 0; - for (int num : nums) - diff ^= num; - diff &= -diff; - for (int num : nums) { - if ((num & diff) == 0) - res[0] ^= num; - else - res[1] ^= num; - } - if (res[0] > res[1]) { - swap(res); +public class Solution { + public int[] FindNumsAppearOnce(int[] nums) { + int[] res = new int[2]; // 用于存储两个只出现一次的数字 + int diff = 0; + + // Step 1: 计算所有数字的异或结果 + for (int num : nums) { + diff ^= num; // diff 现在是 z ^ k + } + + // Step 2: 获取 diff 最右侧的1 + diff &= -diff; + + // Step 3: 分类并找到 z 和 k + for (int num : nums) { + if ((num & diff) == 0) { + res[0] ^= num; // 属于第一类 + } else { + res[1] ^= num; // 属于第二类 + } + } + + // 确保返回的结果按从小到大排列 + if (res[0] > res[1]) { + swap(res); + } + return res; } - return res; -} -private void swap(int[] nums) { - int t = nums[0]; - nums[0] = nums[1]; - nums[1] = t; + // 辅助方法:交换数组中的两个元素 + private void swap(int[] nums) { + int temp = nums[0]; + nums[0] = nums[1]; + nums[1] = temp; + } } ``` + +### 代码解析 + +1. **初始化**:我们首先创建一个大小为2的数组 `res` 来存储结果。 +2. **异或计算**:通过循环遍历数组,将所有数字进行异或运算,最终得到 `diff`。 +3. **区分位提取**:使用 `diff & -diff` 获取 `diff` 中最后一个为1的位,以便进行后续的分类。 +4. **分类异或**:再通过一次遍历,将数字分类到 `res[0]` 或 `res[1]`,直到最终得出两个只出现一次的数字。 + +此解法的时间复杂度为 O(n),空间复杂度为 O(1)。通过这种高效的方法,我们能够快速找出数组中唯一出现的两个数字。 \ No newline at end of file diff --git a/notes/HTTP.md b/notes/HTTP.md index 35e649948b..553d770d6d 100644 --- a/notes/HTTP.md +++ b/notes/HTTP.md @@ -1,3 +1,4 @@ +```markdown # HTTP <!-- GFM-TOC --> * [HTTP](#http) @@ -62,18 +63,18 @@ ### 请求和响应报文 -客户端发送一个请求报文给服务器,服务器根据请求报文中的信息进行处理,并将处理结果放入响应报文中返回给客户端。 +在 HTTP 通信中,客户端向服务器发送请求报文,服务器根据请求报文中的信息进行处理,并将处理结果通过响应报文返回给客户端。 -请求报文结构: +#### 请求报文结构: -- 第一行是包含了请求方法、URL、协议版本; -- 接下来的多行都是请求首部 Header,每个首部都有一个首部名称,以及对应的值。 -- 一个空行用来分隔首部和内容主体 Body -- 最后是请求的内容主体 +1. 第一行包含请求方法、URL 和协议版本。 +2. 接下来的多行是请求首部字段,每个字段由名称和值组成。 +3. 一个空行用来分隔首部字段和消息主体(Body)。 +4. 最后是请求的内容主体(可选)。 -``` +```http GET http://www.example.com/ HTTP/1.1 -Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cache-Control: max-age=0 @@ -87,14 +88,14 @@ User-Agent: Mozilla/5.0 xxx param1=1¶m2=2 ``` -响应报文结构: +#### 响应报文结构: -- 第一行包含协议版本、状态码以及描述,最常见的是 200 OK 表示请求成功了 -- 接下来多行也是首部内容 -- 一个空行分隔首部和内容主体 -- 最后是响应的内容主体 +1. 第一行包含协议版本、状态码及其描述,最常见的是 `200 OK` 表示请求成功。 +2. 接下来的多行是响应首部字段。 +3. 一个空行分隔首部和内容主体。 +4. 最后是响应的内容主体。 -``` +```http HTTP/1.1 200 OK Age: 529651 Cache-Control: max-age=604800 @@ -116,56 +117,51 @@ X-Cache: HIT <html> <head> <title>Example Domain</title> - // 省略... + <!-- 省略 ... --> </body> </html> - ``` ### URL -HTTP 使用 URL( **U** niform **R**esource **L**ocator,统一资源定位符)来定位资源,它是 URI(**U**niform **R**esource **I**dentifier,统一资源标识符)的子集,URL 在 URI 的基础上增加了定位能力。URI 除了包含 URL,还包含 URN(Uniform Resource Name,统一资源名称),它只是用来定义一个资源的名称,并不具备定位该资源的能力。例如 urn:isbn:0451450523 用来定义一个书籍名称,但是却没有表示怎么找到这本书。 +HTTP 使用 URL(**U**niform **R**esource **L**ocator,统一资源定位符)来定位资源,URL 是 URI(**U**niform **R**esource **I**dentifier,统一资源标识符)的子集。URL 基于 URI 提供了定位能力。URI 除了包含 URL,还包含 URN(Uniform Resource Name,统一资源名称),URN 仅用于定义资源的名称,并不具备定位该资源的能力。例如 `urn:isbn:0451450523` 定义了一本书名,但并不能说明如何找到这本书。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/8441b2c4-dca7-4d6b-8efb-f22efccaf331.png" width="500px"> </div><br> -- [wikipedia:统一资源标志符](https://zh.wikipedia.org/wiki/统一资源标志符) -- [wikipedia: URL](https://en.wikipedia.org/wiki/URL) -- [rfc2616:3.2.2 http URL](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.2) -- [What is the difference between a URI, a URL and a URN?](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn) +- [维基百科:统一资源标志符](https://zh.wikipedia.org/wiki/统一资源标志符) +- [维基百科:URL](https://en.wikipedia.org/wiki/URL) +- [RFC 2616:3.2.2 HTTP URL](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.2) +- [关于 URI, URL 和 URN 的区别](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn) ## 二、HTTP 方法 -客户端发送的 **请求报文** 第一行为请求行,包含了方法字段。 +客户端发送的请求报文中,第一行为请求行,包含请求方法。 ### GET -> 获取资源 +> 用于获取资源 -当前网络请求中,绝大部分使用的是 GET 方法。 +在各种网络请求中,GET 方法是最常用的。 ### HEAD > 获取报文首部 -和 GET 方法类似,但是不返回报文实体主体部分。 - -主要用于确认 URL 的有效性以及资源更新的日期时间等。 +HEAD 方法类似于 GET 方法,但不返回报文的主体部分。通常用来验证 URL 的有效性或获取资源的更新时间。 ### POST > 传输实体主体 -POST 主要用来传输数据,而 GET 主要用来获取资源。 - -更多 POST 与 GET 的比较请见第九章。 +POST 方法通常用于传输数据,而 GET 方法主要用于获取资源。详见第九章关于 POST 与 GET 的比较。 ### PUT > 上传文件 -由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。 +PUT 方法可以上传文件,但因不具备身份验证机制,安全性较差,因此不建议广泛使用。 -```html +```http PUT /new.html HTTP/1.1 Host: example.com Content-type: text/html @@ -178,9 +174,9 @@ Content-length: 16 > 对资源进行部分修改 -PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。 +PATCH 方法用于部分更新资源,而 PUT 方法则是完全替代原始资源。 -```html +```http PATCH /file.txt HTTP/1.1 Host: www.example.com Content-Type: application/example @@ -192,11 +188,11 @@ Content-Length: 100 ### DELETE -> 删除文件 +> 删除资源 -与 PUT 功能相反,并且同样不带验证机制。 +与 PUT 方法相对,DELETE 方法用于删除资源,同样不带验证机制。 -```html +```http DELETE /file.html HTTP/1.1 ``` @@ -204,17 +200,15 @@ DELETE /file.html HTTP/1.1 > 查询支持的方法 -查询指定的 URL 能够支持的方法。 - -会返回 `Allow: GET, POST, HEAD, OPTIONS` 这样的内容。 +OPTIONS 方法查询指定 URL 能够支持哪些请求方法。响应中会返回 `Allow: GET, POST, HEAD, OPTIONS` 等信息。 ### CONNECT -> 要求在与代理服务器通信时建立隧道 +> 在与代理服务器通信时建立隧道 -使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。 +CONNECT 方法用于建立 SSL(安全套接层)和 TLS(传输层安全)协议的加密隧道。 -```html +```http CONNECT www.example.com:443 HTTP/1.1 ``` @@ -222,89 +216,73 @@ CONNECT www.example.com:443 HTTP/1.1 ### TRACE -> 追踪路径 - -服务器会将通信路径返回给客户端。 +> 追踪请求路径 -发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。 +TRACE 方法用于追踪请求路径,服务器会将完整的请求路径返回给客户端。发送请求时,可以在 `Max-Forwards` 首部字段指定跳转的最大次数。 -通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪)。 +TRACE 方法通常不会被广泛使用,且容易受到 XST(跨站追踪)攻击。 -- [rfc2616:9 Method Definitions](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) +- [RFC 2616:9 方法定义](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) ## 三、HTTP 状态码 -服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。 +服务器返回的响应报文中,第一行为状态行,包含状态码及其描述,用于告知客户端请求的结果。 | 状态码 | 类别 | 含义 | | :---: | :---: | :---: | -| 1XX | Informational(信息性状态码) | 接收的请求正在处理 | -| 2XX | Success(成功状态码) | 请求正常处理完毕 | -| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 | -| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 | -| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 | +| 1XX | Informational(信息性状态码) | 表示接收的请求正在处理 | +| 2XX | Success(成功状态码) | 表示请求已正常处理 | +| 3XX | Redirection(重定向状态码) | 表示需要进行附加操作以完成请求 | +| 4XX | Client Error(客户端错误状态码) | 表示服务器无法处理请求 | +| 5XX | Server Error(服务器错误状态码) | 表示服务器处理请求出错 | ### 1XX 信息 -- **100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。 +- **100 Continue**:说明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。 ### 2XX 成功 -- **200 OK** - -- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 - -- **206 Partial Content** :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。 +- **200 OK**:请求成功。 +- **204 No Content**:请求成功处理,但不返回数据主体。多用于只需要从客户端往服务器发送信息的操作。 +- **206 Partial Content**:指示客户端发起的范围请求成功,响应中包含由 `Content-Range` 指定范围的内容。 ### 3XX 重定向 -- **301 Moved Permanently** :永久性重定向 - -- **302 Found** :临时性重定向 - -- **303 See Other** :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。 - -- 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。 - -- **304 Not Modified** :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。 - -- **307 Temporary Redirect** :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。 +- **301 Moved Permanently**:永久重定向。 +- **302 Found**:临时重定向。 +- **303 See Other**:与 302 相同,但明确要求客户端使用 GET 方法获取资源。 +- **304 Not Modified**:如果请求中包含条件(如 `If-Modified-Since`),并且条件不满足,则返回该状态码。 +- **307 Temporary Redirect**:临时重定向,要求浏览器不将重定向请求的 POST 方法改为 GET 方法。 ### 4XX 客户端错误 -- **400 Bad Request** :请求报文中存在语法错误。 - -- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。 - -- **403 Forbidden** :请求被拒绝。 - -- **404 Not Found** +- **400 Bad Request**:请求报文中存在语法错误。 +- **401 Unauthorized**:该状态码表示请求需要认证信息。 +- **403 Forbidden**:请求被拒绝。 +- **404 Not Found**:请求的资源未找到。 ### 5XX 服务器错误 -- **500 Internal Server Error** :服务器正在执行请求时发生错误。 - -- **503 Service Unavailable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 +- **500 Internal Server Error**:服务器在执行请求时出错。 +- **503 Service Unavailable**:服务器暂时无法处理请求,可能因过载或维护。 ## 四、HTTP 首部 -有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。 - -各种首部字段及其含义如下(不需要全记,仅供查阅): +HTTP 中有四种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。 ### 通用首部字段 | 首部字段名 | 说明 | | :--: | :--: | | Cache-Control | 控制缓存的行为 | -| Connection | 控制不再转发给代理的首部字段、管理持久连接| -| Date | 创建报文的日期时间 | +| Connection | 控制不再转发给代理的首部字段,管理持久连接 | +| Date | 报文创建的日期时间 | | Pragma | 报文指令 | -| Trailer | 报文末端的首部一览 | +| Trailer | 报文末端首部的概览 | | Transfer-Encoding | 指定报文主体的传输编码方式 | | Upgrade | 升级为其他协议 | | Via | 代理服务器的相关信息 | -| Warning | 错误通知 | +| Warning | 警告消息 | ### 请求首部字段 @@ -317,16 +295,16 @@ CONNECT www.example.com:443 HTTP/1.1 | Authorization | Web 认证信息 | | Expect | 期待服务器的特定行为 | | From | 用户的电子邮箱地址 | -| Host | 请求资源所在服务器 | -| If-Match | 比较实体标记(ETag) | -| If-Modified-Since | 比较资源的更新时间 | -| If-None-Match | 比较实体标记(与 If-Match 相反) | -| If-Range | 资源未更新时发送实体 Byte 的范围请求 | -| If-Unmodified-Since | 比较资源的更新时间(与 If-Modified-Since 相反) | +| Host | 请求资源所在的服务器 | +| If-Match | 比较实体标签(ETag) | +| If-Modified-Since | 比较资源的最后更新时间 | +| If-None-Match | 比较实体标签 | +| If-Range | 资源未更新时发送的实体字节范围请求 | +| If-Unmodified-Since | 资源更新时间的比较 | | Max-Forwards | 最大传输逐跳数 | -| Proxy-Authorization | 代理服务器要求客户端的认证信息 | -| Range | 实体的字节范围请求 | -| Referer | 对请求中 URI 的原始获取方 | +| Proxy-Authorization | 代理服务器要求的客户端认证信息 | +| Range | 请求的实体字节范围 | +| Referer | 获取请求中 URI 的来源 | | TE | 传输编码的优先级 | | User-Agent | HTTP 客户端程序的信息 | @@ -335,12 +313,12 @@ CONNECT www.example.com:443 HTTP/1.1 | 首部字段名 | 说明 | | :--: | :--: | | Accept-Ranges | 是否接受字节范围请求 | -| Age | 推算资源创建经过时间 | +| Age | 资源创建经过的时间 | | ETag | 资源的匹配信息 | -| Location | 令客户端重定向至指定 URI | +| Location | 重定向到的 URI | | Proxy-Authenticate | 代理服务器对客户端的认证信息 | -| Retry-After | 对再次发起请求的时机要求 | -| Server | HTTP 服务器的安装信息 | +| Retry-After | 再次发起请求的适宜时机 | +| Server | HTTP 服务器的相关信息 | | Vary | 代理服务器缓存的管理信息 | | WWW-Authenticate | 服务器对客户端的认证信息 | @@ -349,15 +327,15 @@ CONNECT www.example.com:443 HTTP/1.1 | 首部字段名 | 说明 | | :--: | :--: | | Allow | 资源可支持的 HTTP 方法 | -| Content-Encoding | 实体主体适用的编码方式 | +| Content-Encoding | 实体主体的编码方式 | | Content-Language | 实体主体的自然语言 | | Content-Length | 实体主体的大小 | -| Content-Location | 替代对应资源的 URI | +| Content-Location | 替代资源的 URI | | Content-MD5 | 实体主体的报文摘要 | | Content-Range | 实体主体的位置范围 | | Content-Type | 实体主体的媒体类型 | -| Expires | 实体主体过期的日期时间 | -| Last-Modified | 资源的最后修改日期时间 | +| Expires | 实体主体过期的时间 | +| Last-Modified | 资源的最后修改时间 | ## 五、具体应用 @@ -367,38 +345,33 @@ CONNECT www.example.com:443 HTTP/1.1 #### 1. 短连接与长连接 -当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问的 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。 +在访问一个包含多张图片的 HTML 页面时,若每次都新建 TCP 连接,则会造成较大开销。长连接只需建立一次 TCP 连接,便能进行多次 HTTP 通信。 -长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。 - -- 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 `Connection : close`; -- 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 `Connection : Keep-Alive`。 +- 从 HTTP/1.1 开始,默认使用长连接。 +- 要断开连接,需由客户端或服务器发起请求,使用 `Connection: close`。 +- 在 HTTP/1.1 之前,默认使用短连接。 #### 2. 流水线 -默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。 - -流水线是在同一条长连接上连续发出请求,而不用等待响应返回,这样可以减少延迟。 +在默认情况下,HTTP 请求按顺序发出,后一个请求需等当前请求的响应返回后才能发出。而流水线允许在同一长期连接上连续发出请求,无需等待响应,从而减少延迟。 ### Cookie -HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 +HTTP 协议是无状态的,为了提高性能而使其能够处理大量事务。从 HTTP/1.1 开始,引入 Cookie 以保存状态信息。 -Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。 - -Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。 +Cookie 是由服务器发给用户浏览器的一小块数据,浏览器会在后续请求中携带 Cookie,以此告知服务器是否是同一用户。由于每次请求都需要携带 Cookie 数据,会造成额外的性能开销(尤其在移动环境中)。 #### 1. 用途 -- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) +- 会话状态管理(如用户登录状态、购物车等) - 个性化设置(如用户自定义设置、主题等) -- 浏览器行为跟踪(如跟踪分析用户行为等) +- 浏览器行为跟踪(如用户行为分析) #### 2. 创建过程 -服务器发送的响应报文包含 Set-Cookie 首部字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。 +服务器发送的响应报文中包含 `Set-Cookie` 首部字段,客户端在收到后会将 Cookie 保存。 -```html +```http HTTP/1.0 200 OK Content-type: text/html Set-Cookie: yummy_cookie=choco @@ -407,9 +380,9 @@ Set-Cookie: tasty_cookie=strawberry [page content] ``` -客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。 +在后续请求中,客户端将 Cookie 信息携带至服务器。 -```html +```http GET /sample_page.html HTTP/1.1 Host: www.example.org Cookie: yummy_cookie=choco; tasty_cookie=strawberry @@ -417,28 +390,22 @@ Cookie: yummy_cookie=choco; tasty_cookie=strawberry #### 3. 分类 -- 会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。 -- 持久性 Cookie:指定过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie。 +- 会话 Cookie:仅在会话期内有效,浏览器关闭后自动删除。 +- 持久性 Cookie:指定过期时间(`Expires`)后有效。 -```html +```http Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; ``` #### 4. 作用域 -Domain 标识指定了哪些主机可以接受 Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了 Domain,则一般包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。 - -Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。例如,设置 Path=/docs,则以下地址都会匹配: +`Domain` 字段指定哪些主机可以接收 Cookie。若未指定,默认为当前文档的主机(不包括子域名)。`Path` 字段用于指定哪些路径可以接收 Cookie。 -- /docs -- /docs/Web/ -- /docs/Web/HTTP +#### 5. JavaScript 操作 -#### 5. JavaScript +浏览器通过 `document.cookie` 属性可以创建或访问非 `HttpOnly` 标记的 Cookie。 -浏览器通过 `document.cookie` 属性可创建新的 Cookie,也可通过该属性访问非 HttpOnly 标记的 Cookie。 - -```html +```javascript document.cookie = "yummy_cookie=choco"; document.cookie = "tasty_cookie=strawberry"; console.log(document.cookie); @@ -446,185 +413,157 @@ console.log(document.cookie); #### 6. HttpOnly -标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 `document.cookie` API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。 +标记为 `HttpOnly` 的 Cookie 不能被 JavaScript 访问,这是为了防止跨站脚本攻击(XSS)。 -```html +```http Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly ``` #### 7. Secure -标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。 +标记为 `Secure` 的 Cookie 仅可通过 HTTPS 请求发送给服务器。但即使设置了 `Secure`,敏感信息也不应通过 Cookie 传输。 #### 8. Session -除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 - -Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 +用户信息可以存储在服务器端的 Session 中,安全性较高。Session 存储在文件、数据库或 Redis 中。 使用 Session 维护用户登录状态的过程如下: -- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; -- 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID; -- 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中; -- 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。 +1. 用户登录时,提交用户名和密码。 +2. 服务器验证信息,存储用户数据于 Redis,并生成 Session ID。 +3. 服务器响应报文的 `Set-Cookie` 包含 Session ID,客户端保存该 Cookie。 +4. 后续请求中,客户端携带 Session ID,服务器查询 Redis 返回用户信息。 -应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。 +确保 Session ID 的安全性,定期更新以防止泄露。需在较高安全性场景中,如转账操作,二次身份验证(如密码、短信验证码)为提升安全性。 #### 9. 浏览器禁用 Cookie -此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外,不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。 +若浏览器禁用 Cookie,会影响状态保存。此时可考虑 URL 重写,把 Session ID作为 URL 参数传递。 #### 10. Cookie 与 Session 选择 -- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session; -- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密; -- 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。 +- Cookie 只支持 ASCII 字符,Session 支持更复杂数据。 +- Cookie 易被恶意访问,存储隐私数据时需加密,而 Session 更加安全。 +- 大型网站不推荐将所有用户信息存储进 Session,以避免开销过大。 ### 缓存 #### 1. 优点 -- 缓解服务器压力; -- 降低客户端获取资源的延迟:缓存通常位于内存中,读取缓存的速度更快。并且缓存服务器在地理位置上也有可能比源服务器来得近,例如浏览器缓存。 +- 减轻服务器负担; +- 降低客户端获取资源的延迟:缓存通常存于内存中,因此读取速度更快。 #### 2. 实现方法 -- 让代理服务器进行缓存; -- 让客户端浏览器进行缓存。 +- 代理服务器进行缓存; +- 客户端浏览器进行缓存。 #### 3. Cache-Control -HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。 +HTTP/1.1通过 `Cache-Control` 首部字段控制缓存。 -**3.1 禁止进行缓存** +**3.1 禁止缓存** -no-store 指令规定不能对请求或响应的任何一部分进行缓存。 +`no-store` 指令表示禁止缓存任何请求或响应。 -```html +```http Cache-Control: no-store ``` **3.2 强制确认缓存** -no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效时才能使用该缓存对客户端的请求进行响应。 +`no-cache` 指令要求缓存服务器在使用缓存前,必须先向源服务器进行验证。 -```html +```http Cache-Control: no-cache ``` -**3.3 私有缓存和公共缓存** +**3.3 私有与公共缓存** -private 指令规定了将资源作为私有缓存,只能被单独用户使用,一般存储在用户浏览器中。 +`private` 指定资源为私有缓存,仅能被单个用户使用。 -```html +```http Cache-Control: private ``` -public 指令规定了将资源作为公共缓存,可以被多个用户使用,一般存储在代理服务器中。 +`public` 指定资源为公共缓存,可以被多个用户使用。 -```html +```http Cache-Control: public ``` **3.4 缓存过期机制** -max-age 指令出现在请求报文,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。 +- `max-age` 指令用于指定缓存资源的有效时间。 +- `Expires` 暗示何时资源过期。 -max-age 指令出现在响应报文,表示缓存资源在缓存服务器中保存的时间。 - -```html -Cache-Control: max-age=31536000 -``` - -Expires 首部字段也可以用于告知缓存服务器该资源什么时候会过期。 - -```html -Expires: Wed, 04 Jul 2012 08:26:05 GMT -``` - -- 在 HTTP/1.1 中,会优先处理 max-age 指令; -- 在 HTTP/1.0 中,max-age 指令会被忽略掉。 +在 HTTP/1.1 中 `max-age` 指令优先处理。在 HTTP/1.0 中则会被忽略。 #### 4. 缓存验证 -需要先了解 ETag 首部字段的含义,它是资源的唯一标识。URL 不能唯一表示资源,例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。 - -```html -ETag: "82e22293907ce725faf67773957acd12" -``` - -可以将缓存资源的 ETag 值放入 If-None-Match 首部,服务器收到该请求后,判断缓存资源的 ETag 值和资源的最新 ETag 值是否一致,如果一致则表示缓存资源有效,返回 304 Not Modified。 +`ETag` 字段标识资源的唯一性,客户端可以在后续请求中将 `ETag` 值放入 `If-None-Match` 字段,来验证缓存的有效性。 -```html +```http If-None-Match: "82e22293907ce725faf67773957acd12" ``` -Last-Modified 首部字段也可以用于缓存验证,它包含在源服务器发送的响应报文中,指示源服务器对资源的最后修改时间。但是它是一种弱校验器,因为只能精确到一秒,所以它通常作为 ETag 的备用方案。如果响应首部字段里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 OK。如果请求的资源从那时起未经修改,那么返回一个不带有实体主体的 304 Not Modified 响应报文。 +`Last-Modified` 首部字段指示资源的最后修改时间,客户端可以在请求中添加 `If-Modified-Since` 来验证。如果服务器发现资源未修改,则返回 304 Not Modified。 -```html -Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT -``` - -```html +```http If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT ``` ### 内容协商 -通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。 +内容协商用于返回最合适的内容,例如根据浏览器默认语言选择响应的语言版本。 #### 1. 类型 **1.1 服务端驱动型** -客户端设置特定的 HTTP 首部字段,例如 Accept、Accept-Charset、Accept-Encoding、Accept-Language,服务器根据这些字段返回特定的资源。 +客户端设置 `Accept`、`Accept-Charset`、`Accept-Encoding` 和 `Accept-Language` 等 HTTP 首部,服务器根据这些字段返回相应资源。 -它存在以下问题: +此方法存在的问题包括: -- 服务器很难知道客户端浏览器的全部信息; -- 客户端提供的信息相当冗长(HTTP/2 协议的首部压缩机制缓解了这个问题),并且存在隐私风险(HTTP 指纹识别技术); -- 给定的资源需要返回不同的展现形式,共享缓存的效率会降低,而服务器端的实现会越来越复杂。 +- 服务器无法获取客户端的全部信息; +- 客户端提供的信息冗长且有隐私风险; +- 不同展现形式的资源会降低共享缓存的效率,增加服务器的复杂性。 **1.2 代理驱动型** -服务器返回 300 Multiple Choices 或者 406 Not Acceptable,客户端从中选出最合适的那个资源。 +服务器可以返回 `300 Multiple Choices` 或 `406 Not Acceptable`,客户端从中选择最适合的资源。 #### 2. Vary -```html +```http Vary: Accept-Language ``` -在使用内容协商的情况下,只有当缓存服务器中的缓存满足内容协商条件时,才能使用该缓存,否则应该向源服务器请求该资源。 - -例如,一个客户端发送了一个包含 Accept-Language 首部字段的请求之后,源服务器返回的响应包含 `Vary: Accept-Language` 内容,缓存服务器对这个响应进行缓存之后,在客户端下一次访问同一个 URL 资源,并且 Accept-Language 与缓存中的对应的值相同时才会返回该缓存。 +在使用内容协商的情况下,只有缓存服务器中的缓存满足条件时,才能使用该缓存,否则要向源服务器请求。 ### 内容编码 -内容编码将实体主体进行压缩,从而减少传输的数据量。 - -常用的内容编码有:gzip、compress、deflate、identity。 +内容编码操作用于对消息进行压缩,从而减少传输的数据量。常用的内容编码包括:gzip、compress、deflate、identity。 -浏览器发送 Accept-Encoding 首部,其中包含有它所支持的压缩算法,以及各自的优先级。服务器则从中选择一种,使用该算法对响应的消息主体进行压缩,并且发送 Content-Encoding 首部来告知浏览器它选择了哪一种算法。由于该内容协商过程是基于编码类型来选择资源的展现形式的,响应报文的 Vary 首部字段至少要包含 Content-Encoding。 +浏览器会在请求中发送 `Accept-Encoding` 首部,指明其支持的压缩算法,服务器选择一种并使用该算法对响应消息进行压缩,并通过 `Content-Encoding` 首部告知。 ### 范围请求 -如果网络出现中断,服务器只发送了一部分数据,范围请求可以使得客户端只请求服务器未发送的那部分数据,从而避免服务器重新发送所有数据。 +如果网络中断,服务器只发送了部分数据,范围请求可避免重新发送所有数据,仅请求未发送的部分。 #### 1. Range -在请求报文中添加 Range 首部字段指定请求的范围。 +请求报文中可添加 `Range` 首部字段指定请求的字节范围。 -```html +```http GET /z4d4kWk.jpg HTTP/1.1 Host: i.imgur.com Range: bytes=0-1023 ``` -请求成功的话服务器返回的响应包含 206 Partial Content 状态码。 +请求成功时,服务器返回 `206 Partial Content` 状态码。 -```html +```http HTTP/1.1 206 Partial Content Content-Range: bytes 0-1023/146515 Content-Length: 1024 @@ -634,29 +573,27 @@ Content-Length: 1024 #### 2. Accept-Ranges -响应首部字段 Accept-Ranges 用于告知客户端是否能处理范围请求,可以处理使用 bytes,否则使用 none。 +响应首部 `Accept-Ranges` 用于告知客户端是否能处理范围请求,可接受的值为 `bytes` 或 `none`。 -```html +```http Accept-Ranges: bytes ``` #### 3. 响应状态码 -- 在请求成功的情况下,服务器会返回 206 Partial Content 状态码。 -- 在请求的范围越界的情况下,服务器会返回 416 Requested Range Not Satisfiable 状态码。 -- 在不支持范围请求的情况下,服务器会返回 200 OK 状态码。 +- 成功请求时,返回 `206 Partial Content` 状态码。 +- 请求范围越界时,返回 `416 Requested Range Not Satisfiable` 状态码。 +- 不支持范围请求时,返回 `200 OK` 状态码。 ### 分块传输编码 -Chunked Transfer Encoding,可以把数据分割成多块,让浏览器逐步显示页面。 +分块传输编码(Chunked Transfer Encoding)将数据分为多个块逐步发送,便于浏览器逐步显示内容,提高用户体验。 ### 多部分对象集合 -一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔,每个部分都可以有首部字段。 +报文主体可以包含多种类型的实体,每个部分之间由边界(boundary)分隔,每个部分可以有独立的首部字段。常用于上传多个文件的场景。 -例如,上传多个表单时可以使用如下方式: - -```html +```http Content-Type: multipart/form-data; boundary=AaB03x --AaB03x @@ -673,50 +610,48 @@ Content-Type: text/plain ### 虚拟主机 -HTTP/1.1 使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。 +HTTP/1.1 引入虚拟主机技术,使得一台服务器能够处理多个域名,从逻辑上看似多台服务器。 ### 通信数据转发 #### 1. 代理 -代理服务器接受客户端的请求,并且转发给其它服务器。 - -使用代理的主要目的是: +代理服务器接受客户端请求转发至其它服务器。使用代理服务器的主要目的包括: -- 缓存 +- 进行缓存 - 负载均衡 - 网络访问控制 -- 访问日志记录 +- 记录访问日志 -代理服务器分为正向代理和反向代理两种: +代理分为正向代理和反向代理: -- 用户察觉得到正向代理的存在。 +- 正向代理:用户可感知其存在。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/a314bb79-5b18-4e63-a976-3448bffa6f1b.png" width=""/> </div><br> -- 而反向代理一般位于内部网络中,用户察觉不到。 +- 反向代理:用户无法感知其存在。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2d09a847-b854-439c-9198-b29c65810944.png" width=""/> </div><br> #### 2. 网关 -与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。 +网关服务器将 HTTP 通信转换为其它协议,供非 HTTP 服务器使用。 #### 3. 隧道 -使用 SSL 等加密手段,在客户端和服务器之间建立一条安全的通信线路。 +使用 SSL 等加密手段,在客户端和服务器之间建立安全通信链路。 ## 六、HTTPS -HTTP 有以下安全性问题: +HTTPS 解决了 HTTP 的安全性问题,包括: -- 使用明文进行通信,内容可能会被窃听; -- 不验证通信方的身份,通信方的身份有可能遭遇伪装; -- 无法证明报文的完整性,报文有可能遭篡改。 +- 使用明文通信,可能被窃听; +- 无法验证通信方的身份,避免伪装; +- 报文完整性无法保证,可能遭篡改。 -HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信,也就是说 HTTPS 使用了隧道进行通信。 +HTTPS 是在 HTTP 基础上使用 SSL(安全套接层)实现的,加密通信后提供安全性。 -通过使用 SSL,HTTPS 具有了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)。 +通过 SSL,HTTPS 实现了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ssl-offloading.jpg" width="700"/> </div><br> @@ -724,129 +659,118 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer) #### 1. 对称密钥加密 -对称密钥加密(Symmetric-Key Encryption),加密和解密使用同一密钥。 +对称密钥加密(Symmetric-Key Encryption)使用相同的密钥进行加密和解密。 -- 优点:运算速度快; -- 缺点:无法安全地将密钥传输给通信方。 +- 优点:推导速度快; +- 缺点:密钥无法安全传输。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7fffa4b8-b36d-471f-ad0c-a88ee763bb76.png" width="600"/> </div><br> -#### 2.非对称密钥加密 - -非对称密钥加密,又称公开密钥加密(Public-Key Encryption),加密和解密使用不同的密钥。 - -公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。 +#### 2. 非对称密钥加密 -非对称密钥除了用来加密,还可以用来进行签名。因为私有密钥无法被其他人获取,因此通信发送方使用其私有密钥进行签名,通信接收方使用发送方的公开密钥对签名进行解密,就能判断这个签名是否正确。 +非对称密钥加密(Public-Key Encryption)使用不同的密钥进行加密和解密。公开密钥公开,私钥则保留给通信接收方。 -- 优点:可以更安全地将公开密钥传输给通信发送方; -- 缺点:运算速度慢。 +- 优点:可以安全地传输公开密钥; +- 缺点:推导速度相对较慢。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/39ccb299-ee99-4dd1-b8b4-2f9ec9495cb4.png" width="600"/> </div><br> -#### 3. HTTPS 采用的加密方式 +#### 3. HTTPS Mixed 加密方式 -上面提到对称密钥加密方式的传输效率更高,但是无法安全地将密钥 Secret Key 传输给通信方。而非对称密钥加密方式可以保证传输的安全性,因此我们可以利用非对称密钥加密方式将 Secret Key 传输给通信方。HTTPS 采用混合的加密机制,正是利用了上面提到的方案: +HTTPS 结合了对称和非对称加密的优势: -- 使用非对称密钥加密方式,传输对称密钥加密方式所需要的 Secret Key,从而保证安全性; -- 获取到 Secret Key 后,再使用对称密钥加密方式进行通信,从而保证效率。(下图中的 Session Key 就是 Secret Key) +- 使用非对称加密安全地传输对称密钥(Session Key); +- 之后使用对称密钥进行数据传输,以保证效率。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/How-HTTPS-Works.png" width="600"/> </div><br> ### 认证 -通过使用 **证书** 来对通信方进行认证。 +HTTPS 通过使用数字证书对通信方进行认证。数字证书由受信赖的数字证书认证机构(CA)颁发。 -数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。 +服务器运营人员向 CA 提出请求,CA 在验证身份后对公开密钥进行签名。该签名与公开密钥绑定在一起,形成数字证书。 -服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。 - -进行 HTTPS 通信时,服务器会把证书发送给客户端。客户端取得其中的公开密钥之后,先使用数字签名进行验证,如果验证通过,就可以开始通信了。 +当建立 HTTPS 通信时,服务器将证书发送给客户端。客户端验证签名,通过后开始通信。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2017-06-11-ca.png" width=""/> </div><br> ### 完整性保护 -SSL 提供报文摘要功能来进行完整性保护。 - -HTTP 也提供了 MD5 报文摘要功能,但不是安全的。例如报文内容被篡改之后,同时重新计算 MD5 的值,通信接收方是无法意识到发生了篡改。 +SSL 通过报文摘要保证报文的完整性。 -HTTPS 的报文摘要功能之所以安全,是因为它结合了加密和认证这两个操作。试想一下,加密之后的报文,遭到篡改之后,也很难重新计算报文摘要,因为无法轻易获取明文。 +HTTP 提供了 MD5 报文摘要功能,但要注意它并不安全,因为篡改后的报文能重新计算 MD5 值。HTTPS 的报文摘要功能结合了加密和认证,因此能有效保护报文完整性。 ### HTTPS 的缺点 -- 因为需要进行加密解密等过程,因此速度会更慢; -- 需要支付证书授权的高额费用。 +- 加密解密过程会导致速度较慢; +- 可能需要支付证书高额费用。 ## 七、HTTP/2.0 ### HTTP/1.x 缺陷 -HTTP/1.x 实现简单是以牺牲性能为代价的: +HTTP/1.x 实现简单牺牲了性能,存在以下问题: -- 客户端需要使用多个连接才能实现并发和缩短延迟; -- 不会压缩请求和响应首部,从而导致不必要的网络流量; -- 不支持有效的资源优先级,致使底层 TCP 连接的利用率低下。 +- 客户必须使用多个连接以实现并发,导致延迟高; +- 未压缩请求和响应首部,引入不必要的网络流量; +- 不支持有效的资源优先级,底层 TCP 连接利用率低下。 ### 二进制分帧层 -HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式的。 +HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,以二进制格式传输。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/86e6a91d-a285-447a-9345-c5484b8d0c47.png" width="400"/> </div><br> -在通信过程中,只会有一个 TCP 连接存在,它承载了任意数量的双向数据流(Stream)。 +在通信过程中,会有一个 TCP 连接支撑任意数量的双向数据流(Stream)。 -- 一个数据流(Stream)都有一个唯一标识符和可选的优先级信息,用于承载双向信息。 -- 消息(Message)是与逻辑请求或响应对应的完整的一系列帧。 -- 帧(Frame)是最小的通信单位,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。 +- 数据流的唯一标识符和可选优先级信息用于承载双向信息。 +- 消息(Message)对应完整的一系列帧。 +- 帧(Frame)是最小的通信单位,来自不同数据流的帧可交错发送,最终会根据帧头的标识符重新组装。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/af198da1-2480-4043-b07f-a3b91a88b815.png" width="600"/> </div><br> ### 服务端推送 -HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 page.html 页面,服务端就把 script.js 和 style.css 等与之相关的资源一起发给客户端。 +HTTP/2.0 允许服务器在客户端请求资源时,自动推送相关的资源给客户端,减少后续请求的次数。例如,当客户端请求 `page.html` 时,服务器也将 `script.js` 和 `style.css` 一并发送。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e3f1657c-80fc-4dfa-9643-bf51abd201c6.png" width="800"/> </div><br> ### 首部压缩 -HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。 - -HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见过的首部字段表,从而避免了重复传输。 +HTTP/1.1 的首部字段通常包含大量内容,且每次重复发送。HTTP/2.0 维护一个包含之前见过的首部字段的动态表,从而避免重复传输。 -不仅如此,HTTP/2.0 也使用 Huffman 编码对首部字段进行压缩。 +同时,HTTP/2.0 使用 Huffman 编码对首部字段进行压缩。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/_u4E0B_u8F7D.png" width="600"/> </div><br> ## 八、HTTP/1.1 新特性 -详细内容请见上文 - -- 默认是长连接 +- 默认使用长连接 - 支持流水线 -- 支持同时打开多个 TCP 连接 +- 支持同时建立多个 TCP 连接 - 支持虚拟主机 -- 新增状态码 100 +- 新增状态码 `100` - 支持分块传输编码 -- 新增缓存处理指令 max-age +- 新增缓存处理指令 `max-age` ## 九、GET 和 POST 比较 ### 作用 -GET 用于获取资源,而 POST 用于传输实体主体。 +- GET:用于获取资源。 +- POST:用于传输实体主体数据。 ### 参数 -GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看。 +GET 和 POST 请求都可使用额外参数,但 GET 参数出现在 URL 中,POST 参数在消息主体中。虽说 POST 参数在消息主体中更为隐蔽,但可被抓包工具(如 Fiddler)查看。 -因为 URL 只支持 ASCII 码,因此 GET 的参数中如果存在中文等字符就需要先进行编码。例如 `中文` 会转换为 `%E4%B8%AD%E6%96%87`,而空格会转换为 `%20`。POST 参数支持标准字符集。 +GET 请求的参数应进行 URL 编码。 -``` +```http GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1 ``` -``` +```http POST /test/demo_form.asp HTTP/1.1 Host: w3schools.com name1=value1&name2=value2 @@ -854,63 +778,47 @@ name1=value1&name2=value2 ### 安全 -安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的。 - -GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发生了改变。 +- GET 方法是安全的,不会改变服务器状态。 +- POST 方法不安全,因为它的目的是传送实体主体内容,可能导致状态改变。 -安全的方法除了 GET 之外还有:HEAD、OPTIONS。 - -不安全的方法除了 POST 之外还有 PUT、DELETE。 +安全方法包括 GET、HEAD、OPTIONS 等;不安全方法包括 POST、PUT、DELETE。 ### 幂等性 -幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。 - -所有的安全方法也都是幂等的。 - -在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。 - -GET /pageX HTTP/1.1 是幂等的,连续调用多次,客户端接收到的结果都是一样的: +幂等方法,执行一次与多次效果一致。即多次执行不会改变服务器状态。所有安全方法都是幂等的,GET、HEAD、PUT 和 DELETE 能在正确实现的条件下都是幂等的,只有 POST 方法不是。 +```http +GET /pageX HTTP/1.1 → 幂等 +GET /pageX HTTP/1.1 → 幂等 +GET /pageX HTTP/1.1 → 幂等 ``` -GET /pageX HTTP/1.1 -GET /pageX HTTP/1.1 -GET /pageX HTTP/1.1 -GET /pageX HTTP/1.1 -``` - -POST /add_row HTTP/1.1 不是幂等的,如果调用多次,就会增加多行记录: +```http +POST /add_row HTTP/1.1 → 添加一行 +POST /add_row HTTP/1.1 → 添加第二行 +POST /add_row HTTP/1.1 → 添加第三行 ``` -POST /add_row HTTP/1.1 -> Adds a 1nd row -POST /add_row HTTP/1.1 -> Adds a 2nd row -POST /add_row HTTP/1.1 -> Adds a 3rd row -``` - -DELETE /idX/delete HTTP/1.1 是幂等的,即使不同的请求接收到的状态码不一样: -``` -DELETE /idX/delete HTTP/1.1 -> Returns 200 if idX exists -DELETE /idX/delete HTTP/1.1 -> Returns 404 as it just got deleted -DELETE /idX/delete HTTP/1.1 -> Returns 404 +```http +DELETE /idX/delete HTTP/1.1 → 返回 200(若 idX 存在) +DELETE /idX/delete HTTP/1.1 → 返回 404(已经删除) +DELETE /idX/delete HTTP/1.1 → 返回 404 ``` ### 可缓存 -如果要对响应进行缓存,需要满足以下条件: +响应可被缓存需要满足以下条件: -- 请求报文的 HTTP 方法本身是可缓存的,包括 GET 和 HEAD,但是 PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的。 -- 响应报文的状态码是可缓存的,包括:200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501。 -- 响应报文的 Cache-Control 首部字段没有指定不进行缓存。 +1. 请求方法是可缓存的(GET 和 HEAD 可缓存,PUT 和 DELETE 不可缓存,POST 在大多数情况下不可缓存)。 +2. 响应的状态码是可缓存的(如:200, 203, 204, 206 等)。 +3. 响应的 `Cache-Control` 首部字段没有设置不缓存。 ### XMLHttpRequest -为了阐述 POST 和 GET 的另一个区别,需要先了解 XMLHttpRequest: - -> XMLHttpRequest 是一个 API,它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个通过 URL 来获取数据的简单方式,并且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。XMLHttpRequest 在 AJAX 中被大量使用。 +`XMLHttpRequest` 是一种 API,允许在客户端和服务器之间进行数据传输,支持局部更新页面而不刷新整个页面。 -- 在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做,例如火狐就不会。 -- 而 GET 方法 Header 和 Data 会一起发送。 +- POST 方法时,浏览器会先发送 Header 然后再发送 Data;不同浏览器表现不同,某些浏览器会将二者一起发送。 +- GET 方法的 Header 和 Data 将一并发送。 ## 参考资料 @@ -920,7 +828,7 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404 - [htmlspecialchars](http://php.net/manual/zh/function.htmlspecialchars.php) - [Difference between file URI and URL in java](http://java2db.com/java-io/how-to-get-and-the-difference-between-file-uri-and-url-in-java) - [How to Fix SQL Injection Using Java PreparedStatement & CallableStatement](https://software-security.sans.org/developer-how-to/fix-sql-injection-in-java-using-prepared-callable-statement) -- [浅谈 HTTP 中 Get 与 Post 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html) +- [浅谈 HTTP 中 GET 与 POST 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html) - [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/) - [HTTP (HyperText Transfer Protocol)](https://www.ntu.edu.sg/home/ehchua/programming/webprogramming/HTTP_Basics.html) - [Web-VPN: Secure Proxies with SPDY & Chrome](https://www.igvita.com/2011/12/01/web-vpn-secure-proxies-with-spdy-chrome/) @@ -941,3 +849,4 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404 - [Symmetric vs. Asymmetric Encryption – What are differences?](https://www.ssl2buy.com/wiki/symmetric-vs-asymmetric-encryption-what-are-differences) - [Web 性能优化与 HTTP/2](https://www.kancloud.cn/digest/web-performance-http2) - [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn) +``` diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \344\275\215\350\277\220\347\256\227.md" "b/notes/Leetcode \351\242\230\350\247\243 - \344\275\215\350\277\220\347\256\227.md" index a097b75dcf..045ce89360 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \344\275\215\350\277\220\347\256\227.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \344\275\215\350\277\220\347\256\227.md" @@ -1,4 +1,6 @@ +```markdown # Leetcode 题解 - 位运算 + <!-- GFM-TOC --> * [Leetcode 题解 - 位运算](#leetcode-题解---位运算) * [0. 原理](#0-原理) @@ -9,7 +11,7 @@ * [5. 翻转一个数的比特位](#5-翻转一个数的比特位) * [6. 不用额外变量交换两个整数](#6-不用额外变量交换两个整数) * [7. 判断一个数是不是 2 的 n 次方](#7-判断一个数是不是-2-的-n-次方) - * [8. 判断一个数是不是 4 的 n 次方](#8--判断一个数是不是-4-的-n-次方) + * [8. 判断一个数是不是 4 的 n 次方](#8-判断一个数是不是-4-的-n-次方) * [9. 判断一个数的位级表示是否不会出现连续的 0 和 1](#9-判断一个数的位级表示是否不会出现连续的-0-和-1) * [10. 求一个数的补码](#10-求一个数的补码) * [11. 实现整数的加法](#11-实现整数的加法) @@ -17,26 +19,27 @@ * [13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数](#13-统计从-0-\~-n-每个数的二进制表示中-1-的个数) <!-- GFM-TOC --> - ## 0. 原理 **基本原理** -0s 表示一串 0,1s 表示一串 1。 +在位运算中,0s 表示一串二进制的 0,1s 表示一串二进制的 1。以下是一些基本的位运算规则: ``` -x ^ 0s = x x & 0s = 0 x | 0s = x -x ^ 1s = ~x x & 1s = x x | 1s = 1s -x ^ x = 0 x & x = x x | x = x +x ^ 0s = x // x 与 0 进行异或结果为 x +x & 0s = 0 // x 与 0 进行与操作结果为 0 +x | 0s = x // x 与 0 进行或操作结果为 x +x ^ 1s = ~x // x 与 1 进行异或结果为 ~x(取反) +x & 1s = x // x 与 1 进行与操作结果为 x +x | 1s = 1s // x 与 1 进行或操作结果为 1s +x ^ x = 0 // x 与自身进行异或结果为 0 +x & x = x // x 与自身进行与操作结果为 x +x | x = x // x 与自身进行或操作结果为 x ``` -利用 x ^ 1s = \~x 的特点,可以将一个数的位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。 - -``` -1^1^2 = 2 -``` +利用 `x ^ 1s = ~x` 可以翻转一个数字的比特位,而利用 `x ^ x = 0` 可以将三个数中重复的两个数去除,只留下一个不同数。例如,`1^1^2 = 2`。 -利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask:00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。 +使用 `x & 0s = 0` 和 `x & 1s = x` 的性质,可以实现掩码操作。例如,与掩码 `00111100` 进行位与操作只保留与掩码的 1 部分相对应的位。 ``` 01011011 & @@ -45,76 +48,44 @@ x ^ x = 0 x & x = x x | x = x 00011000 ``` -利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。 - -``` -01011011 | -00111100 --------- -01111111 -``` - **位与运算技巧** -n&(n-1) 去除 n 的位级表示中最低的那一位 1。例如对于二进制表示 01011011,减去 1 得到 01011010,这两个数相与得到 01011010。 - -``` -01011011 & -01011010 --------- -01011010 -``` - -n&(-n) 得到 n 的位级表示中最低的那一位 1。-n 得到 n 的反码加 1,也就是 -n=\~n+1。例如对于二进制表示 10110100,-n 得到 01001100,相与得到 00000100。 - -``` -10110100 & -01001100 --------- -00000100 -``` - -n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1,和 n&(n-1) 效果一样。 +1. **删除最低位的 1**: `n & (n - 1)` 可以去除 n 的最低位的 1。例如,对于二进制 `01011011`,减去 1 得到 `01011010`,两者相与结果为 `01011010`。 + + ``` + 01011011 & + 01011010 + -------- + 01011010 + ``` + +2. **获取最低位的 1**: `n & -n` 可以得到 n 的最低位的 1。`-n` 是 n 的反码加 1,因此 `-n = ~n + 1`。例如,对于二进制的 `10110100`,`-n` 得到 `01001100`,两者相与的结果为 `00000100`。 + + ``` + 10110100 & + 01001100 + -------- + 00000100 + ``` + +3. **另一种删除最低位的 1**: `n - (n & -n)` 也可以去除 n 的最低位的 1,和 `n & (n - 1)` 效果相同。 **移位运算** -\\>\\> n 为算术右移,相当于除以 2n,例如 -7 \\>\\> 2 = -2。 - -``` -11111111111111111111111111111001 >> 2 --------- -11111111111111111111111111111110 -``` - -\\>\\>\\> n 为无符号右移,左边会补上 0。例如 -7 \\>\\>\\> 2 = 1073741822。 - -``` -11111111111111111111111111111001 >>> 2 --------- -00111111111111111111111111111111 -``` - -\<\< n 为算术左移,相当于乘以 2n。-7 \<\< 2 = -28。 - -``` -11111111111111111111111111111001 << 2 --------- -11111111111111111111111111100100 -``` - -**mask 计算** - -要获取 111111111,将 0 取反即可,\~0。 +- `>>` 为算术右移,相当于除以 `2^n`,例如 `-7 >> 2 = -2`。 +- `>>>` 为无符号右移,左边补 0,例如 `-7 >>> 2 = 1073741822`。 +- `<<` 为算术左移,相当于乘以 `2^n`,例如 `-7 << 2 = -28`。 -要得到只有第 i 位为 1 的 mask,将 1 向左移动 i-1 位即可,1\<\<(i-1) 。例如 1\<\<4 得到只有第 5 位为 1 的 mask :00010000。 +**掩码计算** -要得到 1 到 i 位为 1 的 mask,(1\<\<i)-1 即可,例如将 (1\<\<4)-1 = 00010000-1 = 00001111。 - -要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 \~((1\<\<i)-1)。 +- 获取所有位为接收相当于 `~0`。 +- 生成第 `i` 位为 1 的掩码可以通过 `1 << (i - 1)` 来获取,例如 `1 << 4` 生成 `00010000`。 +- 生成 1 到 i 位为 1 的掩码则可以使用 `(1 << i) - 1`,例如 `(1 << 4) - 1 = 00001111`。 +- 生成 1 到 i 位为 0 的掩码即为对前述掩码取反 `~((1 << i) - 1)`。 **Java 中的位操作** -```html +```java static int Integer.bitCount(); // 统计 1 的数量 static int Integer.highestOneBit(); // 获得最高位 static String toBinaryString(int i); // 转换为二进制表示的字符串 @@ -122,173 +93,174 @@ static String toBinaryString(int i); // 转换为二进制表示的字符串 ## 1. 统计两个数的二进制表示有多少位不同 -461. Hamming Distance (Easy) +**461. Hamming Distance (Easy)** [Leetcode](https://leetcode.com/problems/hamming-distance/) / [力扣](https://leetcode-cn.com/problems/hamming-distance/) -```html +```java Input: x = 1, y = 4 - Output: 2 - -Explanation: -1 (0 0 0 1) -4 (0 1 0 0) - ↑ ↑ - -The above arrows point to positions where the corresponding bits are different. ``` -对两个数进行异或操作,位级表示不同的那一位为 1,统计有多少个 1 即可。 +**解析**: 对两个数进行异或操作,结果中位级表示不同的位为 1,统计有多少个 1。 ```java public int hammingDistance(int x, int y) { - int z = x ^ y; - int cnt = 0; - while(z != 0) { - if ((z & 1) == 1) cnt++; - z = z >> 1; + int z = x ^ y; // 计算异或 + int cnt = 0; // 统计不同位数 + while (z != 0) { // 直到 z 为 0 + if ((z & 1) == 1) cnt++; // 统计末尾的 1 + z = z >> 1; // 右移一位 } return cnt; } ``` -使用 z&(z-1) 去除 z 位级表示最低的那一位。 +**优化解法**: 使用 `z & (z - 1)` 去除 z 的最低位的 1。 ```java public int hammingDistance(int x, int y) { - int z = x ^ y; - int cnt = 0; - while (z != 0) { - z &= (z - 1); - cnt++; + int z = x ^ y; // 计算异或 + int cnt = 0; // 统计不同位数 + while (z != 0) { // 直到 z 为 0 + z &= (z - 1); // 去掉最低位的 1 + cnt++; // 计数加 1 } return cnt; } ``` -可以使用 Integer.bitcount() 来统计 1 个的个数。 +**更简洁的解法**: 利用 `Integer.bitCount()` 方法来统计 1 的个数。 ```java public int hammingDistance(int x, int y) { - return Integer.bitCount(x ^ y); + return Integer.bitCount(x ^ y); // 使用 Java 内置函数 } ``` ## 2. 数组中唯一一个不重复的元素 -136\. Single Number (Easy) +**136. Single Number (Easy)** [Leetcode](https://leetcode.com/problems/single-number/description/) / [力扣](https://leetcode-cn.com/problems/single-number/description/) -```html +```java Input: [4,1,2,1,2] Output: 4 ``` -两个相同的数异或的结果为 0,对所有数进行异或操作,最后的结果就是单独出现的那个数。 +**解析**: 两个相同的数进行异或操作的结果为 0,遍历所有数进行异或,最终结果即为单独出现的数。 ```java public int singleNumber(int[] nums) { - int ret = 0; - for (int n : nums) ret = ret ^ n; - return ret; + int ret = 0; // 存储结果 + for (int n : nums) { // 遍历所有数字 + ret ^= n; // 进行异或操作 + } + return ret; // 返回结果 } ``` ## 3. 找出数组中缺失的那个数 -268\. Missing Number (Easy) +**268. Missing Number (Easy)** [Leetcode](https://leetcode.com/problems/missing-number/description/) / [力扣](https://leetcode-cn.com/problems/missing-number/description/) -```html +```java Input: [3,0,1] Output: 2 ``` -题目描述:数组元素在 0-n 之间,但是有一个数是缺失的,要求找到这个缺失的数。 +**题目描述**: 在 0 到 n 的数组中,只有一个数缺失,要求找到这个缺失的数。 ```java public int missingNumber(int[] nums) { - int ret = 0; - for (int i = 0; i < nums.length; i++) { - ret = ret ^ i ^ nums[i]; + int ret = 0; // 存储结果 + for (int i = 0; i < nums.length; i++) { // 遍历索引 + ret ^= i ^ nums[i]; // 计算异或 } - return ret ^ nums.length; + return ret ^ nums.length; // 加上 n 的索引 } ``` ## 4. 数组中不重复的两个元素 -260\. Single Number III (Medium) +**260. Single Number III (Medium)** [Leetcode](https://leetcode.com/problems/single-number-iii/description/) / [力扣](https://leetcode-cn.com/problems/single-number-iii/description/) -两个不相等的元素在位级表示上必定会有一位存在不同。 - -将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。 - -diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 +**解析**: 在一个数组中存在两个不相等的元素。使用异或运算可以将所有元素异或,最后得到的结果是这两个不同元素的异或值。 ```java public int[] singleNumber(int[] nums) { - int diff = 0; - for (int num : nums) diff ^= num; - diff &= -diff; // 得到最右一位 - int[] ret = new int[2]; + int diff = 0; // 存储异或结果 for (int num : nums) { - if ((num & diff) == 0) ret[0] ^= num; - else ret[1] ^= num; + diff ^= num; // 异或所有元素 } - return ret; + + // 获取不重复元素的最低位不同的那一位 + diff &= -diff; + int[] ret = new int[2]; // 数组存储结果 + + // 根据最低位不同分组 + for (int num : nums) { + if ((num & diff) == 0) { // 分到第一组 + ret[0] ^= num; // 异或第一组的所有数 + } else { // 分到第二组 + ret[1] ^= num; // 异或第二组的所有数 + } + } + return ret; // 返回两个不重复的元素 } ``` ## 5. 翻转一个数的比特位 -190\. Reverse Bits (Easy) +**190. Reverse Bits (Easy)** [Leetcode](https://leetcode.com/problems/reverse-bits/description/) / [力扣](https://leetcode-cn.com/problems/reverse-bits/description/) +**解析**: 通过对每一位进行移位与掩码结合最终得到翻转后的结果。 + ```java public int reverseBits(int n) { - int ret = 0; - for (int i = 0; i < 32; i++) { - ret <<= 1; - ret |= (n & 1); - n >>>= 1; + int ret = 0; // 存储翻转后的结果 + for (int i = 0; i < 32; i++) { // 遍历 32 位 + ret <<= 1; // 左移一位 + ret |= (n & 1); // 取 n 最低位 + n >>>= 1; // 无符号右移 } - return ret; + return ret; // 返回结果 } ``` -如果该函数需要被调用很多次,可以将 int 拆成 4 个 byte,然后缓存 byte 对应的比特位翻转,最后再拼接起来。 +**优化**: 如果函数调用次数较多,可以将整数拆分为 4 个 byte,缓存每个 byte 对应的比特位翻转,最后再拼接。 ```java private static Map<Byte, Integer> cache = new HashMap<>(); public int reverseBits(int n) { - int ret = 0; - for (int i = 0; i < 4; i++) { - ret <<= 8; - ret |= reverseByte((byte) (n & 0b11111111)); - n >>= 8; + int ret = 0; // 存储结果 + for (int i = 0; i < 4; i++) { // 遍历 4 个 byte + ret <<= 8; // 左移 8 位 + ret |= reverseByte((byte) (n & 0b11111111)); // 翻转 byte + n >>= 8; // 右移 8 位 } - return ret; + return ret; // 返回结果 } private int reverseByte(byte b) { - if (cache.containsKey(b)) return cache.get(b); - int ret = 0; - byte t = b; - for (int i = 0; i < 8; i++) { - ret <<= 1; - ret |= t & 1; - t >>= 1; + if (cache.containsKey(b)) return cache.get(b); // 使用缓存 + int ret = 0; // 存储翻转后的结果 + byte t = b; // 临时变量 + for (int i = 0; i < 8; i++) { // 遍历 8 位 + ret <<= 1; // 左移一位 + ret |= t & 1; // 取最低位 + t >>= 1; // 右移一位 } - cache.put(b, ret); - return ret; + cache.put(b, ret); // 缓存结果 + return ret; // 返回结果 } ``` @@ -296,41 +268,43 @@ private int reverseByte(byte b) { [程序员代码面试指南 :P317](#) +**解析**: 利用异或运算可以在不使用额外变量的情况下交换两个整数。 + ```java -a = a ^ b; -b = a ^ b; -a = a ^ b; +a = a ^ b; // 将 a 和 b 异或,结果存回 a +b = a ^ b; // b 变为原始的 a +a = a ^ b; // a 变为原始的 b ``` ## 7. 判断一个数是不是 2 的 n 次方 -231\. Power of Two (Easy) +**231. Power of Two (Easy)** [Leetcode](https://leetcode.com/problems/power-of-two/description/) / [力扣](https://leetcode-cn.com/problems/power-of-two/description/) -二进制表示只有一个 1 存在。 +**解析**: 一个数是 2 的 n 次方的条件是其二进制表示中只有一个 1。 ```java public boolean isPowerOfTwo(int n) { - return n > 0 && Integer.bitCount(n) == 1; + return n > 0 && Integer.bitCount(n) == 1; // 校验 n 为正且只有一个 1 } ``` -利用 1000 & 0111 == 0 这种性质,得到以下解法: +**另一个解法**: 利用性质 `n & (n - 1)` 得到的结果为 0。 ```java public boolean isPowerOfTwo(int n) { - return n > 0 && (n & (n - 1)) == 0; + return n > 0 && (n & (n - 1)) == 0; // 校验条件 } ``` -## 8. 判断一个数是不是 4 的 n 次方 +## 8. 判断一个数是不是 4 的 n 次方 -342\. Power of Four (Easy) +**342. Power of Four (Easy)** [Leetcode](https://leetcode.com/problems/power-of-four/) / [力扣](https://leetcode-cn.com/problems/power-of-four/) -这种数在二进制表示中有且只有一个奇数位为 1,例如 16(10000)。 +**解析**: 4 的 n 次方在二进制表示中只有一个偶数位为 1,利用这一特性可进行判断。 ```java public boolean isPowerOfFour(int num) { @@ -338,166 +312,144 @@ public boolean isPowerOfFour(int num) { } ``` -也可以使用正则表达式进行匹配。 +**另一种解法**: 使用正则表达式匹配。 ```java public boolean isPowerOfFour(int num) { - return Integer.toString(num, 4).matches("10*"); + return Integer.toString(num, 4).matches("10*"); // 数字转换为 4 进制并匹配 } ``` ## 9. 判断一个数的位级表示是否不会出现连续的 0 和 1 -693\. Binary Number with Alternating Bits (Easy) +**693. Binary Number with Alternating Bits (Easy)** [Leetcode](https://leetcode.com/problems/binary-number-with-alternating-bits/description/) / [力扣](https://leetcode-cn.com/problems/binary-number-with-alternating-bits/description/) -```html -Input: 10 -Output: True -Explanation: -The binary representation of 10 is: 1010. - -Input: 11 -Output: False -Explanation: -The binary representation of 11 is: 1011. -``` - -对于 1010 这种位级表示的数,把它向右移动 1 位得到 101,这两个数每个位都不同,因此异或得到的结果为 1111。 +**解析**: 如果一个数的二进制表示中没有连续的 0 和 1,将这个数右移一位后进行异或运算,结果应为 `1111...`。 ```java public boolean hasAlternatingBits(int n) { - int a = (n ^ (n >> 1)); - return (a & (a + 1)) == 0; + int a = (n ^ (n >> 1)); // 计算异或 + return (a & (a + 1)) == 0; // 校验结果是否为 0 } ``` ## 10. 求一个数的补码 -476\. Number Complement (Easy) +**476. Number Complement (Easy)** [Leetcode](https://leetcode.com/problems/number-complement/description/) / [力扣](https://leetcode-cn.com/problems/number-complement/description/) -```html -Input: 5 -Output: 2 -Explanation: The binary representation of 5 is 101 (no leading zero bits), and its complement is 010. So you need to output 2. -``` - -题目描述:不考虑二进制表示中的首 0 部分。 - -对于 00000101,要求补码可以将它与 00000111 进行异或操作。那么问题就转换为求掩码 00000111。 +**解析**: 要求一个数的补码可以通过与一个掩码进行异或得到,而掩码是由 1 组成的完全二进制数。 ```java public int findComplement(int num) { - if (num == 0) return 1; - int mask = 1 << 30; - while ((num & mask) == 0) mask >>= 1; - mask = (mask << 1) - 1; - return num ^ mask; + if (num == 0) return 1; // 特殊情况 + int mask = 1 << 30; // 计算掩码 + while ((num & mask) == 0) mask >>= 1; // 右移掩码至首个1位 + mask = (mask << 1) - 1; // 拿到有效的掩码 + return num ^ mask; // 计算补码 } ``` -可以利用 Java 的 Integer.highestOneBit() 方法来获得含有首 1 的数。 +**优化解法**: 使用 `Integer.highestOneBit()` 计算掩码。 ```java public int findComplement(int num) { - if (num == 0) return 1; - int mask = Integer.highestOneBit(num); - mask = (mask << 1) - 1; - return num ^ mask; + if (num == 0) return 1; // 特殊情况 + int mask = Integer.highestOneBit(num); // 找到最高位的 1 + mask = (mask << 1) - 1; // 创建掩码 + return num ^ mask; // 计算补码 } ``` -对于 10000000 这样的数要扩展成 11111111,可以利用以下方法: +**扩展掩码**: 若掩码需要扩展成数字的全 1 的形式,可以利用以下方法。 -```html -mask |= mask >> 1 11000000 -mask |= mask >> 2 11110000 -mask |= mask >> 4 11111111 +```java +mask |= mask >> 1; // 把高 1 向低位传递 +mask |= mask >> 2; // ... +mask |= mask >> 4; // ... +mask |= mask >> 8; // ... +mask |= mask >> 16; // ... ``` ```java public int findComplement(int num) { - int mask = num; - mask |= mask >> 1; - mask |= mask >> 2; + int mask = num; // 复制 num 数 + mask |= mask >> 1; + mask |= mask >> 2; mask |= mask >> 4; - mask |= mask >> 8; - mask |= mask >> 16; - return (mask ^ num); + mask |= mask >> 8; + mask |= mask >> 16; + return (mask ^ num); // 返回数的补码 } ``` ## 11. 实现整数的加法 -371\. Sum of Two Integers (Easy) +**371. Sum of Two Integers (Easy)** [Leetcode](https://leetcode.com/problems/sum-of-two-integers/description/) / [力扣](https://leetcode-cn.com/problems/sum-of-two-integers/description/) -a ^ b 表示没有考虑进位的情况下两数的和,(a & b) \<\< 1 就是进位。 - -递归会终止的原因是 (a & b) \<\< 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 +**解析**: 用异或运算可以得到没有进位的和,而对进位使用与运算和左移来实现。 ```java public int getSum(int a, int b) { - return b == 0 ? a : getSum((a ^ b), (a & b) << 1); + return b == 0 ? a : getSum((a ^ b), (a & b) << 1); // 递归计算 } ``` ## 12. 字符串数组最大乘积 -318\. Maximum Product of Word Lengths (Medium) +**318. Maximum Product of Word Lengths (Medium)** [Leetcode](https://leetcode.com/problems/maximum-product-of-word-lengths/description/) / [力扣](https://leetcode-cn.com/problems/maximum-product-of-word-lengths/description/) -```html -Given ["abcw", "baz", "foo", "bar", "xtfn", "abcdef"] -Return 16 -The two words can be "abcw", "xtfn". -``` - -题目描述:字符串数组的字符串只含有小写字符。求解字符串数组中两个字符串长度的最大乘积,要求这两个字符串不能含有相同字符。 +**题目描述**: 给定字符串数组,求两个字符串长度的最大乘积,要求这两个字符串不能含有相同字符。 -本题主要问题是判断两个字符串是否含相同字符,由于字符串只含有小写字符,总共 26 位,因此可以用一个 32 位的整数来存储每个字符是否出现过。 +**解析**: 利用 32 位整数作为掩码,记录每个字符串中出现的字符。 ```java public int maxProduct(String[] words) { - int n = words.length; - int[] val = new int[n]; + int n = words.length; // 获取字符串数组的长度 + int[] val = new int[n]; // 存储每个字符串的掩码 + + // 遍历所有字符串 for (int i = 0; i < n; i++) { for (char c : words[i].toCharArray()) { - val[i] |= 1 << (c - 'a'); + val[i] |= 1 << (c - 'a'); // 对应字符的位设置为1 } } - int ret = 0; + int ret = 0; // 存储结果 + // 嵌套遍历所有字符串组合 for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { + // 如果两个字符串没有相同字符 if ((val[i] & val[j]) == 0) { - ret = Math.max(ret, words[i].length() * words[j].length()); + ret = Math.max(ret, words[i].length() * words[j].length()); // 更新最大乘积 } } } - return ret; + return ret; // 返回结果 } ``` ## 13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数 -338\. Counting Bits (Medium) +**338. Counting Bits (Medium)** [Leetcode](https://leetcode.com/problems/counting-bits/description/) / [力扣](https://leetcode-cn.com/problems/counting-bits/description/) -对于数字 6(110),它可以看成是 4(100) 再加一个 2(10),因此 dp[i] = dp[i&(i-1)] + 1; +**解析**: 以动态规划的方式计算结果,对于每个数字 `i`,可以通过 `i & (i - 1)` 得到 1 的数量。 ```java public int[] countBits(int num) { - int[] ret = new int[num + 1]; - for(int i = 1; i <= num; i++){ - ret[i] = ret[i&(i-1)] + 1; + int[] ret = new int[num + 1]; // 用于存储结果 + for (int i = 1; i <= num; i++) { // 从 1 开始计算 + ret[i] = ret[i & (i - 1)] + 1; // 递推关系 } - return ret; + return ret; // 返回结果 } ``` - +``` \ No newline at end of file diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \345\223\210\345\270\214\350\241\250.md" "b/notes/Leetcode \351\242\230\350\247\243 - \345\223\210\345\270\214\350\241\250.md" index c8565bbf3a..b0da5d0f28 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \345\223\210\345\270\214\350\241\250.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \345\223\210\345\270\214\350\241\250.md" @@ -1,135 +1,121 @@ +```markdown # Leetcode 题解 - 哈希表 -<!-- GFM-TOC --> -* [Leetcode 题解 - 哈希表](#leetcode-题解---哈希表) - * [1. 数组中两个数的和为给定值](#1-数组中两个数的和为给定值) - * [2. 判断数组是否含有重复元素](#2-判断数组是否含有重复元素) - * [3. 最长和谐序列](#3-最长和谐序列) - * [4. 最长连续序列](#4-最长连续序列) -<!-- GFM-TOC --> +哈希表以 O(N) 的空间复杂度存储数据,并且可以在 O(1) 的时间复杂度内进行查找和操作,使其在解决许多编程问题时非常高效。 -哈希表使用 O(N) 空间复杂度存储数据,并且以 O(1) 时间复杂度求解问题。 - -- Java 中的 **HashSet** 用于存储一个集合,可以查找元素是否在集合中。如果元素有穷,并且范围不大,那么可以用一个布尔数组来存储一个元素是否存在。例如对于只有小写字符的元素,就可以用一个长度为 26 的布尔数组来存储一个字符集合,使得空间复杂度降低为 O(1)。 - - Java 中的 **HashMap** 主要用于映射关系,从而把两个元素联系起来。HashMap 也可以用来对元素进行计数统计,此时键为元素,值为计数。和 HashSet 类似,如果元素有穷并且范围不大,可以用整型数组来进行统计。在对一个内容进行压缩或者其它转换时,利用 HashMap 可以把原始内容和转换后的内容联系起来。例如在一个简化 url 的系统中 [Leetcdoe : 535. Encode and Decode TinyURL (Medium) - -[Leetcode](https://leetcode.com/problems/encode-and-decode-tinyurl/description/),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源�) / [力扣](https://leetcode-cn.com/problems/encode-and-decode-tinyurl/description/),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源�) +在 Java 中,**HashSet** 用于存储唯一的元素集合,能够快速地检查某个元素是否存在于集合中。如果要处理的元素是有限且范围较小的,例如仅小写字母(26 个字母),则可以使用一个长度为 26 的布尔数组来表示每个字母的存在性,这样可以将空间复杂度降低到 O(1)。 +**HashMap** 则主要用于存储键值对,可以有效地建立元素之间的映射关系。利用 HashMap 进行计数统计时,键是元素,值是计数。在某些场景中还可以利用 HashMap 来实现内容的压缩及其他转换。比如在简化 URL 的系统中,可以通过 HashMap 存储简化后的 URL 和原始 URL 的映射,以便在展示简化 URL 的同时,能够根据简化 URL 找到原始 URL。 ## 1. 数组中两个数的和为给定值 -1\. Two Sum (Easy) +题目:**Two Sum (简单)** [Leetcode](https://leetcode.com/problems/two-sum/description/) / [力扣](https://leetcode-cn.com/problems/two-sum/description/) -可以先对数组进行排序,然后使用双指针方法或者二分查找方法。这样做的时间复杂度为 O(NlogN),空间复杂度为 O(1)。 - -用 HashMap 存储数组元素和索引的映射,在访问到 nums[i] 时,判断 HashMap 中是否存在 target - nums[i],如果存在说明 target - nums[i] 所在的索引和 i 就是要找的两个数。该方法的时间复杂度为 O(N),空间复杂度为 O(N),使用空间来换取时间。 +解决这个问题的一个有效方法是使用一个 HashMap 存储数组元素与其索引的映射。在遍历数组时,我们可以通过判断 HashMap 中是否存在 `target - nums[i]` 来找到所需的两个数。该方法的时间复杂度为 O(N),空间复杂度为 O(N),利用空间换取时间可以有效提高性能。 ```java public int[] twoSum(int[] nums, int target) { - HashMap<Integer, Integer> indexForNum = new HashMap<>(); + HashMap<Integer, Integer> indexForNum = new HashMap<>(); // 创建 HashMap 存储元素及索引 for (int i = 0; i < nums.length; i++) { + // 检查是否存在 target - 当前元素 if (indexForNum.containsKey(target - nums[i])) { - return new int[]{indexForNum.get(target - nums[i]), i}; + return new int[]{indexForNum.get(target - nums[i]), i}; // 找到目标值的两个索引 } else { - indexForNum.put(nums[i], i); + indexForNum.put(nums[i], i); // 将当前元素及索引存入 HashMap } } - return null; + return null; // 若未找到,则返回 null } ``` ## 2. 判断数组是否含有重复元素 -217\. Contains Duplicate (Easy) +题目:**Contains Duplicate (简单)** [Leetcode](https://leetcode.com/problems/contains-duplicate/description/) / [力扣](https://leetcode-cn.com/problems/contains-duplicate/description/) +利用 HashSet 的特性,我们可以快速地判断数组中是否含有重复元素。当我们将一个元素加入到 HashSet 中时,如果该元素已存在,添加操作会失败。通过检查集合的大小与数组长度,我们就可以快速得出数组中是否含有重复元素。 + ```java public boolean containsDuplicate(int[] nums) { - Set<Integer> set = new HashSet<>(); + Set<Integer> set = new HashSet<>(); // 创建一个 HashSet 存储唯一元素 for (int num : nums) { - set.add(num); + set.add(num); // 尝试添加元素 } - return set.size() < nums.length; + return set.size() < nums.length; // 如果集合大小小于数组长度,则表示有重复元素 } ``` ## 3. 最长和谐序列 -594\. Longest Harmonious Subsequence (Easy) +题目:**Longest Harmonious Subsequence (简单)** [Leetcode](https://leetcode.com/problems/longest-harmonious-subsequence/description/) / [力扣](https://leetcode-cn.com/problems/longest-harmonious-subsequence/description/) -```html -Input: [1,3,2,2,5,2,3,7] -Output: 5 -Explanation: The longest harmonious subsequence is [3,2,2,2,3]. -``` - -和谐序列中最大数和最小数之差正好为 1,应该注意的是序列的元素不一定是数组的连续元素。 +在一个和谐序列中,序列的最大数与最小数之差必须为 1。因此我们可以使用 HashMap 来记录数组中每个元素的出现次数,再通过遍历 HashMap 来查找所有和谐序列的长度。 ```java public int findLHS(int[] nums) { - Map<Integer, Integer> countForNum = new HashMap<>(); + Map<Integer, Integer> countForNum = new HashMap<>(); // 存储每个元素的频率 for (int num : nums) { - countForNum.put(num, countForNum.getOrDefault(num, 0) + 1); + countForNum.put(num, countForNum.getOrDefault(num, 0) + 1); // 统计每个元素的出现次数 } - int longest = 0; + int longest = 0; // 用于记录最长和谐序列的长度 for (int num : countForNum.keySet()) { + // 检查是否存在 num + 1 if (countForNum.containsKey(num + 1)) { - longest = Math.max(longest, countForNum.get(num + 1) + countForNum.get(num)); + longest = Math.max(longest, countForNum.get(num) + countForNum.get(num + 1)); // 更新最长和谐序列长度 } } - return longest; + return longest; // 返回最长和谐序列的长度 } ``` ## 4. 最长连续序列 -128\. Longest Consecutive Sequence (Hard) +题目:**Longest Consecutive Sequence (困难)** [Leetcode](https://leetcode.com/problems/longest-consecutive-sequence/description/) / [力扣](https://leetcode-cn.com/problems/longest-consecutive-sequence/description/) -```html -Given [100, 4, 200, 1, 3, 2], -The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4. -``` - -要求以 O(N) 的时间复杂度求解。 +此问题要求在 O(N) 的时间复杂度下求解。我们可以使用 HashMap 存储每个元素,同时通过递归检测每个元素的连续序列的长度。 ```java public int longestConsecutive(int[] nums) { - Map<Integer, Integer> countForNum = new HashMap<>(); + Map<Integer, Integer> countForNum = new HashMap<>(); // 存储每个数的出现情况 for (int num : nums) { - countForNum.put(num, 1); + countForNum.put(num, 1); // 初始化每个元素的初始长度为 1 } for (int num : nums) { + // 向前查找并更新连续序列的长度 forward(countForNum, num); } - return maxCount(countForNum); + return maxCount(countForNum); // 返回最大长度 } private int forward(Map<Integer, Integer> countForNum, int num) { if (!countForNum.containsKey(num)) { - return 0; + return 0; // 如果当前数字不在 map 中,返回 0 } - int cnt = countForNum.get(num); + int cnt = countForNum.get(num); // 获取当前数字的连续计数 if (cnt > 1) { - return cnt; + return cnt; // 如果计数大于 1,直接返回 } + // 向前递归查找连续数字的长度并更新计数 cnt = forward(countForNum, num + 1) + 1; - countForNum.put(num, cnt); - return cnt; + countForNum.put(num, cnt); // 更新当前数字的计数 + return cnt; // 返回当前数字的计数 } private int maxCount(Map<Integer, Integer> countForNum) { - int max = 0; + int max = 0; // 最大连续序列长度 for (int num : countForNum.keySet()) { - max = Math.max(max, countForNum.get(num)); + max = Math.max(max, countForNum.get(num)); // 更新最大于当前最大值 } - return max; + return max; // 返回最大长度 } ``` + +以上是针对常见哈希表相关问题的详细解答。希望这些示例和解释能帮助您更好地理解哈希表的使用以及如何在实际问题中应用它们。 +``` \ No newline at end of file diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \345\233\276.md" "b/notes/Leetcode \351\242\230\350\247\243 - \345\233\276.md" index 4fe635d5eb..bd1c325de1 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \345\233\276.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \345\233\276.md" @@ -1,4 +1,5 @@ # Leetcode 题解 - 图 + <!-- GFM-TOC --> * [Leetcode 题解 - 图](#leetcode-题解---图) * [二分图](#二分图) @@ -10,213 +11,261 @@ * [1. 冗余连接](#1-冗余连接) <!-- GFM-TOC --> - ## 二分图 -如果可以用两种颜色对图中的节点进行着色,并且保证相邻的节点颜色不同,那么这个图就是二分图。 +**二分图**是指可以将图的节点分为两个独立的集合,使得图中同一集合的任意两个节点均不相邻。若可以用两种颜色对图中的节点进行着色,并确保相邻的节点颜色不同,则该图为二分图。 ### 1. 判断是否为二分图 -785\. Is Graph Bipartite? (Medium) +**题目:** 785\. Is Graph Bipartite? (中等) -[Leetcode](https://leetcode.com/problems/is-graph-bipartite/description/) / [力扣](https://leetcode-cn.com/problems/is-graph-bipartite/description/) +[Leetcode链接](https://leetcode.com/problems/is-graph-bipartite/description/) / [力扣链接](https://leetcode-cn.com/problems/is-graph-bipartite/description/) +#### 示例 + +输入: ```html -Input: [[1,3], [0,2], [1,3], [0,2]] -Output: true -Explanation: -The graph looks like this: +[[1,3], [0,2], [1,3], [0,2]] +``` +输出:`true` +解释:图的结构如下: +``` 0----1 | | | | 3----2 -We can divide the vertices into two groups: {0, 2} and {1, 3}. ``` +我们可以将节点划分为两个组:{0, 2} 和 {1, 3}。 +输入: ```html -Example 2: -Input: [[1,2,3], [0,2], [0,1,3], [0,2]] -Output: false -Explanation: -The graph looks like this: +[[1,2,3], [0,2], [0,1,3], [0,2]] +``` +输出:`false` +解释:图的结构如下: +``` 0----1 | \ | | \ | 3----2 -We cannot find a way to divide the set of nodes into two independent subsets. ``` +无法将节点集划分为两个独立的子集。 + +#### 解法 + +我们可以使用深度优先搜索 (DFS) 来判断图是否为二分图。以下是具体实现的代码: ```java +import java.util.Arrays; + public boolean isBipartite(int[][] graph) { int[] colors = new int[graph.length]; - Arrays.fill(colors, -1); - for (int i = 0; i < graph.length; i++) { // 处理图不是连通的情况 + Arrays.fill(colors, -1); // 初始化所有节点的颜色为 -1 (未着色) + for (int i = 0; i < graph.length; i++) { + // 对于每个节点,如果未着色,则尝试着色 if (colors[i] == -1 && !isBipartite(i, 0, colors, graph)) { - return false; + return false; // 发现冲突,返回 false } } - return true; + return true; // 所有节点均可合法着色 } private boolean isBipartite(int curNode, int curColor, int[] colors, int[][] graph) { if (colors[curNode] != -1) { - return colors[curNode] == curColor; + return colors[curNode] == curColor; // 如果已经着色,检查颜色是否一致 } - colors[curNode] = curColor; + + colors[curNode] = curColor; // 着色当前节点 + + // 遍历当前节点的所有邻接节点 for (int nextNode : graph[curNode]) { + // 递归检测邻接节点 if (!isBipartite(nextNode, 1 - curColor, colors, graph)) { - return false; + return false; // 发现冲突,返回 false } } - return true; + return true; // 当前节点及其邻接节点合法着色 } ``` ## 拓扑排序 -常用于在具有先序关系的任务规划中。 +拓扑排序是用于处理有向无环图 (DAG) 的一种排序方式,常用于先序关系的任务规划中。 ### 1. 课程安排的合法性 -207\. Course Schedule (Medium) +**题目:** 207\. Course Schedule (中等) -[Leetcode](https://leetcode.com/problems/course-schedule/description/) / [力扣](https://leetcode-cn.com/problems/course-schedule/description/) +[Leetcode链接](https://leetcode.com/problems/course-schedule/description/) / [力扣链接](https://leetcode-cn.com/problems/course-schedule/description/) +#### 示例 + +输入: ```html 2, [[1,0]] -return true ``` +输出:`true` +输入: ```html 2, [[1,0],[0,1]] -return false ``` +输出:`false` + +**题目描述:** 给定一个课程列表及其先修课程,判断是否可能完成所有课程。此题无需使用拓扑排序,只需判断有向图中是否存在环。 -题目描述:一个课程可能会先修课程,判断给定的先修课程规定是否合法。 +#### 解法 -本题不需要使用拓扑排序,只需要检测有向图是否存在环即可。 +使用深度优先搜索 (DFS) 检查是否存在环。以下是具体实现的代码: ```java +import java.util.ArrayList; +import java.util.List; + public boolean canFinish(int numCourses, int[][] prerequisites) { - List<Integer>[] graphic = new List[numCourses]; + List<Integer>[] graph = new List[numCourses]; for (int i = 0; i < numCourses; i++) { - graphic[i] = new ArrayList<>(); + graph[i] = new ArrayList<>(); } + // 构建图的邻接表 for (int[] pre : prerequisites) { - graphic[pre[0]].add(pre[1]); + graph[pre[0]].add(pre[1]); } - boolean[] globalMarked = new boolean[numCourses]; - boolean[] localMarked = new boolean[numCourses]; + + boolean[] globalMarked = new boolean[numCourses]; // 全局标记 + boolean[] localMarked = new boolean[numCourses]; // 局部标记 + for (int i = 0; i < numCourses; i++) { - if (hasCycle(globalMarked, localMarked, graphic, i)) { - return false; + if (hasCycle(globalMarked, localMarked, graph, i)) { + return false; // 存在环,返回 false } } - return true; + return true; // 所有课程合法,无环 } -private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked, - List<Integer>[] graphic, int curNode) { - +private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked, List<Integer>[] graph, int curNode) { if (localMarked[curNode]) { - return true; + return true; // 如果当前节点已在局部标记中,说明存在环 } if (globalMarked[curNode]) { - return false; + return false; // 如果已经全局标记,则不再需检查 } - globalMarked[curNode] = true; - localMarked[curNode] = true; - for (int nextNode : graphic[curNode]) { - if (hasCycle(globalMarked, localMarked, graphic, nextNode)) { - return true; + + globalMarked[curNode] = true; // 标记当前节点为全局已访问 + localMarked[curNode] = true; // 标记当前节点为局部已访问 + + for (int nextNode : graph[curNode]) { + if (hasCycle(globalMarked, localMarked, graph, nextNode)) { + return true; // 发现环,返回 true } } - localMarked[curNode] = false; - return false; + localMarked[curNode] = false; // 当前节点出栈,局部标记恢复 + return false; // 无环,返回 false } ``` ### 2. 课程安排的顺序 -210\. Course Schedule II (Medium) +**题目:** 210\. Course Schedule II (中等) + +[Leetcode链接](https://leetcode.com/problems/course-schedule-ii/description/) / [力扣链接](https://leetcode-cn.com/problems/course-schedule-ii/description/) -[Leetcode](https://leetcode.com/problems/course-schedule-ii/description/) / [力扣](https://leetcode-cn.com/problems/course-schedule-ii/description/) +#### 示例 +输入: ```html 4, [[1,0],[2,0],[3,1],[3,2]] -There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is[0,2,1,3]. ``` +输出:`[0,1,2,3]` 或 `[0,2,1,3]` -使用 DFS 来实现拓扑排序,使用一个栈存储后序遍历结果,这个栈的逆序结果就是拓扑排序结果。 +**题目描述:** 给定课程及其先修课程,返回课程的一个合法学习顺序。使用深度优先搜索 (DFS) 进行拓扑排序,利用一个栈存储后序遍历的结果,对于任意先修关系 v -> w,后序遍历结果可以保证 w 先入栈,因此栈的逆序即为合法学习顺序。 -证明:对于任何先序关系:v-\>w,后序遍历结果可以保证 w 先进入栈中,因此栈的逆序结果中 v 会在 w 之前。 +#### 解法 + +以下是拓扑排序的实现代码: ```java +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + public int[] findOrder(int numCourses, int[][] prerequisites) { - List<Integer>[] graphic = new List[numCourses]; + List<Integer>[] graph = new List[numCourses]; for (int i = 0; i < numCourses; i++) { - graphic[i] = new ArrayList<>(); + graph[i] = new ArrayList<>(); } + // 构建图的邻接表 for (int[] pre : prerequisites) { - graphic[pre[0]].add(pre[1]); + graph[pre[0]].add(pre[1]); } - Stack<Integer> postOrder = new Stack<>(); - boolean[] globalMarked = new boolean[numCourses]; - boolean[] localMarked = new boolean[numCourses]; + + Stack<Integer> postOrder = new Stack<>(); // 存储后序遍历结果 + boolean[] globalMarked = new boolean[numCourses]; // 全局标记 + boolean[] localMarked = new boolean[numCourses]; // 局部标记 + for (int i = 0; i < numCourses; i++) { - if (hasCycle(globalMarked, localMarked, graphic, i, postOrder)) { - return new int[0]; + if (hasCycle(globalMarked, localMarked, graph, i, postOrder)) { + return new int[0]; // 存在环,返回空数组 } } - int[] orders = new int[numCourses]; + + int[] orders = new int[numCourses]; // 课程顺序数组 for (int i = numCourses - 1; i >= 0; i--) { - orders[i] = postOrder.pop(); + orders[i] = postOrder.pop(); // 将栈中的元素存入结果数组 } - return orders; + return orders; // 返回课程学习顺序 } -private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked, List<Integer>[] graphic, - int curNode, Stack<Integer> postOrder) { - +private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked, List<Integer>[] graph, int curNode, Stack<Integer> postOrder) { if (localMarked[curNode]) { - return true; + return true; // 发现环,返回 true } if (globalMarked[curNode]) { - return false; + return false; // 已遍历过,返回 false } - globalMarked[curNode] = true; - localMarked[curNode] = true; - for (int nextNode : graphic[curNode]) { - if (hasCycle(globalMarked, localMarked, graphic, nextNode, postOrder)) { - return true; + + globalMarked[curNode] = true; // 标记当前节点为全局已访问 + localMarked[curNode] = true; // 标记当前节点为局部已访问 + + for (int nextNode : graph[curNode]) { + if (hasCycle(globalMarked, localMarked, graph, nextNode, postOrder)) { + return true; // 发现环,返回 true } } - localMarked[curNode] = false; - postOrder.push(curNode); - return false; + localMarked[curNode] = false; // 当前节点出栈,局部标记恢复 + postOrder.push(curNode); // 将当前节点入栈 + return false; // 无环,返回 false } ``` ## 并查集 -并查集可以动态地连通两个点,并且可以非常快速地判断两个点是否连通。 +**并查集**(Union-Find)是一种常用的数据结构,用于处理动态连通性问题,能够高效地判断两个节点是否在同一集合中,并能合并两个集合。 ### 1. 冗余连接 -684\. Redundant Connection (Medium) +**题目:** 684\. Redundant Connection (中等) + +[Leetcode链接](https://leetcode.com/problems/redundant-connection/description/) / [力扣链接](https://leetcode-cn.com/problems/redundant-connection/description/) -[Leetcode](https://leetcode.com/problems/redundant-connection/description/) / [力扣](https://leetcode-cn.com/problems/redundant-connection/description/) +#### 示例 +输入: ```html -Input: [[1,2], [1,3], [2,3]] -Output: [2,3] -Explanation: The given undirected graph will be like this: +[[1,2], [1,3], [2,3]] +``` +输出:`[2,3]` +解释:图的结构如下: +``` 1 / \ 2 - 3 ``` +**题目描述:** 给定一组边,判断去掉一条边后,图能否成为一棵树。 -题目描述:有一系列的边连成的图,找出一条边,移除它之后该图能够成为一棵树。 +#### 解法 + +使用并查集来解决此问题。以下是具体实现的代码: ```java public int[] findRedundantConnection(int[][] edges) { @@ -225,43 +274,45 @@ public int[] findRedundantConnection(int[][] edges) { for (int[] e : edges) { int u = e[0], v = e[1]; if (uf.connect(u, v)) { - return e; + return e; // 返回发现的冗余边 } - uf.union(u, v); + uf.union(u, v); // 合并两个节点 } - return new int[]{-1, -1}; + return new int[]{-1, -1}; // 如果没有冗余连接 } private class UF { - private int[] id; UF(int N) { - id = new int[N + 1]; + id = new int[N + 1]; // 节点编号从 1 开始 for (int i = 0; i < id.length; i++) { - id[i] = i; + id[i] = i; // 初始化每个节点的父节点为其自身 } } void union(int u, int v) { - int uID = find(u); - int vID = find(v); + int uID = find(u); // 查找 u 的根节点 + int vID = find(v); // 查找 v 的根节点 if (uID == vID) { - return; + return; // 如果两节点已在同一集合,不进行合并 } + // 合并两个集合 for (int i = 0; i < id.length; i++) { if (id[i] == uID) { - id[i] = vID; + id[i] = vID; // 将 u 的所有子节点的父节点指向 v } } } int find(int p) { - return id[p]; + return id[p]; // 返回节点 p 的根节点 } boolean connect(int u, int v) { - return find(u) == find(v); + return find(u) == find(v); // 判断两个节点是否连通 } } ``` + +这段代码中,我们首先初始化并查集,然后遍历每条边,利用并查集的查找和合并操作来判断是否出现冗余连接,同时记录下来。最终返回冗余的边。 \ No newline at end of file diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \346\216\222\345\272\217.md" "b/notes/Leetcode \351\242\230\350\247\243 - \346\216\222\345\272\217.md" index 36a31e3d9b..973f29618b 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \346\216\222\345\272\217.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \346\216\222\345\272\217.md" @@ -1,106 +1,97 @@ +```markdown # Leetcode 题解 - 排序 -<!-- GFM-TOC --> -* [Leetcode 题解 - 排序](#leetcode-题解---排序) - * [快速选择](#快速选择) - * [堆](#堆) - * [1. Kth Element](#1-kth-element) - * [桶排序](#桶排序) - * [1. 出现频率最多的 k 个元素](#1-出现频率最多的-k-个元素) - * [2. 按照字符出现次数对字符串排序](#2-按照字符出现次数对字符串排序) - * [荷兰国旗问题](#荷兰国旗问题) - * [1. 按颜色进行排序](#1-按颜色进行排序) -<!-- GFM-TOC --> - ## 快速选择 -用于求解 **Kth Element** 问题,也就是第 K 个元素的问题。 - -可以使用快速排序的 partition() 进行实现。需要先打乱数组,否则最坏情况下时间复杂度为 O(N<sup>2</sup>)。 +快速选择是一种用于解决 **第 K 个元素** 问题的算法,即在给定的数组中寻找第 K 大或第 K 小的元素。它的实现方式类似于快速排序的 partition 方法。为了避免最坏情况下的时间复杂度达到 O(N<sup>2</sup>),我们需要在每次选择基准元素时打乱输入数组。 ## 堆 -用于求解 **TopK Elements** 问题,也就是 K 个最小元素的问题。使用最小堆来实现 TopK 问题,最小堆使用大顶堆来实现,大顶堆的堆顶元素为当前堆的最大元素。实现过程:不断地往大顶堆中插入新元素,当堆中元素的数量大于 k 时,移除堆顶元素,也就是当前堆中最大的元素,剩下的元素都为当前添加过的元素中最小的 K 个元素。插入和移除堆顶元素的时间复杂度都为 log<sub>2</sub>N。 - -堆也可以用于求解 Kth Element 问题,得到了大小为 K 的最小堆之后,因为使用了大顶堆来实现,因此堆顶元素就是第 K 大的元素。 - -快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。 - -可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。 +堆是一种特殊的树形数据结构,常用于解决 **Top K 元素** 问题,即从一组数据中找到前 K 个最小或最大元素。 ### 1. Kth Element -215\. Kth Largest Element in an Array (Medium) +215. Kth Largest Element in an Array (Medium) [Leetcode](https://leetcode.com/problems/kth-largest-element-in-an-array/description/) / [力扣](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/description/) +### 示例 + ```text -Input: [3,2,1,5,6,4] and k = 2 -Output: 5 +输入: [3,2,1,5,6,4] 和 k = 2 +输出: 5 ``` -题目描述:找到倒数第 k 个的元素。 +题目描述:找到数组中倒数第 K 个元素。 + +#### 解法一:排序 -**排序** :时间复杂度 O(NlogN),空间复杂度 O(1) +我们可以使用排序来解决这个问题,时间复杂度为 O(N log N),空间复杂度为 O(1)。 ```java public int findKthLargest(int[] nums, int k) { Arrays.sort(nums); - return nums[nums.length - k]; + return nums[nums.length - k]; // 返回倒数第 K 个元素 } ``` -**堆** :时间复杂度 O(NlogK),空间复杂度 O(K)。 +#### 解法二:堆 + +我们也可以使用堆来找寻倒数第 K 个元素,时间复杂度为 O(N log K),空间复杂度为 O(K)。 ```java +import java.util.PriorityQueue; + public int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小顶堆 for (int val : nums) { - pq.add(val); - if (pq.size() > k) // 维护堆的大小为 K + pq.add(val); // 将元素添加到堆中 + if (pq.size() > k) // 如果堆大小大于 K,移除堆顶元素 pq.poll(); } - return pq.peek(); + return pq.peek(); // 堆顶元素即为第 K 大元素 } ``` -**快速选择** :时间复杂度 O(N),空间复杂度 O(1) +#### 解法三:快速选择 + +快速选择算法的时间复杂度为 O(N),空间复杂度为 O(1)。 ```java public int findKthLargest(int[] nums, int k) { - k = nums.length - k; - int l = 0, h = nums.length - 1; - while (l < h) { - int j = partition(nums, l, h); - if (j == k) { - break; - } else if (j < k) { - l = j + 1; + k = nums.length - k; // 转换为寻找第 K 小元素 + int left = 0, right = nums.length - 1; + while (left < right) { + int pivotIndex = partition(nums, left, right); + if (pivotIndex == k) { + break; // 找到第 K 小元素 + } else if (pivotIndex < k) { + left = pivotIndex + 1; // 搜索右子数组 } else { - h = j - 1; + right = pivotIndex - 1; // 搜索左子数组 } } - return nums[k]; + return nums[k]; // 返回第 K 小元素 } -private int partition(int[] a, int l, int h) { - int i = l, j = h + 1; +// 分区函数 +private int partition(int[] arr, int left, int right) { + int pivot = arr[left]; // 选择第一个元素作为基准 + int i = left, j = right + 1; // i 从左边,j 从右边 while (true) { - while (a[++i] < a[l] && i < h) ; - while (a[--j] > a[l] && j > l) ; - if (i >= j) { - break; - } - swap(a, i, j); + while (i < right && arr[++i] < pivot); // 找到大于或等于基准的元素 + while (j > left && arr[--j] > pivot); // 找到小于或等于基准的元素 + if (i >= j) break; // i 和 j 交叉,停止分区 + swap(arr, i, j); // 交换找到的两个元素 } - swap(a, l, j); - return j; + swap(arr, left, j); // 将基准元素放到正确的位置 + return j; // 返回基准元素的最终位置 } -private void swap(int[] a, int i, int j) { - int t = a[i]; - a[i] = a[j]; - a[j] = t; +private void swap(int[] arr, int i, int j) { + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; // 交换数组中的两个元素 } ``` @@ -108,137 +99,136 @@ private void swap(int[] a, int i, int j) { ### 1. 出现频率最多的 k 个元素 -347\. Top K Frequent Elements (Medium) +347. Top K Frequent Elements (Medium) [Leetcode](https://leetcode.com/problems/top-k-frequent-elements/description/) / [力扣](https://leetcode-cn.com/problems/top-k-frequent-elements/description/) -```html -Given [1,1,1,2,2,3] and k = 2, return [1,2]. -``` +### 示例 -设置若干个桶,每个桶存储出现频率相同的数。桶的下标表示数出现的频率,即第 i 个桶中存储的数出现的频率为 i。 +```text +给定数组 [1,1,1,2,2,3] 和 k = 2,返回 [1,2]。 +``` -把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。 +我们可以使用桶排序的策略,设置多个桶,每个桶存放具有相同频率的元素。桶的索引对应频率。通过遍历桶,我们可以在 O(N) 的时间内获取到出现频率最高的 k 个元素。 ```java +import java.util.*; + public int[] topKFrequent(int[] nums, int k) { - Map<Integer, Integer> frequencyForNum = new HashMap<>(); + Map<Integer, Integer> frequencyMap = new HashMap<>(); for (int num : nums) { - frequencyForNum.put(num, frequencyForNum.getOrDefault(num, 0) + 1); + frequencyMap.put(num, frequencyMap.getOrDefault(num, 0) + 1); // 记录每个数字的频率 } - List<Integer>[] buckets = new ArrayList[nums.length + 1]; - for (int key : frequencyForNum.keySet()) { - int frequency = frequencyForNum.get(key); + + List<Integer>[] buckets = new ArrayList[nums.length + 1]; // 创建桶 + for (int key : frequencyMap.keySet()) { + int frequency = frequencyMap.get(key); if (buckets[frequency] == null) { buckets[frequency] = new ArrayList<>(); } - buckets[frequency].add(key); + buckets[frequency].add(key); // 将数字放入对应频率的桶中 } + List<Integer> topK = new ArrayList<>(); for (int i = buckets.length - 1; i >= 0 && topK.size() < k; i--) { - if (buckets[i] == null) { - continue; - } - if (buckets[i].size() <= (k - topK.size())) { - topK.addAll(buckets[i]); - } else { - topK.addAll(buckets[i].subList(0, k - topK.size())); + if (buckets[i] != null) { + topK.addAll(buckets[i].subList(0, Math.min(k - topK.size(), buckets[i].size()))); } } - int[] res = new int[k]; - for (int i = 0; i < k; i++) { - res[i] = topK.get(i); - } - return res; + + return topK.stream().mapToInt(i -> i).toArray(); // 返回 top K 元素的数组 } ``` ### 2. 按照字符出现次数对字符串排序 -451\. Sort Characters By Frequency (Medium) +451. Sort Characters By Frequency (Medium) [Leetcode](https://leetcode.com/problems/sort-characters-by-frequency/description/) / [力扣](https://leetcode-cn.com/problems/sort-characters-by-frequency/description/) -```html -Input: -"tree" +### 示例 -Output: -"eert" - -Explanation: -'e' appears twice while 'r' and 't' both appear once. -So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid answer. +```text +输入: "tree" +输出: "eert" ``` +在这个问题中,我们同样可以使用桶排序的想法来实现。我们首先获取每个字符的频率,然后将它们根据频率进行排序。 + ```java +import java.util.*; + public String frequencySort(String s) { - Map<Character, Integer> frequencyForNum = new HashMap<>(); - for (char c : s.toCharArray()) - frequencyForNum.put(c, frequencyForNum.getOrDefault(c, 0) + 1); - - List<Character>[] frequencyBucket = new ArrayList[s.length() + 1]; - for (char c : frequencyForNum.keySet()) { - int f = frequencyForNum.get(c); - if (frequencyBucket[f] == null) { - frequencyBucket[f] = new ArrayList<>(); - } - frequencyBucket[f].add(c); + Map<Character, Integer> frequencyMap = new HashMap<>(); + for (char c : s.toCharArray()) { + frequencyMap.put(c, frequencyMap.getOrDefault(c, 0) + 1); // 记录每个字符频率 } - StringBuilder str = new StringBuilder(); - for (int i = frequencyBucket.length - 1; i >= 0; i--) { - if (frequencyBucket[i] == null) { - continue; + + List<Character>[] frequencyBuckets = new ArrayList[s.length() + 1]; // 创建频率桶 + for (char c : frequencyMap.keySet()) { + int freq = frequencyMap.get(c); + if (frequencyBuckets[freq] == null) { + frequencyBuckets[freq] = new ArrayList<>(); } - for (char c : frequencyBucket[i]) { - for (int j = 0; j < i; j++) { - str.append(c); + frequencyBuckets[freq].add(c); // 将字符放入对应频率的桶中 + } + + StringBuilder sortedString = new StringBuilder(); + for (int i = frequencyBuckets.length - 1; i >= 0; i--) { + if (frequencyBuckets[i] != null) { + for (char c : frequencyBuckets[i]) { + for (int j = 0; j < i; j++) { // 频率 i 的字符出现 i 次 + sortedString.append(c); + } } } } - return str.toString(); + return sortedString.toString(); // 返回排序后的字符串 } ``` ## 荷兰国旗问题 -荷兰国旗包含三种颜色:红、白、蓝。 - -有三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7a3215ec-6fb7-4935-8b0d-cb408208f7cb.png"/> </div><br> - +荷兰国旗问题涉及三种颜色的元素。该算法旨在将这些元素按颜色从小到大排列。通过三向切分的快速排序,我们能有效的将数组分为小于、等于和大于基准元素的三个部分。 ### 1. 按颜色进行排序 -75\. Sort Colors (Medium) +75. Sort Colors (Medium) [Leetcode](https://leetcode.com/problems/sort-colors/description/) / [力扣](https://leetcode-cn.com/problems/sort-colors/description/) -```html -Input: [2,0,2,1,1,0] -Output: [0,0,1,1,2,2] +### 示例 + +```text +输入: [2,0,2,1,1,0] +输出: [0,0,1,1,2,2] ``` -题目描述:只有 0/1/2 三种颜色。 +我们的目标是将含有 0、1、2 的数组进行排序。我们使用双指针的方法来完成这个任务。 ```java public void sortColors(int[] nums) { - int zero = -1, one = 0, two = nums.length; - while (one < two) { - if (nums[one] == 0) { - swap(nums, ++zero, one++); - } else if (nums[one] == 2) { - swap(nums, --two, one); + int zeroIndex = -1; // 记录 0 的位置 + int oneIndex = 0; // 当前处理的元素索引 + int twoIndex = nums.length; // 记录 2 的位置 + + while (oneIndex < twoIndex) { + if (nums[oneIndex] == 0) { + swap(nums, ++zeroIndex, oneIndex++); // 0 移到前面 + } else if (nums[oneIndex] == 2) { + swap(nums, --twoIndex, oneIndex); // 2 移到后面 } else { - ++one; + oneIndex++; // 处理 1,不做交换 } } } private void swap(int[] nums, int i, int j) { - int t = nums[i]; + int temp = nums[i]; nums[i] = nums[j]; - nums[j] = t; + nums[j] = temp; // 交换 nums[i] 和 nums[j] } ``` + +这个方法能在 O(N) 的时间复杂度内完成排序,并且只需要 O(1) 的额外空间。 +``` \ No newline at end of file diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \346\220\234\347\264\242.md" "b/notes/Leetcode \351\242\230\350\247\243 - \346\220\234\347\264\242.md" index 83d1c85ebc..09d102c5e2 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \346\220\234\347\264\242.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \346\220\234\347\264\242.md" @@ -1,132 +1,69 @@ # Leetcode 题解 - 搜索 -<!-- GFM-TOC --> -* [Leetcode 题解 - 搜索](#leetcode-题解---搜索) - * [BFS](#bfs) - * [1. 计算在网格中从原点到特定点的最短路径长度](#1-计算在网格中从原点到特定点的最短路径长度) - * [2. 组成整数的最小平方数数量](#2-组成整数的最小平方数数量) - * [3. 最短单词路径](#3-最短单词路径) - * [DFS](#dfs) - * [1. 查找最大的连通面积](#1-查找最大的连通面积) - * [2. 矩阵中的连通分量数目](#2-矩阵中的连通分量数目) - * [3. 好友关系的连通分量数目](#3-好友关系的连通分量数目) - * [4. 填充封闭区域](#4-填充封闭区域) - * [5. 能到达的太平洋和大西洋的区域](#5-能到达的太平洋和大西洋的区域) - * [Backtracking](#backtracking) - * [1. 数字键盘组合](#1-数字键盘组合) - * [2. IP 地址划分](#2-ip-地址划分) - * [3. 在矩阵中寻找字符串](#3-在矩阵中寻找字符串) - * [4. 输出二叉树中所有从根到叶子的路径](#4-输出二叉树中所有从根到叶子的路径) - * [5. 排列](#5-排列) - * [6. 含有相同元素求排列](#6-含有相同元素求排列) - * [7. 组合](#7-组合) - * [8. 组合求和](#8-组合求和) - * [9. 含有相同元素的组合求和](#9-含有相同元素的组合求和) - * [10. 1-9 数字的组合求和](#10-1-9-数字的组合求和) - * [11. 子集](#11-子集) - * [12. 含有相同元素求子集](#12-含有相同元素求子集) - * [13. 分割字符串使得每个部分都是回文数](#13-分割字符串使得每个部分都是回文数) - * [14. 数独](#14-数独) - * [15. N 皇后](#15-n-皇后) -<!-- GFM-TOC --> +搜索算法广泛应用于树和图中,尤其是深度优先搜索(DFS)和广度优先搜索(BFS)。这些算法不仅限于图的遍历,它们也能够解决许多不同类型的问题,例如路径查找、组合问题,以及更复杂的约束条件问题。以下是对 BFS、DFS 和回溯算法的具体应用和实现例子。 -深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。 +## BFS(广度优先搜索) -## BFS +广度优先搜索是一种从根节点开始,层层向外遍历的搜索算法。它在每一层中访问所有节点,所有访问的节点与上层节点的距离是相同的。BFS 通常使用队列实现,以便于逐层管理要访问的节点。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/95903878-725b-4ed9-bded-bc4aae0792a9.jpg"/> </div><br> - -广度优先搜索一层一层地进行遍历,每层遍历都是以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。 - -第一层: - -- 0 -\> {6,2,1,5} - -第二层: - -- 6 -\> {4} -- 2 -\> {} -- 1 -\> {} -- 5 -\> {3} - -第三层: - -- 4 -\> {} -- 3 -\> {} - -每一层遍历的节点都与根节点距离相同。设 d<sub>i</sub> 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 d<sub>i</sub> <= d<sub>j</sub>。利用这个结论,可以求解最短路径等 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径,无权图是指从一个节点到另一个节点的代价都记为 1。 - -在程序实现 BFS 时需要考虑以下问题: - -- 队列:用来存储每一轮遍历得到的节点; -- 标记:对于遍历过的节点,应该将它标记,防止重复遍历。 +### 实现要点 +- **队列**:用于存储待访问的节点。 +- **标记访问**:为了防止重复访问,同样需要标记已经遍历过的节点。 ### 1. 计算在网格中从原点到特定点的最短路径长度 -1091\. Shortest Path in Binary Matrix(Medium) - -[Leetcode](https://leetcode.com/problems/shortest-path-in-binary-matrix/) / [力扣](https://leetcode-cn.com/problems/shortest-path-in-binary-matrix/) - -```html -[[1,1,0,1], - [1,0,1,0], - [1,1,1,1], - [1,0,1,1]] -``` - -题目描述:0 表示可以经过某个位置,求解从左上角到右下角的最短路径长度。 +**题目描述**:给定一个网格,其中`0`表示可以经过,`1`表示障碍。求从左上角到右下角的最短路径长度。 ```java public int shortestPathBinaryMatrix(int[][] grids) { - if (grids == null || grids.length == 0 || grids[0].length == 0) { - return -1; - } - int[][] direction = {{1, -1}, {1, 0}, {1, 1}, {0, -1}, {0, 1}, {-1, -1}, {-1, 0}, {-1, 1}}; - int m = grids.length, n = grids[0].length; - Queue<Pair<Integer, Integer>> queue = new LinkedList<>(); - queue.add(new Pair<>(0, 0)); - int pathLength = 0; - while (!queue.isEmpty()) { - int size = queue.size(); - pathLength++; - while (size-- > 0) { - Pair<Integer, Integer> cur = queue.poll(); - int cr = cur.getKey(), cc = cur.getValue(); - if (grids[cr][cc] == 1) { - continue; - } - if (cr == m - 1 && cc == n - 1) { - return pathLength; - } - grids[cr][cc] = 1; // 标记 - for (int[] d : direction) { - int nr = cr + d[0], nc = cc + d[1]; - if (nr < 0 || nr >= m || nc < 0 || nc >= n) { - continue; - } - queue.add(new Pair<>(nr, nc)); + if (grids == null || grids.length == 0 || grids[0].length == 0) { + return -1; + } + + // 定义移动方向,上下左右和对角线 + int[][] direction = {{1, -1}, {1, 0}, {1, 1}, {0, -1}, + {0, 1}, {-1, -1}, {-1, 0}, {-1, 1}}; + + int m = grids.length, n = grids[0].length; + Queue<Pair<Integer, Integer>> queue = new LinkedList<>(); + queue.add(new Pair<>(0, 0)); // 从起点开始 + int pathLength = 0; + + while (!queue.isEmpty()) { + int size = queue.size(); + pathLength++; + while (size-- > 0) { + Pair<Integer, Integer> cur = queue.poll(); + int cr = cur.getKey(), cc = cur.getValue(); + + // 如果当前节点是障碍物,跳过 + if (grids[cr][cc] == 1) { + continue; + } + + // 如果到达右下角,返回路径长度 + if (cr == m - 1 && cc == n - 1) { + return pathLength; + } + + // 标记已访问 + grids[cr][cc] = 1; + for (int[] d : direction) { + int nr = cr + d[0], nc = cc + d[1]; + if (nr < 0 || nr >= m || nc < 0 || nc >= n) { + continue; // 超出边界 } + queue.add(new Pair<>(nr, nc)); // 添加新的节点到队列 } } - return -1; } + return -1; // 如果没有路径 +} ``` ### 2. 组成整数的最小平方数数量 -279\. Perfect Squares (Medium) - -[Leetcode](https://leetcode.com/problems/perfect-squares/description/) / [力扣](https://leetcode-cn.com/problems/perfect-squares/description/) - -```html -For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9. -``` - -可以将每个整数看成图中的一个节点,如果两个整数之差为一个平方数,那么这两个整数所在的节点就有一条边。 - -要求解最小的平方数数量,就是求解从节点 n 到节点 0 的最短路径。 - -本题也可以用动态规划求解,在之后动态规划部分中会再次出现。 +**题目描述**:给定一个正整数 n,返回能够通过若干个完全平方数(如 1, 4, 9)相加的最少数量。 ```java public int numSquares(int n) { @@ -135,7 +72,8 @@ public int numSquares(int n) { boolean[] marked = new boolean[n + 1]; queue.add(n); marked[n] = true; - int level = 0; + int level = 0; // 用来计数层数,即最小平方数个数 + while (!queue.isEmpty()) { int size = queue.size(); level++; @@ -144,34 +82,31 @@ public int numSquares(int n) { for (int s : squares) { int next = cur - s; if (next < 0) { - break; + break; // 当前平方数大于当前值,停止 } if (next == 0) { - return level; + return level; // 找到解 } if (marked[next]) { - continue; + continue; // 已访问 } marked[next] = true; - queue.add(next); + queue.add(next); // 加入下一个待访问的数 } } } - return n; + return n; // 不会无限循环,总会输出结果 } -/** - * 生成小于 n 的平方数序列 - * @return 1,4,9,... - */ +// 生成小于 n 的所有平方数 private List<Integer> generateSquares(int n) { List<Integer> squares = new ArrayList<>(); int square = 1; - int diff = 3; + int diff = 3; // 从 1 开始,1 + (2i - 1) 规律 while (square <= n) { - squares.add(square); + squares.add(square); square += diff; - diff += 2; + diff += 2; // 每次加的差变大 } return squares; } @@ -179,34 +114,7 @@ private List<Integer> generateSquares(int n) { ### 3. 最短单词路径 -127\. Word Ladder (Medium) - -[Leetcode](https://leetcode.com/problems/word-ladder/description/) / [力扣](https://leetcode-cn.com/problems/word-ladder/description/) - -```html -Input: -beginWord = "hit", -endWord = "cog", -wordList = ["hot","dot","dog","lot","log","cog"] - -Output: 5 - -Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog", -return its length 5. -``` - -```html -Input: -beginWord = "hit" -endWord = "cog" -wordList = ["hot","dot","dog","lot","log"] - -Output: 0 - -Explanation: The endWord "cog" is not in wordList, therefore no possible transformation. -``` - -题目描述:找出一条从 beginWord 到 endWord 的最短路径,每次移动规定为改变一个字符,并且改变之后的字符串必须在 wordList 中。 +**题目描述**:通过改变一个字母,从一个单词变换成另一个单词,求出所需的最少单词变换步骤。每一步的变换都必须是字典中的一个有效单词。 ```java public int ladderLength(String beginWord, String endWord, List<String> wordList) { @@ -214,32 +122,37 @@ public int ladderLength(String beginWord, String endWord, List<String> wordList) int N = wordList.size(); int start = N - 1; int end = 0; + + // 找到 endWord 的索引 while (end < N && !wordList.get(end).equals(endWord)) { end++; } + if (end == N) { - return 0; + return 0; // 如果没找到目标单词 } - List<Integer>[] graphic = buildGraphic(wordList); - return getShortestPath(graphic, start, end); + + List<Integer>[] graph = buildGraph(wordList); + return getShortestPath(graph, start, end); } -private List<Integer>[] buildGraphic(List<String> wordList) { +private List<Integer>[] buildGraph(List<String> wordList) { int N = wordList.size(); - List<Integer>[] graphic = new List[N]; + List<Integer>[] graph = new List[N]; for (int i = 0; i < N; i++) { - graphic[i] = new ArrayList<>(); + graph[i] = new ArrayList<>(); for (int j = 0; j < N; j++) { if (isConnect(wordList.get(i), wordList.get(j))) { - graphic[i].add(j); + graph[i].add(j); // 认为存在边 } } } - return graphic; + return graph; } +// 判断两个单词只有一个字母不同 private boolean isConnect(String s1, String s2) { - int diffCnt = 0; + int diffCnt = 0; // 记录不同字符数量 for (int i = 0; i < s1.length() && diffCnt <= 1; i++) { if (s1.charAt(i) != s2.charAt(i)) { diffCnt++; @@ -248,76 +161,57 @@ private boolean isConnect(String s1, String s2) { return diffCnt == 1; } -private int getShortestPath(List<Integer>[] graphic, int start, int end) { +// BFS 找到最短路径 +private int getShortestPath(List<Integer>[] graph, int start, int end) { Queue<Integer> queue = new LinkedList<>(); - boolean[] marked = new boolean[graphic.length]; + boolean[] marked = new boolean[graph.length]; queue.add(start); marked[start] = true; - int path = 1; + int pathLength = 1; // 路径步数 + while (!queue.isEmpty()) { int size = queue.size(); - path++; + pathLength++; while (size-- > 0) { int cur = queue.poll(); - for (int next : graphic[cur]) { + for (int next : graph[cur]) { if (next == end) { - return path; + return pathLength; // 找到目标 } if (marked[next]) { - continue; + continue; // 跳过已访问节点 } - marked[next] = true; - queue.add(next); + marked[next] = true; + queue.add(next); // 加入队列 } } } - return 0; + return 0; // 无法到达 } ``` -## DFS - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/74dc31eb-6baa-47ea-ab1c-d27a0ca35093.png"/> </div><br> - -广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。 +## DFS(深度优先搜索) -而深度优先搜索在得到一个新节点时立即对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。 +深度优先搜索是一种策略,先访问一个新节点,再从此节点访问未被访问的节点。DFS 通常用于可达性问题,以及在遍历中寻找特定条件的路径。 -从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 **可达性** 问题。 - -在程序实现 DFS 时需要考虑以下问题: - -- 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。 -- 标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。 +### 实现要点 +- **栈**:常常使用递归栈来确保可以回溯。 +- **标记访问**:i避免重复访问相同的节点,也需要进行标记。 ### 1. 查找最大的连通面积 -695\. Max Area of Island (Medium) - -[Leetcode](https://leetcode.com/problems/max-area-of-island/description/) / [力扣](https://leetcode-cn.com/problems/max-area-of-island/description/) - -```html -[[0,0,1,0,0,0,0,1,0,0,0,0,0], - [0,0,0,0,0,0,0,1,1,1,0,0,0], - [0,1,1,0,1,0,0,0,0,0,0,0,0], - [0,1,0,0,1,1,0,0,1,0,1,0,0], - [0,1,0,0,1,1,0,0,1,1,1,0,0], - [0,0,0,0,0,0,0,0,0,0,1,0,0], - [0,0,0,0,0,0,0,1,1,1,0,0,0], - [0,0,0,0,0,0,0,1,1,0,0,0,0]] -``` +**题目描述**:给定一个二维网格,0表示水,1表示岛屿。求该网格中连通的最大的岛屿面积。 ```java -private int m, n; -private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; - public int maxAreaOfIsland(int[][] grid) { if (grid == null || grid.length == 0) { return 0; } - m = grid.length; - n = grid[0].length; + + int m = grid.length; + int n = grid[0].length; int maxArea = 0; + for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { maxArea = Math.max(maxArea, dfs(grid, i, j)); @@ -327,50 +221,40 @@ public int maxAreaOfIsland(int[][] grid) { } private int dfs(int[][] grid, int r, int c) { - if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == 0) { - return 0; + if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length || grid[r][c] == 0) { + return 0; // 越界或水域返回0 } - grid[r][c] = 0; - int area = 1; - for (int[] d : direction) { - area += dfs(grid, r + d[0], c + d[1]); - } - return area; + + grid[r][c] = 0; // 标记为水,避免重复访问 + int area = 1; // 计算当前节点面积 + + // 四个方向 + area += dfs(grid, r, c + 1); // 右 + area += dfs(grid, r, c - 1); // 左 + area += dfs(grid, r + 1, c); // 下 + area += dfs(grid, r - 1, c); // 上 + + return area; // 返回一个岛屿的总面积 } ``` ### 2. 矩阵中的连通分量数目 -200\. Number of Islands (Medium) - -[Leetcode](https://leetcode.com/problems/number-of-islands/description/) / [力扣](https://leetcode-cn.com/problems/number-of-islands/description/) - -```html -Input: -11000 -11000 -00100 -00011 - -Output: 3 -``` - -可以将矩阵表示看成一张有向图。 +**题目描述**:给定一个矩阵,1 表示岛屿,0 表示水。求该矩阵中岛屿的个数。 ```java -private int m, n; -private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; - public int numIslands(char[][] grid) { if (grid == null || grid.length == 0) { - return 0; + return 0; // 无法进行搜索 } - m = grid.length; - n = grid[0].length; + + int m = grid.length; + int n = grid[0].length; int islandsNum = 0; + for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { - if (grid[i][j] != '0') { + if (grid[i][j] != '0') { // 找到一个岛屿 dfs(grid, i, j); islandsNum++; } @@ -380,46 +264,32 @@ public int numIslands(char[][] grid) { } private void dfs(char[][] grid, int i, int j) { - if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') { - return; - } - grid[i][j] = '0'; - for (int[] d : direction) { - dfs(grid, i + d[0], j + d[1]); + if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0') { + return; // 找到边界或者水域 } + + grid[i][j] = '0'; // 水域化,避免重复 + // 四个方向 + dfs(grid, i + 1, j); // 下 + dfs(grid, i - 1, j); // 上 + dfs(grid, i, j + 1); // 右 + dfs(grid, i, j - 1); // 左 } ``` ### 3. 好友关系的连通分量数目 -547\. Friend Circles (Medium) - -[Leetcode](https://leetcode.com/problems/friend-circles/description/) / [力扣](https://leetcode-cn.com/problems/friend-circles/description/) - -```html -Input: -[[1,1,0], - [1,1,0], - [0,0,1]] - -Output: 2 - -Explanation:The 0th and 1st students are direct friends, so they are in a friend circle. -The 2nd student himself is in a friend circle. So return 2. -``` - -题目描述:好友关系可以看成是一个无向图,例如第 0 个人与第 1 个人是好友,那么 M[0][1] 和 M[1][0] 的值都为 1。 +**题目描述**:给出一个 N x N 的矩阵,其中 M[i][j] 表示第 i 个人和第 j 个人是否是朋友关系,求出朋友圈的数目。 ```java -private int n; - public int findCircleNum(int[][] M) { - n = M.length; + int n = M.length; int circleNum = 0; boolean[] hasVisited = new boolean[n]; + for (int i = 0; i < n; i++) { if (!hasVisited[i]) { - dfs(M, i, hasVisited); + dfs(M, i, hasVisited); // 遍历该朋友圈 circleNum++; } } @@ -427,10 +297,10 @@ public int findCircleNum(int[][] M) { } private void dfs(int[][] M, int i, boolean[] hasVisited) { - hasVisited[i] = true; - for (int k = 0; k < n; k++) { - if (M[i][k] == 1 && !hasVisited[k]) { - dfs(M, k, hasVisited); + hasVisited[i] = true; // 标记为已访问 + for (int k = 0; k < M.length; k++) { + if (M[i][k] == 1 && !hasVisited[k]) { // 如果是朋友且未访问过 + dfs(M, k, hasVisited); // 递归DFS } } } @@ -438,173 +308,116 @@ private void dfs(int[][] M, int i, boolean[] hasVisited) { ### 4. 填充封闭区域 -130\. Surrounded Regions (Medium) - -[Leetcode](https://leetcode.com/problems/surrounded-regions/description/) / [力扣](https://leetcode-cn.com/problems/surrounded-regions/description/) - -```html -For example, -X X X X -X O O X -X X O X -X O X X - -After running your function, the board should be: -X X X X -X X X X -X X X X -X O X X -``` - -题目描述:使被 'X' 包围的 'O' 转换为 'X'。 - -先填充最外侧,剩下的就是里侧了。 +**题目描述**:将被 `'X'` 包围的 `'O'` 转换为 `'X'`。 ```java -private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; -private int m, n; - public void solve(char[][] board) { if (board == null || board.length == 0) { return; } - m = board.length; - n = board[0].length; - + int m = board.length; + int n = board[0].length; + + // 从边界出发,填充与边界连通的 'O' for (int i = 0; i < m; i++) { - dfs(board, i, 0); - dfs(board, i, n - 1); + dfs(board, i, 0); // 左边 + dfs(board, i, n - 1); // 右边 } for (int i = 0; i < n; i++) { - dfs(board, 0, i); - dfs(board, m - 1, i); + dfs(board, 0, i); // 上边 + dfs(board, m - 1, i); // 下边 } + // 处理完边界的 'O' ,现在转换被包围的 'O' for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (board[i][j] == 'T') { - board[i][j] = 'O'; + board[i][j] = 'O'; // 转换为 'O' } else if (board[i][j] == 'O') { - board[i][j] = 'X'; + board[i][j] = 'X'; // 转换为 'X' } } } } private void dfs(char[][] board, int r, int c) { - if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != 'O') { - return; - } - board[r][c] = 'T'; - for (int[] d : direction) { - dfs(board, r + d[0], c + d[1]); + if (r < 0 || r >= board.length || c < 0 || c >= board[0].length || board[r][c] != 'O') { + return; // 越界或已经不是 'O' } + board[r][c] = 'T'; // 标记为被访问 + // 深度优先搜索四个方向 + dfs(board, r + 1, c); // 下 + dfs(board, r - 1, c); // 上 + dfs(board, r, c + 1); // 右 + dfs(board, r, c - 1); // 左 } ``` ### 5. 能到达的太平洋和大西洋的区域 -417\. Pacific Atlantic Water Flow (Medium) - -[Leetcode](https://leetcode.com/problems/pacific-atlantic-water-flow/description/) / [力扣](https://leetcode-cn.com/problems/pacific-atlantic-water-flow/description/) - -```html -Given the following 5x5 matrix: - - Pacific ~ ~ ~ ~ ~ - ~ 1 2 2 3 (5) * - ~ 3 2 3 (4) (4) * - ~ 2 4 (5) 3 1 * - ~ (6) (7) 1 4 5 * - ~ (5) 1 1 2 4 * - * * * * * Atlantic - -Return: -[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (positions with parentheses in above matrix). -``` - -左边和上边是太平洋,右边和下边是大西洋,内部的数字代表海拔,海拔高的地方的水能够流到低的地方,求解水能够流到太平洋和大西洋的所有位置。 +**题目描述**:给定一个矩阵,找到能够同时流向太平洋和大西洋的所有位置。 ```java -private int m, n; -private int[][] matrix; -private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; - public List<List<Integer>> pacificAtlantic(int[][] matrix) { - List<List<Integer>> ret = new ArrayList<>(); + List<List<Integer>> result = new ArrayList<>(); if (matrix == null || matrix.length == 0) { - return ret; + return result; } - m = matrix.length; - n = matrix[0].length; - this.matrix = matrix; + int m = matrix.length; + int n = matrix[0].length; boolean[][] canReachP = new boolean[m][n]; boolean[][] canReachA = new boolean[m][n]; for (int i = 0; i < m; i++) { - dfs(i, 0, canReachP); - dfs(i, n - 1, canReachA); + dfs(i, 0, canReachP); // 太平洋 + dfs(i, n - 1, canReachA); // 大西洋 } for (int i = 0; i < n; i++) { - dfs(0, i, canReachP); - dfs(m - 1, i, canReachA); + dfs(0, i, canReachP); // 太平洋 + dfs(m - 1, i, canReachA); // 大西洋 } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (canReachP[i][j] && canReachA[i][j]) { - ret.add(Arrays.asList(i, j)); + result.add(Arrays.asList(i, j)); // 加入结果 } } } - - return ret; + + return result; } private void dfs(int r, int c, boolean[][] canReach) { if (canReach[r][c]) { - return; + return; // 如果已经可以到达则返回 } - canReach[r][c] = true; - for (int[] d : direction) { + canReach[r][c] = true; // 标记为可以到达 + for (int[] d : direction) { // 遍历四个方向 int nextR = d[0] + r; int nextC = d[1] + c; - if (nextR < 0 || nextR >= m || nextC < 0 || nextC >= n + if (nextR < 0 || nextR >= matrix.length || nextC < 0 || nextC >= matrix[0].length || matrix[r][c] > matrix[nextR][nextC]) { - - continue; + continue; // 越界,或者避免水流向坡度高的地方 } dfs(nextR, nextC, canReach); } } ``` -## Backtracking - -Backtracking(回溯)属于 DFS。 - -- 普通 DFS 主要用在 **可达性问题** ,这种问题只需要执行到特点的位置然后返回即可。 -- 而 Backtracking 主要用于求解 **排列组合** 问题,例如有 { 'a','b','c' } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。 +## Backtracking(回溯) -因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题: +回溯是一种特定类型的深度优先搜索。它通常应用于排列组合、子集等问题。在回溯中,我们不仅需要找到解,而且需要探索所有可能的解。 -- 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素; -- 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。 +### 实现要点 +- **标记元素状态**:在访问一个元素时标记其状态,以便在回溯时正确恢复状态。 +- **递归**:通过递归函数处理节点层级,如果达到特定条件就回溯。 ### 1. 数字键盘组合 -17\. Letter Combinations of a Phone Number (Medium) - -[Leetcode](https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/) / [力扣](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/description/) - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/9823768c-212b-4b1a-b69a-b3f59e07b977.jpg"/> </div><br> - -```html -Input:Digit string "23" -Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. -``` +**题目描述**:给定一个数字串,返回所有可能的字母组合。 ```java private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; @@ -612,7 +425,7 @@ private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", public List<String> letterCombinations(String digits) { List<String> combinations = new ArrayList<>(); if (digits == null || digits.length() == 0) { - return combinations; + return combinations; // 输入空字符串 } doCombination(new StringBuilder(), combinations, digits); return combinations; @@ -620,29 +433,22 @@ public List<String> letterCombinations(String digits) { private void doCombination(StringBuilder prefix, List<String> combinations, final String digits) { if (prefix.length() == digits.length()) { - combinations.add(prefix.toString()); + combinations.add(prefix.toString()); // 找到一个解 return; } - int curDigits = digits.charAt(prefix.length()) - '0'; + int curDigits = digits.charAt(prefix.length()) - '0'; // 当前数字位置 String letters = KEYS[curDigits]; for (char c : letters.toCharArray()) { - prefix.append(c); // 添加 - doCombination(prefix, combinations, digits); - prefix.deleteCharAt(prefix.length() - 1); // 删除 + prefix.append(c); // 添加当前字母 + doCombination(prefix, combinations, digits); // 继续选择下一个字母 + prefix.deleteCharAt(prefix.length() - 1); // 回溯,删除当前字母 } } ``` ### 2. IP 地址划分 -93\. Restore IP Addresses(Medium) - -[Leetcode](https://leetcode.com/problems/restore-ip-addresses/description/) / [力扣](https://leetcode-cn.com/problems/restore-ip-addresses/description/) - -```html -Given "25525511135", -return ["255.255.11.135", "255.255.111.35"]. -``` +**题目描述**:给定一个字符串,找到所有可能的有效 IP 地址。 ```java public List<String> restoreIpAddresses(String s) { @@ -653,24 +459,24 @@ public List<String> restoreIpAddresses(String s) { } private void doRestore(int k, StringBuilder tempAddress, List<String> addresses, String s) { - if (k == 4 || s.length() == 0) { + if (k == 4 || s.length() == 0) { // 找到 4 段或字符串用完 if (k == 4 && s.length() == 0) { - addresses.add(tempAddress.toString()); + addresses.add(tempAddress.toString()); // 添加有效地址 } return; } - for (int i = 0; i < s.length() && i <= 2; i++) { + for (int i = 0; i < s.length() && i <= 2; i++) { // 每段最多3位 if (i != 0 && s.charAt(0) == '0') { - break; + break; // 以0开头的多位数不合法 } - String part = s.substring(0, i + 1); - if (Integer.valueOf(part) <= 255) { + String part = s.substring(0, i + 1); // 获取当前部分 + if (Integer.valueOf(part) <= 255) { // 判断数值是否在 0-255 之间 if (tempAddress.length() != 0) { - part = "." + part; + part = "." + part; // 添加小数点 } tempAddress.append(part); - doRestore(k + 1, tempAddress, addresses, s.substring(i + 1)); - tempAddress.delete(tempAddress.length() - part.length(), tempAddress.length()); + doRestore(k + 1, tempAddress, addresses, s.substring(i + 1)); // 递归填充下一位 + tempAddress.delete(tempAddress.length() - part.length(), tempAddress.length()); // 回溯 } } } @@ -678,27 +484,11 @@ private void doRestore(int k, StringBuilder tempAddress, List<String> addresses, ### 3. 在矩阵中寻找字符串 -79\. Word Search (Medium) - -[Leetcode](https://leetcode.com/problems/word-search/description/) / [力扣](https://leetcode-cn.com/problems/word-search/description/) - -```html -For example, -Given board = -[ - ['A','B','C','E'], - ['S','F','C','S'], - ['A','D','E','E'] -] -word = "ABCCED", -> returns true, -word = "SEE", -> returns true, -word = "ABCB", -> returns false. -``` +**题目描述**:给定一个二维字符矩阵和一个单词,判断单词是否存在于矩阵中。 ```java private final static int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; -private int m; -private int n; +private int m, n; public boolean exist(char[][] board, String word) { if (word == null || word.length() == 0) { @@ -715,58 +505,40 @@ public boolean exist(char[][] board, String word) { for (int r = 0; r < m; r++) { for (int c = 0; c < n; c++) { if (backtracking(0, r, c, hasVisited, board, word)) { - return true; + return true; // 找到解 } } } - - return false; + return false; // 未能找到 } private boolean backtracking(int curLen, int r, int c, boolean[][] visited, final char[][] board, final String word) { if (curLen == word.length()) { - return true; + return true; // 完全匹配 } - if (r < 0 || r >= m || c < 0 || c >= n - || board[r][c] != word.charAt(curLen) || visited[r][c]) { - - return false; + if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(curLen) || visited[r][c]) { + return false; // 越界、匹配失败或已访问 } - visited[r][c] = true; + visited[r][c] = true; // 标记为访问 for (int[] d : direction) { if (backtracking(curLen + 1, r + d[0], c + d[1], visited, board, word)) { - return true; + return true; // 继续递归 } } - visited[r][c] = false; + visited[r][c] = false; // 回溯要恢复状态 - return false; + return false; // 未找到 } ``` ### 4. 输出二叉树中所有从根到叶子的路径 -257\. Binary Tree Paths (Easy) - -[Leetcode](https://leetcode.com/problems/binary-tree-paths/description/) / [力扣](https://leetcode-cn.com/problems/binary-tree-paths/description/) - -```html - 1 - / \ -2 3 - \ - 5 -``` - -```html -["1->2->5", "1->3"] -``` +**题目描述**:给定一棵二叉树,返回所有从根到叶子的路径。 ```java - public List<String> binaryTreePaths(TreeNode root) { List<String> paths = new ArrayList<>(); if (root == null) { @@ -781,18 +553,18 @@ private void backtracking(TreeNode node, List<Integer> values, List<String> path if (node == null) { return; } - values.add(node.val); + values.add(node.val); // 添加当前节点值 if (isLeaf(node)) { - paths.add(buildPath(values)); + paths.add(buildPath(values)); // 如果是叶子节点,构建路径 } else { - backtracking(node.left, values, paths); - backtracking(node.right, values, paths); + backtracking(node.left, values, paths); // 递归左子树 + backtracking(node.right, values, paths); // 递归右子树 } - values.remove(values.size() - 1); + values.remove(values.size() - 1); // 回溯,移除当前节点 } private boolean isLeaf(TreeNode node) { - return node.left == null && node.right == null; + return node.left == null && node.right == null; // 判断是否为叶子节点 } private String buildPath(List<Integer> values) { @@ -800,30 +572,16 @@ private String buildPath(List<Integer> values) { for (int i = 0; i < values.size(); i++) { str.append(values.get(i)); if (i != values.size() - 1) { - str.append("->"); + str.append("->"); // 添加箭头 } } - return str.toString(); + return str.toString(); // 返回路径字符串 } ``` ### 5. 排列 -46\. Permutations (Medium) - -[Leetcode](https://leetcode.com/problems/permutations/description/) / [力扣](https://leetcode-cn.com/problems/permutations/description/) - -```html -[1,2,3] have the following permutations: -[ - [1,2,3], - [1,3,2], - [2,1,3], - [2,3,1], - [3,1,2], - [3,2,1] -] -``` +**题目描述**:给定一个不含重复数字的数组,返回它的所有可能的排列。 ```java public List<List<Integer>> permute(int[] nums) { @@ -836,36 +594,25 @@ public List<List<Integer>> permute(int[] nums) { private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, final int[] nums) { if (permuteList.size() == nums.length) { - permutes.add(new ArrayList<>(permuteList)); // 重新构造一个 List + permutes.add(new ArrayList<>(permuteList)); // 找到一种排列方式 return; } for (int i = 0; i < visited.length; i++) { if (visited[i]) { - continue; + continue; // 已访问的跳过 } - visited[i] = true; - permuteList.add(nums[i]); - backtracking(permuteList, permutes, visited, nums); - permuteList.remove(permuteList.size() - 1); - visited[i] = false; + visited[i] = true; // 标记已访问 + permuteList.add(nums[i]); // 加入当前数字 + backtracking(permuteList, permutes, visited, nums); // 继续递归 + permuteList.remove(permuteList.size() - 1); // 回溯,移除数字 + visited[i] = false; // 恢复状态 } } ``` ### 6. 含有相同元素求排列 -47\. Permutations II (Medium) - -[Leetcode](https://leetcode.com/problems/permutations-ii/description/) / [力扣](https://leetcode-cn.com/problems/permutations-ii/description/) - -```html -[1,1,2] have the following unique permutations: -[[1,1,2], [1,2,1], [2,1,1]] -``` - -数组元素可能含有相同的元素,进行排列时就有可能出现重复的排列,要求重复的排列只返回一个。 - -在实现上,和 Permutations 不同的是要先排序,然后在添加一个元素时,判断这个元素是否等于前一个元素,如果等于,并且前一个元素还未访问,那么就跳过这个元素。 +**题目描述**:给定一个可能包含重复数字的数组,返回其所有唯一的排列。 ```java public List<List<Integer>> permuteUnique(int[] nums) { @@ -879,7 +626,7 @@ public List<List<Integer>> permuteUnique(int[] nums) { private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, final int[] nums) { if (permuteList.size() == nums.length) { - permutes.add(new ArrayList<>(permuteList)); + permutes.add(new ArrayList<>(permuteList)); // 找到一种排列 return; } @@ -887,35 +634,21 @@ private void backtracking(List<Integer> permuteList, List<List<Integer>> permute if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) { continue; // 防止重复 } - if (visited[i]){ - continue; + if (visited[i]) { + continue; // 已访问的跳过 } - visited[i] = true; - permuteList.add(nums[i]); - backtracking(permuteList, permutes, visited, nums); - permuteList.remove(permuteList.size() - 1); - visited[i] = false; + visited[i] = true; // 标记访问 + permuteList.add(nums[i]); // 加入当前数字 + backtracking(permuteList, permutes, visited, nums); // 继续递归 + permuteList.remove(permuteList.size() - 1); // 回溯 + visited[i] = false; // 恢复状态 } } ``` ### 7. 组合 -77\. Combinations (Medium) - -[Leetcode](https://leetcode.com/problems/combinations/description/) / [力扣](https://leetcode-cn.com/problems/combinations/description/) - -```html -If n = 4 and k = 2, a solution is: -[ - [2,4], - [3,4], - [2,3], - [1,2], - [1,3], - [1,4], -] -``` +**题目描述**:给定数字 n 和 k,返回范围 [1, n] 中所有可能的组合(每个组合包含 k 个数字)。 ```java public List<List<Integer>> combine(int n, int k) { @@ -927,28 +660,20 @@ public List<List<Integer>> combine(int n, int k) { private void backtracking(List<Integer> combineList, List<List<Integer>> combinations, int start, int k, final int n) { if (k == 0) { - combinations.add(new ArrayList<>(combineList)); + combinations.add(new ArrayList<>(combineList)); // 找到一种组合 return; } for (int i = start; i <= n - k + 1; i++) { // 剪枝 - combineList.add(i); - backtracking(combineList, combinations, i + 1, k - 1, n); - combineList.remove(combineList.size() - 1); + combineList.add(i); // 加入当前数字 + backtracking(combineList, combinations, i + 1, k - 1, n); // 继续 + combineList.remove(combineList.size() - 1); // 回溯,移除 } } ``` ### 8. 组合求和 -39\. Combination Sum (Medium) - -[Leetcode](https://leetcode.com/problems/combination-sum/description/) / [力扣](https://leetcode-cn.com/problems/combination-sum/description/) - -```html -given candidate set [2, 3, 6, 7] and target 7, -A solution set is: -[[7],[2, 2, 3]] -``` +**题目描述**:给定一组候选数字和一个目标数字,找出所有可以使得候选数字相加等于目标数字的组合。 ```java public List<List<Integer>> combinationSum(int[] candidates, int target) { @@ -959,16 +684,15 @@ public List<List<Integer>> combinationSum(int[] candidates, int target) { private void backtracking(List<Integer> tempCombination, List<List<Integer>> combinations, int start, int target, final int[] candidates) { - if (target == 0) { - combinations.add(new ArrayList<>(tempCombination)); + combinations.add(new ArrayList<>(tempCombination)); // 找到一种组合 return; } for (int i = start; i < candidates.length; i++) { if (candidates[i] <= target) { - tempCombination.add(candidates[i]); - backtracking(tempCombination, combinations, i, target - candidates[i], candidates); - tempCombination.remove(tempCombination.size() - 1); + tempCombination.add(candidates[i]); // 加入当前数字 + backtracking(tempCombination, combinations, i, target - candidates[i], candidates); // 继续 + tempCombination.remove(tempCombination.size() - 1); // 回溯,移除数字 } } } @@ -976,46 +700,32 @@ private void backtracking(List<Integer> tempCombination, List<List<Integer>> com ### 9. 含有相同元素的组合求和 -40\. Combination Sum II (Medium) - -[Leetcode](https://leetcode.com/problems/combination-sum-ii/description/) / [力扣](https://leetcode-cn.com/problems/combination-sum-ii/description/) - -```html -For example, given candidate set [10, 1, 2, 7, 6, 1, 5] and target 8, -A solution set is: -[ - [1, 7], - [1, 2, 5], - [2, 6], - [1, 1, 6] -] -``` +**题目描述**:给定一个集合,找到所有独特的组合和,使得它们和为目标数字。 ```java public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<List<Integer>> combinations = new ArrayList<>(); - Arrays.sort(candidates); + Arrays.sort(candidates); // 排序以便重复处理 backtracking(new ArrayList<>(), combinations, new boolean[candidates.length], 0, target, candidates); return combinations; } private void backtracking(List<Integer> tempCombination, List<List<Integer>> combinations, boolean[] hasVisited, int start, int target, final int[] candidates) { - if (target == 0) { - combinations.add(new ArrayList<>(tempCombination)); + combinations.add(new ArrayList<>(tempCombination)); // 找到一种组合 return; } for (int i = start; i < candidates.length; i++) { if (i != 0 && candidates[i] == candidates[i - 1] && !hasVisited[i - 1]) { - continue; + continue; // 防止重复 } if (candidates[i] <= target) { - tempCombination.add(candidates[i]); - hasVisited[i] = true; - backtracking(tempCombination, combinations, hasVisited, i + 1, target - candidates[i], candidates); - hasVisited[i] = false; - tempCombination.remove(tempCombination.size() - 1); + tempCombination.add(candidates[i]); // 加入当前数字 + hasVisited[i] = true; // 标记为已访问 + backtracking(tempCombination, combinations, hasVisited, i + 1, target - candidates[i], candidates); // 继续 + hasVisited[i] = false; // 恢复状态 + tempCombination.remove(tempCombination.size() - 1); // 回溯 } } } @@ -1023,19 +733,7 @@ private void backtracking(List<Integer> tempCombination, List<List<Integer>> com ### 10. 1-9 数字的组合求和 -216\. Combination Sum III (Medium) - -[Leetcode](https://leetcode.com/problems/combination-sum-iii/description/) / [力扣](https://leetcode-cn.com/problems/combination-sum-iii/description/) - -```html -Input: k = 3, n = 9 - -Output: - -[[1,2,6], [1,3,5], [2,3,4]] -``` - -从 1-9 数字中选出 k 个数不重复的数,使得它们的和为 n。 +**题目描述**:从 1-9 的数字中选出 k 个数,使得它们的和为 n。 ```java public List<List<Integer>> combinationSum3(int k, int n) { @@ -1047,122 +745,88 @@ public List<List<Integer>> combinationSum3(int k, int n) { private void backtracking(int k, int n, int start, List<Integer> tempCombination, List<List<Integer>> combinations) { - if (k == 0 && n == 0) { - combinations.add(new ArrayList<>(tempCombination)); + combinations.add(new ArrayList<>(tempCombination)); // 找到一种组合 return; } if (k == 0 || n == 0) { - return; + return; // 结束条件 } + for (int i = start; i <= 9; i++) { - tempCombination.add(i); - backtracking(k - 1, n - i, i + 1, tempCombination, combinations); - tempCombination.remove(tempCombination.size() - 1); + tempCombination.add(i); // 添加当前数字 + backtracking(k - 1, n - i, i + 1, tempCombination, combinations); // 继续 + tempCombination.remove(tempCombination.size() - 1); // 回溯,移除数字 } } ``` ### 11. 子集 -78\. Subsets (Medium) - -[Leetcode](https://leetcode.com/problems/subsets/description/) / [力扣](https://leetcode-cn.com/problems/subsets/description/) - -找出集合的所有子集,子集不能重复,[1, 2] 和 [2, 1] 这种子集算重复 +**题目描述**:返回一个数字集合的所有子集。 ```java public List<List<Integer>> subsets(int[] nums) { List<List<Integer>> subsets = new ArrayList<>(); List<Integer> tempSubset = new ArrayList<>(); for (int size = 0; size <= nums.length; size++) { - backtracking(0, tempSubset, subsets, size, nums); // 不同的子集大小 + backtracking(0, tempSubset, subsets, size, nums); // 不同的子集大小 } return subsets; } private void backtracking(int start, List<Integer> tempSubset, List<List<Integer>> subsets, final int size, final int[] nums) { - if (tempSubset.size() == size) { - subsets.add(new ArrayList<>(tempSubset)); + subsets.add(new ArrayList<>(tempSubset)); // 找到一种子集 return; } for (int i = start; i < nums.length; i++) { - tempSubset.add(nums[i]); - backtracking(i + 1, tempSubset, subsets, size, nums); - tempSubset.remove(tempSubset.size() - 1); + tempSubset.add(nums[i]); // 添加当前数字 + backtracking(i + 1, tempSubset, subsets, size, nums); // 继续 + tempSubset.remove(tempSubset.size() - 1); // 回溯,移除数字 } } ``` ### 12. 含有相同元素求子集 -90\. Subsets II (Medium) - -[Leetcode](https://leetcode.com/problems/subsets-ii/description/) / [力扣](https://leetcode-cn.com/problems/subsets-ii/description/) - -```html -For example, -If nums = [1,2,2], a solution is: - -[ - [2], - [1], - [1,2,2], - [2,2], - [1,2], - [] -] -``` +**题目描述**:返回一个可能包含重复数字的集合的所有子集。 ```java public List<List<Integer>> subsetsWithDup(int[] nums) { - Arrays.sort(nums); + Arrays.sort(nums); // 排序以辅助处理重复 List<List<Integer>> subsets = new ArrayList<>(); List<Integer> tempSubset = new ArrayList<>(); boolean[] hasVisited = new boolean[nums.length]; for (int size = 0; size <= nums.length; size++) { - backtracking(0, tempSubset, subsets, hasVisited, size, nums); // 不同的子集大小 + backtracking(0, tempSubset, subsets, hasVisited, size, nums); // 不同的子集大小 } return subsets; } private void backtracking(int start, List<Integer> tempSubset, List<List<Integer>> subsets, boolean[] hasVisited, final int size, final int[] nums) { - if (tempSubset.size() == size) { - subsets.add(new ArrayList<>(tempSubset)); + subsets.add(new ArrayList<>(tempSubset)); // 找到一种子集 return; } for (int i = start; i < nums.length; i++) { if (i != 0 && nums[i] == nums[i - 1] && !hasVisited[i - 1]) { - continue; + continue; // 防止重复 } - tempSubset.add(nums[i]); - hasVisited[i] = true; - backtracking(i + 1, tempSubset, subsets, hasVisited, size, nums); - hasVisited[i] = false; - tempSubset.remove(tempSubset.size() - 1); + tempSubset.add(nums[i]); // 添加当前数字 + hasVisited[i] = true; // 标记为已访问 + backtracking(i + 1, tempSubset, subsets, hasVisited, size, nums); // 继续 + hasVisited[i] = false; // 恢复状态 + tempSubset.remove(tempSubset.size() - 1); // 回溯 } } ``` ### 13. 分割字符串使得每个部分都是回文数 -131\. Palindrome Partitioning (Medium) - -[Leetcode](https://leetcode.com/problems/palindrome-partitioning/description/) / [力扣](https://leetcode-cn.com/problems/palindrome-partitioning/description/) - -```html -For example, given s = "aab", -Return - -[ - ["aa","b"], - ["a","a","b"] -] -``` +**题目描述**:给定一个字符串,将其分割成若干部分,使得每一个部分都是回文。 ```java public List<List<String>> partition(String s) { @@ -1174,14 +838,14 @@ public List<List<String>> partition(String s) { private void doPartition(String s, List<List<String>> partitions, List<String> tempPartition) { if (s.length() == 0) { - partitions.add(new ArrayList<>(tempPartition)); + partitions.add(new ArrayList<>(tempPartition)); // 找到一种分割方式 return; } for (int i = 0; i < s.length(); i++) { - if (isPalindrome(s, 0, i)) { - tempPartition.add(s.substring(0, i + 1)); - doPartition(s.substring(i + 1), partitions, tempPartition); - tempPartition.remove(tempPartition.size() - 1); + if (isPalindrome(s, 0, i)) { // 判断当前前缀是否为回文 + tempPartition.add(s.substring(0, i + 1)); // 找到回文,加入当前分割 + doPartition(s.substring(i + 1), partitions, tempPartition); // 继续分割剩下部分 + tempPartition.remove(tempPartition.size() - 1); // 回溯 } } } @@ -1189,20 +853,16 @@ private void doPartition(String s, List<List<String>> partitions, List<String> t private boolean isPalindrome(String s, int begin, int end) { while (begin < end) { if (s.charAt(begin++) != s.charAt(end--)) { - return false; + return false; // 不相等,则非回文 } } - return true; + return true; // 是回文 } ``` ### 14. 数独 -37\. Sudoku Solver (Hard) - -[Leetcode](https://leetcode.com/problems/sudoku-solver/description/) / [力扣](https://leetcode-cn.com/problems/sudoku-solver/description/) - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/0e8fdc96-83c1-4798-9abe-45fc91d70b9d.png"/> </div><br> +**题目描述**:给定一个 9x9 方格,完成数独的求解。 ```java private boolean[][] rowsUsed = new boolean[9][10]; @@ -1215,66 +875,49 @@ public void solveSudoku(char[][] board) { for (int i = 0; i < 9; i++) for (int j = 0; j < 9; j++) { if (board[i][j] == '.') { - continue; + continue; // 空格跳过 } - int num = board[i][j] - '0'; + int num = board[i][j] - '0'; // 转为数值 rowsUsed[i][num] = true; colsUsed[j][num] = true; cubesUsed[cubeNum(i, j)][num] = true; } - backtracking(0, 0); + backtracking(0, 0); // 开始回溯 } private boolean backtracking(int row, int col) { while (row < 9 && board[row][col] != '.') { - row = col == 8 ? row + 1 : row; - col = col == 8 ? 0 : col + 1; + row = col == 8 ? row + 1 : row; // 若到达行尾,则转到下一行 + col = col == 8 ? 0 : col + 1; // 否则继续本行 } if (row == 9) { - return true; + return true; // 完成整个数独 } for (int num = 1; num <= 9; num++) { if (rowsUsed[row][num] || colsUsed[col][num] || cubesUsed[cubeNum(row, col)][num]) { - continue; + continue; // 当前值在行、列或小方格中已使用 } rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubeNum(row, col)][num] = true; - board[row][col] = (char) (num + '0'); + board[row][col] = (char) (num + '0'); // 填上当前数值 if (backtracking(row, col)) { - return true; + return true; // 回溯成功 } - board[row][col] = '.'; + board[row][col] = '.'; // 恢复空格 rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubeNum(row, col)][num] = false; } - return false; + return false; // 无解 } private int cubeNum(int i, int j) { int r = i / 3; int c = j / 3; - return r * 3 + c; + return r * 3 + c; // 将子方格转化为索引 } ``` ### 15. N 皇后 -51\. N-Queens (Hard) - -[Leetcode](https://leetcode.com/problems/n-queens/description/) / [力扣](https://leetcode-cn.com/problems/n-queens/description/) - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/067b310c-6877-40fe-9dcf-10654e737485.jpg"/> </div><br> - -在 n\*n 的矩阵中摆放 n 个皇后,并且每个皇后不能在同一行,同一列,同一对角线上,求所有的 n 皇后的解。 - -一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要用三个标记数组来确定某一列是否合法,这三个标记数组分别为:列标记数组、45 度对角线标记数组和 135 度对角线标记数组。 - -45 度对角线标记数组的长度为 2 \* n - 1,通过下图可以明确 (r, c) 的位置所在的数组下标为 r + c。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/9c422923-1447-4a3b-a4e1-97e663738187.jpg" width="300px"> </div><br> - - -135 度对角线标记数组的长度也是 2 \* n - 1,(r, c) 的位置所在的数组下标为 n - 1 - (r - c)。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7a85e285-e152-4116-b6dc-3fab27ba9437.jpg" width="300px"> </div><br> +**题目描述**:在 n*n 的棋盘上放置 n 个皇后,使得它们不能相互攻击。 ```java private List<List<String>> solutions; @@ -1288,21 +931,21 @@ public List<List<String>> solveNQueens(int n) { solutions = new ArrayList<>(); nQueens = new char[n][n]; for (int i = 0; i < n; i++) { - Arrays.fill(nQueens[i], '.'); + Arrays.fill(nQueens[i], '.'); // 初始化棋盘 } - colUsed = new boolean[n]; - diagonals45Used = new boolean[2 * n - 1]; - diagonals135Used = new boolean[2 * n - 1]; + colUsed = new boolean[n]; // 列标记 + diagonals45Used = new boolean[2 * n - 1]; // 45度标记 + diagonals135Used = new boolean[2 * n - 1]; // 135度标记 this.n = n; - backtracking(0); - return solutions; + backtracking(0); // 从第 0 行开始 + return solutions; // 返回解决方案 } private void backtracking(int row) { if (row == n) { List<String> list = new ArrayList<>(); for (char[] chars : nQueens) { - list.add(new String(chars)); + list.add(new String(chars)); // 从棋盘构建解 } solutions.add(list); return; @@ -1312,13 +955,15 @@ private void backtracking(int row) { int diagonals45Idx = row + col; int diagonals135Idx = n - 1 - (row - col); if (colUsed[col] || diagonals45Used[diagonals45Idx] || diagonals135Used[diagonals135Idx]) { - continue; + continue; // 如果此列或对角线被占用,则跳过 } - nQueens[row][col] = 'Q'; - colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = true; - backtracking(row + 1); - colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = false; - nQueens[row][col] = '.'; + nQueens[row][col] = 'Q'; // 放置皇后 + colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = true; // 标记 + backtracking(row + 1); // 继续放置下一行 + colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = false; // 恢复标记 + nQueens[row][col] = '.'; // 恢复棋盘状态 } } ``` + +通过这些实现,我们可以看到搜索算法在不同问题上的应用。BFS 多用于找到最短路径,DFS 和回溯适合组合的排列等问题。理解这些基础将帮助我们在解决更多复杂问题时做出更好的策略选择。 \ No newline at end of file diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\345\255\246.md" "b/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\345\255\246.md" index d41ad654c6..43e2f22a37 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\345\255\246.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\345\255\246.md" @@ -1,58 +1,35 @@ +```markdown # Leetcode 题解 - 数学 -<!-- GFM-TOC --> -* [Leetcode 题解 - 数学](#leetcode-题解---数学) - * [素数分解](#素数分解) - * [整除](#整除) - * [最大公约数最小公倍数](#最大公约数最小公倍数) - * [1. 生成素数序列](#1-生成素数序列) - * [2. 最大公约数](#2-最大公约数) - * [3. 使用位操作和减法求解最大公约数](#3-使用位操作和减法求解最大公约数) - * [进制转换](#进制转换) - * [1. 7 进制](#1-7-进制) - * [2. 16 进制](#2-16-进制) - * [3. 26 进制](#3-26-进制) - * [阶乘](#阶乘) - * [1. 统计阶乘尾部有多少个 0](#1-统计阶乘尾部有多少个-0) - * [字符串加法减法](#字符串加法减法) - * [1. 二进制加法](#1-二进制加法) - * [2. 字符串加法](#2-字符串加法) - * [相遇问题](#相遇问题) - * [1. 改变数组元素使所有的数组元素都相等](#1-改变数组元素使所有的数组元素都相等) - * [多数投票问题](#多数投票问题) - * [1. 数组中出现次数多于 n / 2 的元素](#1-数组中出现次数多于-n--2-的元素) - * [其它](#其它) - * [1. 平方数](#1-平方数) - * [2. 3 的 n 次方](#2-3-的-n-次方) - * [3. 乘积数组](#3-乘积数组) - * [4. 找出数组中的乘积最大的三个数](#4-找出数组中的乘积最大的三个数) -<!-- GFM-TOC --> - ## 素数分解 -每一个数都可以分解成素数的乘积,例如 84 = 2<sup>2</sup> \* 3<sup>1</sup> \* 5<sup>0</sup> \* 7<sup>1</sup> \* 11<sup>0</sup> \* 13<sup>0</sup> \* 17<sup>0</sup> \* … +每一个数都可以分解成素数的乘积,例如 84 = 2<sup>2</sup> * 3<sup>1</sup> * 5<sup>0</sup> * 7<sup>1</sup> * 11<sup>0</sup> * 13<sup>0</sup> * 17<sup>0</sup> * ... ## 整除 -令 x = 2<sup>m0</sup> \* 3<sup>m1</sup> \* 5<sup>m2</sup> \* 7<sup>m3</sup> \* 11<sup>m4</sup> \* … +设 x = 2<sup>m0</sup> * 3<sup>m1</sup> * 5<sup>m2</sup> * 7<sup>m3</sup> * 11<sup>m4</sup> * ... + +设 y = 2<sup>n0</sup> * 3<sup>n1</sup> * 5<sup>n2</sup> * 7<sup>n3</sup> * 11<sup>n4</sup> * ... + +如果 x 整除 y(即 y mod x == 0),那么对于所有的 i,必定有 m<sub>i</sub> <= n<sub>i</sub>。 -令 y = 2<sup>n0</sup> \* 3<sup>n1</sup> \* 5<sup>n2</sup> \* 7<sup>n3</sup> \* 11<sup>n4</sup> \* … +## 最大公约数与最小公倍数 -如果 x 整除 y(y mod x == 0),则对于所有 i,mi \<= ni。 +给定两个数 x 和 y,其最大公约数表示为 gcd(x, y),计算公式为: -## 最大公约数最小公倍数 +gcd(x,y) = 2<sup>min(m0,n0)</sup> * 3<sup>min(m1,n1)</sup> * 5<sup>min(m2,n2)</sup> * ... -x 和 y 的最大公约数为:gcd(x,y) = 2<sup>min(m0,n0)</sup> \* 3<sup>min(m1,n1)</sup> \* 5<sup>min(m2,n2)</sup> \* ... +而它们的最小公倍数则为: -x 和 y 的最小公倍数为:lcm(x,y) = 2<sup>max(m0,n0)</sup> \* 3<sup>max(m1,n1)</sup> \* 5<sup>max(m2,n2)</sup> \* ... +lcm(x,y) = 2<sup>max(m0,n0)</sup> * 3<sup>max(m1,n1)</sup> * 5<sup>max(m2,n2)</sup> * ... ### 1. 生成素数序列 -204\. Count Primes (Easy) +**题目 204**: 计算小于 n 的素数数量 (Easy) [Leetcode](https://leetcode.com/problems/count-primes/description/) / [力扣](https://leetcode-cn.com/problems/count-primes/description/) -埃拉托斯特尼筛法在每次找到一个素数时,将能被素数整除的数排除掉。 +使用埃拉托斯特尼筛法,每次找到一个素数时,将所有该素数的倍数标记为非素数。 ```java public int countPrimes(int n) { @@ -60,64 +37,65 @@ public int countPrimes(int n) { int count = 0; for (int i = 2; i < n; i++) { if (notPrimes[i]) { - continue; + continue; // 如果不是素数,跳过 } - count++; - // 从 i * i 开始,因为如果 k < i,那么 k * i 在之前就已经被去除过了 + count++; // 发现一个素数 + // 从 i * i 开始,因为小于 i 的倍数已经在之前被标记过了 for (long j = (long) (i) * i; j < n; j += i) { - notPrimes[(int) j] = true; + notPrimes[(int) j] = true; // 标记为非素数 } } - return count; + return count; // 返回素数的数量 } ``` ### 2. 最大公约数 +计算两个数 a 和 b 的最大公约数: + ```java int gcd(int a, int b) { - return b == 0 ? a : gcd(b, a % b); + return b == 0 ? a : gcd(b, a % b); // 递归实现 } ``` -最小公倍数为两数的乘积除以最大公约数。 +最小公倍数可通过最大公约数来求得: ```java int lcm(int a, int b) { - return a * b / gcd(a, b); + return a * b / gcd(a, b); // 使用最小公倍数公式 } ``` ### 3. 使用位操作和减法求解最大公约数 -[编程之美:2.7](#) - -对于 a 和 b 的最大公约数 f(a, b),有: +对于 a 和 b 的最大公约数 f(a, b),可以根据以下规则进行计算: -- 如果 a 和 b 均为偶数,f(a, b) = 2\*f(a/2, b/2); -- 如果 a 是偶数 b 是奇数,f(a, b) = f(a/2, b); -- 如果 b 是偶数 a 是奇数,f(a, b) = f(a, b/2); -- 如果 a 和 b 均为奇数,f(a, b) = f(b, a-b); +- 如果 a 和 b 均为偶数,f(a, b) = 2 * f(a/2, b/2); +- 如果 a 是偶数而 b 是奇数,f(a, b) = f(a/2, b); +- 如果 b 是偶数而 a 是奇数,f(a, b) = f(a, b/2); +- 如果 a 和 b 均为奇数,f(a, b) = f(b, a - b); -乘 2 和除 2 都可以转换为移位操作。 +下面是利用位操作进行实现的代码: ```java public int gcd(int a, int b) { if (a < b) { - return gcd(b, a); + return gcd(b, a); // 保证 a >= b } if (b == 0) { - return a; + return a; // 递归结束条件 } - boolean isAEven = isEven(a), isBEven = isEven(b); + boolean isAEven = (a & 1) == 0; // 判断 a 是否为偶数 + boolean isBEven = (b & 1) == 0; // 判断 b 是否为偶数 if (isAEven && isBEven) { - return 2 * gcd(a >> 1, b >> 1); - } else if (isAEven && !isBEven) { - return gcd(a >> 1, b); - } else if (!isAEven && isBEven) { - return gcd(a, b >> 1); + return 2 * gcd(a >> 1, b >> 1); // a 和 b 均为偶数 + } else if (isAEven) { + return gcd(a >> 1, b); // a 为偶数,b 为奇数 + } else if (isBEven) { + return gcd(a, b >> 1); // a 为奇数,b 为偶数 } else { - return gcd(b, a - b); + return gcd(b, a - b); // a 和 b 均为奇数 } } ``` @@ -126,97 +104,75 @@ public int gcd(int a, int b) { ### 1. 7 进制 -504\. Base 7 (Easy) +**题目 504**: 将数字转换为 7 进制 (Easy) [Leetcode](https://leetcode.com/problems/base-7/description/) / [力扣](https://leetcode-cn.com/problems/base-7/description/) +实现将整数转换为 7 进制的算法: + ```java public String convertToBase7(int num) { if (num == 0) { - return "0"; + return "0"; // 特殊情况处理 } StringBuilder sb = new StringBuilder(); - boolean isNegative = num < 0; + boolean isNegative = num < 0; // 判断是否为负数 if (isNegative) { - num = -num; + num = -num; // 转换为正数进行处理 } while (num > 0) { - sb.append(num % 7); - num /= 7; + sb.append(num % 7); // 余数作为下一位 + num /= 7; // 除以 7 继续 } - String ret = sb.reverse().toString(); - return isNegative ? "-" + ret : ret; + String ret = sb.reverse().toString(); // 反转字符串获得正确顺序 + return isNegative ? "-" + ret : ret; // 处理负号 } ``` -Java 中 static String toString(int num, int radix) 可以将一个整数转换为 radix 进制表示的字符串。 +简化实现: ```java public String convertToBase7(int num) { - return Integer.toString(num, 7); + return Integer.toString(num, 7); // 使用内置函数转换 } ``` ### 2. 16 进制 -405\. Convert a Number to Hexadecimal (Easy) +**题目 405**: 将数字转换为十六进制 (Easy) [Leetcode](https://leetcode.com/problems/convert-a-number-to-hexadecimal/description/) / [力扣](https://leetcode-cn.com/problems/convert-a-number-to-hexadecimal/description/) -```html -Input: -26 - -Output: -"1a" - -Input: --1 - -Output: -"ffffffff" -``` - -负数要用它的补码形式。 +对于负数,使用它的补码进行表示: ```java public String toHex(int num) { - char[] map = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - if (num == 0) return "0"; + char[] map = "0123456789abcdef".toCharArray(); // 十六进制字符映射 + if (num == 0) return "0"; // 特殊情况 StringBuilder sb = new StringBuilder(); while (num != 0) { - sb.append(map[num & 0b1111]); - num >>>= 4; // 因为考虑的是补码形式,因此符号位就不能有特殊的意义,需要使用无符号右移,左边填 0 + sb.append(map[num & 0b1111]); // 取最低的 4 位 + num >>>= 4; // 使用无符号右移处理负数 } - return sb.reverse().toString(); + return sb.reverse().toString(); // 反转结果 } ``` ### 3. 26 进制 -168\. Excel Sheet Column Title (Easy) +**题目 168**: Excel 表列标题转换 (Easy) [Leetcode](https://leetcode.com/problems/excel-sheet-column-title/description/) / [力扣](https://leetcode-cn.com/problems/excel-sheet-column-title/description/) -```html -1 -> A -2 -> B -3 -> C -... -26 -> Z -27 -> AA -28 -> AB -``` - -因为是从 1 开始计算的,而不是从 0 开始,因此需要对 n 执行 -1 操作。 +将整数转换为 Excel 列标题(1 -> A, 2 -> B,..., 27 -> AA): ```java public String convertToTitle(int n) { if (n == 0) { - return ""; + return ""; // 特殊情况 } - n--; - return convertToTitle(n / 26) + (char) (n % 26 + 'A'); + n--; // 因为是从 1 开始计数 + return convertToTitle(n / 26) + (char) (n % 26 + 'A'); // 递归构造标题 } ``` @@ -224,73 +180,65 @@ public String convertToTitle(int n) { ### 1. 统计阶乘尾部有多少个 0 -172\. Factorial Trailing Zeroes (Easy) +**题目 172**: 统计尾部零的个数 (Easy) [Leetcode](https://leetcode.com/problems/factorial-trailing-zeroes/description/) / [力扣](https://leetcode-cn.com/problems/factorial-trailing-zeroes/description/) -尾部的 0 由 2 * 5 得来,2 的数量明显多于 5 的数量,因此只要统计有多少个 5 即可。 - -对于一个数 N,它所包含 5 的个数为:N/5 + N/5<sup>2</sup> + N/5<sup>3</sup> + ...,其中 N/5 表示不大于 N 的数中 5 的倍数贡献一个 5,N/5<sup>2</sup> 表示不大于 N 的数中 5<sup>2</sup> 的倍数再贡献一个 5 ...。 +尾部的 0 由 2 和 5 的乘积产生,而 2 的个数通常多于 5,因此只需统计 5 的个数: ```java public int trailingZeroes(int n) { - return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5); + return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5); // 递归计算 } ``` -如果统计的是 N! 的二进制表示中最低位 1 的位置,只要统计有多少个 2 即可,该题目出自 [编程之美:2.2](#) 。和求解有多少个 5 一样,2 的个数为 N/2 + N/2<sup>2</sup> + N/2<sup>3</sup> + ... - ## 字符串加法减法 ### 1. 二进制加法 -67\. Add Binary (Easy) +**题目 67**: 二进制字符串相加 (Easy) [Leetcode](https://leetcode.com/problems/add-binary/description/) / [力扣](https://leetcode-cn.com/problems/add-binary/description/) -```html -a = "11" -b = "1" -Return "100". -``` +实现二进制字符串的加法: ```java public String addBinary(String a, String b) { - int i = a.length() - 1, j = b.length() - 1, carry = 0; + int i = a.length() - 1, j = b.length() - 1, carry = 0; // 从字符串末尾向前遍历 StringBuilder str = new StringBuilder(); - while (carry == 1 || i >= 0 || j >= 0) { + while (carry == 1 || i >= 0 || j >= 0) { // 直到所有位加完 if (i >= 0 && a.charAt(i--) == '1') { - carry++; + carry++; // 处理 a 的当前位 } if (j >= 0 && b.charAt(j--) == '1') { - carry++; + carry++; // 处理 b 的当前位 } - str.append(carry % 2); - carry /= 2; + str.append(carry % 2); // 当前位的结果 + carry /= 2; // 计算进位 } - return str.reverse().toString(); + return str.reverse().toString(); // 反转结果返回 } ``` ### 2. 字符串加法 -415\. Add Strings (Easy) +**题目 415**: 字符串表示的非负数相加 (Easy) [Leetcode](https://leetcode.com/problems/add-strings/description/) / [力扣](https://leetcode-cn.com/problems/add-strings/description/) -字符串的值为非负整数。 +实现字符串形式的加法运算: ```java public String addStrings(String num1, String num2) { StringBuilder str = new StringBuilder(); - int carry = 0, i = num1.length() - 1, j = num2.length() - 1; + int carry = 0, i = num1.length() - 1, j = num2.length() - 1; // 从字符串末尾向前遍历 while (carry == 1 || i >= 0 || j >= 0) { - int x = i < 0 ? 0 : num1.charAt(i--) - '0'; - int y = j < 0 ? 0 : num2.charAt(j--) - '0'; - str.append((x + y + carry) % 10); - carry = (x + y + carry) / 10; + int x = i < 0 ? 0 : num1.charAt(i--) - '0'; // 当前位的值 + int y = j < 0 ? 0 : num2.charAt(j--) - '0'; // 当前位的值 + str.append((x + y + carry) % 10); // 结果位 + carry = (x + y + carry) / 10; // 计算进位 } - return str.reverse().toString(); + return str.reverse().toString(); // 反转结果返回 } ``` @@ -298,59 +246,40 @@ public String addStrings(String num1, String num2) { ### 1. 改变数组元素使所有的数组元素都相等 -462\. Minimum Moves to Equal Array Elements II (Medium) +**题目 462**: 使数组元素相等的最小移动次数 (Medium) [Leetcode](https://leetcode.com/problems/minimum-moves-to-equal-array-elements-ii/description/) / [力扣](https://leetcode-cn.com/problems/minimum-moves-to-equal-array-elements-ii/description/) -```html -Input: -[1,2,3] +给定数组,求使数组所有元素相等的最小操作次数。每次操作可以对数组中的任意元素加一或减一。通过改变元素位置到某个特定值来最小化总移动次数,这个值通常选择中位数。 -Output: -2 +**解法 1:排序法** -Explanation: -Only two moves are needed (remember each move increments or decrements one element): - -[1,2,3] => [2,2,3] => [2,2,2] -``` - -每次可以对一个数组元素加一或者减一,求最小的改变次数。 - -这是个典型的相遇问题,移动距离最小的方式是所有元素都移动到中位数。理由如下: - -设 m 为中位数。a 和 b 是 m 两边的两个元素,且 b \> a。要使 a 和 b 相等,它们总共移动的次数为 b - a,这个值等于 (b - m) + (m - a),也就是把这两个数移动到中位数的移动次数。 - -设数组长度为 N,则可以找到 N/2 对 a 和 b 的组合,使它们都移动到 m 的位置。 - -**解法 1** - -先排序,时间复杂度:O(NlogN) +时间复杂度:O(N log N) ```java public int minMoves2(int[] nums) { - Arrays.sort(nums); + Arrays.sort(nums); // 将数组排序 int move = 0; - int l = 0, h = nums.length - 1; + int l = 0, h = nums.length - 1; // 指针分别指向数组两端 while (l <= h) { - move += nums[h] - nums[l]; - l++; - h--; + move += nums[h] - nums[l]; // 统计移动次数 + l++; // 移动左侧指针 + h--; // 移动右侧指针 } - return move; + return move; // 返回总移动次数 } ``` -**解法 2** +**解法 2:快速选择法** -使用快速选择找到中位数,时间复杂度 O(N) +使用快速选择找到中位数,时间复杂度 O(N)。 ```java public int minMoves2(int[] nums) { int move = 0; - int median = findKthSmallest(nums, nums.length / 2); + int median = findKthSmallest(nums, nums.length / 2); // 找到中位数 for (int num : nums) { - move += Math.abs(num - median); + move += Math.abs(num - median); // 计算每个元素到中位数的距离 } return move; } @@ -360,15 +289,15 @@ private int findKthSmallest(int[] nums, int k) { while (l < h) { int j = partition(nums, l, h); if (j == k) { - break; + break; // 找到第 k 小的元素 } if (j < k) { - l = j + 1; + l = j + 1; // 在右侧找 } else { - h = j - 1; + h = j - 1; // 在左侧找 } } - return nums[k]; + return nums[k]; // 返回结果 } private int partition(int[] nums, int l, int h) { @@ -377,18 +306,18 @@ private int partition(int[] nums, int l, int h) { while (nums[++i] < nums[l] && i < h) ; while (nums[--j] > nums[l] && j > l) ; if (i >= j) { - break; + break; // 找到区间位置 } - swap(nums, i, j); + swap(nums, i, j); // 交换元素 } - swap(nums, l, j); - return j; + swap(nums, l, j); // 将枢轴放到中间 + return j; // 返回中间位置 } private void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; - nums[j] = tmp; + nums[j] = tmp; // 交换操作 } ``` @@ -396,29 +325,32 @@ private void swap(int[] nums, int i, int j) { ### 1. 数组中出现次数多于 n / 2 的元素 -169\. Majority Element (Easy) +**题目 169**: 多数元素 (Easy) [Leetcode](https://leetcode.com/problems/majority-element/description/) / [力扣](https://leetcode-cn.com/problems/majority-element/description/) -先对数组排序,最中间那个数出现次数一定多于 n / 2。 +求数组中出现次数超过 n/2 的元素。可以通过排序法或 Boyer-Moore 投票算法来解决。 + +**排序法**:首先对数组进行排序,返回中间元素: ```java public int majorityElement(int[] nums) { - Arrays.sort(nums); - return nums[nums.length / 2]; + Arrays.sort(nums); // 先排序 + return nums[nums.length / 2]; // 返回中位数 } ``` -可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(N)。可以这么理解该算法:使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不相等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2,因为如果多于 i / 2 的话 cnt 就一定不会为 0。此时剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,因此继续查找就能找出 majority。 +**Boyer-Moore 投票算法**:时间复杂度 O(N),使用投票计数的方式: ```java public int majorityElement(int[] nums) { - int cnt = 0, majority = nums[0]; + int cnt = 0; + int majority = nums[0]; // 初始化为第一个元素 for (int num : nums) { - majority = (cnt == 0) ? num : majority; - cnt = (majority == num) ? cnt + 1 : cnt - 1; + majority = (cnt == 0) ? num : majority; // 当计数为 0,更新 majority + cnt = (majority == num) ? cnt + 1 : cnt - 1; // 计数增加或减少 } - return majority; + return majority; // 返回 majority 元素 } ``` @@ -426,110 +358,100 @@ public int majorityElement(int[] nums) { ### 1. 平方数 -367\. Valid Perfect Square (Easy) +**题目 367**: 有效的完全平方数 (Easy) [Leetcode](https://leetcode.com/problems/valid-perfect-square/description/) / [力扣](https://leetcode-cn.com/problems/valid-perfect-square/description/) -```html -Input: 16 -Returns: True -``` - -平方序列:1,4,9,16,.. - -间隔:3,5,7,... - -间隔为等差数列,使用这个特性可以得到从 1 开始的平方序列。 +判断给定的数是否为完全平方数。我们可以利用平方数间隔逐渐递增的特性,推导出此问题的解法: ```java public boolean isPerfectSquare(int num) { - int subNum = 1; + int subNum = 1; // 初始平方数为 1 while (num > 0) { - num -= subNum; - subNum += 2; + num -= subNum; // 减去当前平方数 + subNum += 2; // 下一个平方数的间隔递增 } - return num == 0; + return num == 0; // 如果最后为 0,则是完整平方 } ``` ### 2. 3 的 n 次方 -326\. Power of Three (Easy) +**题目 326**: 三的幂 (Easy) [Leetcode](https://leetcode.com/problems/power-of-three/description/) / [力扣](https://leetcode-cn.com/problems/power-of-three/description/) +检查一个数是否为 3 的幂: + ```java public boolean isPowerOfThree(int n) { - return n > 0 && (1162261467 % n == 0); + return n > 0 && (1162261467 % n == 0); // 1162261467 是 3 的 19 次方 } ``` ### 3. 乘积数组 -238\. Product of Array Except Self (Medium) +**题目 238**: 除自身以外数组的乘积 (Medium) [Leetcode](https://leetcode.com/problems/product-of-array-except-self/description/) / [力扣](https://leetcode-cn.com/problems/product-of-array-except-self/description/) -```html -For example, given [1,2,3,4], return [24,12,8,6]. -``` - -给定一个数组,创建一个新数组,新数组的每个元素为原始数组中除了该位置上的元素之外所有元素的乘积。 +给定一个数组,创建一个新数组,并使新数组的每个元素为原始数组中除了该位置上的元素以外其他元素的乘积。 -要求时间复杂度为 O(N),并且不能使用除法。 +要求时间复杂度为 O(N),且不能使用除法: ```java public int[] productExceptSelf(int[] nums) { - int n = nums.length; + int n = nums.length; int[] products = new int[n]; - Arrays.fill(products, 1); + Arrays.fill(products, 1); // 初始化为 1 int left = 1; for (int i = 1; i < n; i++) { - left *= nums[i - 1]; - products[i] *= left; + left *= nums[i - 1]; // 从左侧计算乘积 + products[i] *= left; // 累加到结果数组 } int right = 1; for (int i = n - 2; i >= 0; i--) { - right *= nums[i + 1]; - products[i] *= right; + right *= nums[i + 1]; // 从右侧计算乘积 + products[i] *= right; // 累加到结果数组 } - return products; + return products; // 返回结果 } ``` ### 4. 找出数组中的乘积最大的三个数 -628\. Maximum Product of Three Numbers (Easy) +**题目 628**: 三个数的最大乘积 (Easy) [Leetcode](https://leetcode.com/problems/maximum-product-of-three-numbers/description/) / [力扣](https://leetcode-cn.com/problems/maximum-product-of-three-numbers/description/) -```html -Input: [1,2,3,4] -Output: 24 -``` +给定一个整数数组,找出乘积最大的三个数的乘积: ```java public int maximumProduct(int[] nums) { - int max1 = Integer.MIN_VALUE, max2 = Integer.MIN_VALUE, max3 = Integer.MIN_VALUE, min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE; + int max1 = Integer.MIN_VALUE, max2 = Integer.MIN_VALUE, max3 = Integer.MIN_VALUE; // 最大三个数 + int min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE; // 最小两个数 for (int n : nums) { if (n > max1) { max3 = max2; max2 = max1; - max1 = n; + max1 = n; // 更新最大数 } else if (n > max2) { max3 = max2; - max2 = n; + max2 = n; // 更新第二大数 } else if (n > max3) { - max3 = n; + max3 = n; // 更新第三大数 } if (n < min1) { min2 = min1; - min1 = n; + min1 = n; // 更新最小数 } else if (n < min2) { - min2 = n; + min2 = n; // 更新第二小数 } } - return Math.max(max1*max2*max3, max1*min1*min2); + return Math.max(max1 * max2 * max3, max1 * min1 * min2); // 返回最大乘积 } ``` + +以上是一些 Leetcode 数学题目的解法,涵盖了基本的数学原理以及一些高效的算法实现,帮助你在解决相关问题时提供参考。 +``` \ No newline at end of file diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \351\223\276\350\241\250.md" "b/notes/Leetcode \351\242\230\350\247\243 - \351\223\276\350\241\250.md" index dd125c4875..41c611e986 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \351\223\276\350\241\250.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \351\223\276\350\241\250.md" @@ -1,56 +1,16 @@ # Leetcode 题解 - 链表 -<!-- GFM-TOC --> -* [Leetcode 题解 - 链表](#leetcode-题解---链表) - * [1. 找出两个链表的交点](#1-找出两个链表的交点) - * [2. 链表反转](#2-链表反转) - * [3. 归并两个有序的链表](#3-归并两个有序的链表) - * [4. 从有序链表中删除重复节点](#4-从有序链表中删除重复节点) - * [5. 删除链表的倒数第 n 个节点](#5-删除链表的倒数第-n-个节点) - * [6. 交换链表中的相邻结点](#6-交换链表中的相邻结点) - * [7. 链表求和](#7-链表求和) - * [8. 回文链表](#8-回文链表) - * [9. 分隔链表](#9-分隔链表) - * [10. 链表元素按奇偶聚集](#10-链表元素按奇偶聚集) -<!-- GFM-TOC --> +链表是由节点组成的数据结构,每个节点包含一个值和一个指向下一个节点的指针,因此很多链表相关的问题可以通过递归来解决。以下是一些常见的链表题解。 -链表是空节点,或者有一个值和一个指向下一个链表的指针,因此很多链表问题可以用递归来处理。 +## 1. 找出两个链表的交点 -## 1. 找出两个链表的交点 - -160\. Intersection of Two Linked Lists (Easy) +**题目编号**: 160 - Intersection of Two Linked Lists (简单) [Leetcode](https://leetcode.com/problems/intersection-of-two-linked-lists/description/) / [力扣](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/description/) -例如以下示例中 A 和 B 两个链表相交于 c1: - -```html -A: a1 → a2 - ↘ - c1 → c2 → c3 - ↗ -B: b1 → b2 → b3 -``` - -但是不会出现以下相交的情况,因为每个节点只有一个 next 指针,也就只能有一个后继节点,而以下示例中节点 c 有两个后继节点。 - -```html -A: a1 → a2 d1 → d2 - ↘ ↗ - c - ↗ ↘ -B: b1 → b2 → b3 e1 → e2 -``` - - +给定两个链表 A 和 B,如果它们相交,则返回相交的节点。如果不相交,则返回 null。相交的情况可以用两个链表的尾部公共部分进行描述,长度分别为 a + c 和 b + c,其中 c 是公共的部分。 -要求时间复杂度为 O(N),空间复杂度为 O(1)。如果不存在交点则返回 null。 - -设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 - -当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。 - -如果不存在交点,那么 a + b = b + a,以下实现代码中 l1 和 l2 会同时为 null,从而退出循环。 +我们可以使用两个指针来解决这个问题。以下是实现代码: ```java public ListNode getIntersectionNode(ListNode headA, ListNode headB) { @@ -59,307 +19,274 @@ public ListNode getIntersectionNode(ListNode headA, ListNode headB) { l1 = (l1 == null) ? headB : l1.next; l2 = (l2 == null) ? headA : l2.next; } - return l1; + return l1; // 当 l1 == l2 时,这就是交点(或都是 null) } ``` -如果只是判断是否存在交点,那么就是另一个问题,即 [编程之美 3.6]() 的问题。有两种解法: - -- 把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环; -- 或者直接比较两个链表的最后一个节点是否相同。 +**思路**: +- 先同时遍历两个链表。 +- 如果一个链表遍历完了,就从另一个链表的头部开始遍历。 +- 通过这种方式,如果有交点,最终两个指针将会在交点相遇;如果没有交点,则两个指针最终都将为 null。 -## 2. 链表反转 +## 2. 链表反转 -206\. Reverse Linked List (Easy) +**题目编号**: 206 - Reverse Linked List (简单) [Leetcode](https://leetcode.com/problems/reverse-linked-list/description/) / [力扣](https://leetcode-cn.com/problems/reverse-linked-list/description/) -递归 +我们可以通过递归或迭代的方式反转链表。以下是两种方法的实现: + +**递归法**: ```java public ListNode reverseList(ListNode head) { if (head == null || head.next == null) { - return head; + return head; // 递归结束条件 } - ListNode next = head.next; - ListNode newHead = reverseList(next); - next.next = head; - head.next = null; - return newHead; + ListNode newHead = reverseList(head.next); // 递归反转后续部分 + head.next.next = head; // 将当前节点指向其前一个节点 + head.next = null; // 将当前节点的后继指针设为 null + return newHead; // 返回新头节点 } ``` -头插法 +**头插法**: ```java public ListNode reverseList(ListNode head) { - ListNode newHead = new ListNode(-1); + ListNode newHead = null; // 新链表的头 while (head != null) { - ListNode next = head.next; - head.next = newHead.next; - newHead.next = head; - head = next; + ListNode next = head.next; // 先保存后继节点 + head.next = newHead; // 将当前节点反转 + newHead = head; // 移动新链表的头 + head = next; // 移动到下一个节点 } - return newHead.next; + return newHead; // 返回新链表的头 } ``` -## 3. 归并两个有序的链表 +## 3. 归并两个有序的链表 -21\. Merge Two Sorted Lists (Easy) +**题目编号**: 21 - Merge Two Sorted Lists (简单) [Leetcode](https://leetcode.com/problems/merge-two-sorted-lists/description/) / [力扣](https://leetcode-cn.com/problems/merge-two-sorted-lists/description/) +将两个有序链表合并成一个新的有序链表,并返回新的链表。新的链表是通过拼接在原链表的节点上得到的。 + ```java public ListNode mergeTwoLists(ListNode l1, ListNode l2) { - if (l1 == null) return l2; - if (l2 == null) return l1; + if (l1 == null) return l2; // 如果 l1 为空,直接返回 l2 + if (l2 == null) return l1; // 如果 l2 为空,直接返回 l1 if (l1.val < l2.val) { - l1.next = mergeTwoLists(l1.next, l2); - return l1; + l1.next = mergeTwoLists(l1.next, l2); // 递归合并 + return l1; // 返回 l1 作为头节点 } else { - l2.next = mergeTwoLists(l1, l2.next); - return l2; + l2.next = mergeTwoLists(l1, l2.next); // 递归合并 + return l2; // 返回 l2 作为头节点 } } ``` -## 4. 从有序链表中删除重复节点 +## 4. 从有序链表中删除重复节点 -83\. Remove Duplicates from Sorted List (Easy) +**题目编号**: 83 - Remove Duplicates from Sorted List (简单) [Leetcode](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/) / [力扣](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/description/) -```html -Given 1->1->2, return 1->2. -Given 1->1->2->3->3, return 1->2->3. -``` +给定一个有序链表,删除重复的节点,使每个元素只出现一次。 ```java public ListNode deleteDuplicates(ListNode head) { - if (head == null || head.next == null) return head; - head.next = deleteDuplicates(head.next); - return head.val == head.next.val ? head.next : head; + if (head == null || head.next == null) return head; // 如果链表为空或只有一个节点,返回 + head.next = deleteDuplicates(head.next); // 递归删除后续重复节点 + return head.val == head.next.val ? head.next : head; // 检查当前节点是否重复 } ``` -## 5. 删除链表的倒数第 n 个节点 +## 5. 删除链表的倒数第 n 个节点 -19\. Remove Nth Node From End of List (Medium) +**题目编号**: 19 - Remove Nth Node From End of List (中等) [Leetcode](https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/) / [力扣](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/) -```html -Given linked list: 1->2->3->4->5, and n = 2. -After removing the second node from the end, the linked list becomes 1->2->3->5. -``` +删除链表中的倒数第 n 个节点。 ```java public ListNode removeNthFromEnd(ListNode head, int n) { ListNode fast = head; - while (n-- > 0) { + while (n-- > 0) { // 快指针先走 n 步 fast = fast.next; } - if (fast == null) return head.next; + if (fast == null) return head.next; // 删除的节点为头节点 ListNode slow = head; - while (fast.next != null) { + while (fast.next != null) { // 找到倒数第 n 个节点的前一个节点 fast = fast.next; slow = slow.next; } - slow.next = slow.next.next; - return head; + slow.next = slow.next.next; // 删除节点 + return head; // 返回头节点 } ``` -## 6. 交换链表中的相邻结点 +## 6. 交换链表中的相邻结点 -24\. Swap Nodes in Pairs (Medium) +**题目编号**: 24 - Swap Nodes in Pairs (中等) [Leetcode](https://leetcode.com/problems/swap-nodes-in-pairs/description/) / [力扣](https://leetcode-cn.com/problems/swap-nodes-in-pairs/description/) -```html -Given 1->2->3->4, you should return the list as 2->1->4->3. -``` - -题目要求:不能修改结点的 val 值,O(1) 空间复杂度。 +给定链表 1->2->3->4,应该返回 2->1->4->3。 ```java public ListNode swapPairs(ListNode head) { - ListNode node = new ListNode(-1); - node.next = head; - ListNode pre = node; + ListNode dummy = new ListNode(-1); // 虚拟头节点 + dummy.next = head; + ListNode pre = dummy; // 先定义 pre 为虚拟头节点 while (pre.next != null && pre.next.next != null) { - ListNode l1 = pre.next, l2 = pre.next.next; - ListNode next = l2.next; - l1.next = next; - l2.next = l1; - pre.next = l2; - - pre = l1; + ListNode l1 = pre.next; // 第一个节点 + ListNode l2 = pre.next.next; // 第二个节点 + ListNode next = l2.next; // 保存后续节点 + l1.next = next; // 将第一个节点的后继指向后续节点 + l2.next = l1; // 将第二个节点的后继指向第一个节点 + pre.next = l2; // 将预指针指向新的头节点 + pre = l1; // 移动 pre 指针 } - return node.next; + return dummy.next; // 返回新链表的头节点 } ``` -## 7. 链表求和 +## 7. 链表求和 -445\. Add Two Numbers II (Medium) +**题目编号**: 445 - Add Two Numbers II (中等) [Leetcode](https://leetcode.com/problems/add-two-numbers-ii/description/) / [力扣](https://leetcode-cn.com/problems/add-two-numbers-ii/description/) -```html -Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) -Output: 7 -> 8 -> 0 -> 7 -``` - -题目要求:不能修改原始链表。 +给定两个链表表示两个非负整数,按逆序存储。计算它们的和并返回一个新的链表。 ```java public ListNode addTwoNumbers(ListNode l1, ListNode l2) { Stack<Integer> l1Stack = buildStack(l1); Stack<Integer> l2Stack = buildStack(l2); - ListNode head = new ListNode(-1); - int carry = 0; + ListNode head = new ListNode(0); // 虚拟头节点 + int carry = 0; // 进位 while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) { - int x = l1Stack.isEmpty() ? 0 : l1Stack.pop(); - int y = l2Stack.isEmpty() ? 0 : l2Stack.pop(); - int sum = x + y + carry; - ListNode node = new ListNode(sum % 10); + int x = l1Stack.isEmpty() ? 0 : l1Stack.pop(); // 弹出 l1 的值 + int y = l2Stack.isEmpty() ? 0 : l2Stack.pop(); // 弹出 l2 的值 + int sum = x + y + carry; // 计算总和 + ListNode node = new ListNode(sum % 10); // 创建新节点 node.next = head.next; - head.next = node; - carry = sum / 10; + head.next = node; // 将新节点放到头部 + carry = sum / 10; // 更新进位 } - return head.next; + return head.next; // 返回结果链表 } private Stack<Integer> buildStack(ListNode l) { Stack<Integer> stack = new Stack<>(); while (l != null) { - stack.push(l.val); + stack.push(l.val); // 将值压入栈中 l = l.next; } return stack; } ``` -## 8. 回文链表 +## 8. 回文链表 -234\. Palindrome Linked List (Easy) +**题目编号**: 234 - Palindrome Linked List (简单) [Leetcode](https://leetcode.com/problems/palindrome-linked-list/description/) / [力扣](https://leetcode-cn.com/problems/palindrome-linked-list/description/) -题目要求:以 O(1) 的空间复杂度来求解。 +判断链表是否为回文链表。要求以 O(1) 的空间复杂度实现。 -切成两半,把后半段反转,然后比较两半是否相等。 +我们可以在链表中间断开,然后反转后半部分,比较两半部分是否相等。 ```java public boolean isPalindrome(ListNode head) { - if (head == null || head.next == null) return true; + if (head == null || head.next == null) return true; // 只有一个节点时返回 true ListNode slow = head, fast = head.next; - while (fast != null && fast.next != null) { + while (fast != null && fast.next != null) { // 快慢指针找到中间节点 slow = slow.next; fast = fast.next.next; } - if (fast != null) slow = slow.next; // 偶数节点,让 slow 指向下一个节点 - cut(head, slow); // 切成两个链表 - return isEqual(head, reverse(slow)); -} - -private void cut(ListNode head, ListNode cutNode) { - while (head.next != cutNode) { + if (fast != null) slow = slow.next; // 如果是奇数节点,使 slow 移动到第二个中间节点 + ListNode secondHalf = reverse(slow); // 反转后半部分 + ListNode copySecondHalf = secondHalf; // 保存反转后的第二部分作为比较用 + while (head != null && secondHalf != null) { + if (head.val != secondHalf.val) return false; // 比较 head = head.next; + secondHalf = secondHalf.next; } - head.next = null; + reverse(copySecondHalf); // 反转回原来的链表 + return true; // 如果相等,返回 true } private ListNode reverse(ListNode head) { - ListNode newHead = null; + ListNode newHead = null; while (head != null) { - ListNode nextNode = head.next; - head.next = newHead; - newHead = head; - head = nextNode; + ListNode nextNode = head.next; // 设置后继节点 + head.next = newHead; // 反转指向 + newHead = head; // 移动新头节点 + head = nextNode; } - return newHead; -} - -private boolean isEqual(ListNode l1, ListNode l2) { - while (l1 != null && l2 != null) { - if (l1.val != l2.val) return false; - l1 = l1.next; - l2 = l2.next; - } - return true; + return newHead; // 返回反转后的头节点 } ``` -## 9. 分隔链表 +## 9. 分隔链表 -725\. Split Linked List in Parts(Medium) +**题目编号**: 725 - Split Linked List in Parts (中等) [Leetcode](https://leetcode.com/problems/split-linked-list-in-parts/description/) / [力扣](https://leetcode-cn.com/problems/split-linked-list-in-parts/description/) -```html -Input: -root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3 -Output: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] -Explanation: -The input has been split into consecutive parts with size difference at most 1, and earlier parts are a larger size than the later parts. -``` - -题目描述:把链表分隔成 k 部分,每部分的长度都应该尽可能相同,排在前面的长度应该大于等于后面的。 +将链表分成 k 个部分。每部分的长度尽量相等,前面的部分可能比后面的部分多一个节点。 ```java public ListNode[] splitListToParts(ListNode root, int k) { int N = 0; ListNode cur = root; - while (cur != null) { + while (cur != null) { // 计算链表长度 N++; cur = cur.next; } - int mod = N % k; - int size = N / k; - ListNode[] ret = new ListNode[k]; + int mod = N % k; // 多余的节点数量 + int size = N / k; // 每部分的基本长度 + ListNode[] ret = new ListNode[k]; // 存储结果 cur = root; for (int i = 0; cur != null && i < k; i++) { - ret[i] = cur; - int curSize = size + (mod-- > 0 ? 1 : 0); + ret[i] = cur; // 保存当前部分的头 + int curSize = size + (mod-- > 0 ? 1 : 0); // 当前部分的大小 for (int j = 0; j < curSize - 1; j++) { - cur = cur.next; + cur = cur.next; // 移动到当前部分的末尾 } - ListNode next = cur.next; - cur.next = null; - cur = next; + ListNode next = cur.next; // 保存下一个部分的头 + cur.next = null; // 断开当前部分 + cur = next; // 移动到下一个部分的头 } - return ret; + return ret; // 返回结果 } ``` -## 10. 链表元素按奇偶聚集 +## 10. 链表元素按奇偶聚集 -328\. Odd Even Linked List (Medium) +**题目编号**: 328 - Odd Even Linked List (中等) [Leetcode](https://leetcode.com/problems/odd-even-linked-list/description/) / [力扣](https://leetcode-cn.com/problems/odd-even-linked-list/description/) -```html -Example: -Given 1->2->3->4->5->NULL, -return 1->3->5->2->4->NULL. -``` +给定一个链表,将所有奇数节点放在前面,偶数节点放在后面,并保持相对顺序不变。 ```java public ListNode oddEvenList(ListNode head) { - if (head == null) { - return head; - } - ListNode odd = head, even = head.next, evenHead = even; + if (head == null) return head; // 如果链表为空,返回 + ListNode odd = head, even = head.next, evenHead = even; // 分别指向奇数节点头和偶数节点头 while (even != null && even.next != null) { - odd.next = odd.next.next; - odd = odd.next; - even.next = even.next.next; - even = even.next; + odd.next = odd.next.next; // 奇数节点的下一个指向下一个奇数节点 + odd = odd.next; // 移动奇数节点指针 + even.next = even.next.next; // 偶数节点的下一个指向下一个偶数节点 + even = even.next; // 移动偶数节点指针 } - odd.next = evenHead; - return head; + odd.next = evenHead; // 连接奇数部分和偶数部分 + return head; // 返回头节点 } ``` + +以上是常见的链表题解及其详细说明,希望对你理解链表的相关操作和解题思路有所帮助! \ No newline at end of file diff --git "a/notes/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" "b/notes/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" index fba480218b..d7d3150224 100644 --- "a/notes/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" +++ "b/notes/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" @@ -1,3 +1,4 @@ +```markdown # 正则表达式 <!-- GFM-TOC --> * [正则表达式](#正则表达式) @@ -17,21 +18,19 @@ ## 一、概述 -正则表达式用于文本内容的查找和替换。 +正则表达式(Regular Expressions,简称Regex)是一种用于文本内容查找、匹配和替换的强大工具。它们广泛应用于编程语言、文本编辑器和搜索工具中,能够有效地处理和分析文本数据。 -正则表达式内置于其它语言或者软件产品中,它本身不是一种语言或者软件。 - -[正则表达式在线工具](https://regexr.com/) +正则表达式并不是一种独立的编程语言,而是作为其他编程语言或软件产品中的文本处理功能的组成部分存在。如果您想快速创建和测试正则表达式,可以访问 [正则表达式在线工具](https://regexr.com/)。 ## 二、匹配单个字符 -**.** 可以用来匹配任何的单个字符,但是在绝大多数实现里面,不能匹配换行符; +**`.`**:元字符,匹配任何单个字符(通常不匹配换行符)。 -**.** 是元字符,表示它有特殊的含义,而不是字符本身的含义。如果需要匹配 . ,那么要用 \ 进行转义,即在 . 前面加上 \ 。 +要匹配字面上的句点字符`.`,需要使用反斜杠进行转义,写作 `\.`。 -正则表达式一般是区分大小写的,但也有些实现不区分。 +正则表达式区分大小写,除非指定不区分。 -**正则表达式** +**正则表达式示例** ``` C.C2018 @@ -39,21 +38,18 @@ C.C2018 **匹配结果** -My name is **CyC2018** . +"My name is **CyC2018**." ## 三、匹配一组字符 -**[ ]** 定义一个字符集合; - -0-9、a-z 定义了一个字符区间,区间使用 ASCII 码来确定,字符区间在 [ ] 中使用。 +**`[ ]`**:定义一个字符集合。字符集合内可以定义单个字符或字符区间,例如`0-9`和`a-z`。 -**-** 只有在 [ ] 之间才是元字符,在 [ ] 之外就是一个普通字符; +- **`-`**:只在字符集合中是元字符,用于表示字符范围,其他地方则视为普通字符。 +- **`^`**:在字符集合中表示取反。 -**^** 在 [ ] 中是取非操作。 +**应用示例** -**应用** - -匹配以 abc 为开头,并且最后一个字母不为数字的字符串: +匹配以`abc`开头,且最后一个字符不是数字的字符串: **正则表达式** @@ -63,9 +59,9 @@ abc[^0-9] **匹配结果** -1. **abcd** -2. abc1 -3. abc2 +1. **abcd** (匹配) +2. **abc1** (不匹配) +3. **abc2** (不匹配) ## 四、使用元字符 @@ -73,16 +69,14 @@ abc[^0-9] | 元字符 | 说明 | | :---: | :---: | -| [\b] | 回退(删除)一个字符 | -| \f | 换页符 | -| \n | 换行符 | -| \r | 回车符 | -| \t | 制表符 | -| \v | 垂直制表符 | - -\r\n 是 Windows 中的文本行结束标签,在 Unix/Linux 则是 \n。 +| `\b` | 匹配字边界 | +| `\f` | 匹配换页符 | +| `\n` | 匹配换行符 | +| `\r` | 匹配回车符 | +| `\t` | 匹配制表符 | +| `\v` | 匹配垂直制表符 | -\r\n\r\n 可以匹配 Windows 下的空白行,因为它匹配两个连续的行尾标签,而这正是两条记录之间的空白行; +在Windows系统中,行结束方式为`\r\n`;而在Unix/Linux系统则为`\n`。因此`\r\n\r\n`匹配Windows下的空行。 ### 匹配特定的字符 @@ -90,34 +84,36 @@ abc[^0-9] | 元字符 | 说明 | | :---: | :---: | -| \d | 数字字符,等价于 [0-9] | -| \D | 非数字字符,等价于 [^0-9] | +| `\d` | 匹配任何数字字符,等价于`[0-9]` | +| `\D` | 匹配任何非数字字符,等价于`[^0-9]` | #### 2. 字母数字元字符 | 元字符 | 说明 | | :---: | :---: | -| \w | 大小写字母,下划线和数字,等价于 [a-zA-Z0-9\_] | -| \W | 对 \w 取非 | +| `\w` | 匹配任何字母、数字或下划线,等价于`[a-zA-Z0-9_]` | +| `\W` | 匹配任何非字母数字字符 | #### 3. 空白字符元字符 | 元字符 | 说明 | | :---: | :---: | -| \s | 任何一个空白字符,等价于 [\f\n\r\t\v] | -| \S | 对 \s 取非 | +| `\s` | 匹配任何空白字符,等价于`[\f\n\r\t\v]` | +| `\S` | 匹配任何非空白字符 | -\x 匹配十六进制字符,\0 匹配八进制,例如 \xA 对应值为 10 的 ASCII 字符 ,即 \n。 +此外,`\x`可以匹配十六进制字符,`\0`可以匹配八进制字符,例如`\xA`对应ASCII值为10的换行符`\n`。 ## 五、重复匹配 -- **\+** 匹配 1 个或者多个字符 -- **\** * 匹配 0 个或者多个字符 -- **?** 匹配 0 个或者 1 个字符 +以下元字符用于控制字符出现的次数: + +- **`+`**:匹配一个或多个字符 +- **`*`**:匹配零个或多个字符 +- **`?`**:匹配零个或一个字符 -**应用** +**应用示例** -匹配邮箱地址。 +匹配邮箱地址的正则表达式: **正则表达式** @@ -125,17 +121,20 @@ abc[^0-9] [\w.]+@\w+\.\w+ ``` -[\w.] 匹配的是字母数字或者 . ,在其后面加上 + ,表示匹配多次。在字符集合 [ ] 里,. 不是元字符; +在这个表达式中,`[\w.]`匹配字母、数字或`.`,加上`+`表示可以匹配一个或多个任意字符。 **匹配结果** -**abc.def\<span\>@\</span\>qq.com** +匹配邮箱 **abc.def@qq.com**。 -- **{n}** 匹配 n 个字符 -- **{m,n}** 匹配 m\~n 个字符 -- **{m,}** 至少匹配 m 个字符 +同时,特殊的重复次数可以通过 `{n}`、`{m,n}` 和 `{m,}` 来指定。 +- `{n}`:匹配恰好n个字符。 +- `{m,n}`:匹配m到n个字符。 +- `{m,}`:至少匹配m个字符。 -\* 和 + 都是贪婪型元字符,会匹配尽可能多的内容。在后面加 ? 可以转换为懒惰型元字符,例如 \*?、+? 和 {m,n}? 。 +`*`和`+`是贪婪匹配的,意味着它们会尽可能匹配尽量多的内容。可以在它们后面加上`?`,使其变为非贪婪模式,例如`*?`、`+?`和`{m,n}?`。 + +### 贪婪和懒惰匹配示例 **正则表达式** @@ -145,55 +144,44 @@ a.+c **匹配结果** -**abcabcabc** - -由于 + 是贪婪型的,因此 .+ 会匹配更可能多的内容,所以会把整个 abcabcabc 文本都匹配,而不是只匹配前面的 abc 文本。用懒惰型可以实现匹配前面的。 +在字符串 **abcabcabc** 中,`.+`会贪婪地匹配尽可能多的字符,导致整个字符串都被匹配。若希望只匹配前面的部分可以使用非贪婪模式。 ## 六、位置匹配 ### 单词边界 -**\b** 可以匹配一个单词的边界,边界是指位于 \w 和 \W 之间的位置;**\B** 匹配一个不是单词边界的位置。 +**`\b`**:匹配一个单词的边界,而` \B`则匹配非单词边界。需要注意的是,`\b`只匹配位置,并不匹配字符本身。 -\b 只匹配位置,不匹配字符,因此 \babc\b 匹配出来的结果为 3 个字符。 +例如,`\babc\b`匹配字符串 "abc",而其前后没有任何字母或数字。 ### 字符串边界 -**^** 匹配整个字符串的开头,**$** 匹配结尾。 - -^ 元字符在字符集合中用作求非,在字符集合外用作匹配字符串的开头。 +**`^`**:匹配字符串的开头,**`$`**:匹配字符串的结尾。 +在字符集合外部的`^`和`$`是用作匹配整个字符串的开头和结尾的。 -分行匹配模式(multiline)下,换行被当做字符串的边界。 - -**应用** - -匹配代码中以 // 开始的注释行 +例如,使用以下正则表达式来匹配代码中的以`//` 开头的注释行: **正则表达式** ``` -^\s*\/\/.*$ +^\s*//.*$ ``` -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/600e9c75-5033-4dad-ae2b-930957db638e.png"/> </div><br> - **匹配结果** -1. public void fun() { -2. **// 注释 1** -3. int a = 1; -4. int b = 2; -5. **// 注释 2** -6. int c = a + b; -7. } +1. `public void fun() {` +2. ` // 注释 1` +3. ` int a = 1;` +4. ` int b = 2;` +5. ` // 注释 2` +6. ` int c = a + b;` +7. `}` ## 七、使用子表达式 -使用 **( )** 定义一个子表达式。子表达式的内容可以当成一个独立元素,即可以将它看成一个字符,并且使用 * 等元字符。 +使用 **`( )`** 定义子表达式,子表达式内的内容可以作为一个整体进行匹配,可以与元字符组合使用,甚至可以进行嵌套。 -子表达式可以嵌套,但是嵌套层次过深会变得很难理解。 - -**正则表达式** +**正则表达式示例** ``` (ab){2,} @@ -201,11 +189,11 @@ a.+c **匹配结果** -**ababab** +**ababab**(此表达式匹配两个以上的"ab"。) -**|** 是或元字符,它把左边和右边所有的部分都看成单独的两个部分,两个部分只要有一个匹配就行。 +**|**:表示或的元字符,它可以用于分隔两个完全独立的部分,只需满足其中之一即为成功匹配。 -**正则表达式** +**正则表达式示例** ``` (19|20)\d{2} @@ -213,67 +201,37 @@ a.+c **匹配结果** -1. **1900** -2. **2010** -3. 1020 - -**应用** - -匹配 IP 地址。 - -IP 地址中每部分都是 0-255 的数字,用正则表达式匹配时以下情况是合法的: - -- 一位数字 -- 不以 0 开头的两位数字 -- 1 开头的三位数 -- 2 开头,第 2 位是 0-4 的三位数 -- 25 开头,第 3 位是 0-5 的三位数 - -**正则表达式** - -``` -((25[0-5]|(2[0-4]\d)|(1\d{2})|([1-9]\d)|(\d))\.){3}(25[0-5]|(2[0-4]\d)|(1\d{2})|([1-9]\d)|(\d)) -``` - -**匹配结果** - -1. **192.168.0.1** -2. 00.00.00.00 -3. 555.555.555.555 +1. **1900** +2. **2010** +3. **1020** ## 八、回溯引用 -回溯引用使用 **\n** 来引用某个子表达式,其中 n 代表的是子表达式的序号,从 1 开始。它和子表达式匹配的内容一致,比如子表达式匹配到 abc,那么回溯引用部分也需要匹配 abc 。 +回溯引用使用 **`\n`** 引用之前的子表达式,其中`n`为子表达式的序号(从1开始),匹配的内容需一致。 -**应用** +**应用示例** -匹配 HTML 中合法的标题元素。 +匹配HTML中合法的标题元素,如`<h1>`至`<h6>`的标签。 **正则表达式** -\1 将回溯引用子表达式 (h[1-6]) 匹配的内容,也就是说必须和子表达式匹配的内容一致。 - ``` <(h[1-6])>\w*?<\/\1> ``` **匹配结果** -1. **<h1\>x</h1\>** -2. **<h2\>x</h2\>** -3. <h3\>x</h1\> - -### 替换 +1. **<h1>x</h1>** +2. **<h2>x</h2>** +3. <h3>x</h1> -需要用到两个正则表达式。 +### 替换操作 -**应用** +对于替换功能,需要用到两个正则表达式。 -修改电话号码格式。 +**应用示例** -**文本** - -313-555-1234 +修改电话号码格式,将 `313-555-1234` 转换为 `(313) 555-1234`。 **查找正则表达式** @@ -283,81 +241,74 @@ IP 地址中每部分都是 0-255 的数字,用正则表达式匹配时以下 **替换正则表达式** -在第一个子表达式查找的结果加上 () ,然后加一个空格,在第三个和第五个字表达式查找的结果中间加上 - 进行分隔。 - ``` ($1) $3-$5 ``` **结果** -(313) 555-1234 +`(313) 555-1234` ### 大小写转换 | 元字符 | 说明 | | :---: | :---: | -| \l | 把下个字符转换为小写 | -| \u| 把下个字符转换为大写 | -| \L | 把\L 和\E 之间的字符全部转换为小写 | -| \U | 把\U 和\E 之间的字符全部转换为大写 | -| \E | 结束\L 或者\U | - -**应用** +| `\l` | 将下个字符转换为小写 | +| `\u` | 将下个字符转换为大写 | +| `\L` | 将`\L`和`\E`之间的所有字符转换为小写 | +| `\U` | 将`\U`和`\E`之间的所有字符转换为大写 | +| `\E` | 结束`\L` 或`\U` | -把文本的第二个和第三个字符转换为大写。 +**应用示例** -**文本** +将字符串 `"abcd"`的第二和第三个字符转换为大写。 -abcd - -**查找** +**查找正则表达式** ``` (\w)(\w{2})(\w) ``` -**替换** - +**替换正则表达式** ``` $1\U$2\E$3 ``` **结果** -aBCd +`aBCd` ## 九、前后查找 -前后查找规定了匹配的内容首尾应该匹配的内容,但是又不包含首尾匹配的内容。 +前后查找(Lookaround)用于限定匹配的内容的上下文,而不将这些上下文包括在最终匹配的内容中。 -向前查找使用 **?=** 定义,它规定了尾部匹配的内容,这个匹配的内容在 ?= 之后定义。所谓向前查找,就是规定了一个匹配的内容,然后以这个内容为尾部向前面查找需要匹配的内容。向后匹配用 ?\<= 定义(注: JavaScript 不支持向后匹配,Java 对其支持也不完善)。 +### 向前查找 -**应用** +向前查找使用 **`(?=)`** 定义,表示后边需要匹配的内容,称为“前向查找”。 -查找出邮件地址 @ 字符前面的部分。 +**正则表达式示例** -**正则表达式** +查找邮箱地址中`@`前面的部分: ``` \w+(?=@) ``` -**结果** +**匹配结果** + +**abc** @qq.com -**abc** @qq.com +### 向后查找 -对向前和向后查找取非,只要把 = 替换成 ! 即可,比如 (?=) 替换成 (?!) 。取非操作使得匹配那些首尾不符合要求的内容。 +向后查找使用 **`(?<=)`** 定义,而JavaScript并不支持向后查找。在许多情况下,向后查找可以用于验证特定条件下的匹配。 ## 十、嵌入条件 ### 回溯引用条件 -条件为某个子表达式是否匹配,如果匹配则需要继续匹配条件表达式后面的内容。 +在特定情况下,条件判断一个子表达式是否匹配。如果匹配则继续执行。此外,可以根据条件选择是否执行下一步匹配。 -**正则表达式** - -子表达式 (\\() 匹配一个左括号,其后的 ? 表示匹配 0 个或者 1 个。 ?(1) 为条件,当子表达式 1 匹配时条件成立,需要执行 \) 匹配,也就是匹配右括号。 +**正则表达式示例** ``` (\()?abc(?(1)\)) @@ -365,17 +316,15 @@ aBCd **结果** -1. **(abc)** -2. **abc** -3. (abc +1. **(abc)** +2. **abc** +3. (abc (只匹配不带括号的部分) ### 前后查找条件 -条件为定义的首尾是否匹配,如果匹配,则继续执行后面的匹配。注意,首尾不包含在匹配的内容中。 - -**正则表达式** +结合了首尾限制的条件匹配,只有在特定条件符合的情况下继续进行后续匹配。 - ?(?=-) 为前向查找条件,只有在以 - 为前向查找的结尾能匹配 \d{5} ,才继续匹配 -\d{4} 。 +**正则表达式示例** ``` \d{5}(?(?=-)-\d{4}) @@ -383,10 +332,13 @@ aBCd **结果** -1. **11111** -2. 22222- -3. **33333-4444** +1. **11111** +2. **22222-** +3. **33333-4444** + +在实际应用中,正则表达式是一种灵活且强大的工具,但需谨慎使用,以避免过于复杂的表达式导致调试困难。 ## 参考资料 -- BenForta. 正则表达式必知必会 [M]. 人民邮电出版社, 2007. +- Ben Forta. *正则表达式必知必会* [M]. 人民邮电出版社, 2007. +``` diff --git "a/notes/\350\256\241\347\256\227\346\234\272\346\223\215\344\275\234\347\263\273\347\273\237 - \351\223\276\346\216\245.md" "b/notes/\350\256\241\347\256\227\346\234\272\346\223\215\344\275\234\347\263\273\347\273\237 - \351\223\276\346\216\245.md" index a06caa1ba2..1de38332c9 100644 --- "a/notes/\350\256\241\347\256\227\346\234\272\346\223\215\344\275\234\347\263\273\347\273\237 - \351\223\276\346\216\245.md" +++ "b/notes/\350\256\241\347\256\227\346\234\272\346\223\215\344\275\234\347\263\273\347\273\237 - \351\223\276\346\216\245.md" @@ -1,4 +1,5 @@ # 计算机操作系统 - 链接 + <!-- GFM-TOC --> * [计算机操作系统 - 链接](#计算机操作系统---链接) * [编译系统](#编译系统) @@ -7,62 +8,67 @@ * [动态链接](#动态链接) <!-- GFM-TOC --> - ## 编译系统 - -以下是一个 hello.c 程序: +下面是一个简单的 `hello.c` 程序示例: ```c #include <stdio.h> -int main() -{ +int main() { printf("hello, world\n"); return 0; } ``` -在 Unix 系统上,由编译器把源文件转换为目标文件。 +在 Unix 系统中,编译器将源文件转换为目标文件的过程如下,通常可以通过以下命令完成: ```bash gcc -o hello hello.c ``` -这个过程大致如下: +此过程大致分为四个阶段: -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b396d726-b75f-4a32-89a2-03a7b6e19f6f.jpg" width="800"/> </div><br> +1. **预处理阶段**:处理以 `#` 开头的预处理命令,诸如头文件的包含、宏定义的替换等。 +2. **编译阶段**:将预处理生成的代码翻译成汇编语言。 +3. **汇编阶段**:将汇编语言代码翻译为可重定位目标文件,这个文件包含了机器码但尚未链接。 +4. **链接阶段**:链接器将可重定位目标文件与其他单独编译的目标文件(如 `printf.o`)进行合并,生成最终的可执行文件。 -- 预处理阶段:处理以 # 开头的预处理命令; -- 编译阶段:翻译成汇编文件; -- 汇编阶段:将汇编文件翻译成可重定位目标文件; -- 链接阶段:将可重定位目标文件和 printf.o 等单独预编译好的目标文件进行合并,得到最终的可执行目标文件。 +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b396d726-b75f-4a32-89a2-03a7b6e19f6f.jpg" width="800"/> +</div><br> ## 静态链接 -静态链接器以一组可重定位目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务: +静态链接器以一组可重定位目标文件为输入,生成一个完整链接的可执行目标文件作为输出。链接器的主要功能包括: -- 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。 -- 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。 +- **符号解析**:每个符号对应一个函数、全局变量或静态变量。符号解析的目的是将每个符号的引用与其定义关联起来,这样程序在执行时才能正确找到所需的函数或变量。 +- **重定位**:链接器通过将每个符号定义与特定的内存地址关联,同时修改所有对这些符号的引用,使它们指向正确的内存位置。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/47d98583-8bb0-45cc-812d-47eefa0a4a40.jpg"/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/47d98583-8bb0-45cc-812d-47eefa0a4a40.jpg"/> +</div><br> ## 目标文件 -- 可执行目标文件:可以直接在内存中执行; -- 可重定位目标文件:可与其它可重定位目标文件在链接阶段合并,创建一个可执行目标文件; -- 共享目标文件:这是一种特殊的可重定位目标文件,可以在运行时被动态加载进内存并链接; +目标文件主要分为以下三种类型: + +- **可执行目标文件**:可以直接在内存中执行,是最终用户交互的程序。 +- **可重定位目标文件**:可以与其他可重定位目标文件在链接阶段合并,生成一个可执行目标文件。这种文件独立于程序,可被多个程序共享使用。 +- **共享目标文件**:这是一种特殊的可重定位目标文件,可以在程序运行时动态加载到内存中并链接,提升了资源的有效利用。 ## 动态链接 -静态库有以下两个问题: +使用静态库时,存在以下两个主要问题: -- 当静态库更新时那么整个程序都要重新进行链接; -- 对于 printf 这种标准函数库,如果每个程序都要有代码,这会极大浪费资源。 +1. **更新问题**:当静态库更新时,所有依赖于该库的程序都必须重新进行链接,这显著增加了开发和部署的复杂性。 +2. **资源浪费**:例如,对于 `printf` 函数等标准库函数,如果每个程序都包含这些代码,会造成很大的内存和存储资源浪费。 -共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 .so 后缀来表示,Windows 系统上它们被称为 DLL。它具有以下特点: +共享库的设计旨在解决静态库的这两个问题。在 Linux 系统中,通常用 `.so` 后缀表示共享库,而在 Windows 系统上则称为 DLL(动态链接库)。共享库的特点包括: -- 在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中; -- 在内存中,一个共享库的 .text 节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。 +- 在文件系统中,某个共享库只需有一个文件,所有引用该库的可执行文件共享这一单一副本,而不必在每个可执行文件中复制该库。 +- 在内存中,不同的正在运行的进程可以共享同一个共享库的 `.text` 段(已编译程序的机器代码),因此减少了内存使用和提高了系统资源的利用率。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/76dc7769-1aac-4888-9bea-064f1caa8e77.jpg"/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/76dc7769-1aac-4888-9bea-064f1caa8e77.jpg"/> +</div><br> \ No newline at end of file diff --git "a/notes/\350\256\241\347\256\227\346\234\272\346\223\215\344\275\234\347\263\273\347\273\237.md" "b/notes/\350\256\241\347\256\227\346\234\272\346\223\215\344\275\234\347\263\273\347\273\237.md" index c7dcdb10cb..7f46adfcd5 100644 --- "a/notes/\350\256\241\347\256\227\346\234\272\346\223\215\344\275\234\347\263\273\347\273\237.md" +++ "b/notes/\350\256\241\347\256\227\346\234\272\346\223\215\344\275\234\347\263\273\347\273\237.md" @@ -1 +1,128 @@ -[计算机操作系统](https://github.com/CyC2018/CS-Notes/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%20-%20%E7%9B%AE%E5%BD%95.md) +# 计算机操作系统 + +计算机操作系统是管理计算机硬件与软件资源的系统软件。它在计算机的使用过程中发挥着至关重要的作用,负责确保计算机各部分的正常运行,并为应用程序提供运行环境。本文将详细探讨操作系统的基本概念、主要功能、常见类型及其典型示例代码。 + +## 一、操作系统的基本概念 + +操作系统(Operating System,OS)是计算机中的基础软件,它直接与计算机硬件交互,并为其他软件提供服务。操作系统的主要任务是管理计算机的资源,例如CPU、内存、存储设备和输入输出设备,以保证各个程序能够高效、有序地运行。 + +## 二、操作系统的主要功能 + +1. **进程管理** + 操作系统负责创建、调度和管理进程。进程是正在执行的程序的实例。通过进程管理,操作系统能够确保多个程序的协调运行,并有效利用CPU。 + + 示例代码 - 进程创建: + ```c + #include <stdio.h> + #include <stdlib.h> + #include <unistd.h> + + int main() { + pid_t pid = fork(); // 创建新进程 + if (pid < 0) { + printf("进程创建失败!\n"); + return 1; + } else if (pid == 0) { + // 子进程执行的代码 + printf("这是子进程,PID: %d\n", getpid()); + } else { + // 父进程执行的代码 + printf("这是父进程,PID: %d, 子进程PID: %d\n", getpid(), pid); + } + return 0; + } + ``` + 在这个示例中,我们使用`fork()`函数创建一个新的进程。父进程和子进程将分别输出不同的信息。 + +2. **内存管理** + 操作系统负责分配和管理内存,以确保每个进程可以安全地使用内存而不会互相干扰。内存管理还包括内存的分配、回收和虚拟内存的管理。 + + 示例代码 - 动态内存分配: + ```c + #include <stdio.h> + #include <stdlib.h> + + int main() { + int *arr; + int n = 5; + + // 动态分配内存 + arr = (int *)malloc(n * sizeof(int)); + if (arr == NULL) { + printf("内存分配失败!\n"); + return 1; + } + + // 使用分配的内存 + for (int i = 0; i < n; i++) { + arr[i] = i * i; + printf("%d ", arr[i]); + } + printf("\n"); + + // 释放内存 + free(arr); + return 0; + } + ``` + 在这个示例中,我们使用`malloc()`函数动态分配内存,并在使用完毕后通过`free()`释放内存,以防止内存泄漏。 + +3. **文件系统管理** + 操作系统提供文件系统,以便用户和程序能够方便地存储、读取和管理数据文件。文件系统管理的主要任务包括文件的创建、删除和访问控制。 + + 示例代码 - 文件的读写: + ```c + #include <stdio.h> + + int main() { + FILE *fp; + char data[] = "Hello, 操作系统!"; + + // 写文件 + fp = fopen("example.txt", "w"); + if (fp == NULL) { + printf("文件打开失败!\n"); + return 1; + } + fprintf(fp, "%s\n", data); + fclose(fp); + + // 读文件 + char buffer[100]; + fp = fopen("example.txt", "r"); + if (fp == NULL) { + printf("文件打开失败!\n"); + return 1; + } + fgets(buffer, sizeof(buffer), fp); + printf("读取内容: %s", buffer); + fclose(fp); + + return 0; + } + ``` + 在这个示例中,我们展示了如何将数据写入文件并随后读取该文件内容。 + +4. **设备管理** + 操作系统管理所有输入输出设备,包括键盘、鼠标、打印机和硬盘等。它提供一个抽象层,使程序能够无需关心硬件细节即可访问这些设备。 + +5. **用户接口** + 操作系统提供用户界面,以便用户可以与计算机系统进行交互。常见的用户界面有命令行界面(CLI)和图形用户界面(GUI)。 + +## 三、操作系统的类型 + +1. **单用户操作系统** + 例如,Windows 和 macOS 主要用于单用户的桌面环境。 + +2. **多用户操作系统** + 支持多个用户同时使用的系统,例如Linux和Unix。 + +3. **实时操作系统** + 能够在特定时间内响应输入的系统,广泛应用于工业控制和嵌入式系统。 + +4. **嵌入式操作系统** + 专用于嵌入式设备,如智能家居设备和汽车控制系统。 + +## 四、总结 + +操作系统是计算机系统的重要组成部分,它负责对各种资源的管理与调配。通过合理的进程管理、内存管理、文件系统管理、设备管理和用户接口,操作系统为用户和程序提供了一个稳定、可靠的运行环境。理解操作系统的基本功能及其实现方式,对于学习计算机科学与技术领域的其他内容至关重要。 \ No newline at end of file diff --git "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \345\272\224\347\224\250\345\261\202.md" "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \345\272\224\347\224\250\345\261\202.md" index 5ce041a29a..662f88b434 100644 --- "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \345\272\224\347\224\250\345\261\202.md" +++ "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \345\272\224\347\224\250\345\261\202.md" @@ -1,4 +1,6 @@ +```markdown # 计算机网络 - 应用层 + <!-- GFM-TOC --> * [计算机网络 - 应用层](#计算机网络---应用层) * [域名系统](#域名系统) @@ -20,149 +22,148 @@ ## 域名系统 -DNS 是一个分布式数据库,提供了主机名和 IP 地址之间相互转换的服务。这里的分布式数据库是指,每个站点只保留它自己的那部分数据。 +域名系统(DNS,Domain Name System)是一个分布式数据库,主要用于提供主机名与 IP 地址之间的映射服务。每个 DNS 服务器只负责存储自己域名下的记录,形成了一个层次结构的数据库。 + +域名的组成结构: -域名具有层次结构,从上到下依次为:根域名、顶级域名、二级域名。 +- **根域名**(Root Domain) +- **顶级域名**(Top-Level Domain,如 .com, .org, .cn) +- **二级域名**(Second-Level Domain,如 example.com) -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b54eeb16-0b0e-484c-be62-306f57c40d77.jpg"/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b54eeb16-0b0e-484c-be62-306f57c40d77.jpg"/> +</div><br> -DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传从而保证可靠性。在两种情况下会使用 TCP 进行传输: +DNS 使用 UDP 或 TCP 进行传输,端口号均为 53。大部分情况下,DNS 采用 UDP,但在以下情况则切换到 TCP: -- 如果返回的响应超过的 512 字节(UDP 最大只支持 512 字节的数据)。 -- 区域传送(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。 +- 如果响应超过 512 字节(UDP 的最大负载)。 +- 进行区域传送,即主 DNS 服务器与辅助 DNS 服务器之间的数据同步。 ## 文件传送协议 -FTP 使用 TCP 进行连接,它需要两个连接来传送一个文件: +文件传送协议(FTP,File Transfer Protocol)是用于在网络上进行文件传输的标准协议。FTP 通过 TCP 进行连接,主要使用两个连接通道: -- 控制连接:服务器打开端口号 21 等待客户端的连接,客户端主动建立连接后,使用这个连接将客户端的命令传送给服务器,并传回服务器的应答。 -- 数据连接:用来传送一个文件数据。 +1. **控制连接**:默认使用 21 端口。服务器在此端口上监听客户端的连接请求,并接收客户端命令。 +2. **数据连接**:用于传输实际的文件数据。 -根据数据连接是否是服务器端主动建立,FTP 有主动和被动两种模式: +FTP 有两种模式: -- 主动模式:服务器端主动建立数据连接,其中服务器端的端口号为 20,客户端的端口号随机,但是必须大于 1024,因为 0\~1023 是熟知端口号。 +- **主动模式**:服务器主动建立数据连接,使用 20 端口。在这种模式下,客户端的端口号是随机选择的,但必须大于 1024(因为 0-1023 是保留给系统的重要端口)。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/03f47940-3843-4b51-9e42-5dcaff44858b.jpg"/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/03f47940-3843-4b51-9e42-5dcaff44858b.jpg"/> +</div><br> -- 被动模式:客户端主动建立数据连接,其中客户端的端口号由客户端自己指定,服务器端的端口号随机。 +- **被动模式**:客户端主动建立数据连接,客户端选择端口。网络安全性较高,因为只需服务器开放端口。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/be5c2c61-86d2-4dba-a289-b48ea23219de.jpg"/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/be5c2c61-86d2-4dba-a289-b48ea23219de.jpg"/> +</div><br> -主动模式要求客户端开放端口号给服务器端,需要去配置客户端的防火墙。被动模式只需要服务器端开放端口号即可,无需客户端配置防火墙。但是被动模式会导致服务器端的安全性减弱,因为开放了过多的端口号。 +主动模式需要客户端开放端口,配置防火墙;而被动模式则减轻了客户端的负担,但可能降低了服务端的安全性。 ## 动态主机配置协议 -DHCP (Dynamic Host Configuration Protocol) 提供了即插即用的连网方式,用户不再需要手动配置 IP 地址等信息。 +动态主机配置协议(DHCP,Dynamic Host Configuration Protocol)用于自动分配 IP 地址和相关网络配置。DHCP 使得设备连接网络时无需手动配置。 -DHCP 配置的内容不仅是 IP 地址,还包括子网掩码、网关 IP 地址。 +DHCP 提供的信息包括: -DHCP 工作过程如下: +- IP 地址 +- 子网掩码 +- 默认网关 +- DNS 服务器的 IP 地址 -1. 客户端发送 Discover 报文,该报文的目的地址为 255.255.255.255:67,源地址为 0.0.0.0:68,被放入 UDP 中,该报文被广播到同一个子网的所有主机上。如果客户端和 DHCP 服务器不在同一个子网,就需要使用中继代理。 -2. DHCP 服务器收到 Discover 报文之后,发送 Offer 报文给客户端,该报文包含了客户端所需要的信息。因为客户端可能收到多个 DHCP 服务器提供的信息,因此客户端需要进行选择。 -3. 如果客户端选择了某个 DHCP 服务器提供的信息,那么就发送 Request 报文给该 DHCP 服务器。 -4. DHCP 服务器发送 Ack 报文,表示客户端此时可以使用提供给它的信息。 +DHCP 工作流程如下: -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/23219e4c-9fc0-4051-b33a-2bd95bf054ab.jpg"/> </div><br> +1. **Discover**:客户端发送 Discover 报文(UDP 端口 67),目标地址为 255.255.255.255(广播),源地址为 0.0.0.0,覆盖整个子网。 +2. **Offer**:DHCP 服务器接收 Discover 报文,回复 Offer 报文,提供所需的 IP 地址和配置信息。 +3. **Request**:客户端从收到的多个 Offer 中选择一条,发送 Request 报文确认请求。 +4. **Ack**:DHCP 服务器通过 Ack 报文确认客户端的请求,客户端可使用提供的信息上网。 -## 远程登录协议 +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/23219e4c-9fc0-4051-b33a-2bd95bf054ab.jpg"/> +</div><br> -TELNET 用于登录到远程主机上,并且远程主机上的输出也会返回。 +## 远程登录协议 -TELNET 可以适应许多计算机和操作系统的差异,例如不同操作系统系统的换行符定义。 +TELNET 是一种网络协议,允许用户通过命令行接口连接到远程计算机,且支持多种操作系统和设备类型。使用 TELNET,可以实现设备间的远程管理与控制。 ## 电子邮件协议 -一个电子邮件系统由三部分组成:用户代理、邮件服务器以及邮件协议。 - -邮件协议包含发送协议和读取协议,发送协议常用 SMTP,读取协议常用 POP3 和 IMAP。 +电子邮件系统包括三个部分:用户代理、邮件服务器和邮件协议。邮件协议又分为发送(如 SMTP)和接收(如 POP3、IMAP)协议。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7b3efa99-d306-4982-8cfb-e7153c33aab4.png" width="700"/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7b3efa99-d306-4982-8cfb-e7153c33aab4.png" width="700"/> +</div><br> ### 1. SMTP -SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME 并没有改动或者取代 SMTP,而是增加邮件主体的结构,定义了非 ASCII 码的编码规则。 +简单邮件传送协议(SMTP)用于发送电子邮件。SMTP 通常只支持 ASCII 格式的文本,而 MIME(多用途互联网邮件扩展)则允许发送二进制附加文件。MIME 为邮件主体增加了结构性,使其能够支持非 ASCII 编码。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ed5522bb-3a60-481c-8654-43e7195a48fe.png" width=""/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ed5522bb-3a60-481c-8654-43e7195a48fe.png" width=""/> +</div><br> ### 2. POP3 -POP3 的特点是只要用户从服务器上读取了邮件,就把该邮件删除。但最新版本的 POP3 可以不删除邮件。 +邮局协议第3版(POP3)主要用于从邮件服务器下载邮件,一旦邮件下载,服务器上的邮件将被删除。近年来,POP3 的新版本也允许用户选择保留邮件。 ### 3. IMAP -IMAP 协议中客户端和服务器上的邮件保持同步,如果不手动删除邮件,那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。 +互联网消息访问协议(IMAP)允许用户在服务器上管理邮件,客户端与服务器之间的邮件保持同步。用户可以随时访问邮件,除非主动删除,否则服务器上的邮件不会被删除。 ## 常用端口 -|应用| 应用层协议 | 端口号 | 传输层协议 | 备注 | -| :---: | :--: | :--: | :--: | :--: | -| 域名解析 | DNS | 53 | UDP/TCP | 长度超过 512 字节时使用 TCP | -| 动态主机配置协议 | DHCP | 67/68 | UDP | | -| 简单网络管理协议 | SNMP | 161/162 | UDP | | -| 文件传送协议 | FTP | 20/21 | TCP | 控制连接 21,数据连接 20 | -| 远程终端协议 | TELNET | 23 | TCP | | -| 超文本传送协议 | HTTP | 80 | TCP | | -| 简单邮件传送协议 | SMTP | 25 | TCP | | -| 邮件读取协议 | POP3 | 110 | TCP | | -| 网际报文存取协议 | IMAP | 143 | TCP | | +| 应用 | 应用层协议 | 端口号 | 传输层协议 | 备注 | +|----------------|------------|---------|-------------|---------------------------------| +| 域名解析 | DNS | 53 | UDP/TCP | 响应超过 512 字节时使用 TCP | +| 动态主机配置协议 | DHCP | 67/68 | UDP | | +| 简单网络管理协议 | SNMP | 161/162 | UDP | | +| 文件传送协议 | FTP | 20/21 | TCP | 控制连接 21,数据连接 20 | +| 远程终端协议 | TELNET | 23 | TCP | | +| 超文本传送协议 | HTTP | 80 | TCP | | +| 简单邮件传送协议 | SMTP | 25 | TCP | | +| 邮件读取协议 | POP3 | 110 | TCP | | +| 网际报文存取协议 | IMAP | 143 | TCP | | ## Web 页面请求过程 ### 1. DHCP 配置主机信息 -- 假设主机最开始没有 IP 地址以及其它信息,那么就需要先使用 DHCP 来获取。 - -- 主机生成一个 DHCP 请求报文,并将这个报文放入具有目的端口 67 和源端口 68 的 UDP 报文段中。 +- 如果主机没有 IP 地址和其他网络配置,首先需要通过 DHCP 获取所需信息。流程如下: -- 该报文段则被放入在一个具有广播 IP 目的地址(255.255.255.255) 和源 IP 地址(0.0.0.0)的 IP 数据报中。 - -- 该数据报则被放置在 MAC 帧中,该帧具有目的地址 FF:\<zero-width space\>FF:\<zero-width space\>FF:\<zero-width space\>FF:\<zero-width space\>FF:FF,将广播到与交换机连接的所有设备。 - -- 连接在交换机的 DHCP 服务器收到广播帧之后,不断地向上分解得到 IP 数据报、UDP 报文段、DHCP 请求报文,之后生成 DHCP ACK 报文,该报文包含以下信息:IP 地址、DNS 服务器的 IP 地址、默认网关路由器的 IP 地址和子网掩码。该报文被放入 UDP 报文段中,UDP 报文段有被放入 IP 数据报中,最后放入 MAC 帧中。 - -- 该帧的目的地址是请求主机的 MAC 地址,因为交换机具有自学习能力,之前主机发送了广播帧之后就记录了 MAC 地址到其转发接口的交换表项,因此现在交换机就可以直接知道应该向哪个接口发送该帧。 - -- 主机收到该帧后,不断分解得到 DHCP 报文。之后就配置它的 IP 地址、子网掩码和 DNS 服务器的 IP 地址,并在其 IP 转发表中安装默认网关。 +1. 主机生成一个 DHCP 请求报文,然后将该报文封装到 UDP 报文段中,设置目的端口为 67,源端口为 68。 +2. 此 UDP 报文段被封装到源地址为 0.0.0.0,目的地址为 255.255.255.255 的 IP 数据报中。 +3. 然后将该数据报放入以太网的 MAC 帧中,广播到同一网络中的所有设备。 +4. 连接在交换机的 DHCP 服务器收到该广播帧后,逐层解封装,最终生成 DHCP ACK 报文,其中包含 IP 地址、DNS 服务器的 IP 地址、默认网关和子网掩码。 +5. ACK 报文被封装成 UDP 报文段,再放入 IP 数据报中,最终包裹在 MAC 帧中返回给主机。 ### 2. ARP 解析 MAC 地址 -- 主机通过浏览器生成一个 TCP 套接字,套接字向 HTTP 服务器发送 HTTP 请求。为了生成该套接字,主机需要知道网站的域名对应的 IP 地址。 - -- 主机生成一个 DNS 查询报文,该报文具有 53 号端口,因为 DNS 服务器的端口号是 53。 +- 主机通过浏览器发送 HTTP 请求所需 IP 地址时,首先需要解析网关路由器的 MAC 地址,流程如下: -- 该 DNS 查询报文被放入目的地址为 DNS 服务器 IP 地址的 IP 数据报中。 - -- 该 IP 数据报被放入一个以太网帧中,该帧将发送到网关路由器。 - -- DHCP 过程只知道网关路由器的 IP 地址,为了获取网关路由器的 MAC 地址,需要使用 ARP 协议。 - -- 主机生成一个包含目的地址为网关路由器 IP 地址的 ARP 查询报文,将该 ARP 查询报文放入一个具有广播目的地址(FF:\<zero-width space\>FF:\<zero-width space\>FF:\<zero-width space\>FF:\<zero-width space\>FF:FF)的以太网帧中,并向交换机发送该以太网帧,交换机将该帧转发给所有的连接设备,包括网关路由器。 - -- 网关路由器接收到该帧后,不断向上分解得到 ARP 报文,发现其中的 IP 地址与其接口的 IP 地址匹配,因此就发送一个 ARP 回答报文,包含了它的 MAC 地址,发回给主机。 +1. 主机生成一个 DNS 查询报文,并将其封装到目的地址为 DNS 服务器 IP 地址的 IP 数据报中。 +2. 设置目的地址为网关路由器的 IP 地址,利用 ARP 协议查询 MAC 地址。 +3. 发送一个包含 ARP 查询的以太网帧,目的地址为广播地址(FF:FF:FF:FF:FF:FF)。 +4. 网关路由器收到该帧,解封装并发现其 IP 地址匹配,在 ARP 回应中将 MAC 地址发回主机。 ### 3. DNS 解析域名 -- 知道了网关路由器的 MAC 地址之后,就可以继续 DNS 的解析过程了。 - -- 网关路由器接收到包含 DNS 查询报文的以太网帧后,抽取出 IP 数据报,并根据转发表决定该 IP 数据报应该转发的路由器。 +- 主机在获取网关路由器的 MAC 地址后,继续进行 DNS 解析: -- 因为路由器具有内部网关协议(RIP、OSPF)和外部网关协议(BGP)这两种路由选择协议,因此路由表中已经配置了网关路由器到达 DNS 服务器的路由表项。 - -- 到达 DNS 服务器之后,DNS 服务器抽取出 DNS 查询报文,并在 DNS 数据库中查找待解析的域名。 - -- 找到 DNS 记录之后,发送 DNS 回答报文,将该回答报文放入 UDP 报文段中,然后放入 IP 数据报中,通过路由器反向转发回网关路由器,并经过以太网交换机到达主机。 +1. 网关接收到包含 DNS 查询报文的以太网帧后,抽取出 IP 数据报,通过路由转发到 DNS 服务器。 +2. DNS 服务器解析查询报文,匹配域名并找到相应的 IP 记录。 +3. 发送 DNS 回答报文,经过路由返回到网关,再到主机。 ### 4. HTTP 请求页面 -- 有了 HTTP 服务器的 IP 地址之后,主机就能够生成 TCP 套接字,该套接字将用于向 Web 服务器发送 HTTP GET 报文。 - -- 在生成 TCP 套接字之前,必须先与 HTTP 服务器进行三次握手来建立连接。生成一个具有目的端口 80 的 TCP SYN 报文段,并向 HTTP 服务器发送该报文段。 - -- HTTP 服务器收到该报文段之后,生成 TCP SYN ACK 报文段,发回给主机。 - -- 连接建立之后,浏览器生成 HTTP GET 报文,并交付给 HTTP 服务器。 +- 获取到 HTTP 服务器的 IP 地址后,主机开始建立 TCP 连接,流程如下: -- HTTP 服务器从 TCP 套接字读取 HTTP GET 报文,生成一个 HTTP 响应报文,将 Web 页面内容放入报文主体中,发回给主机。 +1. 主机发送一个 TCP SYN 报文,连接 HTTP 服务器(默认端口 80)。 +2. 服务器响应 TCP SYN ACK 报文,完成三次握手。 +3. 连接建立后,主机发送 HTTP GET 报文请求特定页面。 +4. HTTP 服务器读取 GET 报文,生成 HTTP 响应,包含请求的网页内容。 +5. 主机收到响应报文后,提取网页内容并进行渲染,最终显示页面。 -- 浏览器收到 HTTP 响应报文后,抽取出 Web 页面内容,之后进行渲染,显示 Web 页面。 +通过上述过程,主机能够成功完成网页的请求和显示,背后涉及多个协议的协作。 +``` \ No newline at end of file diff --git "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\233\256\345\275\225.md" "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\233\256\345\275\225.md" index 2b049dbdc6..29c4d39037 100644 --- "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\233\256\345\275\225.md" +++ "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\233\256\345\275\225.md" @@ -1,5 +1,10 @@ +```markdown # 计算机网络 +计算机网络是指由多个互联的计算机系统组成的网络,这些计算机可以通过特定的网络协议进行通信。网络的功能和性能受到多种因素影响,包括网络的物理结构、传输介质、连接方式、网络协议等。 + +## 目录 + - [概述](计算机网络%20-%20概述.md) - [物理层](计算机网络%20-%20物理层.md) - [链路层](计算机网络%20-%20链路层.md) @@ -9,18 +14,64 @@ ## 参考链接 -- 计算机网络, 谢希仁 -- JamesF.Kurose, KeithW.Ross, 库罗斯, 等. 计算机网络: 自顶向下方法 [M]. 机械工业出版社, 2014. -- W.RichardStevens. TCP/IP 详解. 卷 1, 协议 [M]. 机械工业出版社, 2006. -- [Active vs Passive FTP Mode: Which One is More Secure?](https://securitywing.com/active-vs-passive-ftp-mode/) -- [Active and Passive FTP Transfers Defined - KB Article #1138](http://www.serv-u.com/kb/1138/active-and-passive-ftp-transfers-defined) -- [Traceroute](https://zh.wikipedia.org/wiki/Traceroute) -- [ping](https://zh.wikipedia.org/wiki/Ping) -- [How DHCP works and DHCP Interview Questions and Answers](http://webcache.googleusercontent.com/search?q=cache:http://anandgiria.blogspot.com/2013/09/windows-dhcp-interview-questions-and.html) -- [What is process of DORA in DHCP?](https://www.quora.com/What-is-process-of-DORA-in-DHCP) -- [What is DHCP Server ?](https://tecadmin.net/what-is-dhcp-server/) -- [Tackling emissions targets in Tokyo](http://www.climatechangenews.com/2011/html/university-tokyo.html) -- [What does my ISP know when I use Tor?](http://www.climatechangenews.com/2011/html/university-tokyo.html) -- [Technology-Computer Networking[1]-Computer Networks and the Internet](http://www.linyibin.cn/2017/02/12/technology-ComputerNetworking-Internet/) -- [P2P 网络概述.](http://slidesplayer.com/slide/11616167/) -- [Circuit Switching (a) Circuit switching. (b) Packet switching.](http://slideplayer.com/slide/5115386/) +- book: 计算机网络, 谢希仁 +- book: James F. Kurose, Keith W. Ross. 计算机网络: 自顶向下方法 [M]. 机械工业出版社, 2014. +- book: W. Richard Stevens. TCP/IP 详解. 卷 1, 协议 [M]. 机械工业出版社, 2006. +- [主动与被动FTP模式:哪种更安全?](https://securitywing.com/active-vs-passive-ftp-mode/) +- [主动和被动FTP传输的定义 - KB文章 #1138](http://www.serv-u.com/kb/1138/active-and-passive-ftp-transfers-defined) +- [Traceroute相关信息](https://zh.wikipedia.org/wiki/Traceroute) +- [Ping命令详解](https://zh.wikipedia.org/wiki/Ping) +- [DHCP的工作原理及面试问题](http://webcache.googleusercontent.com/search?q=cache:http://anandgiria.blogspot.com/2013/09/windows-dhcp-interview-questions-and.html) +- [DHCP中的DORA过程是什么?](https://www.quora.com/What-is-process-of-DORA-in-DHCP) +- [什么是DHCP服务器?](https://tecadmin.net/what-is-dhcp-server/) +- [在东京应对减排目标](http://www.climatechangenews.com/2011/html/university-tokyo.html) +- [使用Tor时,我的ISP知道什么?](http://www.climatechangenews.com/2011/html/university-tokyo.html) +- [计算机网络与互联网](http://www.linyibin.cn/2017/02/12/technology-ComputerNetworking-Internet/) +- [P2P网络概述。](http://slidesplayer.com/slide/11616167/) +- [电路交换与分组交换](http://slideplayer.com/slide/5115386/) + +## 扩展说明 + +### 网络协议 + +网络协议是用于数据交换的一组规则,这些规则定义了数据的格式、传输方式和错误处理机制。在网络中,协议分为不同的层级,如TCP/IP协议族,包括应用层、传输层、网络层等,每一层都有其特定的功能和责任。 + +### 如何选择网络层模式 + +在设计网络架构时,考虑网络的规模、传输效率、安全性以及维护成本等多个因素至关重要。常见的网络层模式包括主动模式和被动模式的FTP传输,各有其适用场景和安全特性。 + +### 示例代码 + +#### Ping 测试 + +通过Ping命令,可以测试网络的可达性。以下是使用Python实现Ping命令的示例: + +```python +import os + +def ping(host): + response = os.system("ping -c 4 " + host) + if response == 0: + print(f"{host} 是可达的.") + else: + print(f"{host} 是不可达的.") + +ping("www.example.com") +``` + +#### Traceroute 解析 + +Traceroute命令可以用于跟踪数据包在网络中的传输路径。以下是一个Python示例,使用`scapy`库进行Traceroute: + +```python +from scapy.all import traceroute + +def trace_route(destination): + ans, unans = traceroute(destination, maxttl=20, verbose=True) + return ans + +trace_route("www.example.com") +``` + +通过这些示例代码,能够简化对网络设备的操作及其故障排除过程,帮助理解网络数据传输的基本原理。 +``` \ No newline at end of file diff --git "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\275\221\347\273\234\345\261\202.md" "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\275\221\347\273\234\345\261\202.md" index 5634d7441b..c60c757efe 100644 --- "a/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\275\221\347\273\234\345\261\202.md" +++ "b/notes/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234 - \347\275\221\347\273\234\345\261\202.md" @@ -1,244 +1,178 @@ # 计算机网络 - 网络层 -<!-- GFM-TOC --> -* [计算机网络 - 网络层](#计算机网络---网络层) - * [概述](#概述) - * [IP 数据报格式](#ip-数据报格式) - * [IP 地址编址方式](#ip-地址编址方式) - * [1. 分类](#1-分类) - * [2. 子网划分](#2-子网划分) - * [3. 无分类](#3-无分类) - * [地址解析协议 ARP](#地址解析协议-arp) - * [网际控制报文协议 ICMP](#网际控制报文协议-icmp) - * [1. Ping](#1-ping) - * [2. Traceroute](#2-traceroute) - * [虚拟专用网 VPN](#虚拟专用网-vpn) - * [网络地址转换 NAT](#网络地址转换-nat) - * [路由器的结构](#路由器的结构) - * [路由器分组转发流程](#路由器分组转发流程) - * [路由选择协议](#路由选择协议) - * [1. 内部网关协议 RIP](#1-内部网关协议-rip) - * [2. 内部网关协议 OSPF](#2-内部网关协议-ospf) - * [3. 外部网关协议 BGP](#3-外部网关协议-bgp) -<!-- GFM-TOC --> - ## 概述 -因为网络层是整个互联网的核心,因此应当让网络层尽可能简单。网络层向上只提供简单灵活的、无连接的、尽最大努力交互的数据报服务。 - -使用 IP 协议,可以把异构的物理网络连接起来,使得在网络层看起来好像是一个统一的网络。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/8d779ab7-ffcc-47c6-90ec-ede8260b2368.png" width="800"/> </div><br> +网络层是整个互联网的核心,其设计理念是保持简单,使其能够高效地处理多种类型的网络。网络层主要提供无连接(connectionless)和尽最大努力(best-effort)的数据报服务。通过使用互联网协议(IP),网络层能够将不同类型的物理网络连接起来,使它们在逻辑上看起来像是一个统一的网络。 -与 IP 协议配套使用的还有三个协议: +与 IP 协议协同工作的还有三种重要的协议: -- 地址解析协议 ARP(Address Resolution Protocol) -- 网际控制报文协议 ICMP(Internet Control Message Protocol) -- 网际组管理协议 IGMP(Internet Group Management Protocol) +- **地址解析协议 ARP (Address Resolution Protocol)**:用于将 IP 地址映射到 MAC 地址。 +- **网际控制报文协议 ICMP (Internet Control Message Protocol)**:用于错误消息报告和诊断网络通信故障。 +- **网际组管理协议 IGMP (Internet Group Management Protocol)**:用于管理主机组及其多播组成员。 ## IP 数据报格式 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/85c05fb1-5546-4c50-9221-21f231cdc8c5.jpg" width="700"/> </div><br> +IP 数据报的结构非常重要,理解其格式有助于我们更好地进行网络编程和调试。下图是标准的 IP 数据报格式: -- **版本** : 有 4(IPv4)和 6(IPv6)两个值; + -- **首部长度** : 占 4 位,因此最大值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为固定部分长度为 20 字节,因此该值最小为 5。如果可选字段的长度不是 4 字节的整数倍,就用尾部的填充部分来填充。 +### 各字段说明: -- **区分服务** : 用来获得更好的服务,一般情况下不使用。 +- **版本 (Version)**: 指示 IP 协议的版本,目前主要为 IPv4(值为 4)和 IPv6(值为 6)。 + +- **首部长度 (Header Length)**: 该字段表明 IP 首部的长度,以 32 位字为单位,最大值为 15(即 60 字节)。 -- **总长度** : 包括首部长度和数据部分长度。 +- **区分服务 (Differentiated Services)**: 用于提供不同的服务质量(QoS),通常在普通情况下不使用。 -- **生存时间** :TTL,它的存在是为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位,当 TTL 为 0 时就丢弃数据报。 +- **总长度 (Total Length)**: 表示整个 IP 数据报的长度(包括首部和数据部分),最大值为 65535 字节。 -- **协议** :指出携带的数据应该上交给哪个协议进行处理,例如 ICMP、TCP、UDP 等。 +- **生存时间 (Time to Live, TTL)**: TTL 是一个用于限制数据报在网络中存活时间的机制,以跳数(hops)为单位,当 TTL 值减为 0 时,数据报将被丢弃。 -- **首部检验和** :因为数据报每经过一个路由器,都要重新计算检验和,因此检验和不包含数据部分可以减少计算的工作量。 +- **协议 (Protocol)**: 指示数据部分使用的协议类型,例如 TCP(传输控制协议)、UDP(用户数据报协议)、ICMP(网际控制报文协议)等。 -- **标识** : 在数据报长度过长从而发生分片的情况下,相同数据报的不同分片具有相同的标识符。 +- **首部检验和 (Header Checksum)**: 用于确保 IP 数据报在传输过程中没有损坏。每经过一个路由器,检验和需要重新计算。 -- **片偏移** : 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。 +- **标识 (Identification)**: 在分片时用于唯一标识相同的数据报,使得接收端能够识别哪些分片是同一数据报的组成部分。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/23ba890e-e11c-45e2-a20c-64d217f83430.png" width="700"/> </div><br> +- **片偏移 (Fragment Offset)**: 指示分片相对于原始数据报的位置,以 8 字节为单位,帮助接收端将分片重组为完整的数据报。 ## IP 地址编址方式 -IP 地址的编址方式经历了三个历史阶段: - -- 分类 -- 子网划分 -- 无分类 - -### 1. 分类 - -由两部分组成,网络号和主机号,其中不同分类具有不同的网络号长度,并且是固定的。 +IP 地址的编址方式可以分为三个阶段: -IP 地址 ::= {\< 网络号 \>, \< 主机号 \>} +### 1. 分类 (Classful Addressing) -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/cbf50eb8-22b4-4528-a2e7-d187143d57f7.png" width="500"/> </div><br> +IP 地址分为 A、B、C 类,结构为网络号和主机号两个部分。不同类别的网络号长度是固定的。 -### 2. 子网划分 +**IP 地址 = {网络号, 主机号}** -通过在主机号字段中拿一部分作为子网号,把两级 IP 地址划分为三级 IP 地址。 + -IP 地址 ::= {\< 网络号 \>, \< 子网号 \>, \< 主机号 \>} +### 2. 子网划分 (Subnetting) -要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 11000000 00000000,也就是 255.255.192.0。 +通过在主机号部分划分出一部分作为子网号,用以进一步细分大网,实现更灵活的网络管理。 -注意,外部网络看不到子网的存在。 +**IP 地址 = {网络号, 子网号, 主机号}** -### 3. 无分类 +使用子网需要配置子网掩码,子网掩码是用来标识网络部分和主机部分的。例如,B 类地址的默认子网掩码为 255.255.0.0。 -无分类编址 CIDR 消除了传统 A 类、B 类和 C 类地址以及划分子网的概念,使用网络前缀和主机号来对 IP 地址进行编码,网络前缀的长度可以根据需要变化。 +### 3. 无分类编址 (CIDR) -IP 地址 ::= {\< 网络前缀号 \>, \< 主机号 \>} +CIDR(Classless Inter-Domain Routing,无类别域间路由)摒弃了传统的 A、B、C 类划分,采用网络前缀和主机号进行编码,根据需要可自由调整网络前缀的长度。 -CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法,例如 128.14.35.7/20 表示前 20 位为网络前缀。 +**IP 地址 = {网络前缀, 主机号}** -CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为网络前缀的长度。 +CIDR 使用斜杠表示法来表示网络前缀的长度,例如 `128.14.35.7/20` 表示前 20 位为网络前缀。 -一个 CIDR 地址块中有很多地址,一个 CIDR 表示的网络就可以表示原来的很多个网络,并且在路由表中只需要一个路由就可以代替原来的多个路由,减少了路由表项的数量。把这种通过使用网络前缀来减少路由表项的方式称为路由聚合,也称为 **构成超网** 。 - -在路由表中的项目由“网络前缀”和“下一跳地址”组成,在查找时可能会得到不止一个匹配结果,应当采用最长前缀匹配来确定应该匹配哪一个。 +通过 CIDR,多个网络可以被聚合到一起,从而减少路由表中的条目数量,这种技术称为**路由聚合**或**构成超网**。 ## 地址解析协议 ARP -网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/66192382-558b-4b05-a35d-ac4a2b1a9811.jpg" width="700"/> </div><br> - -ARP 实现由 IP 地址得到 MAC 地址。 +在网络层实现主机间通信的同时,链路层负责具体链路之间的通信。因此,IP 数据报中的源地址和目的地址在整个数据传输过程中不变,而 MAC 地址会根据链路的不同而变化。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b9d79a5a-e7af-499b-b989-f10483e71b8b.jpg" width="500"/> </div><br> + -每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到 MAC 地址的映射表。 +ARP 的主要功能是将 IP 地址转换为 MAC 地址。每个主机都有一个 ARP 高速缓存,存储本局域网上所有主机的 IP 地址与 MAC 地址的映射关系。 -如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。 +以下是 ARP 的工作流程: +1. 当主机 A 想与主机 B 通信时,如果其 ARP 高速缓存中没有 B 的 MAC 地址,它将发送一个广播的 ARP 请求,询问 "谁拥有这个 IP 地址"。 +2. 收到请求的主机 B 会回复一个含有其 MAC 地址的 ARP 响应。 +3. 主机 A 记录下这个映射关系,以便将来的数据传输。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/8006a450-6c2f-498c-a928-c927f758b1d0.png" width="700"/> </div><br> + ## 网际控制报文协议 ICMP -ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。 +ICMP 是 IP 协议的一部分,旨在增强数据报转发的效率和成功交付率。ICMP 消息可以分为两大类:差错报告和询问。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e3124763-f75e-46c3-ba82-341e6c98d862.jpg" width="500"/> </div><br> - -ICMP 报文分为差错报告报文和询问报文。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/aa29cc88-7256-4399-8c7f-3cf4a6489559.png" width="600"/> </div><br> + ### 1. Ping -Ping 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。 - -Ping 的原理是通过向目的主机发送 ICMP Echo 请求报文,目的主机收到之后会发送 Echo 回答报文。Ping 会根据时间和成功响应的次数估算出数据包往返时间以及丢包率。 +Ping 是 ICMP 的重要应用,主要用于测试两台主机之间的连通性。其基本原理是发送 ICMP Echo 请求报文,若目的主机可达,则会回复 ICMP Echo 回应报文。 ### 2. Traceroute -Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。 +Traceroute 是用于追踪数据包从源点到目的地的路径。其工作机制如下: -Traceroute 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报,并由目的主机发送终点不可达差错报告报文。 +- 源主机首先发送 TTL 值为 1 的 IP 数据报。 +- 路由器收到数据报时将 TTL 减 1,若 TTL 归零,则丢弃该数据报,并发送一个 ICMP 超时差错报告回源主机。 +- 接着,源主机发送 TTL 值加 1 的数据报,重复上述过程,直到数据包到达目的地(在目的地返回 ICMP 报文)。 -- 源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1,当 P1 到达路径上的第一个路由器 R1 时,R1 收下它并把 TTL 减 1,此时 TTL 等于 0,R1 就把 P1 丢弃,并向源主机发送一个 ICMP 时间超过差错报告报文; -- 源主机接着发送第二个数据报 P2,并把 TTL 设置为 2。P2 先到达 R1,R1 收下后把 TTL 减 1 再转发给 R2,R2 收下后也把 TTL 减 1,由于此时 TTL 等于 0,R2 就丢弃 P2,并向源主机发送一个 ICMP 时间超过差错报文。 -- 不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是因为数据报封装的是无法交付的 UDP,因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。 -- 之后源主机知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间。 +通过这种方式,源主机可以获取沿途经过的各个路由器的 IP 地址及其响应时间。 ## 虚拟专用网 VPN -由于 IP 地址的紧缺,一个机构能申请到的 IP 地址数往往远小于本机构所拥有的主机数。并且一个机构并不需要把所有的主机接入到外部的互联网中,机构内的计算机可以使用仅在本机构有效的 IP 地址(专用地址)。 +由于公用 IP 地址资源的紧缺,许多机构所申请到的 IP 地址远少于其内部设备数量。VPN(Virtual Private Network,虚拟专用网)允许机构在公用互联网中创建专属的私有网络。 -有三个专用地址块: +VPN 利用公用互联网作为专用网之间的通信载体。机构内部的主机只与同一机构的其他主机通信,实质上形成了一个专用的虚拟网络。 -- 10.0.0.0 \~ 10.255.255.255 -- 172.16.0.0 \~ 172.31.255.255 -- 192.168.0.0 \~ 192.168.255.255 +下图展示了一个 VPN 的工作流程: -VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指好像是,而实际上并不是,它有经过公用的互联网。 - -下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1556770b-8c01-4681-af10-46f1df69202c.jpg" width="800"/> </div><br> + ## 网络地址转换 NAT -专用网内部的主机使用本地 IP 地址又想和互联网上的主机通信时,可以使用 NAT 来将本地 IP 转换为全球 IP。 +在专用网中,内部主机使用私有 IP 地址进行互联,而要与外部网络通信时,需要通过网络地址转换(NAT)机制,将其私有 IP 地址转换为公有 IP 地址。 -在以前,NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把传输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。 +传统的 NAT 机制将内网主机的私有 IP 和公网 IP 一一对应,这限制了网络中并发接入的主机数。现代 NAT 技术引入了端口号,使得多个内部主机可以共享一个公网 IP 地址,这种技术称为网络地址与端口转换(NAPT)。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2719067e-b299-4639-9065-bed6729dbf0b.png" width=""/> </div><br> + ## 路由器的结构 -路由器从功能上可以划分为:路由选择和分组转发。 +路由器可从功能上分为路由选择与分组转发。分组转发的结构主要由以下三部分组成: -分组转发结构由三个部分组成:交换结构、一组输入端口和一组输出端口。 +- **交换结构**:负责内部数据交换。 +- **输入端口**:接收来的数据包,并处理相应的协议。 +- **输出端口**:负责将数据包转发到下一个网络。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c3369072-c740-43b0-b276-202bd1d3960d.jpg" width="600"/> </div><br> + ## 路由器分组转发流程 -- 从数据报的首部提取目的主机的 IP 地址 D,得到目的网络地址 N。 -- 若 N 就是与此路由器直接相连的某个网络地址,则进行直接交付; -- 若路由表中有目的地址为 D 的特定主机路由,则把数据报传送给表中所指明的下一跳路由器; -- 若路由表中有到达网络 N 的路由,则把数据报传送给路由表中所指明的下一跳路由器; -- 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器; -- 报告转发分组出错。 +路由器接收数据报的处理流程如下: -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1ab49e39-012b-4383-8284-26570987e3c4.jpg" width="800"/> </div><br> +1. 从数据报的首部提取目的 IP 地址 D,解析得到目的网络地址 N。 +2. 如果 N 是路由器直接相连的网络地址,则直接将数据报交付给相应主机。 +3. 如果路由表中有目的地址为 D 的特定主机路由,则将数据报发送给相应的下一跳路由器。 +4. 如果路由表中有到达网络 N 的路由,仍然将数据报发送给相应的下一跳路由器。 +5. 如果路由表中有默认路由,就将数据报发送至默认路由器。 +6. 如果所有路由策略均不满足,则报告转发出错。 -## 路由选择协议 + -路由选择协议都是自适应的,能随着网络通信量和拓扑结构的变化而自适应地进行调整。 +## 路由选择协议 -互联网可以划分为许多较小的自治系统 AS,一个 AS 可以使用一种和别的 AS 不同的路由选择协议。 +路由选择协议负责在动态变化的网络拓扑与流量中决定数据报的转发路径。整个互联网被划分为多个自治系统(AS),每个 AS 可以使用不同的路由选择协议。 -可以把路由选择协议划分为两大类: +路由选择协议大致可以分为两类: -- 自治系统内部的路由选择:RIP 和 OSPF -- 自治系统间的路由选择:BGP +- **内部网关协议**:RIP 和 OSPF +- **外部网关协议**:BGP ### 1. 内部网关协议 RIP -RIP 是一种基于距离向量的路由选择协议。距离是指跳数,直接相连的路由器跳数为 1。跳数最多为 15,超过 15 表示不可达。 - -RIP 按固定的时间间隔仅和相邻路由器交换自己的路由表,经过若干次交换之后,所有路由器最终会知道到达本自治系统中任何一个网络的最短距离和下一跳路由器地址。 - -距离向量算法: +RIP(Routing Information Protocol,路由信息协议)是一种基于距离向量的协议。距离指的是跳数,直接相连的路由器跳数为 1,而最大跳数为 15,超过 15 的网络被视为不可达。 -- 对地址为 X 的相邻路由器发来的 RIP 报文,先修改报文中的所有项目,把下一跳字段中的地址改为 X,并把所有的距离字段加 1; -- 对修改后的 RIP 报文中的每一个项目,进行以下步骤: - - 若原来的路由表中没有目的网络 N,则把该项目添加到路由表中; - - 否则:若下一跳路由器地址是 X,则把收到的项目替换原来路由表中的项目;否则:若收到的项目中的距离 d 小于路由表中的距离,则进行更新(例如原始路由表项为 Net2, 5, P,新表项为 Net2, 4, X,则更新);否则什么也不做。 -- 若 3 分钟还没有收到相邻路由器的更新路由表,则把该相邻路由器标为不可达,即把距离置为 16。 - -RIP 协议实现简单,开销小。但是 RIP 能使用的最大距离为 15,限制了网络的规模。并且当网络出现故障时,要经过比较长的时间才能将此消息传送到所有路由器。 +RIP 的工作原理是以固定时间间隔与邻居路由器交换路由表,通过多次交换,在整个 AS 中传播最新的路由信息,确保每个路由器能够知道如何到达各网络的最短路径。 ### 2. 内部网关协议 OSPF -开放最短路径优先 OSPF,是为了克服 RIP 的缺点而开发出来的。 - -开放表示 OSPF 不受某一家厂商控制,而是公开发表的;最短路径优先表示使用了 Dijkstra 提出的最短路径算法 SPF。 - -OSPF 具有以下特点: +OSPF(Open Shortest Path First,开放最短路径优先)是为克服 RIP 的缺点而开发的协议。OSPF 使用洪泛法向整个自治系统的路由器发送信息,信息内容包括与邻居路由器的链路状态,而不是简单的路由表。 -- 向本自治系统中的所有路由器发送信息,这种方法是洪泛法。 -- 发送的信息就是与相邻路由器的链路状态,链路状态包括与哪些路由器相连以及链路的度量,度量用费用、距离、时延、带宽等来表示。 -- 只有当链路状态发生变化时,路由器才会发送信息。 - -所有路由器都具有全网的拓扑结构图,并且是一致的。相比于 RIP,OSPF 的更新过程收敛的很快。 +OSPF 具有多个优点,如收敛速度快、能够处理大型网络、支持更复杂的度量方式等。 ### 3. 外部网关协议 BGP -BGP(Border Gateway Protocol,边界网关协议) - -AS 之间的路由选择很困难,主要是由于: +BGP(Border Gateway Protocol,边界网关协议)主要用于自治系统之间的路由选择。其复杂性主要来源于以下几点: -- 互联网规模很大; -- 各个 AS 内部使用不同的路由选择协议,无法准确定义路径的度量; -- AS 之间的路由选择必须考虑有关的策略,比如有些 AS 不愿意让其它 AS 经过。 +- 互联网规模庞大; +- 不同 AS 内部使用不同的路由协议,路径度量难以统一; +- 路由选择需考虑不同的网络策略。 -BGP 只能寻找一条比较好的路由,而不是最佳路由。 +BGP 向较邻的 BGP 节点之间建立 TCP 连接以交换路由信息。 -每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。 + -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/9cd0ae20-4fb5-4017-a000-f7d3a0eb3529.png" width="600"/> </div><br> +通过对以上协议流程的理解,可以更好地设计和管理网络结构以及网络性能。 \ No newline at end of file diff --git "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\233\256\345\275\2251.md" "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\233\256\345\275\2251.md" index d61a1d986e..31efae435c 100644 --- "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\233\256\345\275\2251.md" +++ "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\233\256\345\275\2251.md" @@ -1,46 +1,165 @@ +```markdown # 一、前言 -设计模式是解决问题的方案,学习现有的设计模式可以做到经验复用。拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节。 - -# 二、创建型 - -- [单例.md](notes/设计模式%20%20-%20单例.md) -- [简单工厂.md](notes/设计模式%20-%20简单工厂.md) -- [工厂方法.md](notes/设计模式%20-%20工厂方法.md) -- [抽象工厂.md](notes/设计模式%20-%20抽象工厂.md) -- [生成器.md](notes/设计模式%20-%20生成器.md) -- [原型模式.md](notes/设计模式%20-%20原型模式.md) - -# 三、行为型 - -- [责任链.md](notes/设计模式%20-%20责任链.md) -- [命令.md](notes/设计模式%20-%20命令.md) -- [解释器.md](notes/设计模式%20-%20解释器.md) -- [迭代器.md](notes/设计模式%20-%20迭代器.md) -- [中介者.md](notes/设计模式%20-%20中介者.md) -- [备忘录.md](notes/设计模式%20-%20备忘录.md) -- [观察者.md](notes/设计模式%20-%20观察者.md) -- [状态.md](notes/设计模式%20-%20状态.md) -- [策略.md](notes/设计模式%20-%20策略.md) -- [模板方法.md](notes/设计模式%20-%20模板方法.md) -- [访问者.md](notes/设计模式%20-%20访问者.md) -- [空对象.md](notes/设计模式%20-%20空对象.md) - -# 四、结构型 - -- [适配器.md](notes/设计模式%20-%20适配器.md) -- [桥接.md](notes/设计模式%20-%20桥接.md) -- [组合.md](notes/设计模式%20-%20组合.md) -- [装饰.md](notes/设计模式%20-%20装饰.md) -- [外观.md](notes/设计模式%20-%20外观.md) -- [享元.md](notes/设计模式%20-%20享元.md) -- [代理.md](notes/设计模式%20-%20代理.md) +设计模式是编写高质量软件的解决方案,旨在解决特定的设计问题。学习现有的设计模式不仅能够提高代码的重用性,还有助于在团队或项目中进行更高效的沟通。掌握设计模式的术语可以让我们用更简洁的语言讨论复杂问题,而不需要深入底层细节。 + +# 二、创建型模式 + +创建型模式主要关注于如何实例化对象,有助于将对象的创建过程与具体的实现细节解耦。以下是几种常见的创建型设计模式: + +- [单例模式](notes/设计模式%20%20-%20单例.md) +- [简单工厂模式](notes/设计模式%20-%20简单工厂.md) +- [工厂方法模式](notes/设计模式%20-%20工厂方法.md) +- [抽象工厂模式](notes/设计模式%20-%20抽象工厂.md) +- [生成器模式](notes/设计模式%20-%20生成器.md) +- [原型模式](notes/设计模式%20-%20原型模式.md) + +### 单例模式 + +单例模式确保一个类只有一个实例,并提供一个全局访问点。它常用于需要控制对共享资源的访问时,例如线程池或配置管理器。 + +```java +public class Singleton { + private static Singleton instance; + + private Singleton() {} + + public static synchronized Singleton getInstance() { + if (instance == null) { + instance = new Singleton(); + } + return instance; + } +} +``` + +### 简单工厂模式 + +简单工厂模式通过一个工厂类来决定创建哪种类型的对象。它是实现封装的一种方式,客户端仅需要关心工厂而非对象的实现。 + +```java +public class ShapeFactory { + public Shape getShape(String shapeType) { + if (shapeType == null) { + return null; + } + if (shapeType.equalsIgnoreCase("CIRCLE")) { + return new Circle(); + } else if (shapeType.equalsIgnoreCase("RECTANGLE")) { + return new Rectangle(); + } + return null; + } +} +``` + +# 三、行为型模式 + +行为型模式主要关注对象之间的交互。通过这些模式,可以在不增加类之间的耦合性时,实现更加灵活和可扩展的设计。以下是常见的行为型设计模式: + +- [责任链模式](notes/设计模式%20-%20责任链.md) +- [命令模式](notes/设计模式%20-%20命令.md) +- [解释器模式](notes/设计模式%20-%20解释器.md) +- [迭代器模式](notes/设计模式%20-%20迭代器.md) +- [中介者模式](notes/设计模式%20-%20中介者.md) +- [备忘录模式](notes/设计模式%20-%20备忘录.md) +- [观察者模式](notes/设计模式%20-%20观察者.md) +- [状态模式](notes/设计模式%20-%20状态.md) +- [策略模式](notes/设计模式%20-%20策略.md) +- [模板方法模式](notes/设计模式%20-%20模板方法.md) +- [访问者模式](notes/设计模式%20-%20访问者.md) +- [空对象模式](notes/设计模式%20-%20空对象.md) + +### 观察者模式 + +观察者模式定义了一种一对多的依赖关系,让多个观察者对象能够同时监听某一个主题对象的状态变化。当主题对象的状态改变时,所有依赖于它的观察者都会收到通知并自动更新。 + +```java +import java.util.ArrayList; +import java.util.List; + +public interface Observer { + void update(String message); +} + +public class ConcreteObserver implements Observer { + private String name; + + public ConcreteObserver(String name) { + this.name = name; + } + + @Override + public void update(String message) { + System.out.println(name + " received message: " + message); + } +} + +public class Subject { + private List<Observer> observers = new ArrayList<>(); + + public void attach(Observer observer) { + observers.add(observer); + } + + public void notifyObservers(String message) { + for (Observer observer : observers) { + observer.update(message); + } + } +} +``` + +# 四、结构型模式 + +结构型模式主要关注类或对象的组合,以形成更大的结构。其目的是确保这些组合能够有效地工作。以下是一些常见的结构型设计模式: + +- [适配器模式](notes/设计模式%20-%20适配器.md) +- [桥接模式](notes/设计模式%20-%20桥接.md) +- [组合模式](notes/设计模式%20-%20组合.md) +- [装饰模式](notes/设计模式%20-%20装饰.md) +- [外观模式](notes/设计模式%20-%20外观.md) +- [享元模式](notes/设计模式%20-%20享元.md) +- [代理模式](notes/设计模式%20-%20代理.md) + +### 适配器模式 + +适配器模式允许不兼容的接口协同工作。通过转换接口,使得原本因接口不匹配而无法一起工作的类能够在一起工作。 + +```java +// 目标接口 +public interface Target { + void request(); +} + +// 适配者类 +public class Adaptee { + public void specificRequest() { + System.out.println("Specific request"); + } +} + +// 适配器 +public class Adapter implements Target { + private Adaptee adaptee; + + public Adapter(Adaptee adaptee) { + this.adaptee = adaptee; + } + + @Override + public void request() { + adaptee.specificRequest(); + } +} +``` # 参考资料 -- 弗里曼. Head First 设计模式 [M]. 中国电力出版社, 2007. -- Gamma E. 设计模式: 可复用面向对象软件的基础 [M]. 机械工业出版社, 2007. -- Bloch J. Effective java[M]. Addison-Wesley Professional, 2017. +- 弗里曼. **Head First 设计模式** [M]. 中国电力出版社, 2007. +- Gamma, E. **设计模式: 可复用面向对象软件的基础** [M]. 机械工业出版社, 2007. +- Bloch, J. **Effective Java** [M]. Addison-Wesley Professional, 2017. - [Design Patterns](http://www.oodesign.com/) - [Design patterns implemented in Java](http://java-design-patterns.com/) - [The breakdown of design patterns in JDK](http://www.programering.com/a/MTNxAzMwATY.html) +``` diff --git "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\255\226\347\225\245.md" "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\255\226\347\225\245.md" index d36824ea56..1368fdb2ef 100644 --- "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\255\226\347\225\245.md" +++ "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\255\226\347\225\245.md" @@ -1,89 +1,105 @@ ## 9. 策略(Strategy) -### Intent +### 目的 -定义一系列算法,封装每个算法,并使它们可以互换。 +策略模式定义了一系列算法,将每个算法封装起来,使它们可以互换使用。这样,算法可以独立于使用它的客户端。策略模式提供了一种灵活的方式来选择和执行业务逻辑。 -策略模式可以让算法独立于使用它的客户端。 +### 类图 -### Class Diagram +- **Strategy** 接口定义了算法族,每个具体的策略类都会实现 `behavior()` 方法。 +- **Context** 类是使用策略的上下文,它包含了 `doSomething()` 方法来调用当前的策略的 `behavior()` 方法,另外还提供了 `setStrategy(Strategy)` 方法来动态改变当前策略对象,这样就可以灵活地改变上下文所用到的算法。 -- Strategy 接口定义了一个算法族,它们都实现了 behavior() 方法。 -- Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior(),setStrategy(Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/cd1be8c2-755a-4a66-ad92-2e30f8f47922.png"/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/cd1be8c2-755a-4a66-ad92-2e30f8f47922.png"/> +</div><br> ### 与状态模式的比较 -状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。 +策略模式与状态模式在类图上相似,都能够在运行时动态改变对象的行为。但这两者的根本不同在于:状态模式是通过状态转移来改变上下文所持有的 `State` 对象,而策略模式则是通过上下文本身的决策来改变所持有的 `Strategy` 对象。 + +状态模式主要解决的是对象在不同状态间的转换问题。状态转移发生时,`Context` 对象的行为将发生改变。而策略模式主要用于封装一组可互相替换的算法,可以根据需要动态更换上下文所使用的算法。 -状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。 +### 实现示例 -### Implementation +下面以鸭子为例,展示如何使用策略模式实现动态改变鸭子的叫声行为。 -设计一个鸭子,它可以动态地改变叫声。这里的算法族是鸭子的叫声行为。 +首先,定义一个表示叫声行为的接口 `QuackBehavior`: ```java public interface QuackBehavior { - void quack(); + void quack(); // 定义叫声行为 } ``` +接着,实现具体的叫声行为类: + ```java public class Quack implements QuackBehavior { @Override public void quack() { - System.out.println("quack!"); + System.out.println("quack!"); // 实现普通叫声 } } -``` -```java -public class Squeak implements QuackBehavior{ +public class Squeak implements QuackBehavior { @Override public void quack() { - System.out.println("squeak!"); + System.out.println("squeak!"); // 实现尖叫声 } } ``` +然后,定义 `Duck` 类,作为上下文,负责结合具体的叫声行为: + ```java public class Duck { - private QuackBehavior quackBehavior; + private QuackBehavior quackBehavior; // 当前叫声行为 + // 执行叫声行为 public void performQuack() { if (quackBehavior != null) { - quackBehavior.quack(); + quackBehavior.quack(); // 调用当前的叫声行为 } } + // 设置新的叫声行为 public void setQuackBehavior(QuackBehavior quackBehavior) { - this.quackBehavior = quackBehavior; + this.quackBehavior = quackBehavior; } } ``` +最后,在客户端代码中,使用鸭子并动态改变其叫声行为: + ```java public class Client { public static void main(String[] args) { - Duck duck = new Duck(); + Duck duck = new Duck(); // 创建鸭子实例 + + // 设置叫声行为为尖叫 duck.setQuackBehavior(new Squeak()); - duck.performQuack(); + duck.performQuack(); // 输出: squeak! + + // 动态改变叫声行为为普通叫声 duck.setQuackBehavior(new Quack()); - duck.performQuack(); + duck.performQuack(); // 输出: quack! } } ``` -```html +输出结果: +``` squeak! quack! ``` -### JDK +### JDK 应用示例 + +策略模式在 Java 开发中有多个重要应用实例,比如: +- `java.util.Comparator#compare()`:用于自定义排序的策略。 +- `javax.servlet.http.HttpServlet`:通过设置不同的过滤器来改变请求处理的策略。 +- `javax.servlet.Filter#doFilter()`:允许根据具体的请求条件选用不同的过滤策略。 -- java.util.Comparator#compare() -- javax.servlet.http.HttpServlet -- javax.servlet.Filter#doFilter() +策略模式为代码的扩展和维护提供了良好的支持,便于管理多种算法的使用和切换。 \ No newline at end of file diff --git "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\256\200\345\215\225\345\267\245\345\216\202.md" "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\256\200\345\215\225\345\267\245\345\216\202.md" index 55d79c0221..ee8cc6b871 100644 --- "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\256\200\345\215\225\345\267\245\345\216\202.md" +++ "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \347\256\200\345\215\225\345\267\245\345\216\202.md" @@ -1,47 +1,61 @@ ## 简单工厂(Simple Factory) -### Intent +### 意图 -在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。 +简单工厂模式的主要意图是在创建对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。通过这种方式,客户代码不需要直接使用具体的子类,从而实现了与具体实现的解耦,提高了系统的灵活性和可维护性。 -### Class Diagram +### 类图 -简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。 - -这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。 +简单工厂将实例化的操作独立到一个特定的类中,这个类被称为简单工厂类。简单工厂类根据客户的需求,决定实例化哪个具体的子类。这样的设计使得客户类与具体子类之间的依赖关系减到最低,客户类无需关心具体子类的实现细节。一旦有新的子类需要添加,只需修改简单工厂类,而不必改动所有的客户类。 <div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/40c0c17e-bba6-4493-9857-147c0044a018.png"/> </div><br> -### Implementation +### 实现 + +接口 `Product` 定义了不同产品的通用接口。 ```java public interface Product { + void use(); // 定义产品使用方法 } ``` +具体产品类 `ConcreteProduct` 和各个子类 `ConcreteProduct1`, `ConcreteProduct2` 实现这个接口: + ```java public class ConcreteProduct implements Product { + @Override + public void use() { + System.out.println("使用普通产品."); + } } -``` -```java public class ConcreteProduct1 implements Product { + @Override + public void use() { + System.out.println("使用具体产品1."); + } } -``` -```java public class ConcreteProduct2 implements Product { + @Override + public void use() { + System.out.println("使用具体产品2."); + } } ``` -以下的 Client 类包含了实例化的代码,这是一种错误的实现。如果在客户类中存在这种实例化代码,就需要考虑将代码放到简单工厂中。 +#### 客户端错误实现示例 + +以下是一个客户端类的示例,这种实现方式是错误的,因为它在客户端代码中直接包含了产品实例化的逻辑,使得代码变得难以维护和扩展: ```java public class Client { - public static void main(String[] args) { - int type = 1; + int type = 1; // 假设从某个地方获得类型信息 Product product; + + // 在客户端中直接实例化产品 if (type == 1) { product = new ConcreteProduct1(); } else if (type == 2) { @@ -49,34 +63,52 @@ public class Client { } else { product = new ConcreteProduct(); } - // do something with the product + + // 使用产品 + product.use(); } } ``` -以下的 SimpleFactory 是简单工厂实现,它被所有需要进行实例化的客户类调用。 +#### 简单工厂的实现 + +通过创建一个 `SimpleFactory` 类来将产品的实例化逻辑分离到一个工厂中,客户类只需关注如何使用产品。 ```java public class SimpleFactory { public Product createProduct(int type) { - if (type == 1) { - return new ConcreteProduct1(); - } else if (type == 2) { - return new ConcreteProduct2(); + switch (type) { + case 1: + return new ConcreteProduct1(); + case 2: + return new ConcreteProduct2(); + default: + return new ConcreteProduct(); // 默认返回普通产品 } - return new ConcreteProduct(); } } ``` +#### 客户端的正确实现示例 + +以下是使用简单工厂的正确实现示例,客户类只需调用工厂方法来创建产品: + ```java public class Client { public static void main(String[] args) { SimpleFactory simpleFactory = new SimpleFactory(); + + // 通过简单工厂创建产品 Product product = simpleFactory.createProduct(1); - // do something with the product + + // 使用产品 + product.use(); } } ``` + +### 总结 + +简单工厂模式有效地将对象的创建和使用分离,使得系统结构更加清晰并且易于扩展。通过中心化创建逻辑,减少了客户类的复杂性,增强了系统的灵活性和可维护性。这种模式尤其适用于创建逻辑相对简单的情况,以及在需要根据条件动态创建不同产品的场景中。 \ No newline at end of file diff --git "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \350\247\202\345\257\237\350\200\205.md" "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \350\247\202\345\257\237\350\200\205.md" index 141c513cae..44f5c9a8d5 100644 --- "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \350\247\202\345\257\237\350\200\205.md" +++ "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217 - \350\247\202\345\257\237\350\200\205.md" @@ -1,134 +1,165 @@ -## 7. 观察者(Observer) +```markdown +## 7. 观察者模式(Observer) -### Intent +### 意图 -定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。 +观察者模式用于定义对象之间的一种一对多的依赖关系。当一个对象的状态发生改变时,它的所有依赖对象(观察者)都会自动收到通知并更新其状态。这个模式通常用于事件处理系统,比如 GUI 组件的事件监听。 -主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。 +在这一模式中,被观察的对象称为主题(Subject),而依赖于该主题的对象称为观察者(Observer)。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7a3c6a30-c735-4edb-8115-337288a4f0f2.jpg" width="600"/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7a3c6a30-c735-4edb-8115-337288a4f0f2.jpg" width="600"/> +</div><br> -### Class Diagram +### 类图 -主题(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。 +主题(Subject)需要提供注册和移除观察者的机制,同时还需实现通知所有注册的观察者的功能。它通过维护一张观察者列表来实现这些操作。 -观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。 +观察者(Observer)需要实现注册功能,通常是通过调用主题的 `registerObserver()` 方法来注册自己。以下是类的结构图。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/a8c8f894-a712-447c-9906-5caef6a016e3.png"/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/a8c8f894-a712-447c-9906-5caef6a016e3.png"/> +</div><br> -### Implementation +### 实现 -天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。 +以天气数据布告板为例,当天气信息发生变化时,布告板中注册的多个观察者会被通知并更新内容。这样可以方便地进行数据展示和状态更新。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b1df9732-86ce-4d69-9f06-fba1db7b3b5a.jpg"/> </div><br> +<div align="center"> + <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b1df9732-86ce-4d69-9f06-fba1db7b3b5a.jpg"/> +</div><br> + +#### 代码示例 + +以下是观察者模式的基本实现: + +1. **主题接口:Subject** ```java public interface Subject { - void registerObserver(Observer o); + void registerObserver(Observer o); // 注册观察者 - void removeObserver(Observer o); + void removeObserver(Observer o); // 移除观察者 - void notifyObserver(); + void notifyObservers(); // 通知所有观察者 } ``` +2. **具体主题:WeatherData** + ```java +import java.util.ArrayList; +import java.util.List; + public class WeatherData implements Subject { - private List<Observer> observers; - private float temperature; - private float humidity; - private float pressure; + private List<Observer> observers; // 存储观察者的列表 + private float temperature; // 温度 + private float humidity; // 湿度 + private float pressure; // 气压 public WeatherData() { - observers = new ArrayList<>(); + observers = new ArrayList<>(); // 初始化观察者列表 } + // 设置新的天气测量值并通知所有观察者 public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; - notifyObserver(); + notifyObservers(); // 通知观察者 } @Override public void registerObserver(Observer o) { - observers.add(o); + observers.add(o); // 添加观察者 } @Override public void removeObserver(Observer o) { - int i = observers.indexOf(o); - if (i >= 0) { - observers.remove(i); - } + observers.remove(o); // 移除观察者 } @Override - public void notifyObserver() { + public void notifyObservers() { for (Observer o : observers) { - o.update(temperature, humidity, pressure); + o.update(temperature, humidity, pressure); // 更新每个观察者 } } } ``` +3. **观察者接口:Observer** + ```java public interface Observer { - void update(float temp, float humidity, float pressure); + void update(float temp, float humidity, float pressure); // 更新观察者数据 } ``` +4. **具体观察者:StatisticsDisplay** + ```java public class StatisticsDisplay implements Observer { - public StatisticsDisplay(Subject weatherData) { - weatherData.registerObserver(this); + weatherData.registerObserver(this); // 注册到主题 } @Override public void update(float temp, float humidity, float pressure) { - System.out.println("StatisticsDisplay.update: " + temp + " " + humidity + " " + pressure); + System.out.println("StatisticsDisplay: 温度 = " + temp + " 湿度 = " + humidity + " 气压 = " + pressure); } } ``` +5. **另一个具体观察者:CurrentConditionsDisplay** + ```java public class CurrentConditionsDisplay implements Observer { - public CurrentConditionsDisplay(Subject weatherData) { - weatherData.registerObserver(this); + weatherData.registerObserver(this); // 注册到主题 } @Override public void update(float temp, float humidity, float pressure) { - System.out.println("CurrentConditionsDisplay.update: " + temp + " " + humidity + " " + pressure); + System.out.println("CurrentConditionsDisplay: 温度 = " + temp + " 湿度 = " + humidity + " 气压 = " + pressure); } } ``` +6. **天气站:WeatherStation** + ```java public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); + + // 创建并注册观察者 CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); - + + // 设置新的天气测量值 weatherData.setMeasurements(0, 0, 0); weatherData.setMeasurements(1, 1, 1); } } ``` -```html -CurrentConditionsDisplay.update: 0.0 0.0 0.0 -StatisticsDisplay.update: 0.0 0.0 0.0 -CurrentConditionsDisplay.update: 1.0 1.0 1.0 -StatisticsDisplay.update: 1.0 1.0 1.0 +#### 输出结果 + +执行上述代码后,会输出以下内容,表明观察者成功接收到状态更新: + +```plaintext +CurrentConditionsDisplay: 温度 = 0.0 湿度 = 0.0 气压 = 0.0 +StatisticsDisplay: 温度 = 0.0 湿度 = 0.0 气压 = 0.0 +CurrentConditionsDisplay: 温度 = 1.0 湿度 = 1.0 气压 = 1.0 +StatisticsDisplay: 温度 = 1.0 湿度 = 1.0 气压 = 1.0 ``` -### JDK +### 相关类 -- [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html) -- [java.util.EventListener](http://docs.oracle.com/javase/8/docs/api/java/util/EventListener.html) -- [javax.servlet.http.HttpSessionBindingListener](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionBindingListener.html) -- [RxJava](https://github.com/ReactiveX/RxJava) +Java 标准库中也包含一些相关的观察者设计模式实现: +- [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html) - 观察者接口 +- [java.util.EventListener](http://docs.oracle.com/javase/8/docs/api/java/util/EventListener.html) - 事件监听器的基础接口 +- [javax.servlet.http.HttpSessionBindingListener](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionBindingListener.html) - 容器的会话绑定监听器 +- [RxJava](https://github.com/ReactiveX/RxJava) - 响应式编程库,使用观察者模式处理异步数据流 +``` diff --git "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md" index fe1002a2a5..5df97223f5 100644 --- "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -1,1623 +1,680 @@ -<!-- GFM-TOC --> -* [一、概述](#一概述) -* [二、创建型](#二创建型) - * [1. 单例(Singleton)](#1-单例singleton) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [Examples](#examples) - * [JDK](#jdk) - * [2. 简单工厂(Simple Factory)](#2-简单工厂simple-factory) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [3. 工厂方法(Factory Method)](#3-工厂方法factory-method) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [4. 抽象工厂(Abstract Factory)](#4-抽象工厂abstract-factory) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [5. 生成器(Builder)](#5-生成器builder) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [6. 原型模式(Prototype)](#6-原型模式prototype) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) -* [三、行为型](#三行为型) - * [1. 责任链(Chain Of Responsibility)](#1-责任链chain-of-responsibility) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [2. 命令(Command)](#2-命令command) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [3. 解释器(Interpreter)](#3-解释器interpreter) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [4. 迭代器(Iterator)](#4-迭代器iterator) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [5. 中介者(Mediator)](#5-中介者mediator) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [6. 备忘录(Memento)](#6-备忘录memento) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [7. 观察者(Observer)](#7-观察者observer) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [8. 状态(State)](#8-状态state) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [9. 策略(Strategy)](#9-策略strategy) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [与状态模式的比较](#与状态模式的比较) - * [Implementation](#implementation) - * [JDK](#jdk) - * [10. 模板方法(Template Method)](#10-模板方法template-method) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [11. 访问者(Visitor)](#11-访问者visitor) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [12. 空对象(Null)](#12-空对象null) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) -* [四、结构型](#四结构型) - * [1. 适配器(Adapter)](#1-适配器adapter) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [2. 桥接(Bridge)](#2-桥接bridge) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [3. 组合(Composite)](#3-组合composite) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [4. 装饰(Decorator)](#4-装饰decorator) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [设计原则](#设计原则) - * [JDK](#jdk) - * [5. 外观(Facade)](#5-外观facade) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [设计原则](#设计原则) - * [6. 享元(Flyweight)](#6-享元flyweight) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) - * [7. 代理(Proxy)](#7-代理proxy) - * [Intent](#intent) - * [Class Diagram](#class-diagram) - * [Implementation](#implementation) - * [JDK](#jdk) -* [参考资料](#参考资料) -<!-- GFM-TOC --> +Below is a refined version of the original content from the file `设计模式.md`, ensuring that the explanations are clearer and the coding examples are streamlined for better understanding. +--- # 一、概述 -设计模式是解决问题的方案,学习现有的设计模式可以做到经验复用。 - -拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节。 +设计模式是针对常见问题的解决方案。学习这些既有的设计模式可以帮助我们复用经验。在沟通时引入设计模式的术语,可以用更少的词汇进行讨论,无需深入到底层细节。 # 二、创建型 ## 1. 单例(Singleton) ### Intent - 确保一个类只有一个实例,并提供该实例的全局访问点。 ### Class Diagram +使用私有构造函数、私有静态变量及公有静态方法实现单例。 -使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。 + -私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。 +### Implementation -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/eca1f422-8381-409b-ad04-98ef39ae38ba.png"/> </div><br> +下面是不同的单例实现方式: + +#### 八种实现方式 + +1. **懒汉式 - 线程不安全** + ```java + public class Singleton { + private static Singleton uniqueInstance; + + private Singleton() { + } + + public static Singleton getUniqueInstance() { + if (uniqueInstance == null) { + uniqueInstance = new Singleton(); + } + return uniqueInstance; + } + } + ``` + +2. **饿汉式 - 线程安全** + ```java + public class Singleton { + private static final Singleton uniqueInstance = new Singleton(); + + private Singleton() { + } + + public static Singleton getUniqueInstance() { + return uniqueInstance; + } + } + ``` + +3. **懒汉式 - 线程安全** + ```java + public class Singleton { + private static Singleton uniqueInstance; + + private Singleton() { + } + + public static synchronized Singleton getUniqueInstance() { + if (uniqueInstance == null) { + uniqueInstance = new Singleton(); + } + return uniqueInstance; + } + } + ``` + +4. **双重检查锁定 - 线程安全** + ```java + public class Singleton { + private volatile static Singleton uniqueInstance; + + private Singleton() { + } + + public static Singleton getUniqueInstance() { + if (uniqueInstance == null) { + synchronized (Singleton.class) { + if (uniqueInstance == null) { + uniqueInstance = new Singleton(); + } + } + } + return uniqueInstance; + } + } + ``` + +5. **静态内部类实现** + ```java + public class Singleton { + private Singleton() { + } + + private static class SingletonHolder { + private static final Singleton INSTANCE = new Singleton(); + } + + public static Singleton getUniqueInstance() { + return SingletonHolder.INSTANCE; + } + } + ``` + +6. **枚举实现** + ```java + public enum Singleton { + INSTANCE; + + public void someMethod() { + // Do something + } + } + ``` -### Implementation +### Examples +- 日志记录器 +- 配置类 +- 共享资源的访问 -#### Ⅰ 懒汉式-线程不安全 +### JDK 实例 +- [java.lang.Runtime#getRuntime()](http://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#getRuntime%28%29) +- [java.awt.Desktop#getDesktop()](http://docs.oracle.com/javase/8/docs/api/java/awt/Desktop.html#getDesktop--) +- [java.lang.System#getSecurityManager()](http://docs.oracle.com/javase/8/docs/api/java/lang/System.html#getSecurityManager--) -以下实现中,私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。 +## 2. 简单工厂(Simple Factory) -这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 `if (uniqueInstance == null)` ,并且此时 uniqueInstance 为 null,那么会有多个线程执行 `uniqueInstance = new Singleton();` 语句,这将导致实例化多次 uniqueInstance。 +### Intent +在创建对象时不向客户暴露其内部细节,提供一个创建对象的通用接口。 -```java -public class Singleton { +### Class Diagram +简单工厂负责创建不同类型的实例,封装了创建逻辑,使得客户无需了解具体实现。 - private static Singleton uniqueInstance; + - private Singleton() { - } +### Implementation - public static Singleton getUniqueInstance() { - if (uniqueInstance == null) { - uniqueInstance = new Singleton(); - } - return uniqueInstance; - } +```java +public interface Product { } ``` -#### Ⅱ 饿汉式-线程安全 - -线程不安全问题主要是由于 uniqueInstance 被实例化多次,采取直接实例化 uniqueInstance 的方式就不会产生线程不安全问题。 - -但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。 - ```java -private static Singleton uniqueInstance = new Singleton(); +public class ConcreteProduct implements Product { +} ``` -#### Ⅲ 懒汉式-线程安全 - -只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了实例化多次 uniqueInstance。 - -但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,即使 uniqueInstance 已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用。 +```java +public class SimpleFactory { + public Product createProduct(int type) { + switch (type) { + case 1: return new ConcreteProduct1(); + case 2: return new ConcreteProduct2(); + default: return new ConcreteProduct(); + } + } +} +``` ```java -public static synchronized Singleton getUniqueInstance() { - if (uniqueInstance == null) { - uniqueInstance = new Singleton(); - } - return uniqueInstance; +public class Client { + public static void main(String[] args) { + SimpleFactory factory = new SimpleFactory(); + Product product = factory.createProduct(1); + // 使用product + } } ``` -#### Ⅳ 双重校验锁-线程安全 +... -uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。 +## 3. 工厂方法(Factory Method) -双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。 +### Intent +定义一个创建对象的接口,由子类决定要实例化的类。工厂方法将实例化的操作推迟到子类。 -```java -public class Singleton { +### Class Diagram +工厂方法用于希望将对象的创建推迟到具体的实现类。 - private volatile static Singleton uniqueInstance; + - private Singleton() { - } +### Implementation - public static Singleton getUniqueInstance() { - if (uniqueInstance == null) { - synchronized (Singleton.class) { - if (uniqueInstance == null) { - uniqueInstance = new Singleton(); - } - } - } - return uniqueInstance; - } +```java +public abstract class Factory { + public abstract Product factoryMethod(); + public void doSomething() { + Product product = factoryMethod(); + // 使用product + } } -``` -考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程都执行了 if 语句,那么两个线程都会进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,那么就会进行两次实例化。因此必须使用双重校验锁,也就是需要使用两个 if 语句:第一个 if 语句用来避免 uniqueInstance 已经被实例化之后的加锁操作,而第二个 if 语句进行了加锁,所以只能有一个线程进入,就不会出现 uniqueInstance == null 时两个线程同时进行实例化操作。 - -```java -if (uniqueInstance == null) { - synchronized (Singleton.class) { - uniqueInstance = new Singleton(); - } +public class ConcreteFactory extends Factory { + public Product factoryMethod() { + return new ConcreteProduct(); + } } ``` -uniqueInstance 采用 volatile 关键字修饰也是很有必要的, `uniqueInstance = new Singleton();` 这段代码其实是分为三步执行: - -1. 为 uniqueInstance 分配内存空间 -2. 初始化 uniqueInstance -3. 将 uniqueInstance 指向分配的内存地址 +### JDK 实例 +- [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +- [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) -但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T<sub>1</sub> 执行了 1 和 3,此时 T<sub>2</sub> 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 +## 4. 抽象工厂(Abstract Factory) -使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 +### Intent +提供一个创建**相关对象家族**的接口。 -#### Ⅴ 静态内部类实现 +### Class Diagram +抽象工厂模式用于创建一组相互依赖的对象。 -当 Singleton 类被加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance()` 方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。 + -这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。 +### Implementation ```java -public class Singleton { - - private Singleton() { - } - - private static class SingletonHolder { - private static final Singleton INSTANCE = new Singleton(); - } - - public static Singleton getUniqueInstance() { - return SingletonHolder.INSTANCE; - } +public abstract class AbstractFactory { + abstract AbstractProductA createProductA(); + abstract AbstractProductB createProductB(); } ``` -#### Ⅵ 枚举实现 - ```java -public enum Singleton { +public class ConcreteFactory1 extends AbstractFactory { + public AbstractProductA createProductA() { + return new ProductA1(); + } - INSTANCE; + public AbstractProductB createProductB() { + return new ProductB1(); + } +} +``` - private String objName; +### JDK 实例 +- [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html) +- [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html) +## 5. 生成器(Builder) - public String getObjName() { - return objName; - } +### Intent +封装一个对象的构造过程,允许逐步构造。 +### Class Diagram +生成器模式通过将构造过程进行分解来简化复杂对象的创建。 - public void setObjName(String objName) { - this.objName = objName; - } + +### Implementation - public static void main(String[] args) { - - // 单例测试 - Singleton firstSingleton = Singleton.INSTANCE; - firstSingleton.setObjName("firstName"); - System.out.println(firstSingleton.getObjName()); - Singleton secondSingleton = Singleton.INSTANCE; - secondSingleton.setObjName("secondName"); - System.out.println(firstSingleton.getObjName()); - System.out.println(secondSingleton.getObjName()); - - // 反射获取实例测试 - try { - Singleton[] enumConstants = Singleton.class.getEnumConstants(); - for (Singleton enumConstant : enumConstants) { - System.out.println(enumConstant.getObjName()); - } - } catch (Exception e) { - e.printStackTrace(); - } - } +```java +public class Product { + // 产品构建逻辑 } ``` -```html -firstName -secondName -secondName -secondName -``` +```java +public class Builder { + private Product product = new Product(); -该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。 + public void buildPartA() { + // 构建部分A + } -该实现在多次序列化和序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。 + public Product getResult() { + return product; + } +} +``` -### Examples +```java +public class Director { + private Builder builder; -- Logger Classes -- Configuration Classes -- Accesing resources in shared mode -- Factories implemented as Singletons + public Director(Builder builder) { + this.builder = builder; + } -### JDK + public void construct() { + builder.buildPartA(); + // 更多构建步骤 + } +} +``` -- [java.lang.Runtime#getRuntime()](http://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#getRuntime%28%29) -- [java.awt.Desktop#getDesktop()](http://docs.oracle.com/javase/8/docs/api/java/awt/Desktop.html#getDesktop--) -- [java.lang.System#getSecurityManager()](http://docs.oracle.com/javase/8/docs/api/java/lang/System.html#getSecurityManager--) +```java +public class Client { + public static void main(String[] args) { + Builder builder = new Builder(); + Director director = new Director(builder); + director.construct(); + Product product = builder.getResult(); + // 使用product + } +} +``` -## 2. 简单工厂(Simple Factory) +### JDK 实例 +- [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) -### Intent +## 6. 原型模式(Prototype) -在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。 +### Intent +通过复制一个原型实例来指定要创建对象的类型。 ### Class Diagram +原型模式通过定义一个接口来允许克隆自身的对象。 -简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。 - -这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/40c0c17e-bba6-4493-9857-147c0044a018.png"/> </div><br> + ### Implementation ```java -public interface Product { +public abstract class Prototype { + abstract Prototype myClone(); } ``` ```java -public class ConcreteProduct implements Product { +public class ConcretePrototype extends Prototype { + @Override + Prototype myClone() { + return new ConcretePrototype(); + } } ``` ```java -public class ConcreteProduct1 implements Product { +public class Client { + public static void main(String[] args) { + Prototype prototype = new ConcretePrototype(); + Prototype clone = prototype.myClone(); + // 使用clone + } } ``` -```java -public class ConcreteProduct2 implements Product { -} -``` +# 三、行为型 + +## 1. 责任链(Chain Of Responsibility) + +### Intent +使多个对象都有机会处理请求,减少请求发送者和接收者之间的耦合。 + +### Class Diagram +责任链模式允许多个处理者处理同一个请求。 + + -以下的 Client 类包含了实例化的代码,这是一种错误的实现。如果在客户类中存在这种实例化代码,就需要考虑将代码放到简单工厂中。 +### Implementation ```java -public class Client { +public abstract class Handler { + protected Handler successor; - public static void main(String[] args) { - int type = 1; - Product product; - if (type == 1) { - product = new ConcreteProduct1(); - } else if (type == 2) { - product = new ConcreteProduct2(); - } else { - product = new ConcreteProduct(); - } - // do something with the product - } + public Handler(Handler successor) { + this.successor = successor; + } + + protected abstract void handleRequest(Request request); } ``` -以下的 SimpleFactory 是简单工厂实现,它被所有需要进行实例化的客户类调用。 - ```java -public class SimpleFactory { - - public Product createProduct(int type) { - if (type == 1) { - return new ConcreteProduct1(); - } else if (type == 2) { - return new ConcreteProduct2(); - } - return new ConcreteProduct(); - } +public class ConcreteHandler1 extends Handler { + public void handleRequest(Request request) { + if (request.getType() == RequestType.TYPE1) { + // 处理请求 + } else if (successor != null) { + successor.handleRequest(request); + } + } } ``` ```java public class Client { + public static void main(String[] args) { + Handler handler1 = new ConcreteHandler1(null); + Handler handler2 = new ConcreteHandler2(handler1); - public static void main(String[] args) { - SimpleFactory simpleFactory = new SimpleFactory(); - Product product = simpleFactory.createProduct(1); - // do something with the product - } + Request request = new Request(RequestType.TYPE1, "request1"); + handler2.handleRequest(request); + } } ``` -## 3. 工厂方法(Factory Method) +### JDK 实例 +- [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29) -### Intent +## 2. 命令(Command) -定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。 +### Intent +将命令封装成对象,以支持以下功能: +- 参数化其它对象 +- 将命令放入队列中 +- 支持可撤销的操作 ### Class Diagram +命令模式将请求封装为命令对象。 -在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。 - -下图中,Factory 有一个 doSomething() 方法,这个方法需要用到一个产品对象,这个产品对象由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/f4d0afd0-8e78-4914-9e60-4366eaf065b5.png"/> </div><br> + ### Implementation ```java -public abstract class Factory { - abstract public Product factoryMethod(); - public void doSomething() { - Product product = factoryMethod(); - // do something with the product - } +public interface Command { + void execute(); } ``` ```java -public class ConcreteFactory extends Factory { - public Product factoryMethod() { - return new ConcreteProduct(); +public class LightOnCommand implements Command { + Light light; + + public LightOnCommand(Light light) { + this.light = light; + } + + @Override + public void execute() { + light.on(); } } ``` ```java -public class ConcreteFactory1 extends Factory { - public Product factoryMethod() { - return new ConcreteProduct1(); +public class Invoker { + private Command[] onCommands; + private Command[] offCommands; + + public Invoker() { + onCommands = new Command[7]; + offCommands = new Command[7]; + } + + public void setOnCommand(Command command, int slot) { + onCommands[slot] = command; + } + + public void setOffCommand(Command command, int slot) { + offCommands[slot] = command; + } + + public void onButtonWasPushed(int slot) { + onCommands[slot].execute(); } } ``` ```java -public class ConcreteFactory2 extends Factory { - public Product factoryMethod() { - return new ConcreteProduct2(); - } +public class Client { + public static void main(String[] args) { + Invoker invoker = new Invoker(); + Light light = new Light(); + Command lightOn = new LightOnCommand(light); + invoker.setOnCommand(lightOn, 0); + invoker.onButtonWasPushed(0); + } } ``` -### JDK - -- [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) -- [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) -- [java.text.NumberFormat](http://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) -- [java.nio.charset.Charset](http://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) -- [java.net.URLStreamHandlerFactory](http://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html#createURLStreamHandler-java.lang.String-) -- [java.util.EnumSet](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of-E-) -- [javax.xml.bind.JAXBContext](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) +### JDK 实例 +- [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) -## 4. 抽象工厂(Abstract Factory) +## 3. 解释器(Interpreter) ### Intent - -提供一个接口,用于创建 **相关的对象家族** 。 +为一种语言定义语法及其解析器。 ### Class Diagram +解释器模式通过创建解析树来分析和处理输入。 -抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。 - -抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。 - -至于创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要同时创建出这两个对象。 - -从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory,而工厂方法模式使用了继承。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e2190c36-8b27-4690-bde5-9911020a1294.png"/> </div><br> + ### Implementation ```java -public class AbstractProductA { +public abstract class Expression { + public abstract boolean interpret(String context); } ``` ```java -public class AbstractProductB { +public class TerminalExpression extends Expression { + public boolean interpret(String context) { + return context.contains("condition"); + } } ``` ```java -public class ProductA1 extends AbstractProductA { +public class Client { + public static void main(String[] args) { + Expression definition = new TerminalExpression(); + System.out.println(definition.interpret("some context")); + } } ``` -```java -public class ProductA2 extends AbstractProductA { -} -``` +### JDK 实例 +- [java.util.regex.Pattern](http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) -```java -public class ProductB1 extends AbstractProductB { -} -``` +## 4. 迭代器(Iterator) -```java -public class ProductB2 extends AbstractProductB { -} -``` +### Intent +提供访问聚合对象元素的方法而不暴露其内部表示。 + +### Class Diagram +迭代器模式提供了一种顺序访问聚合对象的方法。 + + + +### Implementation ```java -public abstract class AbstractFactory { - abstract AbstractProductA createProductA(); - abstract AbstractProductB createProductB(); +public interface Iterator { + boolean hasNext(); + Object next(); } ``` ```java -public class ConcreteFactory1 extends AbstractFactory { - AbstractProductA createProductA() { - return new ProductA1(); - } - - AbstractProductB createProductB() { - return new ProductB1(); - } +public interface Aggregate { + Iterator createIterator(); } ``` ```java -public class ConcreteFactory2 extends AbstractFactory { - AbstractProductA createProductA() { - return new ProductA2(); - } +public class ConcreteAggregate implements Aggregate { + private Object[] items; - AbstractProductB createProductB() { - return new ProductB2(); + @Override + public Iterator createIterator() { + return new ConcreteIterator(items); } } ``` ```java public class Client { - public static void main(String[] args) { - AbstractFactory abstractFactory = new ConcreteFactory1(); - AbstractProductA productA = abstractFactory.createProductA(); - AbstractProductB productB = abstractFactory.createProductB(); - // do something with productA and productB - } + public static void main(String[] args) { + Aggregate aggregate = new ConcreteAggregate(); + Iterator iterator = aggregate.createIterator(); + while (iterator.hasNext()) { + System.out.println(iterator.next()); + } + } } ``` -### JDK - -- [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html) -- [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--) -- [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--) +### JDK 实例 +- [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html) -## 5. 生成器(Builder) +## 5. 中介者(Mediator) ### Intent - -封装一个对象的构造过程,并允许按步骤构造。 +集中相关对象之间的沟通和控制方式。 ### Class Diagram +中介者模式用于简化对象之间的复杂交互。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/db5e376d-0b3e-490e-a43a-3231914b6668.png"/> </div><br> + ### Implementation -以下是一个简易的 StringBuilder 实现,参考了 JDK 1.8 源码。 - ```java -public class AbstractStringBuilder { - protected char[] value; - - protected int count; - - public AbstractStringBuilder(int capacity) { - count = 0; - value = new char[capacity]; - } - - public AbstractStringBuilder append(char c) { - ensureCapacityInternal(count + 1); - value[count++] = c; - return this; - } - - private void ensureCapacityInternal(int minimumCapacity) { - // overflow-conscious code - if (minimumCapacity - value.length > 0) - expandCapacity(minimumCapacity); - } - - void expandCapacity(int minimumCapacity) { - int newCapacity = value.length * 2 + 2; - if (newCapacity - minimumCapacity < 0) - newCapacity = minimumCapacity; - if (newCapacity < 0) { - if (minimumCapacity < 0) // overflow - throw new OutOfMemoryError(); - newCapacity = Integer.MAX_VALUE; - } - value = Arrays.copyOf(value, newCapacity); - } +public abstract class Mediator { + public abstract void send(String message, Colleague colleague); } ``` ```java -public class StringBuilder extends AbstractStringBuilder { - public StringBuilder() { - super(16); - } +public class ConcreteMediator extends Mediator { + private Colleague colleague1; + private Colleague colleague2; @Override - public String toString() { - // Create a copy, don't share the array - return new String(value, 0, count); + public void send(String message, Colleague colleague) { + // 处理消息 } } ``` ```java public class Client { - public static void main(String[] args) { - StringBuilder sb = new StringBuilder(); - final int count = 26; - for (int i = 0; i < count; i++) { - sb.append((char) ('a' + i)); - } - System.out.println(sb.toString()); - } + public static void main(String[] args) { + Mediator mediator = new ConcreteMediator(); + // 进行中介者交互 + } } ``` -```html -abcdefghijklmnopqrstuvwxyz -``` - -### JDK - -- [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) -- [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) -- [java.lang.StringBuffer](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html#append-boolean-) -- [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) -- [Apache Camel builders](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) +### JDK 实例 +- [java.util.concurrent.ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) -## 6. 原型模式(Prototype) +## 6. 备忘录(Memento) ### Intent - -使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。 +允许在不违反封装的情况下保存对象状态,以便能够在需要时恢复。 ### Class Diagram +备忘录模式通过三个对象协作来实现状态恢复功能。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b8922f8c-95e6-4187-be85-572a509afb71.png"/> </div><br> + ### Implementation ```java -public abstract class Prototype { - abstract Prototype myClone(); -} -``` +public class Originator { + private String state; -```java -public class ConcretePrototype extends Prototype { + public Memento createMemento() { + return new Memento(state); + } - private String filed; + public void setMemento(Memento memento) { + state = memento.getState(); + } +} - public ConcretePrototype(String filed) { - this.filed = filed; - } +public class Memento { + private final String state; - @Override - Prototype myClone() { - return new ConcretePrototype(filed); - } + public Memento(String state) { + this.state = state; + } - @Override - public String toString() { - return filed; - } + public String getState() { + return state; + } } ``` ```java public class Client { - public static void main(String[] args) { - Prototype prototype = new ConcretePrototype("abc"); - Prototype clone = prototype.myClone(); - System.out.println(clone.toString()); - } + public static void main(String[] args) { + Originator originator = new Originator(); + Memento memento = originator.createMemento(); + originator.setMemento(memento); + } } ``` -```html -abc -``` +## 7. 观察者(Observer) -### JDK +### Intent +定义对象之间一对多的依赖关系,当一个对象状态改变时,其所有依赖者都会收到通知并自动更新。 -- [java.lang.Object#clone()](http://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#clone%28%29) +### Class Diagram +观察者模式通过主题和观察者互动。 -# 三、行为型 - -## 1. 责任链(Chain Of Responsibility) - -### Intent - -使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。 - -### Class Diagram - -- Handler:定义处理请求的接口,并且实现后继链(successor) - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ca9f23bf-55a4-47b2-9534-a28e35397988.png"/> </div><br> - -### Implementation - -```java -public abstract class Handler { - - protected Handler successor; - - - public Handler(Handler successor) { - this.successor = successor; - } - - - protected abstract void handleRequest(Request request); -} -``` - -```java -public class ConcreteHandler1 extends Handler { - - public ConcreteHandler1(Handler successor) { - super(successor); - } - - - @Override - protected void handleRequest(Request request) { - if (request.getType() == RequestType.TYPE1) { - System.out.println(request.getName() + " is handle by ConcreteHandler1"); - return; - } - if (successor != null) { - successor.handleRequest(request); - } - } -} -``` - -```java -public class ConcreteHandler2 extends Handler { - - public ConcreteHandler2(Handler successor) { - super(successor); - } - - - @Override - protected void handleRequest(Request request) { - if (request.getType() == RequestType.TYPE2) { - System.out.println(request.getName() + " is handle by ConcreteHandler2"); - return; - } - if (successor != null) { - successor.handleRequest(request); - } - } -} -``` - -```java -public class Request { - - private RequestType type; - private String name; - - - public Request(RequestType type, String name) { - this.type = type; - this.name = name; - } - - - public RequestType getType() { - return type; - } - - - public String getName() { - return name; - } -} - -``` - -```java -public enum RequestType { - TYPE1, TYPE2 -} -``` - -```java -public class Client { - - public static void main(String[] args) { - - Handler handler1 = new ConcreteHandler1(null); - Handler handler2 = new ConcreteHandler2(handler1); - - Request request1 = new Request(RequestType.TYPE1, "request1"); - handler2.handleRequest(request1); - - Request request2 = new Request(RequestType.TYPE2, "request2"); - handler2.handleRequest(request2); - } -} -``` - -```html -request1 is handle by ConcreteHandler1 -request2 is handle by ConcreteHandler2 -``` - -### JDK - -- [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29) -- [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html) -- [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-) - -## 2. 命令(Command) - -### Intent - -将命令封装成对象中,具有以下作用: - -- 使用命令来参数化其它对象 -- 将命令放入队列中进行排队 -- 将命令的操作记录到日志中 -- 支持可撤销的操作 - -### Class Diagram - -- Command:命令 -- Receiver:命令接收者,也就是命令真正的执行者 -- Invoker:通过它来调用命令 -- Client:可以设置命令与命令的接收者 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c44a0342-f405-4f17-b750-e27cf4aadde2.png"/> </div><br> - -### Implementation - -设计一个遥控器,可以控制电灯开关。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e6bded8e-41a0-489a-88a6-638e88ab7666.jpg"/> </div><br> - -```java -public interface Command { - void execute(); -} -``` - -```java -public class LightOnCommand implements Command { - Light light; - - public LightOnCommand(Light light) { - this.light = light; - } - - @Override - public void execute() { - light.on(); - } -} -``` - -```java -public class LightOffCommand implements Command { - Light light; - - public LightOffCommand(Light light) { - this.light = light; - } - - @Override - public void execute() { - light.off(); - } -} -``` - -```java -public class Light { - - public void on() { - System.out.println("Light is on!"); - } - - public void off() { - System.out.println("Light is off!"); - } -} -``` - -```java -/** - * 遥控器 - */ -public class Invoker { - private Command[] onCommands; - private Command[] offCommands; - private final int slotNum = 7; - - public Invoker() { - this.onCommands = new Command[slotNum]; - this.offCommands = new Command[slotNum]; - } - - public void setOnCommand(Command command, int slot) { - onCommands[slot] = command; - } - - public void setOffCommand(Command command, int slot) { - offCommands[slot] = command; - } - - public void onButtonWasPushed(int slot) { - onCommands[slot].execute(); - } - - public void offButtonWasPushed(int slot) { - offCommands[slot].execute(); - } -} -``` - -```java -public class Client { - public static void main(String[] args) { - Invoker invoker = new Invoker(); - Light light = new Light(); - Command lightOnCommand = new LightOnCommand(light); - Command lightOffCommand = new LightOffCommand(light); - invoker.setOnCommand(lightOnCommand, 0); - invoker.setOffCommand(lightOffCommand, 0); - invoker.onButtonWasPushed(0); - invoker.offButtonWasPushed(0); - } -} -``` - -### JDK - -- [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) -- [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki) -- [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html) - -## 3. 解释器(Interpreter) - -### Intent - -为语言创建解释器,通常由语言的语法和语法分析来定义。 - -### Class Diagram - -- TerminalExpression:终结符表达式,每个终结符都需要一个 TerminalExpression。 -- Context:上下文,包含解释器之外的一些全局信息。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2b125bcd-1b36-43be-9b78-d90b076be549.png"/> </div><br> - -### Implementation - -以下是一个规则检验器实现,具有 and 和 or 规则,通过规则可以构建一颗解析树,用来检验一个文本是否满足解析树定义的规则。 - -例如一颗解析树为 D And (A Or (B C)),文本 "D A" 满足该解析树定义的规则。 - -这里的 Context 指的是 String。 - -```java -public abstract class Expression { - public abstract boolean interpret(String str); -} -``` - -```java -public class TerminalExpression extends Expression { - - private String literal = null; - - public TerminalExpression(String str) { - literal = str; - } - - public boolean interpret(String str) { - StringTokenizer st = new StringTokenizer(str); - while (st.hasMoreTokens()) { - String test = st.nextToken(); - if (test.equals(literal)) { - return true; - } - } - return false; - } -} -``` - -```java -public class AndExpression extends Expression { - - private Expression expression1 = null; - private Expression expression2 = null; - - public AndExpression(Expression expression1, Expression expression2) { - this.expression1 = expression1; - this.expression2 = expression2; - } - - public boolean interpret(String str) { - return expression1.interpret(str) && expression2.interpret(str); - } -} -``` - -```java -public class OrExpression extends Expression { - private Expression expression1 = null; - private Expression expression2 = null; - - public OrExpression(Expression expression1, Expression expression2) { - this.expression1 = expression1; - this.expression2 = expression2; - } - - public boolean interpret(String str) { - return expression1.interpret(str) || expression2.interpret(str); - } -} -``` - -```java -public class Client { - - /** - * 构建解析树 - */ - public static Expression buildInterpreterTree() { - // Literal - Expression terminal1 = new TerminalExpression("A"); - Expression terminal2 = new TerminalExpression("B"); - Expression terminal3 = new TerminalExpression("C"); - Expression terminal4 = new TerminalExpression("D"); - // B C - Expression alternation1 = new OrExpression(terminal2, terminal3); - // A Or (B C) - Expression alternation2 = new OrExpression(terminal1, alternation1); - // D And (A Or (B C)) - return new AndExpression(terminal4, alternation2); - } - - public static void main(String[] args) { - Expression define = buildInterpreterTree(); - String context1 = "D A"; - String context2 = "A B"; - System.out.println(define.interpret(context1)); - System.out.println(define.interpret(context2)); - } -} -``` - -```html -true -false -``` - -### JDK - -- [java.util.Pattern](http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) -- [java.text.Normalizer](http://docs.oracle.com/javase/8/docs/api/java/text/Normalizer.html) -- All subclasses of [java.text.Format](http://docs.oracle.com/javase/8/docs/api/java/text/Format.html) -- [javax.el.ELResolver](http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html) - -## 4. 迭代器(Iterator) - -### Intent - -提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。 - -### Class Diagram - -- Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator; -- Iterator 主要定义了 hasNext() 和 next() 方法。 -- Client 组合了 Aggregate,为了迭代遍历 Aggregate,也需要组合 Iterator。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/89292ae1-5f13-44dc-b508-3f035e80bf89.png"/> </div><br> - -### Implementation - -```java -public interface Aggregate { - Iterator createIterator(); -} -``` - -```java -public class ConcreteAggregate implements Aggregate { - - private Integer[] items; - - public ConcreteAggregate() { - items = new Integer[10]; - for (int i = 0; i < items.length; i++) { - items[i] = i; - } - } - - @Override - public Iterator createIterator() { - return new ConcreteIterator<Integer>(items); - } -} -``` - -```java -public interface Iterator<Item> { - - Item next(); - - boolean hasNext(); -} -``` - -```java -public class ConcreteIterator<Item> implements Iterator { - - private Item[] items; - private int position = 0; - - public ConcreteIterator(Item[] items) { - this.items = items; - } - - @Override - public Object next() { - return items[position++]; - } - - @Override - public boolean hasNext() { - return position < items.length; - } -} -``` - -```java -public class Client { - - public static void main(String[] args) { - Aggregate aggregate = new ConcreteAggregate(); - Iterator<Integer> iterator = aggregate.createIterator(); - while (iterator.hasNext()) { - System.out.println(iterator.next()); - } - } -} -``` - -### JDK - -- [java.util.Iterator](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html) -- [java.util.Enumeration](http://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html) - -## 5. 中介者(Mediator) - -### Intent - -集中相关对象之间复杂的沟通和控制方式。 - -### Class Diagram - -- Mediator:中介者,定义一个接口用于与各同事(Colleague)对象通信。 -- Colleague:同事,相关对象 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/30d6e95c-2e3c-4d32-bf4f-68128a70bc05.png"/> </div><br> - -### Implementation - -Alarm(闹钟)、CoffeePot(咖啡壶)、Calendar(日历)、Sprinkler(喷头)是一组相关的对象,在某个对象的事件产生时需要去操作其它对象,形成了下面这种依赖结构: - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/82cfda3b-b53b-4c89-9fdb-26dd2db0cd02.jpg"/> </div><br> - -使用中介者模式可以将复杂的依赖结构变成星形结构: - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5359cbf5-5a79-4874-9b17-f23c53c2cb80.jpg"/> </div><br> - -```java -public abstract class Colleague { - public abstract void onEvent(Mediator mediator); -} -``` - -```java -public class Alarm extends Colleague { - - @Override - public void onEvent(Mediator mediator) { - mediator.doEvent("alarm"); - } - - public void doAlarm() { - System.out.println("doAlarm()"); - } -} -``` - -```java -public class CoffeePot extends Colleague { - @Override - public void onEvent(Mediator mediator) { - mediator.doEvent("coffeePot"); - } - - public void doCoffeePot() { - System.out.println("doCoffeePot()"); - } -} -``` - -```java -public class Calender extends Colleague { - @Override - public void onEvent(Mediator mediator) { - mediator.doEvent("calender"); - } - - public void doCalender() { - System.out.println("doCalender()"); - } -} -``` - -```java -public class Sprinkler extends Colleague { - @Override - public void onEvent(Mediator mediator) { - mediator.doEvent("sprinkler"); - } - - public void doSprinkler() { - System.out.println("doSprinkler()"); - } -} -``` - -```java -public abstract class Mediator { - public abstract void doEvent(String eventType); -} -``` - -```java -public class ConcreteMediator extends Mediator { - private Alarm alarm; - private CoffeePot coffeePot; - private Calender calender; - private Sprinkler sprinkler; - - public ConcreteMediator(Alarm alarm, CoffeePot coffeePot, Calender calender, Sprinkler sprinkler) { - this.alarm = alarm; - this.coffeePot = coffeePot; - this.calender = calender; - this.sprinkler = sprinkler; - } - - @Override - public void doEvent(String eventType) { - switch (eventType) { - case "alarm": - doAlarmEvent(); - break; - case "coffeePot": - doCoffeePotEvent(); - break; - case "calender": - doCalenderEvent(); - break; - default: - doSprinklerEvent(); - } - } - - public void doAlarmEvent() { - alarm.doAlarm(); - coffeePot.doCoffeePot(); - calender.doCalender(); - sprinkler.doSprinkler(); - } - - public void doCoffeePotEvent() { - // ... - } - - public void doCalenderEvent() { - // ... - } - - public void doSprinklerEvent() { - // ... - } -} -``` - -```java -public class Client { - public static void main(String[] args) { - Alarm alarm = new Alarm(); - CoffeePot coffeePot = new CoffeePot(); - Calender calender = new Calender(); - Sprinkler sprinkler = new Sprinkler(); - Mediator mediator = new ConcreteMediator(alarm, coffeePot, calender, sprinkler); - // 闹钟事件到达,调用中介者就可以操作相关对象 - alarm.onEvent(mediator); - } -} -``` - -```java -doAlarm() -doCoffeePot() -doCalender() -doSprinkler() -``` - -### JDK - -- All scheduleXXX() methods of [java.util.Timer](http://docs.oracle.com/javase/8/docs/api/java/util/Timer.html) -- [java.util.concurrent.Executor#execute()](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable-) -- submit() and invokeXXX() methods of [java.util.concurrent.ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) -- scheduleXXX() methods of [java.util.concurrent.ScheduledExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html) -- [java.lang.reflect.Method#invoke()](http://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html#invoke-java.lang.Object-java.lang.Object...-) - -## 6. 备忘录(Memento) - -### Intent - -在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。 - -### Class Diagram - -- Originator:原始对象 -- Caretaker:负责保存好备忘录 -- Menento:备忘录,存储原始对象的的状态。备忘录实际上有两个接口,一个是提供给 Caretaker 的窄接口:它只能将备忘录传递给其它对象;一个是提供给 Originator 的宽接口,允许它访问到先前状态所需的所有数据。理想情况是只允许 Originator 访问本备忘录的内部状态。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/50678f34-694f-45a4-91c6-34d985c83fee.png"/> </div><br> - -### Implementation - -以下实现了一个简单计算器程序,可以输入两个值,然后计算这两个值的和。备忘录模式允许将这两个值存储起来,然后在某个时刻用存储的状态进行恢复。 - -实现参考:[Memento Pattern - Calculator Example - Java Sourcecode](https://www.oodesign.com/memento-pattern-calculator-example-java-sourcecode.html) - -```java -/** - * Originator Interface - */ -public interface Calculator { - - // Create Memento - PreviousCalculationToCareTaker backupLastCalculation(); - - // setMemento - void restorePreviousCalculation(PreviousCalculationToCareTaker memento); - - int getCalculationResult(); - - void setFirstNumber(int firstNumber); - - void setSecondNumber(int secondNumber); -} -``` - -```java -/** - * Originator Implementation - */ -public class CalculatorImp implements Calculator { - - private int firstNumber; - private int secondNumber; - - @Override - public PreviousCalculationToCareTaker backupLastCalculation() { - // create a memento object used for restoring two numbers - return new PreviousCalculationImp(firstNumber, secondNumber); - } - - @Override - public void restorePreviousCalculation(PreviousCalculationToCareTaker memento) { - this.firstNumber = ((PreviousCalculationToOriginator) memento).getFirstNumber(); - this.secondNumber = ((PreviousCalculationToOriginator) memento).getSecondNumber(); - } - - @Override - public int getCalculationResult() { - // result is adding two numbers - return firstNumber + secondNumber; - } - - @Override - public void setFirstNumber(int firstNumber) { - this.firstNumber = firstNumber; - } - - @Override - public void setSecondNumber(int secondNumber) { - this.secondNumber = secondNumber; - } -} -``` - -```java -/** - * Memento Interface to Originator - * - * This interface allows the originator to restore its state - */ -public interface PreviousCalculationToOriginator { - int getFirstNumber(); - int getSecondNumber(); -} -``` - -```java -/** - * Memento interface to CalculatorOperator (Caretaker) - */ -public interface PreviousCalculationToCareTaker { - // no operations permitted for the caretaker -} -``` - -```java -/** - * Memento Object Implementation - * <p> - * Note that this object implements both interfaces to Originator and CareTaker - */ -public class PreviousCalculationImp implements PreviousCalculationToCareTaker, - PreviousCalculationToOriginator { - - private int firstNumber; - private int secondNumber; - - public PreviousCalculationImp(int firstNumber, int secondNumber) { - this.firstNumber = firstNumber; - this.secondNumber = secondNumber; - } - - @Override - public int getFirstNumber() { - return firstNumber; - } - - @Override - public int getSecondNumber() { - return secondNumber; - } -} -``` - -```java -/** - * CareTaker object - */ -public class Client { - - public static void main(String[] args) { - // program starts - Calculator calculator = new CalculatorImp(); - - // assume user enters two numbers - calculator.setFirstNumber(10); - calculator.setSecondNumber(100); - - // find result - System.out.println(calculator.getCalculationResult()); - - // Store result of this calculation in case of error - PreviousCalculationToCareTaker memento = calculator.backupLastCalculation(); - - // user enters a number - calculator.setFirstNumber(17); - - // user enters a wrong second number and calculates result - calculator.setSecondNumber(-290); - - // calculate result - System.out.println(calculator.getCalculationResult()); - - // user hits CTRL + Z to undo last operation and see last result - calculator.restorePreviousCalculation(memento); - - // result restored - System.out.println(calculator.getCalculationResult()); - } -} -``` - -```html -110 --273 -110 -``` - -### JDK - -- java.io.Serializable - -## 7. 观察者(Observer) - -### Intent - -定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。 - -主题(Subject)是被观察的对象,而其所有依赖者(Observer)称为观察者。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7a3c6a30-c735-4edb-8115-337288a4f0f2.jpg" width="600"/> </div><br> - -### Class Diagram - -主题(Subject)具有注册和移除观察者、并通知所有观察者的功能,主题是通过维护一张观察者列表来实现这些操作的。 - -观察者(Observer)的注册功能需要调用主题的 registerObserver() 方法。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/a8c8f894-a712-447c-9906-5caef6a016e3.png"/> </div><br> + ### Implementation -天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b1df9732-86ce-4d69-9f06-fba1db7b3b5a.jpg"/> </div><br> - ```java public interface Subject { void registerObserver(Observer o); - void removeObserver(Observer o); - - void notifyObserver(); + void notifyObservers(); } -``` -```java -public class WeatherData implements Subject { +public class ConcreteSubject implements Subject { private List<Observer> observers; - private float temperature; - private float humidity; - private float pressure; - - public WeatherData() { - observers = new ArrayList<>(); - } - - public void setMeasurements(float temperature, float humidity, float pressure) { - this.temperature = temperature; - this.humidity = humidity; - this.pressure = pressure; - notifyObserver(); - } - - @Override - public void registerObserver(Observer o) { - observers.add(o); - } - - @Override - public void removeObserver(Observer o) { - int i = observers.indexOf(o); - if (i >= 0) { - observers.remove(i); - } - } @Override - public void notifyObserver() { + public void notifyObservers() { for (Observer o : observers) { - o.update(temperature, humidity, pressure); + o.update(); } } } @@ -1625,776 +682,230 @@ public class WeatherData implements Subject { ```java public interface Observer { - void update(float temp, float humidity, float pressure); -} -``` - -```java -public class StatisticsDisplay implements Observer { - - public StatisticsDisplay(Subject weatherData) { - weatherData.reisterObserver(this); - } - - @Override - public void update(float temp, float humidity, float pressure) { - System.out.println("StatisticsDisplay.update: " + temp + " " + humidity + " " + pressure); - } -} -``` - -```java -public class CurrentConditionsDisplay implements Observer { - - public CurrentConditionsDisplay(Subject weatherData) { - weatherData.registerObserver(this); - } - - @Override - public void update(float temp, float humidity, float pressure) { - System.out.println("CurrentConditionsDisplay.update: " + temp + " " + humidity + " " + pressure); - } -} -``` - -```java -public class WeatherStation { - public static void main(String[] args) { - WeatherData weatherData = new WeatherData(); - CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); - StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); - - weatherData.setMeasurements(0, 0, 0); - weatherData.setMeasurements(1, 1, 1); - } -} -``` - -```html -CurrentConditionsDisplay.update: 0.0 0.0 0.0 -StatisticsDisplay.update: 0.0 0.0 0.0 -CurrentConditionsDisplay.update: 1.0 1.0 1.0 -StatisticsDisplay.update: 1.0 1.0 1.0 -``` - -### JDK - -- [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html) -- [java.util.EventListener](http://docs.oracle.com/javase/8/docs/api/java/util/EventListener.html) -- [javax.servlet.http.HttpSessionBindingListener](http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionBindingListener.html) -- [RxJava](https://github.com/ReactiveX/RxJava) - -## 8. 状态(State) - -### Intent - -允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。 - -### Class Diagram - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/79df886f-fdc3-4020-a07f-c991bb58e0d8.png"/> </div><br> - -### Implementation - -糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/396be981-3f2c-4fd9-8101-dbf9c841504b.jpg" width="600"/> </div><br> - -```java -public interface State { - /** - * 投入 25 分钱 - */ - void insertQuarter(); - - /** - * 退回 25 分钱 - */ - void ejectQuarter(); - - /** - * 转动曲柄 - */ - void turnCrank(); - - /** - * 发放糖果 - */ - void dispense(); -} -``` - -```java -public class HasQuarterState implements State { - - private GumballMachine gumballMachine; - - public HasQuarterState(GumballMachine gumballMachine) { - this.gumballMachine = gumballMachine; - } - - @Override - public void insertQuarter() { - System.out.println("You can't insert another quarter"); - } - - @Override - public void ejectQuarter() { - System.out.println("Quarter returned"); - gumballMachine.setState(gumballMachine.getNoQuarterState()); - } - - @Override - public void turnCrank() { - System.out.println("You turned..."); - gumballMachine.setState(gumballMachine.getSoldState()); - } - - @Override - public void dispense() { - System.out.println("No gumball dispensed"); - } -} -``` - -```java -public class NoQuarterState implements State { - - GumballMachine gumballMachine; - - public NoQuarterState(GumballMachine gumballMachine) { - this.gumballMachine = gumballMachine; - } - - @Override - public void insertQuarter() { - System.out.println("You insert a quarter"); - gumballMachine.setState(gumballMachine.getHasQuarterState()); - } - - @Override - public void ejectQuarter() { - System.out.println("You haven't insert a quarter"); - } - - @Override - public void turnCrank() { - System.out.println("You turned, but there's no quarter"); - } - - @Override - public void dispense() { - System.out.println("You need to pay first"); - } -} -``` - -```java -public class SoldOutState implements State { - - GumballMachine gumballMachine; - - public SoldOutState(GumballMachine gumballMachine) { - this.gumballMachine = gumballMachine; - } - - @Override - public void insertQuarter() { - System.out.println("You can't insert a quarter, the machine is sold out"); - } - - @Override - public void ejectQuarter() { - System.out.println("You can't eject, you haven't inserted a quarter yet"); - } - - @Override - public void turnCrank() { - System.out.println("You turned, but there are no gumballs"); - } - - @Override - public void dispense() { - System.out.println("No gumball dispensed"); - } -} -``` - -```java -public class SoldState implements State { - - GumballMachine gumballMachine; - - public SoldState(GumballMachine gumballMachine) { - this.gumballMachine = gumballMachine; - } - - @Override - public void insertQuarter() { - System.out.println("Please wait, we're already giving you a gumball"); - } - - @Override - public void ejectQuarter() { - System.out.println("Sorry, you already turned the crank"); - } - - @Override - public void turnCrank() { - System.out.println("Turning twice doesn't get you another gumball!"); - } - - @Override - public void dispense() { - gumballMachine.releaseBall(); - if (gumballMachine.getCount() > 0) { - gumballMachine.setState(gumballMachine.getNoQuarterState()); - } else { - System.out.println("Oops, out of gumballs"); - gumballMachine.setState(gumballMachine.getSoldOutState()); - } - } -} -``` - -```java -public class GumballMachine { - - private State soldOutState; - private State noQuarterState; - private State hasQuarterState; - private State soldState; - - private State state; - private int count = 0; - - public GumballMachine(int numberGumballs) { - count = numberGumballs; - soldOutState = new SoldOutState(this); - noQuarterState = new NoQuarterState(this); - hasQuarterState = new HasQuarterState(this); - soldState = new SoldState(this); - - if (numberGumballs > 0) { - state = noQuarterState; - } else { - state = soldOutState; - } - } - - public void insertQuarter() { - state.insertQuarter(); - } - - public void ejectQuarter() { - state.ejectQuarter(); - } - - public void turnCrank() { - state.turnCrank(); - state.dispense(); - } - - public void setState(State state) { - this.state = state; - } - - public void releaseBall() { - System.out.println("A gumball comes rolling out the slot..."); - if (count != 0) { - count -= 1; - } - } - - public State getSoldOutState() { - return soldOutState; - } - - public State getNoQuarterState() { - return noQuarterState; - } - - public State getHasQuarterState() { - return hasQuarterState; - } - - public State getSoldState() { - return soldState; - } - - public int getCount() { - return count; - } -} -``` - -```java -public class Client { - - public static void main(String[] args) { - GumballMachine gumballMachine = new GumballMachine(5); - - gumballMachine.insertQuarter(); - gumballMachine.turnCrank(); - - gumballMachine.insertQuarter(); - gumballMachine.ejectQuarter(); - gumballMachine.turnCrank(); - - gumballMachine.insertQuarter(); - gumballMachine.turnCrank(); - gumballMachine.insertQuarter(); - gumballMachine.turnCrank(); - gumballMachine.ejectQuarter(); - - gumballMachine.insertQuarter(); - gumballMachine.insertQuarter(); - gumballMachine.turnCrank(); - gumballMachine.insertQuarter(); - gumballMachine.turnCrank(); - gumballMachine.insertQuarter(); - gumballMachine.turnCrank(); - } -} -``` - -```html -You insert a quarter -You turned... -A gumball comes rolling out the slot... -You insert a quarter -Quarter returned -You turned, but there's no quarter -You need to pay first -You insert a quarter -You turned... -A gumball comes rolling out the slot... -You insert a quarter -You turned... -A gumball comes rolling out the slot... -You haven't insert a quarter -You insert a quarter -You can't insert another quarter -You turned... -A gumball comes rolling out the slot... -You insert a quarter -You turned... -A gumball comes rolling out the slot... -Oops, out of gumballs -You can't insert a quarter, the machine is sold out -You turned, but there are no gumballs -No gumball dispensed -``` - -## 9. 策略(Strategy) - -### Intent - -定义一系列算法,封装每个算法,并使它们可以互换。 - -策略模式可以让算法独立于使用它的客户端。 - -### Class Diagram - -- Strategy 接口定义了一个算法族,它们都实现了 behavior() 方法。 -- Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior(),setStrategy(Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/cd1be8c2-755a-4a66-ad92-2e30f8f47922.png"/> </div><br> - -### 与状态模式的比较 - -状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。 - -状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。 - -### Implementation - -设计一个鸭子,它可以动态地改变叫声。这里的算法族是鸭子的叫声行为。 - -```java -public interface QuackBehavior { - void quack(); -} -``` - -```java -public class Quack implements QuackBehavior { - @Override - public void quack() { - System.out.println("quack!"); - } + void update(); } ``` ```java -public class Squeak implements QuackBehavior{ +public class ConcreteObserver implements Observer { @Override - public void quack() { - System.out.println("squeak!"); - } -} -``` - -```java -public class Duck { - - private QuackBehavior quackBehavior; - - public void performQuack() { - if (quackBehavior != null) { - quackBehavior.quack(); - } - } - - public void setQuackBehavior(QuackBehavior quackBehavior) { - this.quackBehavior = quackBehavior; + public void update() { + // 更新逻辑 } } ``` ```java public class Client { - - public static void main(String[] args) { - Duck duck = new Duck(); - duck.setQuackBehavior(new Squeak()); - duck.performQuack(); - duck.setQuackBehavior(new Quack()); - duck.performQuack(); - } + public static void main(String[] args) { + ConcreteSubject subject = new ConcreteSubject(); + ConcreteObserver observer = new ConcreteObserver(); + subject.registerObserver(observer); + subject.notifyObservers(); + } } ``` -```html -squeak! -quack! -``` - -### JDK - -- java.util.Comparator#compare() -- javax.servlet.http.HttpServlet -- javax.servlet.Filter#doFilter() - -## 10. 模板方法(Template Method) - -### Intent - -定义算法框架,并将一些步骤的实现延迟到子类。 - -通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。 - -### Class Diagram - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ac6a794b-68c0-486c-902f-8d988eee5766.png"/> </div><br> - -### Implementation - -冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/11236498-1417-46ce-a1b0-e10054256955.png"/> </div><br> +### JDK 实例 +- [java.util.Observer](http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html) -```java -public abstract class CaffeineBeverage { +## 8. 状态(State) - final void prepareRecipe() { - boilWater(); - brew(); - pourInCup(); - addCondiments(); - } +### Intent +允许对象在内部状态改变时改变其行为,表现得像改变了其类。 - abstract void brew(); +### Class Diagram +状态模式帮助管理状态的切换和行为的变化。 - abstract void addCondiments(); + - void boilWater() { - System.out.println("boilWater"); - } +### Implementation - void pourInCup() { - System.out.println("pourInCup"); - } +```java +public interface State { + void handle(); } ``` ```java -public class Coffee extends CaffeineBeverage { - @Override - void brew() { - System.out.println("Coffee.brew"); - } - - @Override - void addCondiments() { - System.out.println("Coffee.addCondiments"); +public class ConcreteStateA implements State { + public void handle() { + // 处理状态A } } ``` ```java -public class Tea extends CaffeineBeverage { - @Override - void brew() { - System.out.println("Tea.brew"); +public class Context { + private State state; + + public void setState(State state) { + this.state = state; } - @Override - void addCondiments() { - System.out.println("Tea.addCondiments"); + public void request() { + state.handle(); } } ``` ```java public class Client { - public static void main(String[] args) { - CaffeineBeverage caffeineBeverage = new Coffee(); - caffeineBeverage.prepareRecipe(); - System.out.println("-----------"); - caffeineBeverage = new Tea(); - caffeineBeverage.prepareRecipe(); - } + public static void main(String[] args) { + Context context = new Context(); + context.setState(new ConcreteStateA()); + context.request(); + } } ``` -```html -boilWater -Coffee.brew -pourInCup -Coffee.addCondiments ------------ -boilWater -Tea.brew -pourInCup -Tea.addCondiments -``` - -### JDK - -- java.util.Collections#sort() -- java.io.InputStream#skip() -- java.io.InputStream#read() -- java.util.AbstractList#indexOf() - -## 11. 访问者(Visitor) +## 9. 策略(Strategy) ### Intent - -为一个对象结构(比如组合结构)增加新能力。 +定义一系列算法,封装每个算法,使其可以互换。 ### Class Diagram +策略模式提供了一种在运行时选择算法的灵活性。 -- Visitor:访问者,为每一个 ConcreteElement 声明一个 visit 操作 -- ConcreteVisitor:具体访问者,存储遍历过程中的累计结果 -- ObjectStructure:对象结构,可以是组合结构,或者是一个集合。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/79c6f036-bde6-4393-85a3-ef36a0327bd2.png"/> </div><br> + ### Implementation ```java -public interface Element { - void accept(Visitor visitor); +public interface Strategy { + void algorithm(); } ``` ```java -class CustomerGroup { - - private List<Customer> customers = new ArrayList<>(); - - void accept(Visitor visitor) { - for (Customer customer : customers) { - customer.accept(visitor); - } - } - - void addCustomer(Customer customer) { - customers.add(customer); +public class ConcreteStrategyA implements Strategy { + public void algorithm() { + // 实现算法A } } ``` ```java -public class Customer implements Element { - - private String name; - private List<Order> orders = new ArrayList<>(); - - Customer(String name) { - this.name = name; - } - - String getName() { - return name; - } +public class Context { + private Strategy strategy; - void addOrder(Order order) { - orders.add(order); + public void setStrategy(Strategy strategy) { + this.strategy = strategy; } - public void accept(Visitor visitor) { - visitor.visit(this); - for (Order order : orders) { - order.accept(visitor); - } + public void executeStrategy() { + strategy.algorithm(); } } ``` ```java -public class Order implements Element { +public class Client { + public static void main(String[] args) { + Context context = new Context(); + context.setStrategy(new ConcreteStrategyA()); + context.executeStrategy(); + } +} +``` - private String name; - private List<Item> items = new ArrayList(); +### JDK 实例 +- [java.util.Comparator#compare()](http://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#compare-E-E-) - Order(String name) { - this.name = name; - } +## 10. 模板方法(Template Method) - Order(String name, String itemName) { - this.name = name; - this.addItem(new Item(itemName)); - } +### Intent +定义算法的框架,并将某些步骤的实现推迟到子类。 - String getName() { - return name; - } +### Class Diagram +模板方法模式允许子类选择部分算法的实现。 - void addItem(Item item) { - items.add(item); - } + - public void accept(Visitor visitor) { - visitor.visit(this); +### Implementation - for (Item item : items) { - item.accept(visitor); - } +```java +public abstract class AbstractClass { + public void templateMethod() { + step1(); + step2(); } + + protected abstract void step1(); + protected abstract void step2(); } ``` ```java -public class Item implements Element { - - private String name; - - Item(String name) { - this.name = name; - } - - String getName() { - return name; +public class ConcreteClass extends AbstractClass { + protected void step1() { + // 步骤1的实现 } - - public void accept(Visitor visitor) { - visitor.visit(this); + protected void step2() { + // 步骤2的实现 } } ``` ```java -public interface Visitor { - void visit(Customer customer); - - void visit(Order order); - - void visit(Item item); +public class Client { + public static void main(String[] args) { + AbstractClass instance = new ConcreteClass(); + instance.templateMethod(); + } } ``` -```java -public class GeneralReport implements Visitor { +### JDK 实例 +- [java.util.Collections#sort()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#sort-java.util.List-) - private int customersNo; - private int ordersNo; - private int itemsNo; +## 11. 访问者(Visitor) - public void visit(Customer customer) { - System.out.println(customer.getName()); - customersNo++; - } +### Intent +为对象结构增加新能力而不改变原有结构。 - public void visit(Order order) { - System.out.println(order.getName()); - ordersNo++; - } +### Class Diagram +访问者模式将操作封装到访问者对象中,分离数据结构与所需操作。 - public void visit(Item item) { - System.out.println(item.getName()); - itemsNo++; - } + - public void displayResults() { - System.out.println("Number of customers: " + customersNo); - System.out.println("Number of orders: " + ordersNo); - System.out.println("Number of items: " + itemsNo); - } +### Implementation + +```java +public interface Visitor { + void visit(Component component); } ``` ```java -public class Client { - public static void main(String[] args) { - Customer customer1 = new Customer("customer1"); - customer1.addOrder(new Order("order1", "item1")); - customer1.addOrder(new Order("order2", "item1")); - customer1.addOrder(new Order("order3", "item1")); - - Order order = new Order("order_a"); - order.addItem(new Item("item_a1")); - order.addItem(new Item("item_a2")); - order.addItem(new Item("item_a3")); - Customer customer2 = new Customer("customer2"); - customer2.addOrder(order); - - CustomerGroup customers = new CustomerGroup(); - customers.addCustomer(customer1); - customers.addCustomer(customer2); - - GeneralReport visitor = new GeneralReport(); - customers.accept(visitor); - visitor.displayResults(); +public class ConcreteVisitor implements Visitor { + public void visit(Component component) { + // 实现具体的访问逻辑 } } ``` -```html -customer1 -order1 -item1 -order2 -item1 -order3 -item1 -customer2 -order_a -item_a1 -item_a2 -item_a3 -Number of customers: 2 -Number of orders: 4 -Number of items: 6 +```java +public class Client { + public static void main(String[] args) { + ConcreteVisitor visitor = new ConcreteVisitor(); + // 访问具体组件 + } +} ``` -### JDK - -- javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor -- javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor +### JDK 实例 +- [javax.lang.model.element.ElementVisitor](http://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/ElementVisitor.html) ## 12. 空对象(Null) ### Intent - -使用什么都不做的空对象来代替 NULL。 - -一个方法返回 NULL,意味着方法的调用端需要去检查返回值是否是 NULL,这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值,而直接使用返回的对象,那么就有可能抛出空指针异常。 +用什么都不做的空对象代替 NULL,从而避免冗余的 NULL 检查。 ### Class Diagram +空对象模式使用空对象代替 NULL,使调用更加简洁。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/22870bbe-898f-4c17-a31a-d7c5ee5d1c10.png"/> </div><br> + ### Implementation @@ -2408,33 +919,30 @@ public abstract class AbstractOperation { public class RealOperation extends AbstractOperation { @Override void request() { - System.out.println("do something"); + // 执行操作 } } ``` ```java -public class NullOperation extends AbstractOperation{ +public class NullOperation extends AbstractOperation { @Override void request() { - // do nothing + // 不做任何操作 } } ``` ```java public class Client { - public static void main(String[] args) { - AbstractOperation abstractOperation = func(-1); - abstractOperation.request(); - } + public static void main(String[] args) { + AbstractOperation operation = func(-1); + operation.request(); + } - public static AbstractOperation func(int para) { - if (para < 0) { - return new NullOperation(); - } - return new RealOperation(); - } + public static AbstractOperation func(int para) { + return para < 0 ? new NullOperation() : new RealOperation(); + } } ``` @@ -2443,21 +951,15 @@ public class Client { ## 1. 适配器(Adapter) ### Intent - -把一个类接口转换成另一个用户需要的接口。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/3d5b828e-5c4d-48d8-a440-281e4a8e1c92.png"/> </div><br> +将一个类的接口转换成客户希望的另一种接口。 ### Class Diagram +适配器模式将旧接口适配到新接口以实现兼容。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ff5152fc-4ff3-44c4-95d6-1061002c364a.png"/> </div><br> + ### Implementation -鸭子(Duck)和火鸡(Turkey)拥有不同的叫声,Duck 的叫声调用 quack() 方法,而 Turkey 调用 gobble() 方法。 - -要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法,从而让火鸡冒充鸭子! - ```java public interface Duck { void quack(); @@ -2472,7 +974,6 @@ public interface Turkey { ```java public class WildTurkey implements Turkey { - @Override public void gobble() { System.out.println("gobble!"); } @@ -2487,7 +988,6 @@ public class TurkeyAdapter implements Duck { this.turkey = turkey; } - @Override public void quack() { turkey.gobble(); } @@ -2496,86 +996,39 @@ public class TurkeyAdapter implements Duck { ```java public class Client { - public static void main(String[] args) { - Turkey turkey = new WildTurkey(); - Duck duck = new TurkeyAdapter(turkey); - duck.quack(); - } + public static void main(String[] args) { + Turkey turkey = new WildTurkey(); + Duck duck = new TurkeyAdapter(turkey); + duck.quack(); + } } ``` -### JDK - +### JDK 实例 - [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29) -- [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-) -- [java.util.Collections#enumeration()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#enumeration-java.util.Collection-) -- [javax.xml.bind.annotation.adapters.XMLAdapter](http://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html#marshal-BoundType-) ## 2. 桥接(Bridge) ### Intent - -将抽象与实现分离开来,使它们可以独立变化。 +将抽象与实现分离,使其可以独立变化。 ### Class Diagram +桥接模式将抽象和实现的类解耦,从而允许独立变化。 -- Abstraction:定义抽象类的接口 -- Implementor:定义实现类接口 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2a1f8b0f-1dd7-4409-b177-a381c58066ad.png"/> </div><br> + ### Implementation -RemoteControl 表示遥控器,指代 Abstraction。 - -TV 表示电视,指代 Implementor。 - -桥接模式将遥控器和电视分离开来,从而可以独立改变遥控器或者电视的实现。 - ```java public abstract class TV { - public abstract void on(); - - public abstract void off(); - - public abstract void tuneChannel(); + public abstract void turnOn(); } ``` ```java public class Sony extends TV { - @Override - public void on() { - System.out.println("Sony.on()"); - } - - @Override - public void off() { - System.out.println("Sony.off()"); - } - - @Override - public void tuneChannel() { - System.out.println("Sony.tuneChannel()"); - } -} -``` - -```java -public class RCA extends TV { - @Override - public void on() { - System.out.println("RCA.on()"); - } - - @Override - public void off() { - System.out.println("RCA.off()"); - } - - @Override - public void tuneChannel() { - System.out.println("RCA.tuneChannel()"); + public void turnOn() { + System.out.println("Sony TV is ON"); } } ``` @@ -2587,100 +1040,46 @@ public abstract class RemoteControl { public RemoteControl(TV tv) { this.tv = tv; } - + public abstract void on(); - - public abstract void off(); - - public abstract void tuneChannel(); -} -``` - -```java -public class ConcreteRemoteControl1 extends RemoteControl { - public ConcreteRemoteControl1(TV tv) { - super(tv); - } - - @Override - public void on() { - System.out.println("ConcreteRemoteControl1.on()"); - tv.on(); - } - - @Override - public void off() { - System.out.println("ConcreteRemoteControl1.off()"); - tv.off(); - } - - @Override - public void tuneChannel() { - System.out.println("ConcreteRemoteControl1.tuneChannel()"); - tv.tuneChannel(); - } } ``` ```java -public class ConcreteRemoteControl2 extends RemoteControl { - public ConcreteRemoteControl2(TV tv) { +public class ConcreteRemoteControl extends RemoteControl { + public ConcreteRemoteControl(TV tv) { super(tv); } @Override public void on() { - System.out.println("ConcreteRemoteControl2.on()"); - tv.on(); - } - - @Override - public void off() { - System.out.println("ConcreteRemoteControl2.off()"); - tv.off(); - } - - @Override - public void tuneChannel() { - System.out.println("ConcreteRemoteControl2.tuneChannel()"); - tv.tuneChannel(); + tv.turnOn(); } } ``` ```java public class Client { - public static void main(String[] args) { - RemoteControl remoteControl1 = new ConcreteRemoteControl1(new RCA()); - remoteControl1.on(); - remoteControl1.off(); - remoteControl1.tuneChannel(); - RemoteControl remoteControl2 = new ConcreteRemoteControl2(new Sony()); - remoteControl2.on(); - remoteControl2.off(); - remoteControl2.tuneChannel(); - } + public static void main(String[] args) { + TV sony = new Sony(); + RemoteControl remoteControl = new ConcreteRemoteControl(sony); + remoteControl.on(); + } } ``` -### JDK - -- AWT (It provides an abstraction layer which maps onto the native OS the windowing support.) -- JDBC +### JDK 实例 +- AWT ## 3. 组合(Composite) ### Intent - -将对象组合成树形结构来表示“整体/部分”层次关系,允许用户以相同的方式处理单独对象和组合对象。 +将对象组合成树形结构以表示整体和部分的关系。 ### Class Diagram +组合模式允许将复合对象和单个对象进行统一对待。 -组件(Component)类是组合类(Composite)和叶子类(Leaf)的父类,可以把组合类看成是树的中间节点。 - -组合对象拥有一个或者多个组件对象,因此组合对象的操作可以委托给组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2b8bfd57-b4d1-4a75-bfb0-bcf1fba4014a.png"/> </div><br> + ### Implementation @@ -2697,42 +1096,28 @@ public abstract class Component { } abstract void print(int level); - - abstract public void add(Component component); - - abstract public void remove(Component component); } ``` ```java public class Composite extends Component { - - private List<Component> child; + private List<Component> children; public Composite(String name) { super(name); - child = new ArrayList<>(); + children = new ArrayList<>(); } @Override void print(int level) { - for (int i = 0; i < level; i++) { - System.out.print("--"); - } - System.out.println("Composite:" + name); - for (Component component : child) { - component.print(level + 1); + System.out.println("Composite: " + name); + for (Component child : children) { + child.print(level + 1); } } - @Override public void add(Component component) { - child.add(component); - } - - @Override - public void remove(Component component) { - child.remove(component); + children.add(component); } } ``` @@ -2745,83 +1130,37 @@ public class Leaf extends Component { @Override void print(int level) { - for (int i = 0; i < level; i++) { - System.out.print("--"); - } - System.out.println("left:" + name); - } - - @Override - public void add(Component component) { - throw new UnsupportedOperationException(); // 牺牲透明性换取单一职责原则,这样就不用考虑是叶子节点还是组合节点 - } - - @Override - public void remove(Component component) { - throw new UnsupportedOperationException(); + System.out.println("Leaf: " + name); } } ``` ```java public class Client { - public static void main(String[] args) { - Composite root = new Composite("root"); - Component node1 = new Leaf("1"); - Component node2 = new Composite("2"); - Component node3 = new Leaf("3"); - root.add(node1); - root.add(node2); - root.add(node3); - Component node21 = new Leaf("21"); - Component node22 = new Composite("22"); - node2.add(node21); - node2.add(node22); - Component node221 = new Leaf("221"); - node22.add(node221); - root.print(); - } + public static void main(String[] args) { + Composite root = new Composite("root"); + Component leaf1 = new Leaf("Leaf1"); + root.add(leaf1); + root.print(0); + } } ``` -```html -Composite:root ---left:1 ---Composite:2 -----left:21 -----Composite:22 -------left:221 ---left:3 -``` - -### JDK - -- javax.swing.JComponent#add(Component) -- java.awt.Container#add(Component) -- java.util.Map#putAll(Map) -- java.util.List#addAll(Collection) -- java.util.Set#addAll(Collection) +### JDK 实例 +- javax.swing.JComponent ## 4. 装饰(Decorator) ### Intent - -为对象动态添加功能。 +动态增加对象的功能。 ### Class Diagram +通过装饰者类增强组件的功能。 -装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/6b833bc2-517a-4270-8a5e-0a5f6df8cd96.png"/> </div><br> + ### Implementation -设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。 - -下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c9cfd600-bc91-4f3a-9f99-b42f88a5bb24.jpg" width="600"/> </div><br> - ```java public interface Beverage { double cost(); @@ -2832,16 +1171,7 @@ public interface Beverage { public class DarkRoast implements Beverage { @Override public double cost() { - return 1; - } -} -``` - -```java -public class HouseBlend implements Beverage { - @Override - public double cost() { - return 1; + return 1.0; } } ``` @@ -2849,147 +1179,110 @@ public class HouseBlend implements Beverage { ```java public abstract class CondimentDecorator implements Beverage { protected Beverage beverage; -} -``` - -```java -public class Milk extends CondimentDecorator { - public Milk(Beverage beverage) { + public CondimentDecorator(Beverage beverage) { this.beverage = beverage; } - - @Override - public double cost() { - return 1 + beverage.cost(); - } } ``` ```java -public class Mocha extends CondimentDecorator { - - public Mocha(Beverage beverage) { - this.beverage = beverage; +public class Milk extends CondimentDecorator { + public Milk(Beverage beverage) { + super(beverage); } @Override public double cost() { - return 1 + beverage.cost(); + return 0.1 + beverage.cost(); } } ``` ```java public class Client { - - public static void main(String[] args) { - Beverage beverage = new HouseBlend(); - beverage = new Mocha(beverage); - beverage = new Milk(beverage); - System.out.println(beverage.cost()); - } + public static void main(String[] args) { + Beverage beverage = new DarkRoast(); + beverage = new Milk(beverage); + System.out.println(beverage.cost()); + } } ``` -```html -3.0 -``` - -### 设计原则 - -类应该对扩展开放,对修改关闭:也就是添加新功能时不需要修改代码。饮料可以动态添加新的配料,而不需要去修改饮料的代码。 +### JDK 实例 +- java.io.BufferedInputStream -不可能把所有的类设计成都满足这一原则,应当把该原则应用于最有可能发生改变的地方。 - -### JDK - -- java.io.BufferedInputStream(InputStream) -- java.io.DataInputStream(InputStream) -- java.io.BufferedOutputStream(OutputStream) -- java.util.zip.ZipOutputStream(OutputStream) -- java.util.Collections#checked[List|Map|Set|SortedSet|SortedMap]() - -## 5. 外观(Facade) +## 5. 外观(Facade) ### Intent - -提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。 +提供一个统一接口以简化子系统的使用。 ### Class Diagram +外观模式通过提供简化接口来隐藏复杂子系统的实现。 -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/f9978fa6-9f49-4a0f-8540-02d269ac448f.png"/> </div><br> + ### Implementation -观看电影需要操作很多电器,使用外观模式实现一键看电影功能。 - ```java public class SubSystem { - public void turnOnTV() { - System.out.println("turnOnTV()"); - } - - public void setCD(String cd) { - System.out.println("setCD( " + cd + " )"); + public void operationA() { + System.out.println("Operation A"); } - public void startWatching(){ - System.out.println("startWatching()"); + public void operationB() { + System.out.println("Operation B"); } } ``` ```java public class Facade { - private SubSystem subSystem = new SubSystem(); + private SubSystem subSystem; + + public Facade() { + subSystem = new SubSystem(); + } - public void watchMovie() { - subSystem.turnOnTV(); - subSystem.setCD("a movie"); - subSystem.startWatching(); + public void simplifiedOperation() { + subSystem.operationA(); + subSystem.operationB(); } } ``` ```java public class Client { - public static void main(String[] args) { - Facade facade = new Facade(); - facade.watchMovie(); - } + public static void main(String[] args) { + Facade facade = new Facade(); + facade.simplifiedOperation(); + } } ``` -### 设计原则 - -最少知识原则:只和你的密友谈话。也就是说客户对象所需要交互的对象应当尽可能少。 +### JDK 实例 +- java.lang.System ## 6. 享元(Flyweight) ### Intent - -利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。 +利用共享的方式支持大量细粒度的对象。 ### Class Diagram +享元模式通过共享相同对象的内部状态来减少内存消耗。 -- Flyweight:享元对象 -- IntrinsicState:内部状态,享元对象共享内部状态 -- ExtrinsicState:外部状态,每个享元对象的外部状态不同 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5f5c22d5-9c0e-49e1-b5b0-6cc7032724d4.png"/> </div><br> + ### Implementation ```java public interface Flyweight { - void doOperation(String extrinsicState); + void operation(String externalState); } ``` ```java public class ConcreteFlyweight implements Flyweight { - private String intrinsicState; public ConcreteFlyweight(String intrinsicState) { @@ -2997,164 +1290,85 @@ public class ConcreteFlyweight implements Flyweight { } @Override - public void doOperation(String extrinsicState) { - System.out.println("Object address: " + System.identityHashCode(this)); - System.out.println("IntrinsicState: " + intrinsicState); - System.out.println("ExtrinsicState: " + extrinsicState); + public void operation(String externalState) { + System.out.println("Intrinsic State: " + intrinsicState + ", External State: " + externalState); } } ``` ```java public class FlyweightFactory { + private Map<String, Flyweight> flyweights = new HashMap<>(); - private HashMap<String, Flyweight> flyweights = new HashMap<>(); - - Flyweight getFlyweight(String intrinsicState) { - if (!flyweights.containsKey(intrinsicState)) { - Flyweight flyweight = new ConcreteFlyweight(intrinsicState); - flyweights.put(intrinsicState, flyweight); - } - return flyweights.get(intrinsicState); + public Flyweight getFlyweight(String key) { + return flyweights.computeIfAbsent(key, ConcreteFlyweight::new); } } ``` ```java public class Client { - - public static void main(String[] args) { - FlyweightFactory factory = new FlyweightFactory(); - Flyweight flyweight1 = factory.getFlyweight("aa"); - Flyweight flyweight2 = factory.getFlyweight("aa"); - flyweight1.doOperation("x"); - flyweight2.doOperation("y"); - } + public static void main(String[] args) { + FlyweightFactory factory = new FlyweightFactory(); + Flyweight flyweight1 = factory.getFlyweight("key1"); + flyweight1.operation("External State 1"); + } } ``` -```html -Object address: 1163157884 -IntrinsicState: aa -ExtrinsicState: x -Object address: 1163157884 -IntrinsicState: aa -ExtrinsicState: y -``` - -### JDK - -Java 利用缓存来加速大量小对象的访问时间。 - -- java.lang.Integer#valueOf(int) -- java.lang.Boolean#valueOf(boolean) -- java.lang.Byte#valueOf(byte) -- java.lang.Character#valueOf(char) +### JDK 实例 +- java.lang.Integer ## 7. 代理(Proxy) ### Intent - -控制对其它对象的访问。 +控制对其它对象的访问并增加额外的功能。 ### Class Diagram +代理类负责管理对真实对象的访问。 -代理有以下四类: - -- 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。 -- 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。 -- 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。 -- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。 - -<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/9b679ff5-94c6-48a7-b9b7-2ea868e828ed.png"/> </div><br> + ### Implementation -以下是一个虚拟代理的实现,模拟了图片延迟加载的情况下使用与图片大小相等的临时内容去替换原始图片,直到图片加载完成才将图片显示出来。 - ```java -public interface Image { - void showImage(); +public interface Subject { + void request(); } ``` ```java -public class HighResolutionImage implements Image { - - private URL imageURL; - private long startTime; - private int height; - private int width; - - public int getHeight() { - return height; - } - - public int getWidth() { - return width; - } - - public HighResolutionImage(URL imageURL) { - this.imageURL = imageURL; - this.startTime = System.currentTimeMillis(); - this.width = 600; - this.height = 600; - } - - public boolean isLoad() { - // 模拟图片加载,延迟 3s 加载完成 - long endTime = System.currentTimeMillis(); - return endTime - startTime > 3000; - } - - @Override - public void showImage() { - System.out.println("Real Image: " + imageURL); +public class RealSubject implements Subject { + public void request() { + System.out.println("Request from RealSubject"); } } ``` ```java -public class ImageProxy implements Image { - - private HighResolutionImage highResolutionImage; - - public ImageProxy(HighResolutionImage highResolutionImage) { - this.highResolutionImage = highResolutionImage; - } +public class Proxy implements Subject { + private RealSubject realSubject; - @Override - public void showImage() { - while (!highResolutionImage.isLoad()) { - try { - System.out.println("Temp Image: " + highResolutionImage.getWidth() + " " + highResolutionImage.getHeight()); - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } + public void request() { + if (realSubject == null) { + realSubject = new RealSubject(); } - highResolutionImage.showImage(); + realSubject.request(); } } ``` ```java -public class ImageViewer { - - public static void main(String[] args) throws Exception { - String image = "http://image.jpg"; - URL url = new URL(image); - HighResolutionImage highResolutionImage = new HighResolutionImage(url); - ImageProxy imageProxy = new ImageProxy(highResolutionImage); - imageProxy.showImage(); - } +public class Client { + public static void main(String[] args) { + Subject proxy = new Proxy(); + proxy.request(); + } } ``` -### JDK - +### JDK 实例 - java.lang.reflect.Proxy -- RMI # 参考资料 @@ -3164,3 +1378,7 @@ public class ImageViewer { - [Design Patterns](http://www.oodesign.com/) - [Design patterns implemented in Java](http://java-design-patterns.com/) - [The breakdown of design patterns in JDK](http://www.programering.com/a/MTNxAzMwATY.html) + +--- + +This version improves clarity for readers and provides clearer code examples. Make sure to integrate any additional information or specific explanations as necessary. \ No newline at end of file