diff --git a/README.md b/README.md
index d59740a7..7a73df4f 100644
--- a/README.md
+++ b/README.md
@@ -1,136 +1,15 @@
-本项目对应 WebSite(效果更佳): https://duhouan.github.io/Java/#/
+本仓库是对 Java 的一些基础知识、数据库知识、以及框架知识进行收集、整理(持续更新中)。
+仓库对应 WebSite:https://duhouan.github.io/Java/#
+如何流畅访问 Github
-# 📖 目录
+

-## ✏️ 计算机基础
+
-### I、计算机网络
-
-- [第一节 概述](docs/Net/1_概述.md)
-- [第二节 物理层](docs/Net/2_物理层.md)
-- [第三节 数据链路层](docs/Net/3_数据链路层.md)
-- [第四节 网络层](docs/Net/4_网络层.md)
-- [第五节 运输层](docs/Net/5_运输层.md)
-- [第六节 应用层](docs/Net/6_应用层.md)
-
-### II、操作系统
-
-- [第一节 操作系统概述](docs/OS/1_操作系统概述.md)
-- [第二节 进程管理](docs/OS/2_进程管理.md)
-- [第三节 死锁](docs/OS/3_死锁.md)
-- [第四节 内存管理](docs/OS/4_内存管理.md)
-- [第五节 设备管理](docs/OS/4_设备管理.md)
-- [第六节 链接](docs/OS/6_链接.md)
-
-## ☕️ Java 基础
-
-- [第一节 数据类型](docs/JavaBasics/00数据类型.md)
-- [第二节 String](docs/JavaBasics/01String.md)
-- [第三节 运算](docs/JavaBasics/02%E8%BF%90%E7%AE%97.md)
-- [第四节 Object 通用方法](docs/JavaBasics/03Object%E9%80%9A%E7%94%A8%E6%96%B9%E6%B3%95.md)
-- [第五节 关键字](docs/JavaBasics/04%E5%85%B3%E9%94%AE%E5%AD%97.md)
-- [第六节 反射](docs/JavaBasics/05%E5%8F%8D%E5%B0%84.md)
-- [第七节 异常](docs/JavaBasics/06%E5%BC%82%E5%B8%B8.md)
-- [第八节 泛型](docs/JavaBasics/07%E6%B3%9B%E5%9E%8B.md)
-- [第九节 注解](docs/JavaBasics/08%E6%B3%A8%E8%A7%A3.md)
-- [第十节 Java中常见对象](docs/JavaBasics/09Java%E5%B8%B8%E8%A7%81%E5%AF%B9%E8%B1%A1.md)
-- [第十一节 其他](docs/JavaBasics/10%E5%85%B6%E4%BB%96.md)
-
-## 💾 Java 容器*
-
-- [第一节 Java容器概览](docs/JavaContainer/00Java%E5%AE%B9%E5%99%A8%E6%A6%82%E8%A7%88.md)
-- [第二节 容器的设计模式](docs/JavaContainer/01%E5%AE%B9%E5%99%A8%E4%B8%AD%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md)
-- [第三节 容器源码解析-List](docs/JavaContainer/02容器源码分析%20-%20List.md)
-- [第三节 容器源码解析-Map](docs/JavaContainer/03容器源码分析%20-%20Map.md)
-- [第三节 容器源码解析-并发容器](docs/JavaContainer/04容器源码分析%20-%20并发容器.md)
-
-## 🙊 Java 虚拟机**
-
-- [第一节 运行时数据区域](docs/JVM/1_JVM.md)
-- [第二节 HotSpot 虚拟机对象](docs/JVM/2_JVM.md)
-- [第三节 String 类和常量池](docs/JVM/3_JVM.md)
-- [第四节 8 种基本类型的包装类和常量池](docs/JVM/4_JVM.md)
-- [第五节 垃圾收集](docs/JVM/5_JVM.md)
-- [第六节 内存分配与回收策略](docs/JVM/6_JVM.md)
-- [第七节 类加载机制](docs/JVM/7_JVM.md)
-
-## 🔨 Java 并发**
-
-- [第一节 基础知识](docs/Java_Concurrency/1_基础知识.md)
-- [第二节 并发理论](docs/Java_Concurrency/2_并发理论.md)
-- [第三节 并发关键字](docs/Java_Concurrency/3_并发关键字.md)
-- [第四节 Lock 体系](docs/Java_Concurrency/4_Lock%20体系.md)
-- [第五节 原子操作类](docs/Java_Concurrency/5_原子操作类.md)
-- [第六节 并发容器](docs/Java_Concurrency/6_并发容器.md)
-- [第七节 并发工具](docs/Java_Concurrency/7_并发工具.md)
-- [第八节 线程池](docs/Java_Concurrency/8_线程池.md)
-- [第九节 并发实践](docs/Java_Concurrency/9_并发实践.md)
-
-## 💡 JavaIO
-
-- [第一节 概览](docs/JavaIO/00%E6%A6%82%E8%A7%88.md)
-- [第二节 磁盘操作](docs/JavaIO/01%E7%A3%81%E7%9B%98%E6%93%8D%E4%BD%9C.md)
-- [第三节 字节操作](docs/JavaIO/02%E5%AD%97%E8%8A%82%E6%93%8D%E4%BD%9C.md)
-- [第四节 字符操作](docs/JavaIO/03%E5%AD%97%E7%AC%A6%E6%93%8D%E4%BD%9C.md)
-- [第五节 对象操作](docs/JavaIO/04%E5%AF%B9%E8%B1%A1%E6%93%8D%E4%BD%9C.md)
-- [第六节 网络操作](docs/JavaIO/05%E7%BD%91%E7%BB%9C%E6%93%8D%E4%BD%9C.md)
-- [第七节 NIO](docs/JavaIO/06NIO.md)
-
-## 📝 JavaWeb
-
-- [第一节 Servlet 工作原理解析](docs/JavaWeb/00Servlet%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90.md)
-- [第二节 JSP 解析](docs/JavaWeb/01JSP%E8%A7%A3%E6%9E%90.md)
-- [第三节 深入理解 Session 和 Cookie](docs/JavaWeb/02%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Session%E5%92%8CCookie.md)
-
-## 👫 面向对象
-
-### I、设计模式
-
-- [第一节 设计模式概述](docs/OO/00%E6%A6%82%E8%BF%B0.md)
-- [第二节 创建型设计模式](docs/OO/01%E5%88%9B%E5%BB%BA%E5%9E%8B.md)
-- [第三节 行为型设计模式相关代码](docs/OO/src/code_01_activity)
-- [第四节 结构型设计模式](docs/OO/03%E7%BB%93%E6%9E%84%E5%9E%8B.md)
-
-### II、面向对象思想
-
-- [第五节 面向对象三大特性](docs/OO/04%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E4%B8%89%E5%A4%A7%E7%89%B9%E6%80%A7.md)
-- [第六节 关系类图](docs/OO/05%E5%85%B3%E7%B3%BB%E7%B1%BB%E5%9B%BE.md)
-- [第七节 面向对象设计原则](docs/OO/06%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99.md)
-
-## 🎨 剑指 Offer 题解**
-
-- [第一节 数组](docs/AimForOffer/1_数组.md)
-- [第二节 字符串](docs/AimForOffer/2_字符串.md)
-- [第三节 查找](docs/AimForOffer/3_查找.md)
-- [第四节 链表](docs/AimForOffer/4_链表.md)
-- [第五节 树](docs/AimForOffer/5_树.md)
-- [第六节 栈](docs/AimForOffer/6_栈.md)
-- [第七节 队列](docs/AimForOffer/7_队列.md)
-- [第八节 动态规划](docs/AimForOffer/8_动态规划.md)
-- [第九节 回溯](docs/AimForOffer/9_回溯.md)
-- [第十节 深度优先](docs/AimForOffer/10_深度优先.md)
-- [第十一节 贪心](docs/AimForOffer/11_贪心.md)
-- [第十二节 位运算](docs/AimForOffer/12_位运算.md)
-- [第十三节 排序](docs/AimForOffer/13_排序.md)
-- [第十四节 堆](docs/AimForOffer/14_堆.md)
-- [第十五节 哈希](docs/AimForOffer/15_哈希.md)
-
-## 💻 LeetCode 题解**
-
-- [第一节 数组问题](docs/LeetCode/1_数组问题.md)
-- [第二节 查找表相关问题](docs/LeetCode/2_查找表相关问题.md)
-- [第三节 链表问题](docs/LeetCode/3_链表问题.md)
-- [第四节 栈和队列](docs/LeetCode/4_栈和队列.md)
-- [第五节 二叉树](docs/LeetCode/5_二叉树.md)
-- [第六节 回溯法](docs/LeetCode/6_回溯法.md)
-- [第七节 动态规划](docs/LeetCode/7_动态规划.md)
-- [第八节 字符串](docs/LeetCode/8_字符串.md)
-- [第九节 数学问题](docs/LeetCode/9_数学问题.md)
-- [第十节 数据结构设计](docs/LeetCode/10_数据结构设计.md)
-- [第十一节 数据结构](docs/LeetCode/11_数据结构.md)
-- [第十二节 常见算法思想](docs/LeetCode/12_算法思想.md)
-
-## 🔧 关注的小专栏*
-
-- [后端面试进阶指南](docs/https://xiaozhuanlan.com/CyC2018)
-- [Java 面试进阶指南](docs/https://xiaozhuanlan.com/javainterview)
\ No newline at end of file
+| ☕️ Java | 👫 面向对象 | 📝 编程题 | 💾 数据库 | 🎓 系统设计 | ☎️ 常用框架 | 📖 工具 | 📚 参考资料 |
+| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
+|
|
|
|
|
|
|
|
|
diff --git "a/docs/1_\345\237\272\347\241\200.md" "b/docs/1_\345\237\272\347\241\200.md"
new file mode 100644
index 00000000..406b2d57
--- /dev/null
+++ "b/docs/1_\345\237\272\347\241\200.md"
@@ -0,0 +1,99 @@
+# 系统设计基础
+
+## 安全性
+
+要求系统在应对各种攻击手段时能够有可靠的应对措施。
+
+## 性能
+
+### 性能指标
+
+#### 响应时间
+
+指某个请求从发出到接收到响应消耗的时间。
+
+在对响应时间进行测试时,通常采用重复请求的方式,然后计算平均响应时间。
+
+#### 吞吐量
+
+指系统在单位时间内可以处理的请求数量,通常使用每秒的请求数来衡量。
+
+#### 并发量
+
+指系统能同时处理的并发用户请求数量。
+
+在没有并发存在的系统中,请求被顺序执行,此时响应时间为吞吐量的倒数。例如系统支持的吞吐量为 100 req/s,那么平均响应时间应该为 0.01s。
+
+目前的大型系统都支持多线程来处理并发请求,多线程能够提高吞吐量以及缩短响应时间,主要有两个原因:
+
+- 多 CPU
+- IO 等待时间
+
+使用 IO 多路复用等方式,系统在等待一个 IO 操作完成的这段时间内不需要被阻塞,可以去处理其它请求。通过将这个等待时间利用起来,使得 CPU 利用率大大提高。
+
+并发用户数不是越高越好,因为如果并发用户数太高,系统来不及处理这么多的请求,会使得过多的请求需要等待,那么响应时间就会大大提高。
+
+### 性能优化
+
+#### 集群
+
+将多台服务器组成集群,使用负载均衡将请求转发到集群中,避免单一服务器的负载压力过大导致性能降低。
+
+#### 缓存
+
+缓存能够提高性能的原因如下:
+
+- 缓存数据通常位于内存等介质中,这种介质对于读操作特别快;
+- 缓存数据可以位于靠近用户的地理位置上;
+- 可以将计算结果进行缓存,从而避免重复计算。
+
+#### 异步
+
+某些流程可以将操作转换为消息,将消息发送到**消息队列**之后立即返回,之后这个操作会被异步处理。
+
+
+
+## 伸缩性
+
+指不断向集群中添加服务器来缓解不断上升的用户并发访问压力和不断增长的数据存储需求。
+
+### 伸缩性与性能
+
+如果系统存在性能问题,那么单个用户的请求总是很慢的;
+
+如果系统存在伸缩性问题,那么单个用户的请求可能会很快,但是在并发数很高的情况下系统会很慢。
+
+### 实现伸缩性
+
+应用服务器只要不具有状态,那么就可以很容易地通过负载均衡器向集群中添加新的服务器。
+
+关系型数据库的伸缩性通过 Sharding 来实现,将数据按一定的规则分布到不同的节点上,从而解决单台存储服务器的存储空间限制。
+
+对于非关系型数据库,它们天生就是为海量数据而诞生,对伸缩性的支持特别好。
+
+## 扩展性
+
+指的是添加新功能时对现有系统的其它应用无影响,这就要求不同应用具备低耦合的特点。
+
+实现可扩展主要有两种方式:
+
+- 使用消息队列进行解耦,应用之间通过消息传递进行通信;
+- 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以通过调用可复用的服务来实现业务逻辑,对其它产品没有影响。
+
+## 可用性
+
+### 冗余
+
+保证高可用的主要手段是使用冗余,当某个服务器故障时就请求其它服务器。
+
+应用服务器的冗余比较容易实现,只要保证应用服务器不具有状态,那么某个应用服务器故障时,负载均衡器将该应用服务器原先的用户请求转发到另一个应用服务器上,不会对用户有任何影响。
+
+存储服务器的冗余需要使用主从复制来实现,当主服务器故障时,需要提升从服务器为主服务器,这个过程称为切换。
+
+### 监控
+
+对 CPU、内存、磁盘、网络等系统负载信息进行监控,当某个信息达到一定阈值时通知运维人员,从而在系统发生故障之前及时发现问题。
+
+### 服务降级
+
+服务降级是系统为了应对大量的请求,主动关闭部分功能,从而保证核心功能可用。
\ No newline at end of file
diff --git "a/docs/1_\346\266\210\346\201\257\351\230\237\345\210\227.md" "b/docs/1_\346\266\210\346\201\257\351\230\237\345\210\227.md"
new file mode 100644
index 00000000..6b758128
--- /dev/null
+++ "b/docs/1_\346\266\210\346\201\257\351\230\237\345\210\227.md"
@@ -0,0 +1,117 @@
+# 消息队列
+
+消息队列中间件是**分布式系统**中重要的组件,主要用于:异步处理,应用解耦,流量削锋,消息通讯等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有 ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ。
+
+## 消息模型
+
+### 1. 点对点
+
+消息生产者向消息队列中发送了一个消息之后,只能被一个消费者消费一次。
+
+
+
+### 2. 发布/订阅
+
+消息生产者向频道发送一个消息之后,多个消费者可以从该频道订阅到这条消息并消费。
+
+
+
+发布与订阅模式和观察者模式有以下不同:
+
+- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。
+- 观察者模式是同步的,当事件触发时,主题会调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,发布者向频道发送一个消息之后,就不需要关心订阅者何时去订阅这个消息,可以立即返回。
+
+
+
+## 使用场景
+
+### 1、异步处理
+
+发送者将消息发送给消息队列之后,**不需要同步等待消息接收者处理完毕,而是立即返回进行其它操作**。消息接收者从消息队列中订阅消息之后异步处理。
+
+比如:用户注册时,需要发注册邮件和注册短信。传统的做法有两种:
+
+> 1、串行方式
+
+将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。
+
+
+
+> 2、并行方式
+
+将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以减少处理的时间。
+
+
+
+引入消息队列,将不是必须的业务逻辑异步处理,注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是 50 ms。比串行提高了 3 倍,比并行提高了2 倍。
+
+
+
+只有在**业务流程允许异步处理的情况**下才能这么做,例如上面的注册流程中,如果要求用户对验证邮件进行点击之后才能完成注册的话,就不能再使用消息队列。
+
+### 2、应用解耦
+
+如果模块之间不直接进行调用,模块之间耦合度就会很低,那么修改一个模块或者新增一个模块对其它模块的影响会很小,从而实现可扩展性。
+
+通过使用消息队列,一个模块只需要向消息队列中发送消息,其它模块可以**选择性地从消息队列中订阅消息**从而完成调用。
+
+### 3、流量削峰
+
+在高并发的场景下,如果短时间有大量的请求到达会压垮服务器。
+
+可以将请求发送到消息队列中,服务器按照其处理能力从消息队列中订阅消息进行处理。
+
+### 4、消息通讯
+
+消息队列一般都内置了高效的通信机制,因此也可以用在纯消息通讯。比如实现点对点消息队列,或者聊天室等,也就是消息队列的两种消息模式:点对点或发布 / 订阅模式。
+
+
+
+## 消费方式
+
+在 JMS (Java Message Service,Java 消息服务) 中,消息的产生和消费都是异步的。对于消费来说,JMS 的消费者可以通过两种方式来消费消息:同步方式和异步方式。
+
+### 1、同步方式
+
+订阅者或消费者通过 receive() 方法来接收消息,receive() 方法在接收到消息之前(或超时之前)将一直阻塞。
+
+### 2、异步方式
+
+订阅者或消费者可以注册为一个消息监听器。当消息到达之后,系统自动调用监听器的 onMessage() 方法。
+
+
+
+## 可靠性
+
+### 发送端的可靠性
+
+发送端完成操作后一定能将消息成功发送到消息队列中。
+
+实现方法:在本地数据库建一张消息表,将**消息数据与业务数据保存在同一数据库实例**里,这样就可以利用本地数据库的**事务机制**。事务提交成功后,将消息表中的消息转移到消息队列中,若转移消息成功则删除消息表中的数据,否则继续重传。
+
+### 接收端的可靠性
+
+接收端能够从消息队列成功消费一次消息。
+
+两种实现方法:
+
+- 保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次消息,最后处理的结果都是一样的。(幂等性:**被执行一次与连续执行多次的效果是一样的**。)
+- 保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。
+
+
+
+## 带来的问题
+
+### 1、系统可用性降低
+
+系统可用性在某种程度上降低,为什么这样说呢?
+
+在加入消息队列之前,不用考虑消息丢失或者说消息队列挂掉等等的情况,但是,引入消息队列之后你就需要去考虑了。
+
+### 2、系统复杂性提高
+
+加入消息队列之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等问题。
+
+### 3、一致性问题
+
+消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,消息的真正消费者并没有正确消费消息,就会导致数据不一致的情况。
\ No newline at end of file
diff --git "a/docs/AimForOffer/10_\346\267\261\345\272\246\344\274\230\345\205\210.md" "b/docs/AimForOffer/10_\346\267\261\345\272\246\344\274\230\345\205\210.md"
deleted file mode 100644
index 60cf3bd7..00000000
--- "a/docs/AimForOffer/10_\346\267\261\345\272\246\344\274\230\345\205\210.md"
+++ /dev/null
@@ -1,329 +0,0 @@
-# 深度优先
-
-## 1、岛屿数量
-
-[岛屿数量](https://leetcode-cn.com/problems/number-of-islands/)
-
-```java
-// 思路:典型的 flood-fill 算法
-private int m,n;
-
-private boolean[][] visited;
-
-private boolean inArea(int x,int y){
- return (x>=0 && x=0 && y=0 && x=0 && y=0 && x=0 && y0){
- sum += num %10;
- num/=10;
- }
- return sum;
-}
-
-private boolean valid(int threshlod ,int x,int y){
- return (getNum(x)+getNum(y))<=threshlod;
-}
-
-private int walk(int threshold,int startx,int starty){
- visited[startx][starty]=true;
- int walks = 0;
- if(valid(threshold,startx,starty)){
- walks=1;
- }
- for(int i=0;i<4;i++){
- int newX = startx+d[i][0];
- int newY = starty+d[i][1];
- if(inArea(newX,newY)){
- if(!visited[newX][newY] && valid(threshold,newX,newY)){
- walks += walk(threshold,newX,newY);
- }
- }
- }
- return walks;
-}
-
-public int movingCount(int threshold, int rows, int cols) {
- if(threshold<0){ //threshold<0,则机器人就不能走了
- return 0;
- }
- m = rows;
- if(m==0){
- return 0;
- }
- n= cols;
- visited = new boolean[m][n];
- return walk(threshold,0,0);
-}
-```
-
-
-
-## 4、被围绕的区域
-
-[被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions/)
-
-```java
-private int m,n;
-
-private boolean[][] visited;
-
-private int[][] d={
- {-1,0},
- {0,1},
- {1,0},
- {0,-1}
-};
-
-private boolean inArea(int x,int y){
- return (x>=0 && x=0 && y=0 && x=0 && y=matrix[startx][starty]){
- dfs(matrix,newX,newY,visited);
- }
- }
- }
-}
-
-public List> pacificAtlantic(int[][] matrix) {
- List> res=new ArrayList<>();
- m=matrix.length;
- if(m==0){
- return res;
- }
- n=matrix[0].length;
- pacific=new boolean[m][n];
- atlantic=new boolean[m][n];
-
- for(int j=0;j= 2,则表示未多个
-// 所以只需要 2 位就可表示字符出现的次数,即 0次(00)、1次(01)或者多次(11)
-
-public int FirstNotRepeatingChar(String str) {
- BitSet bitSet = new BitSet(256);
- BitSet bitSet2 = new BitSet(256);
-
- for(char c:str.toCharArray()){
- if(!bitSet.get(c) && !bitSet2.get(c)){ //00
- // c 原来出现次数 0次,加入 c 后出现次数为 1
- bitSet2.set(c);
- }
- else if(!bitSet.get(c) && bitSet2.get(c)){ //01
- // c 原来出现次数 1 次,加入 c 后出现次数为 3 (3 表示多次)
- bitSet.set(c);
- }
- }
-
- for(int i=0;i records=new HashMap<>();
- for(int i=0;i 则 就是错误的
- if(records.containsValue(s)){
- return false;
- }
- records.put(c,s);
- }
- }
- return true;
-}
-```
-
-
-
-## 3、洗牌算法
-
-基本思想是:每次从一组数中**随机选出一个数**,然后与最后一个数交换位置,并且不再考虑最后一个数。
-
-```java
-public class Shuffle {
-
- public void shuffle(int[] nums){
- Random random=new Random();
- for(int i=nums.length-1;i>=0;i--){
- //[0,i] 之中随机选择一个数
- int j=random.nextInt(i+1); // j 为[0,i] 中随机的下标
- swap(nums,i,j); // i 始终指向 [0,i] 的最后位置
- }
- }
-
- public void swap(int[] nums,int i,int j) {
- int tmp=nums[i];
- nums[i]=nums[j];
- nums[j]=tmp;
- }
-
- @Test
- public void test(){
- int[] nums={1,2,3,4,5,6,7,8,9,10};
- for(int i=0;i generatePocketByLineCutting(int n,double money){
- Random random=new Random();
-
- //如果是小数的话先转化为整数
- int newMoney=(int)money*100;
-
- //存储线段的的 (n-1) 个随机点,线段长度为 newMoney
- Set set=new TreeSet<>();
-
- while(set.size() res=new ArrayList<>();
-
- int pre=0;
- for(Integer p:set) {
- res.add((p - pre) * 1.0 / 100);
- pre = p;
- }
- return res;
- }
-
- @Test
- public void test(){
- int n=10;
- int money=100;
- List res=generatePocketByLineCutting(n,money);
- System.out.println(res);
- double sum=0.0;
- for(double d:res){
- sum+=d;
- }
- System.out.println(sum);
- }
-}
-```
-
-```html
-[21.53, 10.59, 7.63, 24.2, 1.5, 3.7, 0.03, 18.49, 10.38, 1.95]
-100.0
-```
-
diff --git "a/docs/AimForOffer/9_\345\233\236\346\272\257.md" "b/docs/AimForOffer/9_\345\233\236\346\272\257.md"
deleted file mode 100644
index 9248774a..00000000
--- "a/docs/AimForOffer/9_\345\233\236\346\272\257.md"
+++ /dev/null
@@ -1,591 +0,0 @@
-# 回溯
-
-## 1、二叉树中和为某一值的路径(39)
-
-[二叉树中和为某一值的路径](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
-
-```java
-public ArrayList> FindPath(TreeNode root, int target) {
- ArrayList> res = new ArrayList<>();
- ArrayList values = new ArrayList<>();
- backtrack(root,target,values,res);
- return res;
-}
-
-// values : 记录从根节点到叶子节点的所有路径
-// paths : 存储所有可能的结果
-private void backtrack(TreeNode root, int target,
- ArrayList values,
- ArrayList> paths) {
- if (root == null) {
- return;
- }
- values.add(root.val);
- if(root.left==null && root.right==null && root.val==target){
- paths.add(new ArrayList<>(values));
- }else{
- backtrack(root.left,target-root.val,values,paths);
- backtrack(root.right,target-root.val,values,paths);
- }
- values.remove(values.size()-1);
-}
-```
-
-
-
-## 2、矩阵中的路径(65)
-
-[矩阵中的路径](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
-
-```java
-//思路:典型的回溯法思想
-private int m,n; //矩阵的长度、宽度
-private boolean[][] visted; //标记是否访问
-
-private int[][] d={
- {0,1}, //向右
- {-1,0},//向上
- {0,-1},//向左
- {1,0} //向下
-};
-
-private boolean inArea(int x,int y){
- return (x>=0 && x=0 && y=0 && x=0 && y
-
-```java
-private List> res;
-
-private boolean[] visited;
-
-public List> permute(int[] nums) {
- res = new ArrayList<>();
- if(nums==null || nums.length==0){
- return res;
- }
- visited = new boolean[nums.length];
- List p =new ArrayList<>();
- generatePermutation(nums,0,p);
- return res;
-}
-
-//p中保存一个有 index 的元素的排列
-//向这个排列的末尾添加第 (index+1) 个元素,组成有(index+1) 个元素排列
-private void generatePermutation(int[] nums,int index,List p){
- if(index==nums.length){
- res.add(new ArrayList<>(p));
- return;
- }
- for(int i=0;i> res;
-
-private boolean[] visited;
-
-public List> permuteUnique(int[] nums) {
- res = new ArrayList<>();
- if(nums==null || nums.length==0){
- return res;
- }
- visited = new boolean[nums.length];
- Arrays.sort(nums);
- List p = new ArrayList<>();
- findUniquePermutation(nums,0,p);
- return res;
-}
-
-//p 保存的是有 index 元素的排列
-//向这个排列的末尾添加第 (index+1) 个元素,组成有(index+1) 个元素排列
-private void findUniquePermutation(int[] nums,int index,List p){
- if(index==nums.length){
- res.add(new ArrayList<>(p));
- return;
- }
- for(int i=0;i0 && nums[i-1]==nums[i] &&
- !visited[i-1]){ //注意:相邻元素相同,并且都未被访问
- continue;
- }
- visited[i]=true;
- p.add(nums[i]);
- findUniquePermutation(nums,index+1,p);
- p.remove(p.size()-1);
- visited[i]=false;
- }
- }
- return;
-}
-```
-
-
-
-## *7、字符串的排列
-
-[字符串的排列](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
-
-```java
-private ArrayList res;
-private boolean[] visited;
-
-public ArrayList Permutation(String str) {
- res = new ArrayList<>();
- if(str==null || str.length()==0){
- return res;
- }
- char[] chs = str.toCharArray();
- Arrays.sort(chs); //方便后面的去重处理
- visited = new boolean[str.length()];
- permute(chs,0,new StringBuilder());
- return res;
-}
-
-//产生排列
-//p中保存一个存在index个元素的排列
-//向这个排列的末尾添加第(index+1)个元素,获得包含(index+1)个元素的排列
-private void permute(char[] chs,int index,StringBuilder p){
- if(index==chs.length){
- res.add(p.toString());
- return;
- }
- for(int i=0;i0 && chs[i-1]==chs[i] && !visited[i-1]){
- continue;
- }
- if(!visited[i]){
- p.append(chs[i]);
- visited[i] = true;
- permute(chs,index+1,p);
- p.deleteCharAt(p.length()-1);
- visited[i] = false;
- }
- }
-}
-```
-
-
-
-## 8、字母大小写全排列
-
-[字母大小写全排列](https://leetcode-cn.com/problems/letter-case-permutation/)
-
-```java
-private List res;
-
-//处理 index 位置的数据
-//如果 index 位置是字母的话,则替换
-private void replaceLetter(int index,StringBuilder p){
- if(index==p.length()){
- res.add(p.toString());
- return;
- }
- char ch=p.charAt(index);
- if(Character.isLetter(ch)){
- p.setCharAt(index,Character.toUpperCase(ch));
- replaceLetter(index+1,p);
- p.setCharAt(index,Character.toLowerCase(ch));
- replaceLetter(index+1,p);
- }else{
- replaceLetter(index+1,p);
- }
- return;
-}
-
-public List letterCasePermutation(String S) {
- res=new ArrayList<>();
- if(S==null || S.length()==0){
- return res;
- }
- StringBuilder p=new StringBuilder(S);
- replaceLetter(0,p);
- return res;
-}
-```
-
-
-
-## 9、电话号码的字母组合
-
-[电话号码的字母组合](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/)
-
-```java
-private final String[] letterMap={
- " ",//0
- "",//1
- "abc",//2
- "def",//3
- "ghi",//4
- "jkl",//5
- "mno",//6
- "pqrs",//7
- "tuv",//8
- "wxyz"//9
-};
-
-private List res;
-//s 中保留了从 digits[0...index-1] 翻译得到的字符串
-//寻找和 digits[index] 匹配的字母,获取 digits[0...index] 翻译得到的解
-private void findCombination(String digits,int index,String s){
- if(index==digits.length()){
- res.add(s);
- return;
- }
-
- char c = digits.charAt(index);
- String letters = letterMap[c-'0']; // 翻译字符串
- for(int i=0;i letterCombinations(String digits) {
- res = new ArrayList<>();
- if(digits==null || digits.length()==0){
- return res;
- }
- findCombination(digits,0,"");
- return res;
-}
-```
-
-
-
-## 10、组合
-
-[组合](https://leetcode-cn.com/problems/combinations/)
-
-```java
-//思路一:暴力解法
-
-private List> res;
-
-//c存储已经找到的组合
-//从start开始搜索新的元素
-private void findCombination(int n,int k,int start,List c){
- if(k==c.size()){
- res.add(new ArrayList<>(c));
- return;
- }
- for(int i=start;i<=n;i++){
- c.add(i);
- findCombination(n,k,i+1,c);
- c.remove(c.size()-1);
- }
- return;
-}
-
-public List> combine(int n, int k) {
- res=new ArrayList<>();
- if(n<=0 || k<=0 || n c=new ArrayList<>();
- findCombination(n,k,1,c);
- return res;
-}
-```
-
-
-
-```java
-//思路二:优化,进行剪枝
-private List> res;
-
-//c存储已经找到的组合
-//从start开始搜索新的元素
-private void findCombination(int n,int k,int start,List c){
- if(k==c.size()){
- res.add(new ArrayList<>(c));
- return;
- }
-
- //TODO:优化--剪枝
- //c存储的是已经找到的组合。
- //此时还剩下k-c.size()个空位,
- //则 [i...n]之间的元素最少要有 k-c.size() 个,即 n-i+1 >= k-c.size()
- //所以有 i <= n-(k-c.size())+1
-
- for(int i=start;i<=n-(k-c.size())+1;i++){
- c.add(i);
- findCombination(n,k,i+1,c);
- c.remove(c.size()-1);
- }
- return;
-}
-
-public List> combine(int n, int k) {
- res=new ArrayList<>();
- if(n<=0 || k<=0 || n c=new ArrayList<>();
- findCombination(n,k,1,c);
- return res;
-}
-```
-
-
-
-## 11、N皇后
-
-[N皇后](https://leetcode-cn.com/problems/n-queens/)
-
-
-
-
-
-- 思路:
-
-
-
-
-
-判断对角线不合法的情况:
-
-(1)竖向:col[i] 表示第 i 列被占用
-
-(2)对角线1:dai1[i] 表示第 i 条对角线被1占用
-
-
-
-(3)对角线2:dai2[i]表示第i对角线被2占用
-
-
-
-```java
-private List> res;
-
-//用于判断是否在同一竖线上,因为index表示行数,是变化的,所以不用判断是否在相同行
-private boolean[] cols;
-//判断是否在1类对角线上,相应坐标 i+j
-private boolean[] dial1;
-//判断是否在2类对角线上,相应坐标 i-j+n-1
-private boolean[] dial2;
-
-//在 index 行放皇后
-//row 记录在 index 行能够放皇后的位置
-//比如 row.get(0) = 1,就表示在棋盘的 [0,1] 位置放上皇后
-private void putQueen(int n,int index,List row){
- if(index==n){
- res.add(generateBoard(n,row));
- return;
- }
- // [index,j] 放皇后
- for(int j=0;j generateBoard(int n, List row) {
- List res=new ArrayList<>();
- if(n<=0){
- return res;
- }
-
- char[][] board=new char[n][n];
- for(int i=0;i> solveNQueens(int n) {
- res=new ArrayList<>();
- if(n==0){
- return res;
- }
- cols=new boolean[n];
- dial1=new boolean[2*n-1];
- dial2=new boolean[2*n-1];
- List row=new ArrayList<>();
- putQueen(n,0,row);
- return res;
-}
-```
-
-
-
-## 12、N皇后 II
-
-[N皇后 II](https://leetcode-cn.com/problems/n-queens-ii/)
-
-```java
-
-```
-
-
-
-## 13、解数独
-
-[解数独](https://leetcode-cn.com/problems/sudoku-solver/)
-
-```java
-
-```
-
diff --git "a/docs/AimForOffer/1_\346\225\260\347\273\204.md" "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/1_\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265.md"
similarity index 91%
rename from "docs/AimForOffer/1_\346\225\260\347\273\204.md"
rename to "docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/1_\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265.md"
index 71bb11fe..6d56d3ec 100644
--- "a/docs/AimForOffer/1_\346\225\260\347\273\204.md"
+++ "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/1_\346\225\260\347\273\204\345\222\214\347\237\251\351\230\265.md"
@@ -1,6 +1,6 @@
-# 数组
+# 数组和矩阵
-## 1、二维数组中的查找(1)
+## 1、二维数组中的查找
[二维数组中的查找](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -30,7 +30,7 @@ public boolean Find(int target, int [][] array) {
-## 2、数组中重复的数字(2)
+## 2、数组中重复的数字
[数组中重复的数字](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -61,24 +61,41 @@ private void swap(int[] nums,int i,int j){
-## *3、构建乘积数组(3)
+## *3、构建乘积数组
[构建乘积数组](https://www.nowcoder.com/practice/94a4d381a68b47b7a8bed86f2975db46?tpId=13&tqId=11204&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
```java
+/**
+ 思路:
+ B0 | 1 A1 A2 A3 A4 ... A(n-2) A(n-1)
+ B1 | A0 1 A2 A3 A4 ... A(n-2) A(n-1)
+ B2 | A0 A1 1 A3 A4 ... A(n-2) A(n-1)
+ B3 | A0 A1 A2 1 A4 ... A(n-2) A(n-1)
+ B4 | A0 A1 A2 A3 1 ... A(n-2) A(n-1)
+ ...
+ B(n-2) | A0 A1 A2 A3 A4 ... 1 A(n-1)
+ B(n-1) | A0 A1 A2 A3 A4 ... A(n-2) 1
+*/
public int[] multiply(int[] A) {
- int n = A.length;
- int[] B = new int[n];
+ if(A==null || A.length==0){
+ return null;
+ }
+
+ int n=A.length;
+ int[] B=new int[n];
- //B[i] = A[0]*A[1]*...*A[i-1]
- int product =1;
- for(int i=0;i=0;product*=A[i],i--){
- B[i]*= product;
+
+ //计算右上角乘积
+ int product=1;
+ for(int i=n-2;i>=0;i--){
+ product*=A[i+1];
+ B[i]=B[i]*product; //最后是获取 B[0]=A[1]*A[2]*A[3]*...*A[n-1]
}
return B;
}
@@ -86,7 +103,7 @@ public int[] multiply(int[] A) {
-## *4、调整数组顺序使奇数位于偶数前面(28)
+## *4、调整数组顺序使奇数位于偶数前面
[调整数组顺序使奇数位于偶数前面](https://www.nowcoder.com/practice/beb5aa231adc45b2a5dcc5b62c93f593?tpId=13&tqId=11166&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -150,10 +167,6 @@ public void reOrderArray(int [] array) {
}
}
}
-
- for(int num : array){
- System.out.println(num);
- }
}
private void swap(int[] nums,int i,int j){
@@ -170,7 +183,7 @@ private boolean isEven(int num){
-## 5、顺时针打印矩阵(34)
+## 5、顺时针打印矩阵
[顺时针打印矩阵](https://www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a?tpId=13&tqId=11172&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -210,7 +223,7 @@ public ArrayList printMatrix(int [][] matrix) {
-## 6、连续子数组的最大和(45)
+## 6、连续子数组的最大和
[连续子数组的最大和](https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&tqId=11183&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -275,7 +288,7 @@ public int FindGreatestSumOfSubArray(int[] array) {
-## 7、把数组排成最小的数(47)
+## 7、把数组排成最小的数
[把数组排成最小的数](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -418,4 +431,3 @@ public boolean isContinuous(int [] numbers) {
return cnt>=0;
}
```
-
diff --git "a/docs/AimForOffer/2_\345\255\227\347\254\246\344\270\262.md" "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/2_\345\255\227\347\254\246\344\270\262.md"
similarity index 80%
rename from "docs/AimForOffer/2_\345\255\227\347\254\246\344\270\262.md"
rename to "docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/2_\345\255\227\347\254\246\344\270\262.md"
index e012d7d3..619b66af 100644
--- "a/docs/AimForOffer/2_\345\255\227\347\254\246\344\270\262.md"
+++ "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/2_\345\255\227\347\254\246\344\270\262.md"
@@ -1,6 +1,6 @@
# 字符串
-## 1、替换空格(4)
+## 1、替换空格
[替换空格](https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -36,7 +36,7 @@ public String replaceSpace(StringBuffer str) {
-## *2、正则表达式匹配(5)
+## *2、正则表达式匹配
[正则表达式匹配](https://www.nowcoder.com/practice/45327ae22b7b413ea21df13ee7d6429c?tpId=13&tqId=11205&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -98,7 +98,7 @@ private boolean match(char[] str,int strIndex,char[] pattern,int patternIndex){
-## 3、表示数值的字符串(6)
+## 3、表示数值的字符串
[表示数值的字符串](https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -131,7 +131,7 @@ public boolean isNumeric(char[] str) {
-## 4、字符流中第一个不重复的字符(7)
+## 4、字符流中第一个不重复的字符
[字符流中第一个不重复的字符](https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -188,7 +188,7 @@ public char FirstAppearingOnce(){
-## *5、字符串的排列(42)
+## *5、字符串的排列
[字符串的排列](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -234,7 +234,7 @@ private void permute(char[] chs,int index,StringBuilder p){
-## 6、把数组排成最小的数(47)
+## 6、把数组排成最小的数
[把数组排成最小的数](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -277,7 +277,7 @@ public String PrintMinNumber(int [] numbers) {
-## 7、把字符串转换成整数(64)
+## 7、把字符串转换成整数
[把字符串转换成整数](https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&tqId=11202&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -384,76 +384,111 @@ private void reverse(char[] chs,int start,int end){
}
```
+## *10、实现 strStr()
+[实现 strStr()](https://leetcode-cn.com/problems/implement-strstr/)
-## *10、最长不含重复字符的子字符串
+思路一:朴素模式匹配算法
-[最长不含重复字符的子字符串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/)
+最朴素的方法就是依次从待匹配串的每一个位置开始,逐一与模版串匹配,
+因为最多检查 (n - m)个位置,所以方法的复杂度为 O(m*(n-1))。
+
+
```java
-//思路:
-// 使用滑动窗口 [l,r]
-// 在滑动窗口中的元素是不会重复的
+//haystack 是待匹配串
+//needle 是模板串
+public int strStr(String haystack, String needle) {
+ if(needle==null || needle.length()==0){
+ return 0;
+ }
+ int i=0,j=0;
+ //k存储的是模板串在待匹配串的位置
+ int k=i;
+ while(i=0 || j>=0){
- c+=(i>=0)?chs1[i--]-'0':0;
- c+=(j>=0)?chs2[j--]-'0':0;
- res.append(c%10);
- c/=10;
+ //说明模板串是待匹配串的子串
+ if(j==needle.length()){
+ //返回的是待匹配串的下标
+ return i-needle.length();
}
- if(c==1){
- res.append(1);
+ return -1;
+}
+
+private int[] getNext(String needle) {
+ int[] next=new int[needle.length()];
+ next[0]=-1;
+ int j=0,t=-1;
+ while (j printListFromTailToHead(ListNode listNode) {
-## *2、链表中环的入口结点(9)
+## *2、链表中环的入口结点
[链表中环的入口结点](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -128,7 +128,7 @@ public ListNode removeElements(ListNode head, int val) {
-## *4、删除链表中重复的结点(10)
+## *4、删除链表中重复的结点
[删除链表中重复的结点](https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -171,7 +171,7 @@ public ListNode deleteDuplication(ListNode pHead) {
-## 5、链表中倒数第 k 个结点(29)
+## 5、链表中倒数第 k 个结点
[链表中倒数第 k 个结点](https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -202,7 +202,19 @@ public ListNode FindKthToTail(ListNode head, int k) {
-## 6、反转链表(30)
+## 6、删除链表的倒数第N个节点
+
+[删除链表的倒数第N个节点](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/)
+
+```java
+
+```
+
+
+
+
+
+## 7、反转链表
[反转链表](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -250,7 +262,7 @@ public ListNode ReverseList(ListNode head) {
-## 7、合并两个排序的链表(31)
+## 8、合并两个排序的链表
[合并两个排序的链表](https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -331,7 +343,7 @@ public ListNode Merge(ListNode list1, ListNode list2) {
-## 8、二叉搜索树与双向链表(41)
+## 9、二叉搜索树与双向链表
[二叉搜索树与双向链表](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -374,7 +386,7 @@ private void inOrder(TreeNode node){
-## 9、两个链表的第一个公共结点(51)
+## 10、两个链表的第一个公共结点
[两个链表的第一个公共结点](https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&tqId=11189&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -413,7 +425,7 @@ public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
-## 10、复杂链表的复制
+## 11、复杂链表的复制
[复杂链表的复制](https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&tqId=11178&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -478,7 +490,7 @@ public RandomListNode Clone(RandomListNode pHead) {
-## 11、在 O(1) 时间删除链表节点
+## 12、在 O(1) 时间删除链表节点
```java
//思路:
@@ -515,3 +527,7 @@ public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
}
```
+
+
+
+
diff --git "a/docs/AimForOffer/5_\346\240\221.md" "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/4_\346\240\221.md"
similarity index 97%
rename from "docs/AimForOffer/5_\346\240\221.md"
rename to "docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/4_\346\240\221.md"
index 4b951678..a49dc51d 100644
--- "a/docs/AimForOffer/5_\346\240\221.md"
+++ "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/4_\346\240\221.md"
@@ -1,6 +1,6 @@
# 树
-## 1、重建二叉树(11)
+## 1、重建二叉树
[重建二叉树](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -34,7 +34,7 @@ private TreeNode reConstructBinaryTree(int[] pre,int[] in,
-## 2、二叉树的下一个结点(12)
+## 2、二叉树的下一个结点
[二叉树的下一个结点](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -79,7 +79,7 @@ public TreeLinkNode GetNext(TreeLinkNode pNode){
-## 3、对称的二叉树(13)
+## 3、对称的二叉树
[对称的二叉树](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -109,7 +109,7 @@ private boolean isSymmetrical(TreeNode p,TreeNode q){
-## 4、按之字形顺序打印二叉树(14)
+## 4、按之字形顺序打印二叉树
[按之字形顺序打印二叉树](https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&tqId=11212&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -153,7 +153,7 @@ public ArrayList> Print(TreeNode pRoot) {
-## *5、把二叉树打印成多行(15)
+## *5、把二叉树打印成多行
[把二叉树打印成多行](https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -192,7 +192,7 @@ ArrayList> Print(TreeNode pRoot) {
-## *6、序列化二叉树(16)
+## *6、序列化二叉树
[序列化二叉树](https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -238,7 +238,7 @@ private TreeNode Deserialize(){
-## *7、树的子结构(32)
+## *7、树的子结构
[树的子结构](https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&tqId=11170&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -279,7 +279,7 @@ private boolean isSubtree(TreeNode root1,TreeNode root2){
-## 8、从上往下打印二叉树(37)
+## 8、从上往下打印二叉树
[从上往下打印二叉树](https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -312,7 +312,7 @@ public ArrayList PrintFromTopToBottom(TreeNode root) {
-## 9、二叉树的深度(53)
+## 9、二叉树的深度
[二叉树的深度](https://www.nowcoder.com/practice/435fb86331474282a3499955f0a41e8b?tpId=13&tqId=11191&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -424,7 +424,7 @@ private String buildPath(List values){
-## 13、二叉树中和为某一值的路径(39)
+## 13、二叉树中和为某一值的路径
[二叉树中和为某一值的路径](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -481,7 +481,7 @@ private void swap(TreeNode root){
-## 15、平衡二叉树(54)
+## *15、平衡二叉树
[平衡二叉树](https://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId=13&tqId=11192&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -631,7 +631,7 @@ private void inOrder(TreeNode pRoot,int k){
-## *19、二叉搜索树的后序遍历序列(38)
+## *19、二叉搜索树的后序遍历序列
[二叉搜索树的后序遍历序列](https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&tqId=11176&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -722,7 +722,7 @@ private boolean verify(int[] squence,int first,int last){
-## *20、二叉搜索树与双向链表(41)
+## *20、二叉搜索树与双向链表
[二叉搜索树与双向链表](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
diff --git "a/docs/AimForOffer/6_\346\240\210.md" "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/5_\346\240\210.md"
similarity index 63%
rename from "docs/AimForOffer/6_\346\240\210.md"
rename to "docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/5_\346\240\210.md"
index a77eb867..1bfbef76 100644
--- "a/docs/AimForOffer/6_\346\240\210.md"
+++ "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/5_\346\240\210.md"
@@ -1,6 +1,6 @@
# 栈
-## 1、用两个栈实现队列(19)
+## 1、用两个栈实现队列
[用两个栈实现队列](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -24,7 +24,7 @@ public int pop() {
-## *2、包含 min 函数的栈(35)
+## *2、包含 min 函数的栈
[包含 min 函数的栈](https://www.nowcoder.com/practice/4c776177d2c04c2494f2555c9fcc1e49?tpId=13&tqId=11173&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -58,7 +58,7 @@ public int min() {
-## *3、栈的压入、弹出序列(36)
+## *3、栈的压入、弹出序列
[栈的压入、弹出序列](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&tqId=11174&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -80,3 +80,42 @@ public boolean IsPopOrder(int [] pushA,int [] popA) {
}
```
+
+
+## 4、有效的括号
+
+[有效的括号](https://leetcode-cn.com/problems/valid-parentheses/)
+
+```java
+public boolean isValid(String s) {
+ if(s.length()==0){
+ return true;
+ }
+ Stack stack=new Stack<>();
+ for(int i=0;i queue = new LinkedList<>();
+//Insert one char from stringstream
+public void Insert(char ch) {
+ freq[ch]++;
+ queue.offer(ch);
+
+ //队列中只存储出现一次的字符
+ while (!queue.isEmpty() && freq[queue.peek()]>1){
+ queue.poll();
+ }
+}
+//return the first appearence once char in current stringstream
+public char FirstAppearingOnce(){
+ return queue.isEmpty()? '#':queue.peek();
+}
+```
diff --git "a/docs/AimForOffer/14_\345\240\206.md" "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/7_\345\240\206.md"
similarity index 96%
rename from "docs/AimForOffer/14_\345\240\206.md"
rename to "docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/7_\345\240\206.md"
index 8521dfd4..c5f503d0 100644
--- "a/docs/AimForOffer/14_\345\240\206.md"
+++ "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/7_\345\240\206.md"
@@ -1,6 +1,6 @@
# 堆
-## 1、字符流中第一个不重复的字符(7)
+## 1、字符流中第一个不重复的字符
[字符流中第一个不重复的字符](https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -27,7 +27,7 @@ public char FirstAppearingOnce(){
-## 2、数据流中的中位数(18)
+## 2、数据流中的中位数
[数据流中的中位数](https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&tqId=11216&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -70,7 +70,7 @@ public Double GetMedian() {
-## 3、滑动窗口的最大值(20)
+## 3、滑动窗口的最大值
[滑动窗口的最大值](https://www.nowcoder.com/practice/1624bc35a45c42c0bc17d17fa0cba788?tpId=13&tqId=11217&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -139,7 +139,7 @@ public ArrayList maxInWindows(int [] num, int size){
-## 4、最小的 k 个数(44)
+## 4、最小的 k 个数
[最小的 k 个数](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
diff --git "a/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/8_\345\223\210\345\270\214.md" "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/8_\345\223\210\345\270\214.md"
new file mode 100644
index 00000000..f386aaa9
--- /dev/null
+++ "b/docs/AimForOffer/\346\225\260\346\215\256\347\273\223\346\236\204\347\233\270\345\205\263/8_\345\223\210\345\270\214.md"
@@ -0,0 +1,58 @@
+# 哈希
+
+## 1、第一个只出现一次的字符位置
+
+[第一个只出现一次的字符位置](https://www.nowcoder.com/practice/1c82e8cf713b4bbeb2a5b31cf5b0417c?tpId=13&tqId=11187&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+//思路一:
+public int FirstNotRepeatingChar(String str) {
+ int[] freq = new int[256];
+
+ for(char c:str.toCharArray()){
+ freq[c]++;
+ }
+
+ for(int i=0;i= 2,则表示未多个
+// 所以只需要 2 位就可表示字符出现的次数,即 0次(00)、1次(01)或者多次(11)
+
+public int FirstNotRepeatingChar(String str) {
+ BitSet bitSet = new BitSet(256);
+ BitSet bitSet2 = new BitSet(256);
+
+ for(char c:str.toCharArray()){
+ if(!bitSet.get(c) && !bitSet2.get(c)){ //00
+ // c 原来出现次数 0次,加入 c 后出现次数为 1
+ bitSet2.set(c);
+ }
+ else if(!bitSet.get(c) && bitSet2.get(c)){ //01
+ // c 原来出现次数 1 次,加入 c 后出现次数为 3 (3 表示多次)
+ bitSet.set(c);
+ }
+ }
+
+ for(int i=0;i arr[j]
+//则 arr[i] 后面的元素都会大于 arr[j],即 [i,mid] 的长度就是逆序对数
+
+private long P; //逆序对数
+
+public int InversePairs(int [] array) {
+ sort(array,0,array.length-1);
+ return (int)(P%1000000007);
+}
+
+private void sort(int[] arr,int l,int r){
+ if(l>=r){
+ return;
+ }
+ int m=(r-l)/2+l;
+ sort(arr,l,m);
+ sort(arr,m+1,r);
+ merge(arr,l,m,r);
+}
+
+//合并 [l,m] 和 [m+1,r] 两个有序数组
+private void merge(int[] arr,int l,int m,int r){
+ int[] newArr = new int[r-l+1];
+ int index =0;
+ int i=l;
+ int j=m+1;
+ while(i<=m && j<=r){
+ if(arr[i]<=arr[j]){
+ newArr[index++]=arr[i++];
+ }else{ //arr[i]>arr[j],arr[i]后面的元素都会大于 arr[j]
+ P+=(m-i+1);
+ newArr[index++]=arr[j++];
+ }
+ }
+ while(i<=m){
+ newArr[index++]=arr[i++];
+ }
+ while(j<=r){
+ newArr[index++]=arr[j++];
+ }
+ index=0;
+ for(int k=l;k<=r;k++){
+ arr[k]=newArr[index++];
+ }
+}
+```
+
+
+
+## 2、最小的 k 个数
+
+[最小的 k 个数](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
+ ArrayList res = new ArrayList<>();
+ if(k<=0 || k>input.length){
+ return res;
+ }
+ int index = findKthSmallest(input,k-1);
+ if(index==-1){
+ return res;
+ }
+
+ for(int i=0;i<=index;i++){
+ res.add(input[i]);
+ }
+ return res;
+}
+
+//查找数组中第 k 小的元素
+private int findKthSmallest(int[] input,int k){
+ int l=0,r=input.length-1;
+ while (l<=r){
+ int p = partion(input,l,r);
+ if(p==k){
+ return p;
+ }else if(pk;
+ r = p-1;
+ }
+ }
+ return -1;
+}
+
+//使用快速排序中的切分思路
+private int partion(int[] nums,int start,int end){
+ int pivot = nums[start];
+
+ while (start=pivot){
+ end--;
+ }
+ nums[start]=nums[end];
+ while(start=lcs[i][j-1]){
- lcs[i][j]=lcs[i-1][j];
- x[i][j]='u';
- }else if(lcs[i-1][j]lcs[i][j-1]){
+ lcs[i][j]=lcs[i-1][j];
+ x[i][j]='u';
+ }
}
}
}
- return lcs[m][n];
+ print(s,m,n);
+ return res.toString();
}
-public void print(String s,int m,int n){
- if(m==0 || n==0){
+private void print(String s,int m,int n){
+ if(s==null || s.length()==0){
return;
}
if(x[m][n]=='c'){
@@ -615,3 +619,138 @@ public int minDistance(String word1, String word2) {
}
```
+
+
+## 15、最长上升子序列
+
+[最长上升子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/)
+
+```java
+//思路一:
+//什么是子序列?不包括该序列本身
+//什么是上升?后一个元素大于前一个元素
+//一个序列可能有多个最长上升子序列?但是最长长度是唯一的。
+
+//动态规划思路:dp[i] 表示以 nums[i] 结尾的最长上升子序列
+
+//时间复杂度:O(n^2)
+public int lengthOfLIS(int[] nums) {
+ int n=nums.length;
+ if(n==0){
+ return 0;
+ }
+
+ //dp[i] 表示以 nums[i] 结尾的最长上升子序列
+ //最长上升子序列的最小长度是 1
+ //if(nums[i]>nums[j])
+ // LIS(i)=max(jnums[j]){ //上升子序列
+ dp[i]=Math.max(dp[i],dp[j]+1);
+ }
+ }
+ }
+
+ int res=1; //最长上升子序列的最小长度是 1
+ for(int i=0;ikey){
+ r=mid;
+ }else{
+ assert tails[mid]=0 && j<=N;j++){
+ if(dp[i-j*j]> FindPath(TreeNode root, int target) {
+ ArrayList> res = new ArrayList<>();
+ ArrayList values = new ArrayList<>();
+ backtrack(root,target,values,res);
+ return res;
+}
+
+// values : 记录从根节点到叶子节点的所有路径
+// paths : 存储所有可能的结果
+private void backtrack(TreeNode root, int target,
+ ArrayList values,
+ ArrayList> paths) {
+ if (root == null) {
+ return;
+ }
+ values.add(root.val);
+ if(root.left==null && root.right==null && root.val==target){
+ paths.add(new ArrayList<>(values));
+ }else{
+ backtrack(root.left,target-root.val,values,paths);
+ backtrack(root.right,target-root.val,values,paths);
+ }
+ values.remove(values.size()-1);
+}
+```
+
+
+
+## *2、二叉树的所有路径
+
+[二叉树的所有路径](https://leetcode-cn.com/problems/binary-tree-paths/)
+
+```java
+public List binaryTreePaths(TreeNode root) {
+ List paths=new ArrayList<>();
+ if(root==null){
+ return paths;
+ }
+ List values=new ArrayList<>();
+ backtrack(root,values,paths);
+ return paths;
+}
+
+//values 记录从根节点到叶子节点
+//paths 记录路径
+private void backtrack(TreeNode root, List values,List paths){
+ if(root==null){
+ return;
+ }
+ values.add(root.val);
+ if(root.left==null && root.right==null){ // 到达叶子节点
+ paths.add(buildPath(values));
+ }else{
+ backtrack(root.left,values,paths);
+ backtrack(root.right,values,paths);
+ }
+ values.remove(values.size()-1);
+}
+
+private String buildPath(List values){
+ StringBuilder res=new StringBuilder();
+ for(int i=0;i");
+ }
+ }
+ return res.toString();
+}
+```
+
+
+
+## *3、矩阵中的路径
+
+[矩阵中的路径](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+//思路:典型的回溯法思想
+private int m,n; //矩阵的长度、宽度
+private boolean[][] visted; //标记是否访问
+
+private int[][] d={
+ {0,1}, //向右
+ {-1,0},//向上
+ {0,-1},//向左
+ {1,0} //向下
+};
+
+private boolean inArea(int x,int y){
+ return (x>=0 && x=0 && y=0 && x=0 && y
+
+
+
+- 思路:
+
+
+
+
+
+在第 i 行放上皇后,判断是否其他地方能够放皇后的情况:
+
+(1)竖向:col[j] 表示第 j 列被占用
+
+(2)对角线1:dai1[m] 表示第 m 条对角线1被占用,如 dia1[0]=true 表示[0,0] 位置不能放皇后,
+
+dia1[1] =true 就表示 [0,1]和[1,0] 位置不能放皇后。
+
+对角线1总共有 (2*n-1) 条;m 与 i 、 j 的关系:m=i+j。
+
+
+
+(3)对角线2:dai2[m]表示第 m 条对角线被2占用,如 dai2[0]=true 表示[0,3] 位置不能放皇后,
+
+dai2[2]=true 则表示 [0,2] 和 [1,3] 不能放皇后,
+
+对角线2总共有(2*n-1)条;m 与 i、j 关系:m=i-j+n-1。
+
+
+
+```java
+private List> res;
+
+//用于判断是否在同一竖线上,因为index表示行数,是变化的,所以不用判断是否在相同行
+private boolean[] cols;
+//判断是否在1类对角线上,相应坐标 i+j
+private boolean[] dial1;
+//判断是否在2类对角线上,相应坐标 i-j+n-1
+private boolean[] dial2;
+
+//在 index 行放皇后
+//row 记录在 index 行能够放皇后的位置
+//比如 row.get(0) = 1,就表示在棋盘的 [0,1] 位置放上皇后
+private void putQueen(int n,int index,List row){
+ if(index==n){
+ res.add(generateBoard(n,row));
+ return;
+ }
+ // [index,j] 放皇后
+ for(int j=0;j generateBoard(int n, List row) {
+ List res=new ArrayList<>();
+ if(n<=0){
+ return res;
+ }
+
+ char[][] board=new char[n][n];
+ for(int i=0;i> solveNQueens(int n) {
+ res=new ArrayList<>();
+ if(n==0){
+ return res;
+ }
+ cols=new boolean[n];
+ dial1=new boolean[2*n-1];
+ dial2=new boolean[2*n-1];
+ List row=new ArrayList<>();
+ putQueen(n,0,row);
+ return res;
+}
+```
+
+
+
+## 6、解数独
+
+[解数独](https://leetcode-cn.com/problems/sudoku-solver/)
+
+```java
+// 思路:回溯法
+// 数独矩阵是 9*9 的方阵,也就是有 81 个位置,
+// 可以看成是一个长度为 81 的数组,使用 pos(从0开始) 记录在数组中位置
+// row[i][m] 表示第 i 行填数字 m 的占用情况,即 row[i][m]=true,表示在第i行填上数字 m 了。
+// col[i][m] 表示第 i 列填数字 m 的占用情况,即 col[i][m]=true,表示在第i列填上数字 m 了。
+// block[i][m] 表示第 i个九宫格填数字 m 的占用情况
+public void solveSudoku(char[][] board) {
+ if(board==null){
+ return;
+ }
+ boolean[][] row=new boolean[9][10];
+ boolean[][] col=new boolean[9][10];
+ boolean[][] block=new boolean[9][10];
+ for(int i=0;i<9;i++){
+ for(int j=0;j<9;j++){
+ if(board[i][j]!='.'){ // [i,j]位置已填上数字
+ int num= board[i][j]-'0';
+ row[i][num]=true;
+ col[j][num]=true;
+ block[i/3*3+j/3][num]=true;
+ }
+ }
+ }
+ for(int pos=0;pos<81;pos++){
+ int x=pos/9;
+ int y=pos%9;
+ if(board[x][y]=='.'){
+ if(!putNum(board,pos,row,col,block)){
+ continue;
+ }
+ }
+ }
+}
+
+// 向 pos 位置放数字
+private boolean putNum(char[][] board, int pos,
+ boolean[][] row, boolean[][] col, boolean[][] block){
+ if(pos==81){
+ return true;
+ }
+ int nextPos = pos+1; // pos 位置的下一未填数字的位置
+ for(;nextPos<81;nextPos++){
+ if(board[nextPos/9][nextPos%9]=='.'){
+ break;
+ }
+ }
+ // [x,y] 表示 pos 在表格中位置
+ int x=pos/9;
+ int y=pos%9;
+
+ for(int num=1;num<=9;num++){ // pos 位置可以填 1-9 任意整数
+ if(!row[x][num] && !col[y][num] && !block[x/3*3+y/3][num]){
+ board[x][y]=(char)(num+'0');
+ row[x][num]=true;
+ col[y][num]=true;
+ block[x/3*3+y/3][num]=true;
+ if(putNum(board,nextPos,row,col,block)){
+ return true;
+ }
+ block[x/3*3+y/3][num]=false;
+ col[y][num]=false;
+ row[x][num]=false;
+ board[x][y]='.';
+ }
+ }
+ return false;
+}
+```
+
+
+
+# 广度优先-BFS
+
+## 1、单词接龙
+
+[单词接龙](https://leetcode-cn.com/problems/word-ladder/description/)
+
+```java
+public int ladderLength(String beginWord, String endWord, List wordList) {
+ wordList.add(beginWord);
+ int N = wordList.size();
+ int start = N - 1;
+ int end = 0;
+ while (end < N && !wordList.get(end).equals(endWord)) {
+ end++;
+ }
+ if (end == N) {
+ return 0;
+ }
+ List[] graphic = buildGraphic(wordList);
+ return getShortestPath(graphic, start, end);
+}
+
+private List[] buildGraphic(List wordList) {
+ int N = wordList.size();
+ List[] graphic = new List[N];
+ for (int i = 0; i < N; i++) {
+ graphic[i] = new ArrayList<>();
+ for (int j = 0; j < N; j++) {
+ if (isConnect(wordList.get(i), wordList.get(j))) {
+ graphic[i].add(j);
+ }
+ }
+ }
+ return graphic;
+}
+
+private boolean isConnect(String s1, String s2) {
+ int diffCnt = 0;
+ for (int i = 0; i < s1.length() && diffCnt <= 1; i++) {
+ if (s1.charAt(i) != s2.charAt(i)) {
+ diffCnt++;
+ }
+ }
+ return diffCnt == 1;
+}
+
+private int getShortestPath(List[] graphic, int start, int end) {
+ Queue queue = new LinkedList<>();
+ boolean[] marked = new boolean[graphic.length];
+ queue.add(start);
+ marked[start] = true;
+ int path = 1;
+ while (!queue.isEmpty()) {
+ int size = queue.size();
+ path++;
+ while (size-- > 0) {
+ int cur = queue.poll();
+ for (int next : graphic[cur]) {
+ if (next == end) {
+ return path;
+ }
+ if (marked[next]) {
+ continue;
+ }
+ marked[next] = true;
+ queue.add(next);
+ }
+ }
+ }
+ return 0;
+}
+```
+
+
+
+# 深度优先-DFS
+
+## 1、岛屿数量
+
+[岛屿数量](https://leetcode-cn.com/problems/number-of-islands/)
+
+```java
+// 思路:典型的 flood-fill 算法
+private int m,n;
+
+private boolean[][] visited;
+
+private boolean inArea(int x,int y){
+ return (x>=0 && x=0 && y=0 && x=0 && y=0 && x=0 && y0){
+ sum += num %10;
+ num/=10;
+ }
+ return sum;
+}
+
+private boolean valid(int threshlod ,int x,int y){
+ return (getNum(x)+getNum(y))<=threshlod;
+}
+
+private int walk(int threshold,int startx,int starty){
+ visited[startx][starty]=true;
+ int walks = 0;
+ if(valid(threshold,startx,starty)){
+ walks=1;
+ }
+ for(int i=0;i<4;i++){
+ int newX = startx+d[i][0];
+ int newY = starty+d[i][1];
+ if(inArea(newX,newY)){
+ if(!visited[newX][newY] && valid(threshold,newX,newY)){
+ walks += walk(threshold,newX,newY);
+ }
+ }
+ }
+ return walks;
+}
+
+public int movingCount(int threshold, int rows, int cols) {
+ if(threshold<0){ //threshold<0,则机器人就不能走了
+ return 0;
+ }
+ m = rows;
+ if(m==0){
+ return 0;
+ }
+ n= cols;
+ visited = new boolean[m][n];
+ return walk(threshold,0,0);
+}
+```
+
+
+
+## 4、被围绕的区域
+
+[被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions/)
+
+```java
+private int m,n;
+
+private boolean[][] visited;
+
+private int[][] d={
+ {-1,0},
+ {0,1},
+ {1,0},
+ {0,-1}
+};
+
+private boolean inArea(int x,int y){
+ return (x>=0 && x=0 && y=0 && x=0 && y=matrix[startx][starty]){
+ dfs(matrix,newX,newY,visited);
+ }
+ }
+ }
+}
+
+public List> pacificAtlantic(int[][] matrix) {
+ List> res=new ArrayList<>();
+ m=matrix.length;
+ if(m==0){
+ return res;
+ }
+ n=matrix[0].length;
+ pacific=new boolean[m][n];
+ atlantic=new boolean[m][n];
+
+ for(int j=0;j
+
+```java
+private List> res;
+
+private boolean[] visited;
+
+public List> permute(int[] nums) {
+ res = new ArrayList<>();
+ if(nums==null || nums.length==0){
+ return res;
+ }
+ visited = new boolean[nums.length];
+ List p =new ArrayList<>();
+ generatePermutation(nums,0,p);
+ return res;
+}
+
+//p中保存一个有 index 的元素的排列
+//向这个排列的末尾添加第 (index+1) 个元素,组成有(index+1) 个元素排列
+private void generatePermutation(int[] nums,int index,List p){
+ if(index==nums.length){
+ res.add(new ArrayList<>(p));
+ return;
+ }
+ for(int i=0;i> res;
+
+private boolean[] visited;
+
+public List> permuteUnique(int[] nums) {
+ res = new ArrayList<>();
+ if(nums==null || nums.length==0){
+ return res;
+ }
+ visited = new boolean[nums.length];
+ Arrays.sort(nums);
+ List p = new ArrayList<>();
+ findUniquePermutation(nums,0,p);
+ return res;
+}
+
+//p 保存的是有 index 元素的排列
+//向这个排列的末尾添加第 (index+1) 个元素,组成有(index+1) 个元素排列
+private void findUniquePermutation(int[] nums,int index,List p){
+ if(index==nums.length){
+ res.add(new ArrayList<>(p));
+ return;
+ }
+ for(int i=0;i0 && nums[i-1]==nums[i] &&
+ !visited[i-1]){ //注意:相邻元素相同,并且都未被访问
+ continue;
+ }
+ visited[i]=true;
+ p.add(nums[i]);
+ findUniquePermutation(nums,index+1,p);
+ p.remove(p.size()-1);
+ visited[i]=false;
+ }
+ }
+ return;
+}
+```
+
+
+
+## 3、字符串的排列
+
+[字符串的排列](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+private ArrayList res;
+private boolean[] visited;
+
+public ArrayList Permutation(String str) {
+ res = new ArrayList<>();
+ if(str==null || str.length()==0){
+ return res;
+ }
+ char[] chs = str.toCharArray();
+ Arrays.sort(chs); //方便后面的去重处理
+ visited = new boolean[str.length()];
+ permute(chs,0,new StringBuilder());
+ return res;
+}
+
+//产生排列
+//p中保存一个存在index个元素的排列
+//向这个排列的末尾添加第(index+1)个元素,获得包含(index+1)个元素的排列
+private void permute(char[] chs,int index,StringBuilder p){
+ if(index==chs.length){
+ res.add(p.toString());
+ return;
+ }
+ for(int i=0;i0 && chs[i-1]==chs[i] && !visited[i-1]){
+ continue;
+ }
+ if(!visited[i]){
+ p.append(chs[i]);
+ visited[i] = true;
+ permute(chs,index+1,p);
+ p.deleteCharAt(p.length()-1);
+ visited[i] = false;
+ }
+ }
+}
+```
+
+
+
+## *4、字母大小写全排列
+
+[字母大小写全排列](https://leetcode-cn.com/problems/letter-case-permutation/)
+
+```java
+private List res;
+
+//处理 index 位置的数据
+//如果 index 位置是字母的话,则替换
+private void replaceLetter(int index,StringBuilder p){
+ if(index==p.length()){
+ res.add(p.toString());
+ return;
+ }
+ char ch=p.charAt(index);
+ if(Character.isLetter(ch)){
+ p.setCharAt(index,Character.toUpperCase(ch));
+ replaceLetter(index+1,p);
+ p.setCharAt(index,Character.toLowerCase(ch));
+ replaceLetter(index+1,p);
+ }else{
+ replaceLetter(index+1,p);
+ }
+ return;
+}
+
+public List letterCasePermutation(String S) {
+ res=new ArrayList<>();
+ if(S==null || S.length()==0){
+ return res;
+ }
+ StringBuilder p=new StringBuilder(S);
+ replaceLetter(0,p);
+ return res;
+}
+```
+
+
+
+## *5、电话号码的字母组合
+
+[电话号码的字母组合](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/)
+
+```java
+private final String[] letterMap={
+ " ",//0
+ "",//1
+ "abc",//2
+ "def",//3
+ "ghi",//4
+ "jkl",//5
+ "mno",//6
+ "pqrs",//7
+ "tuv",//8
+ "wxyz"//9
+};
+
+private List res;
+
+public List letterCombinations(String digits) {
+ res=new ArrayList<>();
+ if(digits==null || digits.length()==0){
+ return res;
+ }
+ generateLetterCombinations(digits,0,new StringBuilder());
+ return res;
+}
+
+// p 保存 digits[0,index-1] 翻译得到的字符串
+private void generateLetterCombinations(String digits,int index,StringBuilder p){
+ if(index==digits.length()){
+ res.add(p.toString());
+ return;
+ }
+ char[] chs=letterMap[digits.charAt(index)-'0'].toCharArray();
+ for(int i=0;i> res;
+
+//c存储已经找到的组合
+//从start开始搜索新的元素
+private void findCombination(int n,int k,int start,List c){
+ if(k==c.size()){
+ res.add(new ArrayList<>(c));
+ return;
+ }
+ for(int i=start;i<=n;i++){
+ c.add(i);
+ findCombination(n,k,i+1,c);
+ c.remove(c.size()-1);
+ }
+ return;
+}
+
+public List> combine(int n, int k) {
+ res=new ArrayList<>();
+ if(n<=0 || k<=0 || n c=new ArrayList<>();
+ findCombination(n,k,1,c);
+ return res;
+}
+```
+
+```java
+//思路二:剪枝优化
+private List> res;
+
+//c存储已经找到的组合
+//从start开始搜索新的元素
+private void findCombination(int n,int k,int start,List c){
+ if(k==c.size()){
+ res.add(new ArrayList<>(c));
+ return;
+ }
+
+ //优化--剪枝
+ //c存储的是已经找到的组合。
+ //此时还剩下k-c.size()个空位,
+ //则 [i...n]之间的元素最少要有 k-c.size() 个,即 n-i+1 >= k-c.size()
+ //所以有 i <= n-(k-c.size())+1
+
+ for(int i=start;i<=n-(k-c.size())+1;i++){
+ c.add(i);
+ findCombination(n,k,i+1,c);
+ c.remove(c.size()-1);
+ }
+ return;
+}
+
+public List> combine(int n, int k) {
+ res=new ArrayList<>();
+ if(n<=0 || k<=0 || n c=new ArrayList<>();
+ findCombination(n,k,1,c);
+ return res;
+}
+```
+
diff --git "a/docs/AimForOffer/\347\256\227\346\263\225\346\200\235\346\203\263\347\233\270\345\205\263/6_\350\264\252\345\277\203.md" "b/docs/AimForOffer/\347\256\227\346\263\225\346\200\235\346\203\263\347\233\270\345\205\263/6_\350\264\252\345\277\203.md"
new file mode 100644
index 00000000..b9735576
--- /dev/null
+++ "b/docs/AimForOffer/\347\256\227\346\263\225\346\200\235\346\203\263\347\233\270\345\205\263/6_\350\264\252\345\277\203.md"
@@ -0,0 +1,289 @@
+# 贪心思想
+
+## 1、连续子数组的最大和
+
+[连续子数组的最大和](https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&tqId=11183&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+//思路:
+//贪心策略:遍历数组,到当前位置,上次保留的最大值如果为负数就要刷新一次,
+// 即 curSum<=0时,当即果断丢弃,因为只会越加越小,刷新为当前值;
+// 如果为正数,继续累加。
+
+public int FindGreatestSumOfSubArray(int[] array) {
+ if (array == null || array.length == 0){
+ return 0;
+ }
+ int curSum = 0;
+ int res= Integer.MIN_VALUE;
+ for(int i=0;i=0 && j>=0){
+ if(s[j]>=g[i]){
+ num++;
+ j--;
+ }
+ i--;
+ }
+ return num;
+}
+```
+
+
+
+## 4、救生艇
+
+[救生艇](https://leetcode-cn.com/problems/boats-to-save-people/)
+
+```java
+// 思路:
+// 贪心策略:
+// 将最重的人和最轻的安排在同一艘船上
+// 如果只能载一个人,则载最重的,如果剩下一个人,则只能单独趁一艘船。
+public int numRescueBoats(int[] people, int limit) {
+ if(people==null || people.length==0 || limit<=0){
+ return 0;
+ }
+ Arrays.sort(people);
+ int light=0;
+ int heavy=people.length-1;
+ int res=0;
+ while (light<=heavy){
+ if(light==heavy){ // 只剩下一个人,则单独一艘船
+ res++;
+ break;
+ }
+ if(people[light]+people[heavy]>limit){ // 如果只能载一个人,则载最重的
+ res++;
+ heavy--;
+ }else{ // people[light]+people[heavy]<=limit,船可以搭载两个人
+ res++;
+ light++;
+ heavy--;
+ }
+ }
+ return res;
+}
+```
+
+
+
+## *5、汇总区间
+
+[汇总区间](https://leetcode-cn.com/problems/summary-ranges/)
+
+```java
+public List summaryRanges(int[] nums) {
+ List res=new ArrayList<>();
+ if(nums==null || nums.length==0){
+ return res;
+ }
+ if(nums.length==1) {
+ res.add(nums[0] + "");
+ return res;
+ }
+ int start=nums[0];
+ int end=start;
+ for(int i=1;i"+end;
+}
+```
+
+
+
+## 6、无重叠区间
+
+[无重叠区间](https://leetcode-cn.com/problems/non-overlapping-intervals/)
+
+```java
+// 思路:
+// 首先计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。
+// 其中区间结尾至关重要,选择的区间结尾越小,留给后面的空间就越大,那么后面能够选择的区间个数也就越大。
+public int eraseOverlapIntervals(int[][] intervals) {
+ if(intervals==null || intervals.length==0){
+ return 0;
+ }
+ // 按照区间结尾进行升序排序
+ // 写法一:比较器
+ // 这里使用 p1[1]() {
+ @Override
+ public int compare(int[] p1, int[] p2) {
+ return p1[1] p1[1]p[1]));*/
+ int cnt=1; // 记录最大不重叠区间数
+ int end=intervals[0][1]; // 记录前一个不重叠区间结尾位置
+ for(int i=1;i() {
+ @Override
+ public int compare(int[] o1, int[] o2) {
+ return o1[1](o1[1]o[1]));
+ int res=1;
+ int end=points[0][1];
+ for(int i=1;i() {
+ @Override
+ public int compare(int[] o1, int[] o2) {
+ return (o1[0]
+ o1[0]o[0]));*/
+ List intervalList=new ArrayList<>(); // 保存结果区间
+ int[] cur=intervals[0]; // 记录当前重叠区间
+ int m=intervals.length;
+ for(int i=1;i=intervals[i][0]){ // cur.end >= next.start
+ cur[1]=Math.max(cur[1],intervals[i][1]);
+ }else{
+ intervalList.add(cur);
+ cur=intervals[i];
+ }
+ if(i==m-1){
+ intervalList.add(cur);
+ }
+ }
+ int[][] res=new int[intervalList.size()][2];
+ for(int i=0;i=0 || j>=0){
+ c+=(i>=0)?num1.charAt(i)-'0':0;
+ c+=(j>=0)?num2.charAt(j)-'0':0;
+ res.append(c%10);
+ c/=10;
+ i--;
+ j--;
}
-
- if(exponent<0){
- exponent = -exponent;
- return 1/Power(base,exponent);
- }
-
- if(exponent%2==1){
- return Power(base*base,(exponent-1)/2)*base;
- }else{
- return Power(base*base,exponent/2);
+ if(c==1){ // 考虑 num1="90"、num2="90" 的情况
+ res.append("1");
}
+ return res.reverse().toString();
}
```
-## 6、求 1+2+3+...+n
+## 2、求 1+2+3+...+n
[求 1+2+3+...+n](https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -215,11 +216,46 @@ public int Sum_Solution(int n) {
-## *7、从 1 到 n 整数中 1 出现的次数
+## *3、从 1 到 n 整数中 1 出现的次数
[从 1 到 n 整数中 1 出现的次数](https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&tqId=11184&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
```java
+// 思路一:暴力法
+// 时间复杂度:O(n^2)
+public int NumberOf1Between1AndN_Solution(int n) {
+ if(n<=0){
+ return 0;
+ }
+ int res=0;
+ for(int i=1;i<=n;i++){
+ res+=getNumberOf1(i);
+ }
+ return res;
+}
+
+private int getNumberOf1(int n){
+ int cnt=0;
+ while(n>0){
+ if(n%10==1){
+ cnt++;
+ }
+ n/=10;
+ }
+ return cnt;
+}
+```
+
+```java
+// 思路二:参考 https://blog.csdn.net/yi_Afly/article/details/52012593
+// 总结如下规律:
+// base 为当前位数 base=1,10,100,
+// weight 为当前位值
+// formatter 为 weight 的后一位
+// round 就是 weight 前的所有位
+// 当 weight==0 时,count=round*base
+// 当 weight==1 时,count=round*base+formatter+1
+// 当 weight>1 时,count==round*base+base
public int NumberOf1Between1AndN_Solution(int n) {
if(n<1){
return 0;
@@ -246,13 +282,11 @@ public int NumberOf1Between1AndN_Solution(int n) {
}
```
-> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython)
-
> [参考:从 1 到 n 整数中 1 出现的次数](https://blog.csdn.net/yi_Afly/article/details/52012593)
-## 8、数字序列中的某一位数字
+## *4、数字序列中的某一位数字
数字以 0123456789101112131415... 的格式序列化到一个字符串中,求这个字符串的第 index 位。
@@ -310,7 +344,85 @@ private int getDigitAtIndex(int index, int place) {
-## 9、圆圈中最后剩下的数
+# 经典数学问题
+
+## *1、数值的整数次方
+
+[数值的整数次方](https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&tqId=11165&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+//思路:分治法
+// a^n 次方
+// 若 a 为奇数 a^n = a*(a*a)^((n-1)/2)
+// 若 a 为偶数 a^n = (a*a)^(n/2)
+
+//注意:
+// n<0 时,则 a^n = 1 /(a ^(-n))
+// 但是当 n == Integer.MIN_VALUE 时,-n = Integer.MAX_VALUE+1
+// 我们可以转化为 1 / (a^(Integer.MAX_VALUE)*a)
+
+public double Power(double base, int exponent) {
+ if(exponent==0){
+ return 1.0;
+ }
+ if(exponent==1){
+ return base;
+ }
+ if(exponent==Integer.MIN_VALUE){
+ return 1/(Power(base,Integer.MAX_VALUE)*base);
+ }
+
+ if(exponent<0){
+ exponent = -exponent;
+ return 1/Power(base,exponent);
+ }
+
+ if(exponent%2==1){
+ return Power(base*base,(exponent-1)/2)*base;
+ }else{
+ return Power(base*base,exponent/2);
+ }
+}
+```
+
+
+
+## 2、x 的平方根
+
+[x 的平方根](https://leetcode-cn.com/problems/sqrtx/)
+
+```java
+// 思路:要求是非负整数,则必然 x>=0
+// 当 x<4时,可以证明 sqrt(x) > x/2
+// 当 x=4时,sqrt(4)==4/2=2
+// 当 x>4时,可以证明 sqrt(x) < x/2
+public int mySqrt(int x) {
+ if(x==0){
+ return 0;
+ }
+ if(x<4){ // x=1,x=2,x=3,sqrt(x)=1
+ return 1;
+ }
+ // 考虑 x>=4的情况
+ int l=1;
+ int r=x/2;
+ while(l<=r){
+ int mid=(r-l)/2+l;
+ if(x/midx,采用 x/midmid){
+ l=mid+1;
+ }else{
+ return mid;
+ }
+ }
+ return r; // 只保留整数部分,所以取r
+}
+```
+
+
+
+## *3、圆圈中最后剩下的数
[圆圈中最后剩下的数](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -354,4 +466,189 @@ public int LastRemaining_Solution(int n, int m) {
}
```
-> [参考:约瑟夫环问题](https://blog.csdn.net/u011500062/article/details/72855826)
\ No newline at end of file
+> [参考:约瑟夫环问题](https://blog.csdn.net/u011500062/article/details/72855826)
+
+
+
+## 4、计数质数
+
+[计数质数](https://leetcode-cn.com/problems/count-primes/)
+
+```java
+// 思路:
+// 定义一个非素数数组 notPrimes,其中 notPrimes[i] 表示元素 i 是否是素数
+// notPrimes[i]=true,说明 i 是素数
+// notPrimes[i]=false,说明 i 不是素数
+public int countPrimes(int n) {
+ int cnt=0;
+ if(n<=1){
+ return cnt;
+ }
+ boolean[] notPrimes=new boolean[n];
+ for(int x=2;x=n 循环停止
+ for(long j=(long)x*x;j
+// 11!=11*(2*5)*9*(4*2)*7*(3*2)*(1*5)*(2*2)*3*(1*2)*1=39916800,有2对<2,5>
+// 也就是说,有多少对<2,5>就有多少0
+// 对于含有2的因子的话是 1*2, 2*2, 3*2, 4*2
+// 对于含有5的因子的话是 1*5, 2*5
+// 含有2的因子每两个出现一次,含有5的因子每5个出现一次,所以2出现的个数远远多于 5,
+// 换言之找到一个5,一定能找到一个2与之配对。
+// 所以我们只需要找有多少个 5。
+// n! = 1 * 2 * 3 * 4 * (1 * 5) * ... * (2 * 5) * ... * (3 * 5) *... * n
+// 可以得出如下规律:每隔5个数会出现一个5,每隔25个数,出现2个5,每隔125个数,出现3个5
+// 所以最终 5的个数:n/5+ n/25 (有1个5在每隔5个数已经出现过一次了) + n/125 (有1个在每隔5个数出现过了,另一个则载每隔25个数出现过了)
+public int trailingZeroes(int n) {
+ if(n<5){
+ return 0;
+ }
+ int res=0;
+ while (n>0){
+ res+=n/5;
+ n/=5;
+ }
+ return res;
+}
+```
+
+
+
+## 6、最少移动次数使数组元素相等 II
+
+[ 最少移动次数使数组元素相等 II](https://leetcode-cn.com/problems/minimum-moves-to-equal-array-elements-ii/)
+
+```java
+// 思路:
+// 当 x 为这个 N 个数的中位数时,可以使得距离最小。
+// 具体地,若 N 为奇数,则 x 必须为这 N 个数中的唯一中位数;
+// 若 N 为偶数,中间的两个数为 p 和 q,中位数为 (p + q) / 2,
+// 此时 x 只要是区间 [p, q](注意是闭区间) 中的任意一个数即可。
+
+// 写法一:排序求中位数
+public int minMoves2(int[] nums) {
+ Arrays.sort(nums);
+ // 获取数组中位数获取[p,q]中的数值p
+ int mid=nums[nums.length/2];
+ int res=0;
+ for(int num:nums){
+ res+=Math.abs(num-mid);
+ }
+ return res;
+}
+```
+
+```java
+// 写法二:利用快速排序求中位数
+public int minMoves2(int[] nums) {
+ if(nums.length==1){
+ return 0;
+ }
+ int k=nums.length/2;
+ int mid=nums[findKth(nums,k)];
+ int res=0;
+ for(int num:nums){
+ res+=Math.abs(num-mid);
+ }
+ return res;
+}
+
+private int findKth(int[] nums,int k){
+ int start=0;
+ int end=nums.length-1;
+ while (start<=end){
+ int p=partition(nums,start,end);
+ if(p==k){
+ return p;
+ }else if(pk
+ end=p-1;
+ }
+ }
+ return -1;
+}
+
+private int partition(int[] nums,int start,int end){
+ int pivot=nums[start];
+ while(start=pivot){
+ end--;
+ }
+ nums[start]=nums[end];
+ // 从左向右找第一个大于 pivot 的数
+ while (start n/2,但在本题目 m 即是多数元素
+public int majorityElement(int[] nums) {
+ int m=nums[0];
+ int cnt=0;
+ for(int num:nums){
+ if(cnt==0){
+ m=num;
+ cnt=1;
+ }else{
+ if(num==m){
+ cnt++;
+ }else{
+ cnt--;
+ }
+ }
+ }
+ return m;
+}
+```
+
+
+
+
+
diff --git "a/docs/AimForOffer/\347\256\227\346\263\225\346\200\235\346\203\263\347\233\270\345\205\263/8_\345\205\266\344\273\226.md" "b/docs/AimForOffer/\347\256\227\346\263\225\346\200\235\346\203\263\347\233\270\345\205\263/8_\345\205\266\344\273\226.md"
new file mode 100644
index 00000000..cbf78eab
--- /dev/null
+++ "b/docs/AimForOffer/\347\256\227\346\263\225\346\200\235\346\203\263\347\233\270\345\205\263/8_\345\205\266\344\273\226.md"
@@ -0,0 +1,104 @@
+# 其他
+
+## 1、洗牌算法
+
+基本思想是:每次从一组数中**随机选出一个数**,然后与最后一个数交换位置,并且不再考虑最后一个数。
+
+```java
+public class Shuffle {
+
+ public void shuffle(int[] nums){
+ Random random=new Random();
+ for(int i=nums.length-1;i>=0;i--){
+ //[0,i] 之中随机选择一个数
+ int j=random.nextInt(i+1); // j 为[0,i] 中随机的下标
+ swap(nums,i,j); // i 始终指向 [0,i] 的最后位置
+ }
+ }
+
+ public void swap(int[] nums,int i,int j) {
+ int tmp=nums[i];
+ nums[i]=nums[j];
+ nums[j]=tmp;
+ }
+
+ @Test
+ public void test(){
+ int[] nums={1,2,3,4,5,6,7,8,9,10};
+ for(int i=0;i generatePocketByLineCutting(int n, double money){
+ Random random=new Random();
+
+ //如果是小数的话先转化为整数
+ int newMoney=(int)money*100;
+
+ // 将在长度为 money 线段上随机找出 n-1 个点
+ List points=new ArrayList<>();
+ while (points.size() res=new ArrayList<>();
+ for(int i=1;i res=generatePocketByLineCutting(n,money);
+ System.out.println(res);
+ double sum=0.0;
+ for(double d:res){
+ sum+=d;
+ }
+ System.out.println(sum);
+ }
+}
+```
+
+```html
+[21.53, 10.59, 7.63, 24.2, 1.5, 3.7, 0.03, 18.49, 10.38, 1.95]
+100.0
+```
+
diff --git "a/docs/DataBase/1_\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md" "b/docs/DataBase/1_\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md"
new file mode 100644
index 00000000..885bf913
--- /dev/null
+++ "b/docs/DataBase/1_\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md"
@@ -0,0 +1,97 @@
+# 数据库系统原理
+
+## 事务
+
+### 概念
+
+事务是**逻辑上的一组操作**,要么都执行,要么都不执行。
+
+### 特性 ACID
+
+#### 1. 原子性(Atomicity)
+
+事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
+
+回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
+
+#### 2. 一致性(Consistency)
+
+数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。
+
+#### 3. 隔离性(Isolation)
+
+一个事务所做的修改在最终提交以前,对其它事务是不可见的。
+
+#### 4. 持久性(Durability)
+
+一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。
+
+可以通过数据库备份和恢复来实现,在系统发生崩溃时,使用备份的数据库进行数据恢复。
+
+
+
+## 并发一致性问题
+
+在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。
+
+### 丢失修改
+
+T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。
+
+
+
+### 脏读
+
+T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。
+
+
+
+### 不可重复读
+
+T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。
+
+
+
+### 幻影读
+
+T1 读取某个**范围**的数据,T2 在这个**范围**内插入新的数据,T1 再次读取这个**范围**的数据,此时读取的结果和和第一次读取的结果不同。
+
+
+
+## 事务隔离级别
+
+产生并发不一致性问题主要原因是破坏了事务的**隔离性**,解决方法是通过**并发控制**来保证隔离性。并发控制可以通过封锁(加锁)来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的**隔离级别**,让用户以一种更轻松的方式处理并发一致性问题。
+
+### 未提交读(READ UNCOMMITTED)
+
+事务中的修改,即使没有提交,对其它事务也是可见的。
+
+### 提交读(READ COMMITTED)
+
+一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
+
+### 可重复读(REPEATABLE READ)
+
+保证在同一个事务中多次读取同一数据的结果是一样的。
+
+### 可串行化(SERIALIZABLE)
+
+强制事务串行执行,这样多个事务互不干扰,不会出现并发一致性问题。
+
+隔离级别能解决的并发一致性问题:
+
+| 并发访问问题 | 事务隔离级别 |
+| :----------: | :------------------------------------------: |
+| 丢失修改 | MySQL 所有事务隔离级别在数据库层面上均可避免 |
+| 脏读 | READ-COMMITTED 事务隔离级别以上可避免 |
+| 不可重复读 | REPEATABLE-READ 事务隔离级别以上可避免 |
+| 幻读 | SERIALIZABLE 事务隔离级别以上可避免 |
+
+即:
+
+| 事务隔离级别 \ 并发问题 | 丢失修改 | 脏读 | 不可重复读 | 幻读 |
+| :---------------------------: | :------: | :--: | :--------: | :--: |
+| 未提交读 (READ UNCOMMITTED) | 避免 | 发生 | 发生 | 发生 |
+| 提交读 (READ COMMITTED) | 避免 | 避免 | 发生 | 发生 |
+| 可重复读 (REPEATABLE READ) | 避免 | 避免 | 避免 | 发生 |
+| 可串行化 (SERIALIZABLE) | 避免 | 避免 | 避免 | 避免 |
diff --git "a/docs/DataBase/2_\345\205\263\347\263\273\346\225\260\346\215\256\345\272\223\350\256\276\350\256\241\347\220\206\350\256\272.md" "b/docs/DataBase/2_\345\205\263\347\263\273\346\225\260\346\215\256\345\272\223\350\256\276\350\256\241\347\220\206\350\256\272.md"
new file mode 100644
index 00000000..244398b2
--- /dev/null
+++ "b/docs/DataBase/2_\345\205\263\347\263\273\346\225\260\346\215\256\345\272\223\350\256\276\350\256\241\347\220\206\350\256\272.md"
@@ -0,0 +1,125 @@
+# 关系数据库设计理论
+
+## 函数依赖
+
+记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。
+
+如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
+
+对于 A->B,如果能找到 A 的真子集 A',使得 A'-> B,那么 A->B 就是部分函数依赖,否则就是完全函数依赖。
+
+对于 A->B,B->C,则 A->C 是一个传递函数依赖。
+
+## 异常
+
+以下的学生课程关系的函数依赖为 Sno, Cname -> Sname, Sdept, Mname, Grade,键码为 {Sno, Cname}。也就是说,确定学生和课程之后,就能确定其它信息。
+
+| Sno | Sname | Sdept | Mname | Cname | Grade |
+| ---- | ------ | ------ | ------ | ------ | ----- |
+| 1 | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90 |
+| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80 |
+| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 |
+| 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 |
+
+不符合范式的关系,会产生很多异常,主要有以下四种异常:
+
+- 冗余数据:例如 `学生-2` 出现了两次。
+- 修改异常:修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。
+- 删除异常:删除一个信息,那么也会丢失其它信息。例如删除了 `课程-1` 需要删除第一行和第三行,那么 `学生-1` 的信息就会丢失。
+- 插入异常:例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。
+
+## 范式
+
+范式理论是为了解决以上提到四种异常。
+
+高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。
+
+
+
+
+
+### 1. 第一范式 (1NF)
+
+属性不可分。
+
+### 2. 第二范式 (2NF)
+
+每个非主属性完全函数依赖于键码。
+
+(一是表必须有一个主键;二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的部分。)
+
+可以通过分解来满足。
+
+**分解前**
+
+| Sno | Sname | Sdept | Mname | Cname | Grade |
+| ---- | ------ | ------ | ------ | ------ | ----- |
+| 1 | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90 |
+| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80 |
+| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 |
+| 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 |
+
+以上学生课程关系中,{Sno, Cname} 为键码,有如下函数依赖:
+
+- Sno -> Sname, Sdept
+- Sdept -> Mname
+- Sno, Cname-> Grade
+
+Grade 完全函数依赖于键码,它没有任何冗余数据,每个学生的每门课都有特定的成绩。
+
+Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。
+
+**分解后**
+
+关系-1
+
+| Sno | Sname | Sdept | Mname |
+| ---- | ------ | ------ | ------ |
+| 1 | 学生-1 | 学院-1 | 院长-1 |
+| 2 | 学生-2 | 学院-2 | 院长-2 |
+| 3 | 学生-3 | 学院-2 | 院长-2 |
+
+有以下函数依赖:
+
+- Sno -> Sname, Sdept
+- Sdept -> Mname
+
+关系-2
+
+| Sno | Cname | Grade |
+| ---- | ------ | ----- |
+| 1 | 课程-1 | 90 |
+| 2 | 课程-2 | 80 |
+| 2 | 课程-1 | 100 |
+| 3 | 课程-2 | 95 |
+
+有以下函数依赖:
+
+- Sno, Cname -> Grade
+
+### 3. 第三范式 (3NF)
+
+非主属性不传递函数依赖于键码。
+
+(确保每列都和主键列直接相关,而不是间接相关。)
+
+上面的 关系-1 中存在以下传递函数依赖:
+
+- Sno -> Sdept -> Mname
+
+可以进行以下分解:
+
+关系-11
+
+| Sno | Sname | Sdept |
+| ---- | ------ | ------ |
+| 1 | 学生-1 | 学院-1 |
+| 2 | 学生-2 | 学院-2 |
+| 3 | 学生-3 | 学院-2 |
+
+关系-12
+
+| Sdept | Mname |
+| ------ | ------ |
+| 学院-1 | 院长-1 |
+| 学院-2 | 院长-2 |
diff --git "a/docs/DataBase/3_\350\256\276\350\256\241\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223.md" "b/docs/DataBase/3_\350\256\276\350\256\241\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223.md"
new file mode 100644
index 00000000..0309567f
--- /dev/null
+++ "b/docs/DataBase/3_\350\256\276\350\256\241\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223.md"
@@ -0,0 +1,49 @@
+# 设计关系型数据库
+
+关系数据库管理系统(RDBMS)架构图如下:
+
+
+
+## 存储
+
+存储即文件系统。存储介质可以是机械硬盘、SSD 固态。
+
+## 程序实例
+
+- **存储管理**
+
+ 对数据格式、文件风格进行统一管理,将物理数据通过逻辑形式组织、表示出来。
+
+ 优化:一次性读取多行,逻辑存取单位是页(page)。
+
+- **缓存机制**
+
+ 将取出的数据放入缓存中,一次性加载多个页数据,相当一部分不是本次访问所需的行。根据“一旦数据被访问,其相邻数据极有可能下次被访问到”,优化访问效率。
+
+ 缓存管理机制:可以使用 LRU 对缓存进行管理。
+
+- **SQL 解析**
+
+ 提供给外界指令来操作数据库,即可读的 SQL 语言。
+
+ 优化:可以将 SQL 缓存,方便下次解析。缓存不宜过大,管理缓存的算法中要有淘汰机制。
+
+- **日志管理**
+
+ 主从同步、灾难恢复。
+
+- **权限划分**
+
+ 支持多用户。
+
+- **容灾机制**
+
+ 数据库挂了,进行恢复,恢复到什么程度。
+
+- **索引管理**
+
+ 引入索引,提高查询效率。
+
+- **锁管理**
+
+ 引入锁机制,支持并发操作。
\ No newline at end of file
diff --git "a/docs/DataBase/5_LeetCode_Database\351\242\230\350\247\243.md" "b/docs/DataBase/5_LeetCode_Database\351\242\230\350\247\243.md"
new file mode 100644
index 00000000..0f3123f8
--- /dev/null
+++ "b/docs/DataBase/5_LeetCode_Database\351\242\230\350\247\243.md"
@@ -0,0 +1,1012 @@
+# 1、大的国家(595)
+
+[595. 大的国家](https://leetcode-cn.com/problems/big-countries/)
+
+- 问题描述
+
+```html
++-----------------+------------+------------+--------------+---------------+
+| name | continent | area | population | gdp |
++-----------------+------------+------------+--------------+---------------+
+| Afghanistan | Asia | 652230 | 25500100 | 20343000 |
+| Albania | Europe | 28748 | 2831741 | 12960000 |
+| Algeria | Africa | 2381741 | 37100000 | 188681000 |
+| Andorra | Europe | 468 | 78115 | 3712000 |
+| Angola | Africa | 1246700 | 20609294 | 100990000 |
++-----------------+------------+------------+--------------+---------------+
+```
+
+查找面积超过 3,000,000 或者人口数超过 25,000,000 的国家。
+
+```html
++--------------+-------------+--------------+
+| name | population | area |
++--------------+-------------+--------------+
+| Afghanistan | 25500100 | 652230 |
+| Algeria | 37100000 | 2381741 |
++--------------+-------------+--------------+
+```
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS World;
+CREATE TABLE World ( NAME VARCHAR ( 255 ), continent VARCHAR ( 255 ), area INT, population INT, gdp INT );
+INSERT INTO World ( NAME, continent, area, population, gdp )
+VALUES
+ ( 'Afghanistan', 'Asia', '652230', '25500100', '203430000' ),
+ ( 'Albania', 'Europe', '28748', '2831741', '129600000' ),
+ ( 'Algeria', 'Africa', '2381741', '37100000', '1886810000' ),
+ ( 'Andorra', 'Europe', '468', '78115', '37120000' ),
+ ( 'Angola', 'Africa', '1246700', '20609294', '1009900000' );
+```
+
+- 解题
+
+```sql
+# 思路:
+# 1、根据样例,我们知道。查询字段是 name population 和 area
+# 2、查询条件是 area > 3000000 || population > 25000000
+SELECT name,population,area
+FROM World
+WHERE area > 3000000 || population > 25000000;
+```
+
+# 2、交换工资(627)
+
+[627. 交换工资](https://leetcode-cn.com/problems/swap-salary/)
+
+- 问题描述
+
+```html
+| id | name | sex | salary |
+|----|------|-----|--------|
+| 1 | A | m | 2500 |
+| 2 | B | f | 1500 |
+| 3 | C | m | 5500 |
+| 4 | D | f | 500 |
+```
+
+只用一个 SQL 查询,将 sex 字段反转。
+
+```html
+| id | name | sex | salary |
+|----|------|-----|--------|
+| 1 | A | f | 2500 |
+| 2 | B | m | 1500 |
+| 3 | C | f | 5500 |
+| 4 | D | m | 500 |
+```
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS salary;
+CREATE TABLE salary ( id INT, NAME VARCHAR ( 100 ), sex CHAR ( 1 ), salary INT );
+INSERT INTO salary ( id, NAME, sex, salary )
+VALUES
+ ( '1', 'A', 'm', '2500' ),
+ ( '2', 'B', 'f', '1500' ),
+ ( '3', 'C', 'm', '5500' ),
+ ( '4', 'D', 'f', '500' );
+```
+
+- 解题
+
+```sql
+# 思路:
+# l、利用位运算:比如若 x^b^a = a,则判断 x=b
+# 2、利用 ASCII 函数将字符转换为数值进行运算,然后再利用 CHAR 函数将数值转换为字符
+UPDATE
+salary
+SET sex = CHAR(ASCII(sex)^ASCII('m')^ASCII('f'));
+```
+
+# 3、有趣的电影(62)
+
+[620. 有趣的电影](https://leetcode-cn.com/problems/not-boring-movies/)
+
+- 问题描述
+
+
+```html
++---------+-----------+--------------+-----------+
+| id | movie | description | rating |
++---------+-----------+--------------+-----------+
+| 1 | War | great 3D | 8.9 |
+| 2 | Science | fiction | 8.5 |
+| 3 | irish | boring | 6.2 |
+| 4 | Ice song | Fantacy | 8.6 |
+| 5 | House card| Interesting| 9.1 |
++---------+-----------+--------------+-----------+
+```
+
+查找 id 为奇数,并且 description 不是 boring 的电影,按 rating 降序。
+
+```html
++---------+-----------+--------------+-----------+
+| id | movie | description | rating |
++---------+-----------+--------------+-----------+
+| 5 | House card| Interesting| 9.1 |
+| 1 | War | great 3D | 8.9 |
++---------+-----------+--------------+-----------+
+```
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS cinema;
+CREATE TABLE cinema ( id INT, movie VARCHAR ( 255 ), description VARCHAR ( 255 ), rating FLOAT ( 2, 1 ) );
+INSERT INTO cinema ( id, movie, description, rating )
+VALUES
+ ( 1, 'War', 'great 3D', 8.9 ),
+ ( 2, 'Science', 'fiction', 8.5 ),
+ ( 3, 'irish', 'boring', 6.2 ),
+ ( 4, 'Ice song', 'Fantacy', 8.6 ),
+ ( 5, 'House card', 'Interesting', 9.1 );
+```
+
+- 解题
+
+```sql
+#思路:
+#1、观察测试用例的查询结果,我们知道,其实查询的是所有的字段
+#2、id 为奇数,则查询条件为 id % 2 = 1
+SELECT id,movie,description,rating
+FROM cinema
+WHERE id%2=1 AND description != 'boring'
+ORDER BY rating DESC;
+```
+
+# 4、超过5名学生的课(596)
+
+[596. 超过5名学生的课](https://leetcode-cn.com/problems/classes-more-than-5-students/)
+
+- 问题描述
+
+```html
++---------+------------+
+| student | class |
++---------+------------+
+| A | Math |
+| B | English |
+| C | Math |
+| D | Biology |
+| E | Math |
+| F | Computer |
+| G | Math |
+| H | Math |
+| I | Math |
++---------+------------+
+```
+
+查找有五名及以上 student 的 class。
+
+```html
++---------+
+| class |
++---------+
+| Math |
++---------+
+```
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS courses;
+CREATE TABLE courses ( student VARCHAR ( 255 ), class VARCHAR ( 255 ) );
+INSERT INTO courses ( student, class )
+VALUES
+ ( 'A', 'Math' ),
+ ( 'B', 'English' ),
+ ( 'C', 'Math' ),
+ ( 'D', 'Biology' ),
+ ( 'E', 'Math' ),
+ ( 'F', 'Computer' ),
+ ( 'G', 'Math' ),
+ ( 'H', 'Math' ),
+ ( 'I', 'Math' );
+```
+
+- 解答
+
+```sql
+# 思路:
+# 1、很显然要按照 class 进行分组
+# 2、然后按照分组后的 class 来统计学生的人数
+SELECT class
+FROM courses
+GROUP BY class
+HAVING COUNT( DISTINCT student) >= 5;
+```
+
+# 5、查找重复的电子邮箱(182)
+
+[182. 查找重复的电子邮箱](https://leetcode-cn.com/problems/duplicate-emails/)
+
+- 问题描述
+
+邮件地址表:
+
+```html
++----+---------+
+| Id | Email |
++----+---------+
+| 1 | a@b.com |
+| 2 | c@d.com |
+| 3 | a@b.com |
++----+---------+
+```
+
+查找重复的邮件地址:
+
+```html
++---------+
+| Email |
++---------+
+| a@b.com |
++---------+
+```
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS Person;
+CREATE TABLE Person ( Id INT, Email VARCHAR ( 255 ) );
+INSERT INTO Person ( Id, Email )
+VALUES
+ ( 1, 'a@b.com' ),
+ ( 2, 'c@d.com' ),
+ ( 3, 'a@b.com' );
+```
+
+- 解题
+
+```sql
+# 思路:与 596 题类似
+# 1、按照 mail 进行分组
+# 2、统计出现次数 >=2 就是重复的邮件
+SELECT Email
+FROM Person
+GROUP BY EMAIL
+HAVING COUNT(id) >= 2;
+```
+
+# *6、删除重复的电子邮箱(196)
+
+[196. 删除重复的电子邮箱](https://leetcode-cn.com/problems/delete-duplicate-emails/)
+
+- 问题描述
+
+邮件地址表:
+
+```html
++----+---------+
+| Id | Email |
++----+---------+
+| 1 | a@b.com |
+| 2 | c@d.com |
+| 3 | a@b.com |
++----+---------+
+```
+
+删除重复的邮件地址:
+
+```html
++----+------------------+
+| Id | Email |
++----+------------------+
+| 1 | john@example.com |
+| 2 | bob@example.com |
++----+------------------+
+```
+
+- SQL Schema
+
+与 182 相同。
+
+- 解题:
+
+```sql
+# 思路一:将一张表看成两张表来进行操作
+DELETE p1
+FROM
+ Person p1,
+ Person p2
+WHERE
+ p1.Email = p2.Email
+ AND p1.Id > p2.Id
+```
+
+```sql
+# 思路二:
+# 第一步:根据 email 进行分组,获取 email 对应的最小 id,一个 email 对应一个最小的 id
+SELECT min( id ) AS id FROM Person GROUP BY email;
+
+# 第二步:删除不在该 id 集合中的数据
+DELETE
+FROM
+ Person
+WHERE
+ id NOT IN ( SELECT id FROM ( SELECT min( id ) AS id FROM Person GROUP BY email ) AS m );
+# 应该注意的是上述解法额外嵌套了一个 SELECT 语句。
+# 如果不这么做,会出现错误:You can't specify target table 'Person' for update in FROM clause。
+```
+
+```sql
+DELETE
+FROM
+ Person
+WHERE
+ id NOT IN ( SELECT id FROM ( SELECT min( id ) AS id FROM Person GROUP BY email ) AS m );
+# 发生 You can't specify target table 'Person' for update in FROM clause。
+```
+
+参考:[pMySQL Error 1093 - Can't specify target table for update in FROM clause](https://stackoverflow.com/questions/45494/mysql-error-1093-cant-specify-target-table-for-update-in-from-clause)
+
+# 7、组合两个表(175)
+
+[175. 组合两个表](https://leetcode-cn.com/problems/combine-two-tables/)
+
+- 问题描述:
+
+Person 表:
+
+```html
++-------------+---------+
+| Column Name | Type |
++-------------+---------+
+| PersonId | int |
+| FirstName | varchar |
+| LastName | varchar |
++-------------+---------+
+PersonId is the primary key column for this table.
+```
+
+Address 表:
+
+```html
++-------------+---------+
+| Column Name | Type |
++-------------+---------+
+| AddressId | int |
+| PersonId | int |
+| City | varchar |
+| State | varchar |
++-------------+---------+
+AddressId is the primary key column for this table.
+```
+
+查找 FirstName, LastName, City, State 数据,而不管一个用户有没有填地址信息。
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS Person;
+CREATE TABLE Person ( PersonId INT, FirstName VARCHAR ( 255 ), LastName VARCHAR ( 255 ) );
+DROP TABLE
+IF
+ EXISTS Address;
+CREATE TABLE Address ( AddressId INT, PersonId INT, City VARCHAR ( 255 ), State VARCHAR ( 255 ) );
+INSERT INTO Person ( PersonId, LastName, FirstName )
+VALUES
+ ( 1, 'Wang', 'Allen' );
+INSERT INTO Address ( AddressId, PersonId, City, State )
+VALUES
+ ( 1, 2, 'New York City', 'New York' );
+```
+
+- 解题:
+
+```sql
+# 思路:左外连接
+SELECT p.FirstName,p.LastName,a.City,a.State
+FROM Person p
+LEFT JOIN Address a
+ON p.PersonId=a.PersonId;
+```
+
+- 扩展:
+
+ * 内连接:返回两张表的交集部分。
+
+
+
+ * 左连接:
+
+
+
+ * 右连接:
+
+
+
+# *8、超过经理收入的员工(181)
+
+[181. 超过经理收入的员工](https://leetcode-cn.com/problems/employees-earning-more-than-their-managers/)
+
+- 问题描述:
+
+Employee 表:
+
+```html
++----+-------+--------+-----------+
+| Id | Name | Salary | ManagerId |
++----+-------+--------+-----------+
+| 1 | Joe | 70000 | 3 |
+| 2 | Henry | 80000 | 4 |
+| 3 | Sam | 60000 | NULL |
+| 4 | Max | 90000 | NULL |
++----+-------+--------+-----------+
+```
+
+查找薪资大于其经理薪资的员工信息。
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS Employee;
+CREATE TABLE Employee ( Id INT, NAME VARCHAR ( 255 ), Salary INT, ManagerId INT );
+INSERT INTO Employee ( Id, NAME, Salary, ManagerId )
+VALUES
+ ( 1, 'Joe', 70000, 3 ),
+ ( 2, 'Henry', 80000, 4 ),
+ ( 3, 'Sam', 60000, NULL ),
+ ( 4, 'Max', 90000, NULL );
+```
+
+- 解题:
+
+```sql
+# 思路:Employee e1 INNER JOIN Employee e2 ON e1.managerid = e2.id 比如
+
++----+-------+--------+-----------+
+| Id | Name | Salary | ManagerId |
++----+-------+--------+-----------+
+| 1 | Joe | 70000 | 3 |
+| 2 | Henry | 80000 | 4 |
+| 3 | Sam | 60000 | NULL |
+| 4 | Max | 90000 | NULL |
++----+-------+--------+-----------+
+
+根据 e1.managerid = e2.id 条件进行内连接后,得到
+
++----+-------+--------+-----------+-------+--------+-----------+
+| Id | Name | Salary | ManagerId | Name | Salary | ManagerId |
++----+-------+--------+-----------+-------+--------+-----------+
+| 1 | Joe | 70000 | 3 | Sam | 60000 | NULL |
+| 2 | Henry | 80000 | 4 | Max | 90000 | NULL |
++----+-------+--------+-----------+-------+--------+-----------+
+```
+
+```sql
+SELECT
+ e1.name AS Employee # 注意:这里的 Employee 是给该字段起的别名,实际上查找的是姓名
+FROM
+ Employee e1
+ INNER JOIN Employee e2
+ ON e1.managerid = e2.id
+ AND e1.salary > e2.salary;
+```
+
+# 9、从不订购的客户(183)
+
+[181. 超过经理收入的员工](https://leetcode-cn.com/problems/employees-earning-more-than-their-managers/)
+
+- 问题描述:
+
+Curstomers 表:
+
+```html
++----+-------+
+| Id | Name |
++----+-------+
+| 1 | Joe |
+| 2 | Henry |
+| 3 | Sam |
+| 4 | Max |
++----+-------+
+```
+
+Orders 表:
+
+```html
++----+------------+
+| Id | CustomerId |
++----+------------+
+| 1 | 3 |
+| 2 | 1 |
++----+------------+
+```
+
+查找没有订单的顾客信息:
+
+```html
++-----------+
+| Customers |
++-----------+
+| Henry |
+| Max |
++-----------+
+```
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS Customers;
+CREATE TABLE Customers ( Id INT, NAME VARCHAR ( 255 ) );
+DROP TABLE
+IF
+ EXISTS Orders;
+CREATE TABLE Orders ( Id INT, CustomerId INT );
+INSERT INTO Customers ( Id, NAME )
+VALUES
+ ( 1, 'Joe' ),
+ ( 2, 'Henry' ),
+ ( 3, 'Sam' ),
+ ( 4, 'Max' );
+INSERT INTO Orders ( Id, CustomerId )
+VALUES
+ ( 1, 3 ),
+ ( 2, 1 );
+```
+
+- 解答
+
+```sql
+# 解法一:左外连接
+SELECT
+ c.Name AS Customers
+FROM
+ Customers c
+ LEFT JOIN Orders o
+ ON c.Id = o.CustomerId
+WHERE
+ o.CustomerId IS NULL;
+```
+
+```sql
+# 解法二:子查询方式
+SELECT Name AS Customers
+FROM
+ Customers
+WHERE
+ Id NOT IN (SELECT CustomerId FROM Orders);
+```
+
+# *10、部门工资最高的员工(184)
+
+[184. 部门工资最高的员工](https://leetcode-cn.com/problems/department-highest-salary/)
+
+- 问题描述:
+
+Employee 表:
+
+```html
++----+-------+--------+--------------+
+| Id | Name | Salary | DepartmentId |
++----+-------+--------+--------------+
+| 1 | Joe | 70000 | 1 |
+| 2 | Henry | 80000 | 2 |
+| 3 | Sam | 60000 | 2 |
+| 4 | Max | 90000 | 1 |
++----+-------+--------+--------------+
+```
+
+Department 表:
+
+```html
++----+----------+
+| Id | Name |
++----+----------+
+| 1 | IT |
+| 2 | Sales |
++----+----------+
+```
+
+查找一个 Department 中收入最高者的信息:
+
+```html
++------------+----------+--------+
+| Department | Employee | Salary |
++------------+----------+--------+
+| IT | Max | 90000 |
+| Sales | Henry | 80000 |
++------------+----------+--------+
+```
+
+- SQL Schema
+
+```sql
+DROP TABLE IF EXISTS Employee;
+CREATE TABLE Employee ( Id INT, NAME VARCHAR ( 255 ), Salary INT, DepartmentId INT );
+DROP TABLE IF EXISTS Department;
+CREATE TABLE Department ( Id INT, NAME VARCHAR ( 255 ) );
+INSERT INTO Employee ( Id, NAME, Salary, DepartmentId )
+VALUES
+ ( 1, 'Joe', 70000, 1 ),
+ ( 2, 'Henry', 80000, 2 ),
+ ( 3, 'Sam', 60000, 2 ),
+ ( 4, 'Max', 90000, 1 );
+INSERT INTO Department ( Id, NAME )
+VALUES
+ ( 1, 'IT' ),
+ ( 2, 'Sales' );
+```
+
+- 解题:
+
+```sql
+# 创建一个临时表,包含了部门员工的最大薪资。
+# 可以对部门进行分组,然后使用 MAX() 汇总函数取得最大薪资。
+
+SELECT DepartmentId, MAX( Salary ) Salary FROM Employee GROUP BY DepartmentId;
+
+# 结果:
++--------------+--------+
+| DepartmentId | Salary |
++--------------+--------+
+| 1 | 90000 |
+| 2 | 80000 |
++--------------+--------+
+```
+
+使用连接找到一个部门中薪资等于临时表中最大薪资的员工。
+
+```sql
+SELECT d.name as Department, e.name as Employee, m.Salary
+FROM
+ Employee e,
+ Department d,
+ (SELECT DepartmentId, MAX( Salary ) Salary FROM Employee GROUP BY DepartmentId) m
+WHERE
+ e.DepartmentId=d.Id
+ AND e.DepartmentId=m.DepartmentId
+ AND e.Salary=m.Salary;
+```
+
+# 11、第二高的薪水(176)
+
+[176. 第二高的薪水](https://leetcode-cn.com/problems/second-highest-salary/)
+
+- 问题描述:
+
+```html
++----+--------+
+| Id | Salary |
++----+--------+
+| 1 | 100 |
+| 2 | 200 |
+| 3 | 300 |
++----+--------+
+```
+
+查找工资第二高的员工。
+
+```html
++---------------------+
+| SecondHighestSalary |
++---------------------+
+| 200 |
++---------------------+
+```
+
+没有找到返回 null 而不是不返回数据。
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS Employee;
+CREATE TABLE Employee ( Id INT, Salary INT );
+INSERT INTO Employee ( Id, Salary )
+VALUES
+ ( 1, 100 ),
+ ( 2, 200 ),
+ ( 3, 300 );
+```
+
+- 解题:
+
+```sql
+# 查询所有 salary 按照降序排列
+SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC;
+```
+
+```sql
+# 要获取第二高的的薪水,就是获取第二个元素,
+# 使用 limit start,count; start:开始查询的位置,count 是查询多少条语句
+SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1,1;
+```
+
+```sql
+# 为了在没有查找到数据时返回 null,需要在查询结果外面再套一层 SELECT。
+SELECT
+ ( SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT 1, 1 ) SecondHighestSalary;
+```
+
+# 12、第N高的薪水(177)
+
+[177. 第N高的薪水](https://leetcode-cn.com/problems/nth-highest-salary/)
+
+- 问题描述:
+
+查找工资第 N 高的员工。
+
+- SQL Schema
+
+同 176。
+
+- 解题:
+
+```sql
+# 思路:其实与 176题目类似
+CREATE FUNCTION getNthHighestSalary ( N INT ) RETURNS INT BEGIN
+
+SET N = N - 1; #注意 LIMIT 的 start 是从 0 开始的,第 N 实际上是第 (N-1)
+RETURN ( SELECT ( SELECT DISTINCT Salary FROM Employee ORDER BY Salary DESC LIMIT N, 1 ) );
+
+END
+```
+
+# 13、分数排名(178)
+
+[178. 分数排名](https://leetcode-cn.com/problems/rank-scores/)
+
+- 问题描述:
+
+得分表:
+
+```html
++----+-------+
+| Id | Score |
++----+-------+
+| 1 | 3.50 |
+| 2 | 3.65 |
+| 3 | 4.00 |
+| 4 | 3.85 |
+| 5 | 4.00 |
+| 6 | 3.65 |
++----+-------+
+```
+
+将得分排序,并统计排名。
+
+```html
++-------+------+
+| Score | Rank |
++-------+------+
+| 4.00 | 1 |
+| 4.00 | 1 |
+| 3.85 | 2 |
+| 3.65 | 3 |
+| 3.65 | 3 |
+| 3.50 | 4 |
++-------+------+
+```
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS Scores;
+CREATE TABLE Scores ( Id INT, Score DECIMAL ( 3, 2 ) );
+INSERT INTO Scores ( Id, Score )
+VALUES
+ ( 1, 3.5 ),
+ ( 2, 3.65 ),
+ ( 3, 4.0 ),
+ ( 4, 3.85 ),
+ ( 5, 4.0 ),
+ ( 6, 3.65 );
+```
+
+- 解题:
+
+```sql
+#思路:关键在于如何统计 Rank
+#这里使用同一张表进行内连接,注意连接的条件 S1.score <= S2.score 就是为了方便统计 Rank
+SELECT
+ S1.score,
+ COUNT( DISTINCT S2.score ) Rank
+FROM
+ Scores S1
+ INNER JOIN Scores S2
+ ON S1.score <= S2.score
+GROUP BY
+ S1.id
+ORDER BY
+ S1.score DESC;
+```
+
+# *14、连续出现的数字(180)
+
+[180. 连续出现的数字](https://leetcode-cn.com/problems/consecutive-numbers/)
+
+- 问题描述:
+
+数字表:
+
+```html
++----+-----+
+| Id | Num |
++----+-----+
+| 1 | 1 |
+| 2 | 1 |
+| 3 | 1 |
+| 4 | 2 |
+| 5 | 1 |
+| 6 | 2 |
+| 7 | 2 |
++----+-----+
+```
+
+查找连续出现三次的数字。
+
+```html
++-----------------+
+| ConsecutiveNums |
++-----------------+
+| 1 |
++-----------------+
+```
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS LOGS;
+CREATE TABLE LOGS ( Id INT, Num INT );
+INSERT INTO LOGS ( Id, Num )
+VALUES
+ ( 1, 1 ),
+ ( 2, 1 ),
+ ( 3, 1 ),
+ ( 4, 2 ),
+ ( 5, 1 ),
+ ( 6, 2 ),
+ ( 7, 2 );
+```
+
+- 解题:
+
+
+```sql
+# 思路:要求是连续出现 3 次,可使用 3 张该表
+SELECT L1.Num ConsecutiveNums
+FROM
+ Logs L1,
+ Logs L2,
+ Logs L3
+WHERE
+ L1.Id = L2.Id-1
+ AND L2.ID = L3.Id-1
+ AND L1.Num = L2.Num
+ AND L2.Num = L3.Num;
+# 判断条件 Id 是不相同的,但是 Num 是相同的,并且 Id 是连续变化的
+```
+
+```sql
+# 由于要求是至少出现 3 次,所以需要 DISTINCT 进行去重
+SELECT DISTINCT L1.Num ConsecutiveNums
+FROM
+ Logs L1,
+ Logs L2,
+ Logs L3
+WHERE
+ L1.Id = L2.Id-1
+ AND L2.ID = L3.Id-1
+ AND L1.Num = L2.Num
+ AND L2.Num = L3.Num;
+```
+
+```sql
+# 另外一种写法
+SELECT DISTINCT L1.Num ConsecutiveNums
+FROM
+ Logs L1
+ LEFT JOIN Logs L2 ON L1.Id = L2.Id-1
+ LEFT JOIN Logs L3 ON L1.Id = L3.Id-2
+WHERE L1.Num = L2.Num
+ AND L2.Num = L3.Num;
+```
+
+# 15、换座位(626)(了解)
+
+[626. 换座位](https://leetcode-cn.com/problems/exchange-seats/)
+
+- 问题描述:
+
+seat 表存储着座位对应的学生。
+
+```html
++---------+---------+
+| id | student |
++---------+---------+
+| 1 | Abbot |
+| 2 | Doris |
+| 3 | Emerson |
+| 4 | Green |
+| 5 | Jeames |
++---------+---------+
+```
+
+要求交换相邻座位的两个学生,如果最后一个座位是奇数,那么不交换这个座位上的学生。
+
+```html
++---------+---------+
+| id | student |
++---------+---------+
+| 1 | Doris |
+| 2 | Abbot |
+| 3 | Green |
+| 4 | Emerson |
+| 5 | Jeames |
++---------+---------+
+```
+
+- SQL Schema
+
+```sql
+DROP TABLE
+IF
+ EXISTS seat;
+CREATE TABLE seat ( id INT, student VARCHAR ( 255 ) );
+INSERT INTO seat ( id, student )
+VALUES
+ ( '1', 'Abbot' ),
+ ( '2', 'Doris' ),
+ ( '3', 'Emerson' ),
+ ( '4', 'Green' ),
+ ( '5', 'Jeames' );
+```
+
+- 解题:
+
+使用多个 union。
+
+```sql
+SELECT
+ s1.id - 1 AS id,
+ s1.student
+FROM
+ seat s1
+WHERE
+ s1.id MOD 2 = 0 UNION
+SELECT
+ s2.id + 1 AS id,
+ s2.student
+FROM
+ seat s2
+WHERE
+ s2.id MOD 2 = 1
+ AND s2.id != ( SELECT max( s3.id ) FROM seat s3 ) UNION
+SELECT
+ s4.id AS id,
+ s4.student
+FROM
+ seat s4
+WHERE
+ s4.id MOD 2 = 1
+ AND s4.id = ( SELECT max( s5.id ) FROM seat s5 )
+ORDER BY
+ id;
+```
diff --git "a/docs/HTTP/1_HTTP\346\246\202\350\277\260.md" "b/docs/HTTP/1_HTTP\346\246\202\350\277\260.md"
new file mode 100644
index 00000000..edddb6c4
--- /dev/null
+++ "b/docs/HTTP/1_HTTP\346\246\202\350\277\260.md"
@@ -0,0 +1,85 @@
+# HTTP概述
+
+## HTTP 协议的特点
+
+- **支持客户端/服务器模式**。
+
+- **简单快速。**
+
+ 客户端向服务端请求服务时,只需要**传输请求方法和路径**。请求的方法常用的有 GET、POST。
+
+ HTTP 协议简单,是使得 **HTTP 服务程序规模小**,所以通信速度快。
+
+- **灵活。**
+
+ HTTP 允许传输任意类型的数据对象。
+
+- **无连接。**
+
+ 这里的无连接是指**限制每次连接只处理一个请求**。服务器处理客户端的请求,并且收到客户端的应答后,即可断开连接,可节省传输时间。
+
+- **无状态。**
+
+ HTTP 协议是无状态协议。缺少状态意味着如果候选处理需要前面的的信息,则必须重传。另一方面,服务端不需要先前信息时,应答较快。
+
+## URL
+
+URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
+
+- URI(Uniform Resource Identifier,统一资源标识符)
+- URL(Uniform Resource Locator,统一资源定位符)
+- URN(Uniform Resource Name,统一资源名称)
+
+
+
+## 请求和响应报文
+
+### 1. 请求报文
+
+
+
+### 2. 响应报文
+
+
+
+### 3. 请求响应步骤
+
+- 客户端连接到 Web 服务器
+- 发送 HTTP 请求
+- 服务器接受 HTTP 请求并且返回 HTTP 响应
+- 释放 TCP 连接
+- 客户端浏览器解析 HTML 内容
+
+## 输入 URL 地址,显示主页
+
+- DNS 解析获取相应的 IP 地址。
+
+- TCP 连接。根据 IP 地址、端口号和服务器建立 TCP 连接。
+
+- 发送 HTTP 请求。
+
+ 将请求发送给服务器,Cookie 也会随着请求发送给服务器。
+
+- 服务器接收请求并处理,返回 HTTP 报文。
+
+- 客户端收到 HTML 进行解析并渲染页面。
+
+- 断开连接。
+
+## HTTP 1.0 与 HTTP 1.1 的区别
+
+- HTTP/1.1 默认是长连接。关闭长连接 `Connection : close`
+
+ HTTP/1.0 默认是短连接。使用长连接 `Connection:Keep-Alive`
+
+- HTTP/1.1 支持同时打开多个 TCP 连接
+
+- HTTP/1.1 支持虚拟主机。
+
+- HTTP/1.1 新增状态码。比如 409 Conflict 表请求的资源与**资源的当前状态**发生冲突。
+
+- 带宽优化:
+
+ HTTP/1.0 存在一些浪费带宽的现象,如客户端只需要某个对象的一部分,而服务器却将整个对象发送过过来。
+
+ HTTP/1.1 在请求头中引入 range,它允许请求资源的某个部分,返回状态码为 206 Partial Content。
\ No newline at end of file
diff --git "a/docs/HTTP/2_HTTP\347\212\266\346\200\201\347\240\201.md" "b/docs/HTTP/2_HTTP\347\212\266\346\200\201\347\240\201.md"
new file mode 100644
index 00000000..8b6d9264
--- /dev/null
+++ "b/docs/HTTP/2_HTTP\347\212\266\346\200\201\347\240\201.md"
@@ -0,0 +1,50 @@
+# HTTP状态码
+
+服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。
+
+| 状态码 | 类别 | 原因短语 |
+| :---: | :---: | :---: |
+| 1XX | Informational(信息性状态码) | 表示请求已接收,继续处理 |
+| 2XX | Success(成功状态码) | 请求已被成功接收 |
+| 3XX | Redirection(重定向状态码) | 要完成请求必须进行更进一步的处理的操作 |
+| 4XX | Client Error(客户端错误状态码) | 请求无法错误或请求无法实现 |
+| 5XX | Server Error(服务器错误状态码) | 服务器未能处理合法请求 |
+
+## 1XX 信息
+
+- **100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
+
+## 2XX 成功
+
+- **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 方法。
+
+## 4XX 客户端错误
+
+- **400 Bad Request** :客户端请求有语法错误,不能被服务器所理解
+- **401 Unauthorized** :请求未经授权,这个状态码必须和 *WWW-Authenticate* 一起使用
+- **403 Forbidden** :服务器收到请求,但是拒绝提供服务
+- **404 Not Found** :请求的资源不存在,比如输入错误的 URL
+
+## 5XX 服务器错误
+
+- **500 Internal Server Error** :服务器发生不可预期的错误。
+
+- **503 Service Unavailable** :服务器当前不能处理客户端的请求,一段时间后可能恢复正常。
diff --git "a/docs/HTTP/3_\345\205\267\344\275\223\345\272\224\347\224\250.md" "b/docs/HTTP/3_\345\205\267\344\275\223\345\272\224\347\224\250.md"
new file mode 100644
index 00000000..9f3dc365
--- /dev/null
+++ "b/docs/HTTP/3_\345\205\267\344\275\223\345\272\224\347\224\250.md"
@@ -0,0 +1,214 @@
+# 具体应用
+
+## 连接管理
+
+
+
+### 1. 短连接与长连接
+
+当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
+
+长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
+
+- 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 `Connection : close`;
+- 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 `Connection : Keep-Alive`。
+
+### 2. 流水线
+
+默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
+
+流水线是在同一条长连接上发出连续的请求,而不用等待响应返回,这样可以避免连接延迟。
+
+## Cookie
+
+HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。
+
+HTTP/1.1 引入 Cookie 来**保存状态信息**。
+
+
+

+
+
+Cookie 是服务器发送到用户浏览器并**保存在本地的一小块数据**,在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。
+
+Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。
+
+### 1. 用途
+
+- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
+- 个性化设置(如用户自定义设置、主题等)
+- 浏览器行为跟踪(如跟踪分析用户行为等)
+
+### 2. 创建过程
+
+服务器发送的响应报文包含 **Set-Cookie 首部字段**,客户端得到响应报文后把 Cookie 内容保存到浏览器中。
+
+```html
+HTTP/1.0 200 OK
+Content-type: text/html
+Set-Cookie: yummy_cookie=choco
+Set-Cookie: tasty_cookie=strawberry
+
+[page content]
+```
+
+客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。
+
+```html
+GET /sample_page.html HTTP/1.1
+Host: www.example.org
+Cookie: yummy_cookie=choco; tasty_cookie=strawberry
+```
+
+### 3. 分类
+
+- 会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
+- 持久性 Cookie:指定一个特定的过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie。
+
+```html
+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,则以下地址都会匹配:
+
+- /docs
+- /docs/Web/
+- /docs/Web/HTTP
+
+### 5. Session
+
+除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
+
+服务器为每个 Session 分配一个唯一的 Session ID。Session 被创建后,调用 Session 相关方法向 Session 中添加内容,而这些内容保存在服务端,响应到客户端的只有 Session ID。
+
+客户端再次发送请求后,会根据这个 Session ID,查找到相应的 Session。
+
+Session 可以**存储在服务器**上的文件、数据库或者内存中。也可以**将 Session 存储在 Redis 这种内存型数据库中,效率会更高**。
+
+使用 Session 维护用户登录状态的过程如下:
+
+- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
+- 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID;
+- 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中;
+- 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。
+
+应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。
+
+> **补充:Session 的实现方式**
+
+> - 使用Cookie 来实现
+
+服务器为每个Session分配一个唯一的JSESSIONID,并通过cookie发送给客户端。当客户端发起新的请求的时候,会在请求中携带这个JSESSIONID,这样服务器就能找到该客户端对应的Session。
+
+> - 使用 URL 回写来实现
+
+URL回写是指,服务器在发送给浏览器的所有连接中,都携带JSESSIONID的参数。
+
+TOMCAT是默认使用两种方式,当 Cookie 被禁用就使用 URL 回写的方式。
+
+### 6. 浏览器禁用 Cookie
+
+此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外,不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。
+
+### 7. Cookie 与 Session 选择
+
+- Cookie 只能存储 ASCII 码字符串,
+
+ Session 则可以存取任何类型的数据,因此在考虑数据复杂性时首选 Session;
+
+- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;
+
+- 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。若考虑减轻服务器负担,应当使用 Cookie。
+
+## 缓存
+
+### 1. 优点
+
+- 缓解服务器压力;
+- 降低客户端获取资源的延迟:缓存通常位于内存中,读取缓存的速度更快。并且缓存在地理位置上也有可能比源服务器来得近,例如浏览器缓存。
+
+### 2. 实现方法
+
+- 让代理服务器进行缓存;
+- 让客户端浏览器进行缓存。
+
+### 3. Cache-Control
+
+HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。
+
+**3.1 禁止进行缓存**
+
+no-store 指令规定不能对请求或响应的任何一部分进行缓存。
+
+```html
+Cache-Control: no-store
+```
+
+**3.2 强制确认缓存**
+
+no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效才将能使用该缓存对客户端的请求进行响应。
+
+```html
+Cache-Control: no-cache
+```
+
+**3.3 私有缓存和公共缓存**
+
+private 指令规定了将资源作为私有缓存,只能被单独用户所使用,一般存储在用户浏览器中。
+
+```html
+Cache-Control: private
+```
+
+public 指令规定了将资源作为公共缓存,可以被多个用户所使用,一般存储在代理服务器中。
+
+```html
+Cache-Control: public
+```
+
+**3.4 缓存过期机制**
+
+max-age 指令出现在请求报文中,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。
+
+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 指令会被忽略掉。
+
+### 4. 缓存验证
+
+需要先了解 ETag 首部字段的含义,它是资源的唯一标识。URL 不能唯一表示资源,例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。
+
+```html
+ETag: "82e22293907ce725faf67773957acd12"
+```
+
+可以将缓存资源的 ETag 值放入 If-None-Match 首部,服务器收到该请求后,判断缓存资源的 ETag 值和资源的最新 ETag 值是否一致,如果一致则表示缓存资源有效,返回 304 Not Modified。
+
+```html
+If-None-Match: "82e22293907ce725faf67773957acd12"
+```
+
+Last-Modified 首部字段也可以用于缓存验证,它包含在源服务器发送的响应报文中,指示源服务器对资源的最后修改时间。但是它是一种弱校验器,因为只能精确到一秒,所以它通常作为 ETag 的备用方案。如果响应首部字段里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 OK。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的 304 Not Modified 响应。
+
+```html
+Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
+```
+
+```html
+If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
+```
diff --git a/docs/HTTP/4_HTTPs.md b/docs/HTTP/4_HTTPs.md
new file mode 100644
index 00000000..e246b341
--- /dev/null
+++ b/docs/HTTP/4_HTTPs.md
@@ -0,0 +1,83 @@
+# HTTPS
+
+HTTP 有以下安全性问题:
+
+- 使用明文进行通信,内容可能会被窃听;
+- 不验证通信方的身份,通信方的身份有可能遭遇伪装;
+- 无法证明报文的完整性,报文有可能遭篡改。
+
+HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信,也就是说 HTTPS 使用了隧道进行通信。
+
+通过使用 SSL,HTTPS 具有了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)。
+
+
+
+## 加密
+
+### 1. 对称密钥加密
+
+对称密钥加密(Symmetric-Key Encryption),加密和解密使用同一密钥。
+
+- 优点:运算速度快;
+- 缺点:无法安全地将密钥传输给通信方。
+
+
+
+### 2.非对称密钥加密
+
+非对称密钥加密,又称公开密钥加密(Public-Key Encryption),加密和解密使用不同的密钥。
+
+公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
+
+非对称密钥除了用来加密,还可以用来进行签名。因为私有密钥无法被其他人获取,因此通信发送方使用其私有密钥进行签名,通信接收方使用发送方的公开密钥对签名进行解密,就能判断这个签名是否正确。
+
+- 优点:可以更安全地将公开密钥传输给通信发送方;
+- 缺点:运算速度慢。
+
+
+
+### 3. HTTPs 采用的加密方式
+
+HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率。(下图中的 Session Key 就是对称密钥)
+
+
+
+## 认证
+
+通过使用 **证书** 来对通信方进行认证。
+
+数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。
+
+服务器的运营人员向 CA 提出**公开密钥**的申请,CA 在判明提出申请者的身份之后,会对已申请的**服务器的公开密钥**做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。
+
+进行 HTTPs 通信时,服务器会把证书发送给客户端。客户端取得其中的公开密钥之后,先使用数字签名进行验证,如果验证通过,就可以开始通信了。(客户端验证服务器的公钥是否有效)
+
+通信开始时,客户端需要使用服务器的公开密钥将自己的私有密钥传输给服务器,之后再进行对称密钥加密。
+
+
+
+## 完整性保护
+
+SSL 提供报文摘要功能来进行完整性保护。
+
+HTTP 也提供了 MD5 报文摘要功能,但不是安全的。例如报文内容被篡改之后,同时重新计算 MD5 的值,通信接收方是无法意识到发生了篡改。
+
+HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认证这两个操作。试想一下,加密之后的报文,遭到篡改之后,也很难重新计算报文摘要,因为无法轻易获取明文。
+
+
+
+## HTTP 与 HTTPS 的区别
+
+- HTTPS 需要向 CA 申请证书,需要支持证书授权的费用;
+
+ HTTP 不需要收费
+
+- HTTPS 是密文传输的,存在加密、加密过程,速度会更慢;
+
+ HTTP 是密文传输
+
+- HTTPS 默认使用 443 端口;
+
+ HTTP 默认使用 80 端口
+
+- HTTPS 先与 SSL 通信,再由 SSL 与 TCP 通信,在 HTTP 基础上增加了加密、认证、完整性保护的功能,比 HTTP 更安全。
\ No newline at end of file
diff --git "a/docs/HTTP/5_get\345\222\214post\346\257\224\350\276\203.md" "b/docs/HTTP/5_get\345\222\214post\346\257\224\350\276\203.md"
new file mode 100644
index 00000000..f3946147
--- /dev/null
+++ "b/docs/HTTP/5_get\345\222\214post\346\257\224\350\276\203.md"
@@ -0,0 +1,87 @@
+# GET 和 POST 比较
+
+## Http报文层面:
+
+### 作用
+
+GET 用于获取资源,而 POST 用于传输**实体主体**。
+
+### 参数
+
+GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看。
+
+因为 URL 只支持 ASCII 码,因此 GET 的参数中如果存在中文等字符就需要先进行编码。例如 `中文` 会转换为 `%E4%B8%AD%E6%96%87`,而空格会转换为 `%20`。POST 参考支持标准字符集。
+
+```
+GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1
+```
+
+```
+POST /test/demo_form.asp HTTP/1.1
+Host: w3schools.com
+name1=value1&name2=value2
+```
+
+## 数据库层面:
+
+### 安全性
+
+**安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的**。
+
+GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发生了改变。
+
+安全的方法除了 GET 之外还有:HEAD、OPTIONS。
+
+不安全的方法除了 POST 之外还有 PUT、DELETE。
+
+### 幂等性
+
+**幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的**。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。
+
+**所有的安全方法也都是幂等的**。
+
+在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。
+
+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 不是幂等的,如果调用多次,就会增加多行记录:
+
+```
+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 方法本身是可缓存的,包括 GET 和 HEAD,但是 PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的。
+- 响应报文的状态码是可缓存的,包括:200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501。
+- 响应报文的 Cache-Control 首部字段没有指定不进行缓存。
+
+### XMLHttpRequest
+
+为了阐述 POST 和 GET 的另一个区别,需要先了解 XMLHttpRequest:
+
+> XMLHttpRequest 是一个 API,它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个通过 URL 来获取数据的简单方式,并且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。XMLHttpRequest 在 AJAX 中被大量使用。
+
+- 在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做,例如火狐就不会。
+- 而 GET 方法 Header 和 Data 会一起发送。
\ No newline at end of file
diff --git a/docs/JVM/1_JVM.md b/docs/JVM/1_JVM.md
index df862793..3ec65d58 100644
--- a/docs/JVM/1_JVM.md
+++ b/docs/JVM/1_JVM.md
@@ -1,8 +1,8 @@
-## 一、运行时数据区域
+# 运行时数据区域
-### 程序计数器(Program Counter Register)
+## 程序计数器(Program Counter Register)
- 当前线程所执行的字节码行号指示器(逻辑)
- 通过改变计数器的值来选取下一条需要执行的字节码指令
@@ -10,7 +10,7 @@
- 对 Java 方法计数,如果是 Native 方法则计数器值为 Undefined
- 只是计数,不会发生内存泄漏
-### Java 虚拟机栈
+## Java 虚拟机栈
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
@@ -57,7 +57,7 @@ javap -verbose JVMTest
-### 本地方法栈
+## 本地方法栈
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
@@ -65,7 +65,7 @@ javap -verbose JVMTest
-### 堆
+## 堆
所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。
@@ -90,25 +90,49 @@ java -Xms1M -Xmx2M HackTheJava
3、堆式存储:编译时或运行时模块入口都无法确定,动态分配
-> JVM 内存模型中堆和栈的联系
+
+
+> **问题一:堆和栈的联系**
引用对象、数组时,栈里定义变量保存堆中目标的首地址。
-> JVM 内存模型中堆和栈的区别
+> **问题二:栈和堆的区别**
+
+- 物理地址
+
+ 堆的物理内存分配是不连续的;
+
+ 栈的物理内存分配是连续的
+
+- 分配内存
+
+ 堆是不连续的,分配的内存是在运行期确定的,大小不固定;
+
+ 栈是连续的,分配的内存在编译器就已经确定,大小固定
+
+- 存放内容
+
+ 堆中存放的是对象和数组,关注的是数据的存储;
+
+ 栈中存放局部变量,关注的是程序方法的执行
+
+- 是否线程私有
+
+ 堆内存中的对象对所有线程可见,可被所有线程访问;
+
+ 栈内存属于某个线程私有的
-管理方式:栈自动释放,堆需要 GC
+- 异常
-空间大小:栈比堆小
+ 栈扩展失败,会抛出 StackOverflowError;
-碎片相关:栈产生的碎片远小于堆
+ 堆内存不足,会抛出 OutOfMemoryError
-分配方式:栈支持静态和动态分配,而堆仅支持动态分配
-效率:栈的效率比堆高
-### 方法区
+## 方法区
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
@@ -132,7 +156,7 @@ HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永
3、永久代会为 GC 带来不必要的复杂性
-### 运行时常量池
+## 运行时常量池
运行时常量池是方法区的一部分。
@@ -140,11 +164,11 @@ Class 文件中的常量池(编译器生成的字面量和符号引用)会
除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。
-### 直接内存
+## 直接内存
在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。
-### JVM常见参数
+## JVM常见参数
| 参数 | 含义 |
| ---- | ------------------------------------------------------------ |
diff --git "a/docs/JVM/1_Java\345\206\205\345\255\230\345\214\272\345\237\237.md" "b/docs/JVM/1_Java\345\206\205\345\255\230\345\214\272\345\237\237.md"
new file mode 100644
index 00000000..ae76568a
--- /dev/null
+++ "b/docs/JVM/1_Java\345\206\205\345\255\230\345\214\272\345\237\237.md"
@@ -0,0 +1,458 @@
+# Java 运行数数据区域
+
+Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。
+
+JDK 1.8 和之前的版本略有不同:
+
+- JDK 1.7 之前**运行时常量池**逻辑包含**字符串常量池**存放在方法区
+- JDK 1.7 字符串常量池被从方法区中拿到了堆,运行时常量池剩下的内容还在方法区
+- JDK1.8 HotSpot 虚拟机**移除了永久代**,采用**元空间(Metaspace)** 代替方法区,这时候**字符串常量池还在堆**,运行时常量池还在方法区,只不过方法区的实现从永久代变成了元空间。
+
+
+
+## 程序计数器
+
+程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的**行号指示器(逻辑上)**。主要有以下两个作用:
+
+- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
+- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
+
+此外,程序计数器还有如下特性:
+
+- 通过改变计数器的值来选取下一条需要执行的字节码指令
+- 和线程一对一的关系,即“线程私有”
+- 对 Java 方法计数,如果是 Native 方法则计数器值为 Undefined
+- 只是计数,不会发生内存泄漏,生命周期随着线程的创建而创建,随着线程的结束而死亡。
+
+## Java 虚拟机栈
+
+每个 Java 方法在执行的同时会创建一个栈帧用于存储**局部变量表**、**操作数栈**、动态链接、方法出口信息等。
+
+
+
+从方法调用直至执行完成的过程,就对应着**一个栈帧在 Java 虚拟机栈中入栈和出栈的过程**。Java 方法有两种返回方式:
+
+- return 语句
+- 抛出异常
+
+不管使用哪种返回方式都会导致栈帧被弹出。
+
+可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小:
+
+```java
+java -Xss512M HackTheJava
+```
+
+该区域可能抛出以下异常:
+
+- 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;
+- 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
+
+注意:**HotSpot 虚拟机的栈容量不可以进行动态扩展的**,所以在 HotSpot 虚拟机是不会由于虚拟机栈无法扩展而导致 OOM 的,但是如果申请时就失败,仍然会出现 OOM 异常。
+
+### 局部变量表
+
+局部变量表主要存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)和对象引用。
+
+### 操作数栈
+
+- 局部变量表:包含方法执行过程中的所有变量
+- 操作数栈:入栈、出栈、复制、交换、产生消费变量
+
+通过 javap 命令分析 Java 汇编指令,感受操作数栈和局部变量表的关系。
+
+定义测试类:该类中定义了一个静态方法 add()
+
+```java
+public class JVMTest {
+ public static int add(int a ,int b) {
+ int c = 0;
+ c = a + b;
+ return c;
+ }
+}
+```
+
+使用 javap 命令(javap 分析的是字节码文件)
+
+```html
+javap -verbose JVMTest
+```
+
+得到如下汇编指令:
+
+```html
+ public static int add(int, int);
+ descriptor: (II)I
+ flags: ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=3, args_size=2
+ 0: iconst_0
+ 1: istore_2
+ 2: iload_0
+ 3: iload_1
+ 4: iadd
+ 5: istore_2
+ 6: iload_2
+ 7: ireturn
+ LineNumberTable:
+ line 5: 0
+ line 6: 2
+ line 7: 6
+```
+
+解读上述指令:
+
+- stack = 2 说明栈的深度是 2 ;
+- locals = 3 说明有 3 个本地变量 ;
+- args_size = 2 说明该方法需传入 2 个参数
+- load 指令表示入操作数栈,store 表示出操作数栈
+
+执行 add(1,2),说明局部变量表和操作数栈的关系:
+
+- 首先会将栈帧按照程序计数器指令从大到小依次入栈,栈帧按照程序计数器指令依次出栈。
+- 数据 1、2 是入参,已经存在局部变量表 0、1 位置
+- 首先执行 iconst_0,将数据 0 push 进操作数栈
+- 执行 istore_2,将数据 0 pop出操作数栈并放入局部变量表中 2 位置
+- 执行 iload_0,将 0 位置元素(数值 1) push 进操作数栈
+- 执行 iload_1,将 1 位置元素(数值 2) push 进操作数栈
+- 执行 iadd,将数值1和数值2元素 pop出操作数栈,执行加法运算后,得到结果3,将 3 push 进操作数栈
+- 执行 istore_2,将数据 3 pop出操作数栈并放入局部变量表中 2 位置
+- 执行 iload_2,将 2 位置元素(数值 3)push 进操作数栈
+- 执行 ireturn,返回操作数栈栈顶元素
+
+ 
+
+
+## 本地方法栈
+
+本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
+
+本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
+
+本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
+
+方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 。
+
+## 堆
+
+几乎所有的对象实例以及数组都在这里分配内存,是垃圾收集的主要区域,所以也被称作 **GC 堆(Garbage Collected Heap)**。
+
+现代的垃圾收集器基本都是**采用分代收集算法**,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法。可以将堆分成两块:
+
+- 新生代 (Young Generation),新生代可以划分为 Eden 、From Survivor、To Survivor 空间
+- 老年代 (Old Generation)
+
+堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
+
+可以通过 -Xms 和 -Xmx 这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
+
+```java
+java -Xms1M -Xmx2M HackTheJava
+```
+
+JDK 7 版本及 JDK 7 版本之前,Hotspot 虚拟机的堆结构如下:
+
+- 新生代 (Young Generation)
+- 老年代 (Old Generation)
+- 永久代 (Permanent Generation)
+
+
+
+JDK 8 版本之后 HotSpot 虚拟机的永久代被彻底移除了,取而代之是元空间,元空间使用的是直接内存。
+
+
+
+## 堆和栈的关系
+
+Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中**栈就是现在说的虚拟机栈**,或者说是虚拟机栈中局部变量表部分。引用对象、数组时,栈里定义变量保存堆中目标的首地址。
+
+栈和堆的区别:
+
+- 物理地址
+
+ 堆的物理内存分配是不连续的;
+
+ 栈的物理内存分配是连续的
+
+- 分配内存
+
+ 堆是不连续的,分配的内存是在运行期确定的,大小不固定;
+
+ 栈是连续的,分配的内存在编译器就已经确定,大小固定
+
+- 存放内容
+
+ 堆中存放的是对象和数组,关注的是数据的存储;
+
+ 栈中存放局部变量,关注的是程序方法的执行
+
+- 是否线程私有
+
+ 堆内存中的对象对所有线程可见,可被所有线程访问;
+
+ 栈内存属于某个线程私有的
+
+- 异常
+
+ 栈扩展失败,会抛出 StackOverflowError;
+
+ 堆内存不足,会抛出 OutOfMemoryError
+
+## 方法区
+
+用于存放已被加载的**类信息**、**常量**、**静态变量**、**即时编译器编译后的代码**等数据。
+
+和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。
+
+对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
+
+HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
+
+### 方法区与永久代
+
+**方法区只是一个 JVM 规范**,在不同的 JVM 上方法区的实现可能是不同的。
+
+方法区和永久代的关系类似 Java 中接口和类的关系,类实现了接口,**永久代就是 HotSpot 虚拟机对 JVM 规范中方法区的一种实现方式**。
+
+方法区是 JVM 规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。
+
+### 元空间与永久代
+
+**方法区只是一个 JVM 规范,永久代与元空间都是其一种实现方式**。在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中:**元空间存储类的元信息,静态变量和常量池等则放入堆中**。
+
+元空间与永久代的最大区别在于:元空间使用本地内存,而永久代使用 JVM 的内存,元空间相比永久代具有如下优势:
+
+- 永久代存在一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。当元空间溢出时会得到如下错误: `java.lang.OutOfMemoryError: MetaSpace` 可以使用 `-XX:MaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。
+- 元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 `MaxPermSize` 控制了, 而由系统的实际可用空间来控制,可以加载更多的类。
+
+
+
+## 运行时常量池
+
+运行时常量池是方法区的一部分。
+
+Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表(用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中)。
+
+运行时常量池相对于 Class 文件常量池的另外一个重要特征是具备动态性,Java 语言并不要求常量
+一定只有编译期才能产生,运行期间也可以将新的常量放入池中,例如 String 类的 intern()。
+
+
+
+## 字符串常量池
+
+JDK 1.7 之前**运行时常量池**逻辑包含**字符串常量池**存放在方法区。
+
+JDK 1.7 **字符串常量池被单独拿到堆**,运行时常量池剩下的内容还在方法区。
+
+JDK1.8 HotSpot 虚拟机**移除了永久代**,采用**元空间(Metaspace)** 代替方法区,这时候**字符串常量池还在堆**,运行时常量池还在方法区,只不过方法区的实现从永久代变成了元空间。
+
+### String 对象创建方式
+
+创建方式 1:
+
+```java
+String str1 = "abcd";
+```
+
+创建方式 2:
+
+```java
+String str2 = new String("abcd");
+```
+
+这两种不同的创建方法是有差别的:
+
+方式 1 是在**常量池**中获取对象("abcd" 属于字符串字面量,因此编译时期会在常量池中创建一个字符串对象)。
+
+方式 2 会创建两个字符串对象(前提是常量池中还没有 "abcd" 字符串对象):
+
+- "abcd" 属于字符串字面量,因此编译时期会在常量池中创建一个字符串对象,该字符串对象指向这个 "abcd" 字符串字面量;
+- 使用 new 的方式会在堆中创建一个字符串对象。
+
+(**字符串常量"abcd"在编译期**就已经确定放入常量池,而 Java **堆上的"abcd"是在运行期**初始化阶段才确定)。
+
+**str1 指向常量池中的 “abcd” 对象,而 str2 指向堆中的字符串对象。**
+
+### String 的 intern() 方法
+
+String 的 intern() 是一个 **Native 方法**,当调用 intern() 方法时:
+
+- 如果运行时常量池中已经包含一个等于该 String 对象内容的字符串,则返回常量池中该字符串的引用。
+- 如果没有等于该 String 对象的字符串,JDK1.7 之前(不包含 1.7)是在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用,**JDK1.7 以及之后的处理方式是对于存在堆中的对象,在常量池中直接保存对象的引用,而不会重新创建对象。**
+
+```java
+String s3 = new String("1") + new String("1");
+s3.intern();
+String s4 = "11";
+System.out.println(s3 == s4);
+```
+
+JDK 6 输出结果:
+
+```html
+false
+```
+
+JDK 8 输出结果:
+
+```html
+true
+```
+
+补充:[String 的 intern() 方法详解](https://www.cnblogs.com/wxgblogs/p/5635099.html)
+
+### 字符串拼接问题
+
+```java
+String str1 = "hello";
+String str2 = "world";
+
+String str3 = "hello" + "world";//常量池中的对象
+String str4 = str1 + str2; //在堆上创建的新的对象
+String str5 = "helloworld";//常量池中的对象
+
+System.out.println(str3 == str4);
+System.out.println(str3 == str5);
+System.out.println(str4 == str5);
+```
+
+输出结果如下:
+
+```html
+false
+true
+false
+```
+
+str1、str2 是从字符串常量池中获取的对象。
+
+对于 str3,字符串 "hello" 和字符串 "world" 相加有后得到 "helloworld",在字符串常量池中创建 "helloworld" 对象。
+
+对于 str4,str1+str2 则会在堆中创建新的 "helloworld" 对象。
+
+对于 str5,字符串常量池已有 "helloworld" 对象,str5 直接引用该对象。
+
+
+
+所以,尽量避免多个字符串拼接,因为这样会重新创建新的对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
+
+## 直接内存
+
+在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。
+
+
+
+# Hotpot 虚拟机对象
+
+## 对象的内存布局
+
+在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:
+
+- 对象头
+- 实例数据
+- 对齐填充
+
+
+
+### 对象头
+
+Hotspot 虚拟机的对象头包括两部分信息:
+
+一部分用于存储对象自身的运行时数据(哈希码、GC分代年龄、锁状态标志等等);
+
+另一部分是类型指针,即对象指向它的**类元数据的指针**,虚拟机通过这个指针来**确定这个对象是哪个类的实例**。
+
+### 实例数据
+
+实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
+
+### 对齐填充
+
+对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起**占位**作用。
+
+因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
+
+
+
+## 对象的创建
+
+Java 对象的创建过程分为以下5步:
+
+- 类加载检查
+- 分配内存
+- 初始化零值
+- 设置对象头
+- 执行 \ 方法
+
+### 1. 类加载检查
+
+虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的**符号引用**,
+并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。
+如果没有,那必须先执行相应的类加载过程。
+
+### 2. 分配内存
+
+在类加载检查通过后,接下来虚拟机将为新生对象分配内存。
+对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定。
+
+ Java 堆内存是否规整,则取决于 GC 收集器的算法是“标记-清除”,还是“标记-整理”(也称作“标记-压缩”),值得注意的是,“复制算法”内存也是规整的。
+
+#### 两种内存分配方式
+
+##### 指针碰撞
+
+- 原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界值指针,只需要向着没用过的内存方向将指针移动一段与对象大小相等的距离。
+- 适用场景:堆内存规整(即没有内存碎片)的情况
+- GC(Garbage Collection)收集器:Serial、ParNew
+
+##### 空闲列表
+
+- 原理:虚拟机会维护一个列表,在该列表中记录哪些内存块是可用的,在分配的时候,找一块足够大的内存块划分给对象示例,然后更新列表记录
+- 适用场景:堆内存规整
+- GC(Garbage Collection)收集器:CMS
+
+#### 内存分配并发问题
+
+在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,
+作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
+
+- CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。
+- TLAB:每一个线程预先在Java堆中分配一块内存,称为**本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)**。哪个线程要分配内存,就在哪个线程的 TLAB 上分配,只有 TLAB 用完并分配新的 TLAB 时,才采用上述的 CAS 进行内存分配。
+
+### 3. 初始化零值
+
+内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作**保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用**,程序能访问到这些字段的数据类型所对应的零值。
+
+### 4. 设置对象头
+
+初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。
+另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
+
+### 5. 执行 init 方法
+
+在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,**\ 方法还没有执行,所有的字段都还为零**。所以一般来说,执行 new 指令之后会接着执行 \ 方法,把**对象按照程序员的意愿进行初始化**,这样一个真正可用的对象才算完全产生出来。
+
+
+
+## 对象的访问定位
+
+建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。
+对象的访问方式视虚拟机的实现而定,目前主流的访问方式有两种:使用句柄、直接指针。
+
+### 使用句柄
+
+如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是**对象的句柄地址**,而句柄中包含了对象实例数据与类型数据各自的具体地址信息 。
+
+
+
+### 直接指针
+
+如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是**对象的地址**。
+
+
+
+这两种对象访问方式各有优势:
+
+- 使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 **reference 本身不需要修改**。
+- 使用直接指针访问方式最大的好处就是**速度快**,它节省了一次指针定位的时间开销。
\ No newline at end of file
diff --git a/docs/JVM/2_JVM.md b/docs/JVM/2_JVM.md
index 3e315ceb..1b14d97e 100644
--- a/docs/JVM/2_JVM.md
+++ b/docs/JVM/2_JVM.md
@@ -1,6 +1,6 @@
-## 二、HotSpot 虚拟机对象
+# HotSpot 虚拟机对象
-### 对象的创建
+## 对象的创建
对象的创建步骤:
@@ -52,7 +52,7 @@
所以一般来说,执行 new 指令之后会接着执行 \ 方法,
把**对象按照程序员的意愿进行初始化**,这样一个真正可用的对象才算完全产生出来。
-### 对象的内存布局
+## 对象的内存布局
在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头、实例数据、对齐填充。
@@ -79,7 +79,7 @@ Hotspot虚拟机的对象头包括两部分信息:
换句话说就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或2倍),
因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
-### 对象的访问定位
+## 对象的访问定位
建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。
对象的访问方式视虚拟机的实现而定,目前主流的访问方式有两种:使用句柄、直接指针。
diff --git a/docs/JavaBasics/01String.md b/docs/JVM/2_String.md
similarity index 99%
rename from docs/JavaBasics/01String.md
rename to docs/JVM/2_String.md
index 39442868..08de7058 100644
--- a/docs/JavaBasics/01String.md
+++ b/docs/JVM/2_String.md
@@ -1,4 +1,4 @@
-# 二、String
+# String
## 概览
diff --git "a/docs/JVM/2_\345\236\203\345\234\276\346\224\266\351\233\206.md" "b/docs/JVM/2_\345\236\203\345\234\276\346\224\266\351\233\206.md"
new file mode 100644
index 00000000..020c1779
--- /dev/null
+++ "b/docs/JVM/2_\345\236\203\345\234\276\346\224\266\351\233\206.md"
@@ -0,0 +1,425 @@
+# 垃圾收集 (Garbage Collection,GC)
+
+垃圾收集主要是针对堆和方法区进行。程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后就会消失,因此不需要对这三个区域进行垃圾回收。
+
+## 判断一个对象是否可被回收
+
+堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。
+
+### 引用计数算法
+
+为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
+
+在两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。正是因为**循环引用**的存在,因此 Java 虚拟机不使用引用计数算法。
+
+```java
+public class Test {
+
+ public Object instance = null;
+
+ public static void main(String[] args) {
+ Test a = new Test();
+ Test b = new Test();
+ a.instance = b;
+ b.instance = a;
+ a = null;
+ b = null;
+ doSomething();
+ }
+}
+```
+
+在上述代码中,a 与 b 引用的对象实例互相持有了对象的引用,因此当我们把对 a 对象与 b 对象的引用去除之后,由于两个对象还存在互相之间的引用,导致两个 Test 对象无法被回收。
+
+- 优点:执行效率高,程序执行受影响较小。
+- 缺点:无法检测出循环引用的情况,引起内存泄漏。
+
+### 可达性分析算法
+
+通过判断对象的引用链是否可达来决定对象是否可以被回收。
+
+以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。
+
+
+
+**Java 虚拟机使用可达性分析算法来判断对象是否可被回收**,GC Roots 一般包含以下几种:
+
+- 虚拟机栈中局部变量表中引用的对象(栈帧中的本地方法变量表)
+- 本地方法栈中 JNI(Native方法) 中引用的对象
+- 方法区中类静态属性引用的对象
+- 方法区中的常量引用的对象
+- 活跃线程的引用对象
+
+
+
+### 方法区的回收
+
+因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,所以在方法区上进行回收性价比不高。
+
+主要是对常量池的回收和对类的卸载。
+
+为了避免内存溢出,在大量使用反射和动态代理的场景都需要虚拟机具备类卸载功能。
+
+类的卸载条件很多,需要满足以下三个条件,并且满足了条件也不一定会被卸载:
+
+- 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例。
+- 加载该类的 ClassLoader 已经被回收。
+- 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
+
+
+
+### finalize()
+
+类似 C++ 的析构函数(注意:只是类似,C++ 析构函数调用确定,而 finalize() 方法是不确定的),用于关闭外部资源。但是 try-finally 等方式可以做得更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
+
+当垃圾回收器要宣告一个对象死亡时,至少要经历两次标记过程。
+
+如果对象在进行可达性分析以后,没有与GC Root 直接相连接的引用量,就会被**第一次标记**,并且判断是否执行 finalize() 方法;
+
+如果这个对象覆盖了 finalize() 方法,并且未被引用,就会被放置于 F-Queue 队列,稍后由虚拟机创建的一个低优先级的 finalize() 线程去执行**触发 finalize() 方法**,在该方法中让对象重新被引用,从而实现自救。但是该线程的优先级比较低,执行过程随时可能会被终止。此外,自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会再调用该方法。
+
+
+
+### 引用类型
+
+无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
+
+JDK1.2 之前,Java 中引用定义:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
+
+JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)。
+
+#### 强引用 (Strong Reference)
+
+被强引用关联的对象不会被回收。
+
+使用 new 一个新对象的方式来创建强引用。
+
+```java
+Object obj = new Object();
+```
+
+当内存空间不足,JVM 抛出 OOM Error 终止程序也不会回收具有强引用的对象,只有通过将对象设置为 null 来弱化引用,才能使其被回收。
+
+#### 软引用 (Soft Reference)
+
+表示对象处在**有用但非必须**的状态。
+
+被软引用关联的对象只有在内存不够的情况下才会被回收。可以用来实现内存敏感的高速缓存。
+
+软引用可以和一个引用队列 ReferenceQueue 联合使用,如果软引用所引用的对象被垃圾回收,JVM 就会把这个软引用加入到与之关联的引用队列中。如果一个弱引用对象本身在引用队列中,就说明该引用对象所指向的对象被回收了。
+
+使用 SoftReference 类来创建软引用。
+
+```java
+Object obj = new Object();
+SoftReference