auto commit

This commit is contained in:
CyC2018 2018-08-09 23:09:29 +08:00
parent 8c67483fe9
commit dc6973b8e1
9 changed files with 1166 additions and 1210 deletions

View File

@ -45,7 +45,7 @@
* [二进制分帧层](#二进制分帧层) * [二进制分帧层](#二进制分帧层)
* [服务端推送](#服务端推送) * [服务端推送](#服务端推送)
* [首部压缩](#首部压缩) * [首部压缩](#首部压缩)
* [八、GET 和 POST 的区别](#八get-和-post-的区别) * [八、GET 和 POST 比较](#八get-和-post-比较)
* [作用](#作用) * [作用](#作用)
* [参数](#参数) * [参数](#参数)
* [安全](#安全) * [安全](#安全)
@ -61,13 +61,13 @@
## URL ## URL
- URIUniform Resource Identifier统一资源标识符
- URLUniform Resource Locator统一资源定位符
- URNUniform Resource Name统一资源名称例如 urn:isbn:0-486-27557-4。
URI 包含 URL 和 URN目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。 URI 包含 URL 和 URN目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
<div align="center"> <img src="../pics//f716427a-94f2-4875-9c86-98793cf5dcc3.jpg" width="400"/> </div><br> - URIUniform Resource Identifier统一资源标识符
- URLUniform Resource Locator统一资源定位符
- URNUniform Resource Name统一资源名称
<div align="center"> <img src="../pics//urlnuri.jpg" width="600"/> </div><br>
## 请求和响应报文 ## 请求和响应报文
@ -197,7 +197,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 - **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
- **206 Partial Content** :表示客户端进行了范围请求响应报文包含由 Content-Range 指定范围的实体内容。 - **206 Partial Content** :表示客户端进行了范围请求响应报文包含由 Content-Range 指定范围的实体内容。
## 3XX 重定向 ## 3XX 重定向
@ -219,7 +219,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **401 Unauthorized** 该状态码表示发送的请求需要有认证信息BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。 - **401 Unauthorized** 该状态码表示发送的请求需要有认证信息BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由 - **403 Forbidden** :请求被拒绝。
- **404 Not Found** - **404 Not Found**
@ -331,7 +331,7 @@ Set-Cookie: tasty_cookie=strawberry
[page content] [page content]
``` ```
客户端之后对同一个服务器发送请求时,会从浏览器中读出 Cookie 信息通过 Cookie 请求首部字段发送给服务器。 客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。
```html ```html
GET /sample_page.html HTTP/1.1 GET /sample_page.html HTTP/1.1
@ -382,9 +382,9 @@ Path 标识指定了主机下的哪些路径可以接受 Cookie该 URL 路径
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在内存型数据库中,比如 Redis,效率会更高。 Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。
使用 Session 维护用户登录的过程如下: 使用 Session 维护用户登录状态的过程如下:
- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; - 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
- 服务器验证该用户名和密码; - 服务器验证该用户名和密码;
@ -499,7 +499,7 @@ If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
### 1. 短连接与长连接 ### 1. 短连接与长连接
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。 当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。 长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
@ -688,7 +688,7 @@ HTTPs 并不是新协议,而是让 HTTP 先和 SSLSecure Sockets Layer
### 3. HTTPs 采用的加密方式 ### 3. HTTPs 采用的加密方式
HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证安全性,之后使用对称密钥加密进行通信来保证效率。(下图中的 Session Key 就是对称密钥) HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率。(下图中的 Session Key 就是对称密钥)
<div align="center"> <img src="../pics//How-HTTPS-Works.png" width="600"/> </div><br> <div align="center"> <img src="../pics//How-HTTPS-Works.png" width="600"/> </div><br>
@ -717,7 +717,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
## HTTPs 的缺点 ## HTTPs 的缺点
- 因为需要进行加密解密等过程,因此速度会更慢; - 因为需要进行加密解密等过程,因此速度会更慢;
- 需要支付证书授权的高费用。 - 需要支付证书授权的高费用。
## 配置 HTTPs ## 配置 HTTPs
@ -727,7 +727,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
## HTTP/1.x 缺陷 ## HTTP/1.x 缺陷
HTTP/1.x 实现简单是以牺牲应用性能为代价的: HTTP/1.x 实现简单是以牺牲性能为代价的:
- 客户端需要使用多个连接才能实现并发和缩短延迟; - 客户端需要使用多个连接才能实现并发和缩短延迟;
- 不会压缩请求和响应首部,从而导致不必要的网络流量; - 不会压缩请求和响应首部,从而导致不必要的网络流量;
@ -763,7 +763,7 @@ HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见
<div align="center"> <img src="../pics//_u4E0B_u8F7D.png" width="600"/> </div><br> <div align="center"> <img src="../pics//_u4E0B_u8F7D.png" width="600"/> </div><br>
# 八、GET 和 POST 的区别 # 八、GET 和 POST 比较
## 作用 ## 作用
@ -870,6 +870,7 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) - [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)
- [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn) - [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn)
- [htmlspecialchars](http://php.net/manual/zh/function.htmlspecialchars.php) - [htmlspecialchars](http://php.net/manual/zh/function.htmlspecialchars.php)
- [Difference between file URI and URL in java](http://java2db.com/java-io/how-to-get-and-the-difference-between-file-uri-and-url-in-java)
- [How to Fix SQL Injection Using Java PreparedStatement & CallableStatement](https://software-security.sans.org/developer-how-to/fix-sql-injection-in-java-using-prepared-callable-statement) - [How to Fix SQL Injection Using Java PreparedStatement & CallableStatement](https://software-security.sans.org/developer-how-to/fix-sql-injection-in-java-using-prepared-callable-statement)
- [浅谈 HTTP 中 Get 与 Post 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html) - [浅谈 HTTP 中 Get 与 Post 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html)
- [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/) - [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/)

File diff suppressed because it is too large Load Diff

View File

@ -275,8 +275,6 @@ BIOS 不可以读取 GPT 分区表,而 UEFI 可以。
## 组成 ## 组成
<div align="center"> <img src="../pics//BSD_disk.png" width="800"/> </div><br>
最主要的几个组成部分如下: 最主要的几个组成部分如下:
- inode一个文件占用一个 inode记录文件的属性同时记录此文件的内容所在的 block 编号; - inode一个文件占用一个 inode记录文件的属性同时记录此文件的内容所在的 block 编号;
@ -287,6 +285,9 @@ BIOS 不可以读取 GPT 分区表,而 UEFI 可以。
- superblock记录文件系统的整体信息包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等; - superblock记录文件系统的整体信息包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等;
- block bitmap记录 block 是否被使用的位域; - block bitmap记录 block 是否被使用的位域;
<div align="center"> <img src="../pics//BSD_disk.png" width="800"/> </div><br>
## 文件读取 ## 文件读取
对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block然后把所有 block 的内容读出来。 对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block然后把所有 block 的内容读出来。
@ -336,7 +337,9 @@ inode 中记录了文件内容所在的 block 编号,但是每个 block 非常
## 目录 ## 目录
建立一个目录时,会分配一个 inode 与至少一个 block。block 记录的内容是目录下所有文件的 inode 编号以及文件名。可以看出文件的 inode 本身不记录文件名,文件名记录在目录中,因此新增文件、删除文件、更改文件名这些操作与目录的 w 权限有关。 建立一个目录时,会分配一个 inode 与至少一个 block。block 记录的内容是目录下所有文件的 inode 编号以及文件名。
可以看出文件的 inode 本身不记录文件名,文件名记录在目录中,因此新增文件、删除文件、更改文件名这些操作与目录的 w 权限有关。
## 日志 ## 日志

View File

@ -139,7 +139,7 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具
### 2. 操作 ### 2. 操作
操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。 进行查找操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。
插入删除操作记录会破坏平衡树的平衡性,因此在插入删除时,需要对树进行一个分裂、合并、旋转等操作。 插入删除操作记录会破坏平衡树的平衡性,因此在插入删除时,需要对树进行一个分裂、合并、旋转等操作。

View File

@ -633,11 +633,12 @@ public int RectCover(int n) {
## 解题思路 ## 解题思路
当 nums[m] <= nums[h] 的情况下,说明解在 [l, m] 之间,此时令 h = m否则解在 [m + 1, h] 之间,令 l = m + 1。 - 当 nums[m] <= nums[h] 的情况下,说明解在 [l, m] 之间,此时令 h = m
- 否则解在 [m + 1, h] 之间,令 l = m + 1。
因为 h 的赋值表达式为 h = m因此循环体的循环条件应该为 l < h详细解释请见 [Leetcode 题解](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3.md#%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE) 二分查找部分 因为 h 的赋值表达式为 h = m因此循环体的循环条件应该为 l < h详细解释请见 [Leetcode 题解](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3.md) 二分查找部分
但是如果出现 nums[l] == nums[m] == nums[h],那么此时无法确定解在哪个区间,因此需要切换到顺序查找。 但是如果出现 nums[l] == nums[m] == nums[h],那么此时无法确定解在哪个区间,需要切换到顺序查找。
复杂度O(logN) + O(1) 复杂度O(logN) + O(1)
@ -1217,8 +1218,7 @@ public ListNode ReverseList(ListNode head) {
### 递归 ### 递归
```java ```java
public ListNode Merge(ListNode list1, ListNode list2) public ListNode Merge(ListNode list1, ListNode list2) {
{
if (list1 == null) if (list1 == null)
return list2; return list2;
if (list2 == null) if (list2 == null)
@ -1236,8 +1236,7 @@ public ListNode Merge(ListNode list1, ListNode list2)
### 迭代 ### 迭代
```java ```java
public ListNode Merge(ListNode list1, ListNode list2) public ListNode Merge(ListNode list1, ListNode list2) {
{
ListNode head = new ListNode(-1); ListNode head = new ListNode(-1);
ListNode cur = head; ListNode cur = head;
while (list1 != null && list2 != null) { while (list1 != null && list2 != null) {
@ -1269,15 +1268,13 @@ public ListNode Merge(ListNode list1, ListNode list2)
## 解题思路 ## 解题思路
```java ```java
public boolean HasSubtree(TreeNode root1, TreeNode root2) public boolean HasSubtree(TreeNode root1, TreeNode root2) {
{
if (root1 == null || root2 == null) if (root1 == null || root2 == null)
return false; return false;
return isSubtreeWithRoot(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2); return isSubtreeWithRoot(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
} }
private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) {
{
if (root2 == null) if (root2 == null)
return true; return true;
if (root1 == null) if (root1 == null)
@ -1298,9 +1295,10 @@ private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2)
## 解题思路 ## 解题思路
### 递归
```java ```java
public void Mirror(TreeNode root) public void Mirror(TreeNode root) {
{
if (root == null) if (root == null)
return; return;
swap(root); swap(root);
@ -1308,14 +1306,36 @@ public void Mirror(TreeNode root)
Mirror(root.right); Mirror(root.right);
} }
private void swap(TreeNode root) private void swap(TreeNode root) {
{
TreeNode t = root.left; TreeNode t = root.left;
root.left = root.right; root.left = root.right;
root.right = t; root.right = t;
} }
``` ```
### 迭代
```java
public void Mirror(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
if (node == null)
continue;
swap(node);
stack.push(node.left);
stack.push(node.right);
}
}
private void swap(TreeNode node) {
TreeNode t = node.left;
node.left = node.right;
node.right = t;
}
```
# 28 对称的二叉树 # 28 对称的二叉树
[NowCder](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) [NowCder](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@ -1327,15 +1347,13 @@ private void swap(TreeNode root)
## 解题思路 ## 解题思路
```java ```java
boolean isSymmetrical(TreeNode pRoot) boolean isSymmetrical(TreeNode pRoot) {
{
if (pRoot == null) if (pRoot == null)
return true; return true;
return isSymmetrical(pRoot.left, pRoot.right); return isSymmetrical(pRoot.left, pRoot.right);
} }
boolean isSymmetrical(TreeNode t1, TreeNode t2) boolean isSymmetrical(TreeNode t1, TreeNode t2) {
{
if (t1 == null && t2 == null) if (t1 == null && t2 == null)
return true; return true;
if (t1 == null || t2 == null) if (t1 == null || t2 == null)
@ -1359,8 +1377,7 @@ boolean isSymmetrical(TreeNode t1, TreeNode t2)
## 解题思路 ## 解题思路
```java ```java
public ArrayList<Integer> printMatrix(int[][] matrix) public ArrayList<Integer> printMatrix(int[][] matrix) {
{
ArrayList<Integer> ret = new ArrayList<>(); ArrayList<Integer> ret = new ArrayList<>();
int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1; int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1;
while (r1 <= r2 && c1 <= c2) { while (r1 <= r2 && c1 <= c2) {
@ -1394,25 +1411,21 @@ public ArrayList<Integer> printMatrix(int[][] matrix)
private Stack<Integer> dataStack = new Stack<>(); private Stack<Integer> dataStack = new Stack<>();
private Stack<Integer> minStack = new Stack<>(); private Stack<Integer> minStack = new Stack<>();
public void push(int node) public void push(int node) {
{
dataStack.push(node); dataStack.push(node);
minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(), node)); minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(), node));
} }
public void pop() public void pop() {
{
dataStack.pop(); dataStack.pop();
minStack.pop(); minStack.pop();
} }
public int top() public int top() {
{
return dataStack.peek(); return dataStack.peek();
} }
public int min() public int min() {
{
return minStack.peek(); return minStack.peek();
} }
``` ```
@ -1430,8 +1443,7 @@ public int min()
使用一个栈来模拟压入弹出操作。 使用一个栈来模拟压入弹出操作。
```java ```java
public boolean IsPopOrder(int[] pushSequence, int[] popSequence) public boolean IsPopOrder(int[] pushSequence, int[] popSequence) {
{
int n = pushSequence.length; int n = pushSequence.length;
Stack<Integer> stack = new Stack<>(); Stack<Integer> stack = new Stack<>();
for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) { for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) {
@ -1464,8 +1476,7 @@ public boolean IsPopOrder(int[] pushSequence, int[] popSequence)
不需要使用两个队列分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。 不需要使用两个队列分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
```java ```java
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
{
Queue<TreeNode> queue = new LinkedList<>(); Queue<TreeNode> queue = new LinkedList<>();
ArrayList<Integer> ret = new ArrayList<>(); ArrayList<Integer> ret = new ArrayList<>();
queue.add(root); queue.add(root);
@ -1495,8 +1506,7 @@ public ArrayList<Integer> PrintFromTopToBottom(TreeNode root)
## 解题思路 ## 解题思路
```java ```java
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
{
ArrayList<ArrayList<Integer>> ret = new ArrayList<>(); ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>(); Queue<TreeNode> queue = new LinkedList<>();
queue.add(pRoot); queue.add(pRoot);
@ -1529,8 +1539,7 @@ ArrayList<ArrayList<Integer>> Print(TreeNode pRoot)
## 解题思路 ## 解题思路
```java ```java
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
{
ArrayList<ArrayList<Integer>> ret = new ArrayList<>(); ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>(); Queue<TreeNode> queue = new LinkedList<>();
queue.add(pRoot); queue.add(pRoot);
@ -1571,15 +1580,13 @@ public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot)
## 解题思路 ## 解题思路
```java ```java
public boolean VerifySquenceOfBST(int[] sequence) public boolean VerifySquenceOfBST(int[] sequence) {
{
if (sequence == null || sequence.length == 0) if (sequence == null || sequence.length == 0)
return false; return false;
return verify(sequence, 0, sequence.length - 1); return verify(sequence, 0, sequence.length - 1);
} }
private boolean verify(int[] sequence, int first, int last) private boolean verify(int[] sequence, int first, int last) {
{
if (last - first <= 1) if (last - first <= 1)
return true; return true;
int rootVal = sequence[last]; int rootVal = sequence[last];
@ -1610,14 +1617,12 @@ private boolean verify(int[] sequence, int first, int last)
```java ```java
private ArrayList<ArrayList<Integer>> ret = new ArrayList<>(); private ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
{
backtracking(root, target, new ArrayList<>()); backtracking(root, target, new ArrayList<>());
return ret; return ret;
} }
private void backtracking(TreeNode node, int target, ArrayList<Integer> path) private void backtracking(TreeNode node, int target, ArrayList<Integer> path) {
{
if (node == null) if (node == null)
return; return;
path.add(node.val); path.add(node.val);
@ -1641,8 +1646,7 @@ private void backtracking(TreeNode node, int target, ArrayList<Integer> path)
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。
```java ```java
public class RandomListNode public class RandomListNode {
{
int label; int label;
RandomListNode next = null; RandomListNode next = null;
RandomListNode random = null; RandomListNode random = null;
@ -1670,8 +1674,7 @@ public class RandomListNode
<div align="center"> <img src="../pics//8f3b9519-d705-48fe-87ad-2e4052fc81d2.png" width="600"/> </div><br> <div align="center"> <img src="../pics//8f3b9519-d705-48fe-87ad-2e4052fc81d2.png" width="600"/> </div><br>
```java ```java
public RandomListNode Clone(RandomListNode pHead) public RandomListNode Clone(RandomListNode pHead) {
{
if (pHead == null) if (pHead == null)
return null; return null;
// 插入新节点 // 插入新节点
@ -1718,14 +1721,12 @@ public RandomListNode Clone(RandomListNode pHead)
private TreeNode pre = null; private TreeNode pre = null;
private TreeNode head = null; private TreeNode head = null;
public TreeNode Convert(TreeNode root) public TreeNode Convert(TreeNode root) {
{
inOrder(root); inOrder(root);
return head; return head;
} }
private void inOrder(TreeNode node) private void inOrder(TreeNode node) {
{
if (node == null) if (node == null)
return; return;
inOrder(node.left); inOrder(node.left);
@ -1752,21 +1753,18 @@ private void inOrder(TreeNode node)
```java ```java
private String deserializeStr; private String deserializeStr;
public String Serialize(TreeNode root) public String Serialize(TreeNode root) {
{
if (root == null) if (root == null)
return "#"; return "#";
return root.val + " " + Serialize(root.left) + " " + Serialize(root.right); return root.val + " " + Serialize(root.left) + " " + Serialize(root.right);
} }
public TreeNode Deserialize(String str) public TreeNode Deserialize(String str) {
{
deserializeStr = str; deserializeStr = str;
return Deserialize(); return Deserialize();
} }
private TreeNode Deserialize() private TreeNode Deserialize() {
{
if (deserializeStr.length() == 0) if (deserializeStr.length() == 0)
return null; return null;
int index = deserializeStr.indexOf(" "); int index = deserializeStr.indexOf(" ");
@ -1795,8 +1793,7 @@ private TreeNode Deserialize()
```java ```java
private ArrayList<String> ret = new ArrayList<>(); private ArrayList<String> ret = new ArrayList<>();
public ArrayList<String> Permutation(String str) public ArrayList<String> Permutation(String str) {
{
if (str.length() == 0) if (str.length() == 0)
return ret; return ret;
char[] chars = str.toCharArray(); char[] chars = str.toCharArray();
@ -1805,8 +1802,7 @@ public ArrayList<String> Permutation(String str)
return ret; return ret;
} }
private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) {
{
if (s.length() == chars.length) { if (s.length() == chars.length) {
ret.add(s.toString()); ret.add(s.toString());
return; return;
@ -1836,8 +1832,7 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s)
使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不相等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0说明前 i 个元素没有 majority或者有 majority但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中majority 的数目依然多于 (n - i) / 2因此继续查找就能找出 majority。 使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不相等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0说明前 i 个元素没有 majority或者有 majority但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中majority 的数目依然多于 (n - i) / 2因此继续查找就能找出 majority。
```java ```java
public int MoreThanHalfNum_Solution(int[] nums) public int MoreThanHalfNum_Solution(int[] nums) {
{
int majority = nums[0]; int majority = nums[0];
for (int i = 1, cnt = 1; i < nums.length; i++) { for (int i = 1, cnt = 1; i < nums.length; i++) {
cnt = nums[i] == majority ? cnt + 1 : cnt - 1; cnt = nums[i] == majority ? cnt + 1 : cnt - 1;
@ -1868,8 +1863,7 @@ public int MoreThanHalfNum_Solution(int[] nums)
快速排序的 partition() 方法,会返回一个整数 j 使得 a[l..j-1] 小于等于 a[j],且 a[j+1..h] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素。可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。 快速排序的 partition() 方法,会返回一个整数 j 使得 a[l..j-1] 小于等于 a[j],且 a[j+1..h] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素。可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。
```java ```java
public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
{
ArrayList<Integer> ret = new ArrayList<>(); ArrayList<Integer> ret = new ArrayList<>();
if (k > nums.length || k <= 0) if (k > nums.length || k <= 0)
return ret; return ret;
@ -1880,8 +1874,7 @@ public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k)
return ret; return ret;
} }
public void findKthSmallest(int[] nums, int k) public void findKthSmallest(int[] nums, int k) {
{
int l = 0, h = nums.length - 1; int l = 0, h = nums.length - 1;
while (l < h) { while (l < h) {
int j = partition(nums, l, h); int j = partition(nums, l, h);
@ -1894,8 +1887,7 @@ public void findKthSmallest(int[] nums, int k)
} }
} }
private int partition(int[] nums, int l, int h) private int partition(int[] nums, int l, int h) {
{
int p = nums[l]; /* 切分元素 */ int p = nums[l]; /* 切分元素 */
int i = l, j = h + 1; int i = l, j = h + 1;
while (true) { while (true) {
@ -1909,8 +1901,7 @@ private int partition(int[] nums, int l, int h)
return j; return j;
} }
private void swap(int[] nums, int i, int j) private void swap(int[] nums, int i, int j) {
{
int t = nums[i]; int t = nums[i];
nums[i] = nums[j]; nums[i] = nums[j];
nums[j] = t; nums[j] = t;
@ -1927,8 +1918,7 @@ private void swap(int[] nums, int i, int j)
维护一个大小为 K 的最小堆过程如下:在添加一个元素之后,如果大顶堆的大小大于 K那么需要将大顶堆的堆顶元素去除。 维护一个大小为 K 的最小堆过程如下:在添加一个元素之后,如果大顶堆的大小大于 K那么需要将大顶堆的堆顶元素去除。
```java ```java
public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
{
if (k > nums.length || k <= 0) if (k > nums.length || k <= 0)
return new ArrayList<>(); return new ArrayList<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1); PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
@ -1959,8 +1949,7 @@ private PriorityQueue<Integer> right = new PriorityQueue<>();
/* 当前数据流读入的元素个数 */ /* 当前数据流读入的元素个数 */
private int N = 0; private int N = 0;
public void Insert(Integer val) public void Insert(Integer val) {
{
/* 插入要保证两个堆存于平衡状态 */ /* 插入要保证两个堆存于平衡状态 */
if (N % 2 == 0) { if (N % 2 == 0) {
/* N 为偶数的情况下插入到右半边。 /* N 为偶数的情况下插入到右半边。
@ -1975,8 +1964,7 @@ public void Insert(Integer val)
N++; N++;
} }
public Double GetMedian() public Double GetMedian() {
{
if (N % 2 == 0) if (N % 2 == 0)
return (left.peek() + right.peek()) / 2.0; return (left.peek() + right.peek()) / 2.0;
else else
@ -1998,16 +1986,14 @@ public Double GetMedian()
private int[] cnts = new int[256]; private int[] cnts = new int[256];
private Queue<Character> queue = new LinkedList<>(); private Queue<Character> queue = new LinkedList<>();
public void Insert(char ch) public void Insert(char ch) {
{
cnts[ch]++; cnts[ch]++;
queue.add(ch); queue.add(ch);
while (!queue.isEmpty() && cnts[queue.peek()] > 1) while (!queue.isEmpty() && cnts[queue.peek()] > 1)
queue.poll(); queue.poll();
} }
public char FirstAppearingOnce() public char FirstAppearingOnce() {
{
return queue.isEmpty() ? '#' : queue.peek(); return queue.isEmpty() ? '#' : queue.peek();
} }
``` ```
@ -2023,8 +2009,7 @@ public char FirstAppearingOnce()
## 解题思路 ## 解题思路
```java ```java
public int FindGreatestSumOfSubArray(int[] nums) public int FindGreatestSumOfSubArray(int[] nums) {
{
if (nums == null || nums.length == 0) if (nums == null || nums.length == 0)
return 0; return 0;
int greatestSum = Integer.MIN_VALUE; int greatestSum = Integer.MIN_VALUE;
@ -2044,8 +2029,7 @@ public int FindGreatestSumOfSubArray(int[] nums)
## 解题思路 ## 解题思路
```java ```java
public int NumberOf1Between1AndN_Solution(int n) public int NumberOf1Between1AndN_Solution(int n) {
{
int cnt = 0; int cnt = 0;
for (int m = 1; m <= n; m *= 10) { for (int m = 1; m <= n; m *= 10) {
int a = n / m, b = n % m; int a = n / m, b = n % m;
@ -2066,8 +2050,7 @@ public int NumberOf1Between1AndN_Solution(int n)
## 解题思路 ## 解题思路
```java ```java
public int getDigitAtIndex(int index) public int getDigitAtIndex(int index) {
{
if (index < 0) if (index < 0)
return -1; return -1;
int place = 1; // 1 表示个位2 表示 十位... int place = 1; // 1 表示个位2 表示 十位...
@ -2085,8 +2068,7 @@ public int getDigitAtIndex(int index)
* place 位数的数字组成的字符串长度 * place 位数的数字组成的字符串长度
* 10, 90, 900, ... * 10, 90, 900, ...
*/ */
private int getAmountOfPlace(int place) private int getAmountOfPlace(int place) {
{
if (place == 1) if (place == 1)
return 10; return 10;
return (int) Math.pow(10, place - 1) * 9; return (int) Math.pow(10, place - 1) * 9;
@ -2096,8 +2078,7 @@ private int getAmountOfPlace(int place)
* place 位数的起始数字 * place 位数的起始数字
* 0, 10, 100, ... * 0, 10, 100, ...
*/ */
private int getBeginNumberOfPlace(int place) private int getBeginNumberOfPlace(int place) {
{
if (place == 1) if (place == 1)
return 0; return 0;
return (int) Math.pow(10, place - 1); return (int) Math.pow(10, place - 1);
@ -2106,8 +2087,7 @@ private int getBeginNumberOfPlace(int place)
/** /**
* 在 place 位数组成的字符串中,第 index 个数 * 在 place 位数组成的字符串中,第 index 个数
*/ */
private int getDigitAtIndex(int index, int place) private int getDigitAtIndex(int index, int place) {
{
int beginNumber = getBeginNumberOfPlace(place); int beginNumber = getBeginNumberOfPlace(place);
int shiftNumber = index / place; int shiftNumber = index / place;
String number = (beginNumber + shiftNumber) + ""; String number = (beginNumber + shiftNumber) + "";
@ -2129,8 +2109,7 @@ private int getDigitAtIndex(int index, int place)
可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 < S2+S1那么应该把 S1 排在前面否则应该把 S2 排在前面 可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 < S2+S1那么应该把 S1 排在前面否则应该把 S2 排在前面
```java ```java
public String PrintMinNumber(int[] numbers) public String PrintMinNumber(int[] numbers) {
{
if (numbers == null || numbers.length == 0) if (numbers == null || numbers.length == 0)
return ""; return "";
int n = numbers.length; int n = numbers.length;
@ -2156,8 +2135,7 @@ public String PrintMinNumber(int[] numbers)
## 解题思路 ## 解题思路
```java ```java
public int numDecodings(String s) public int numDecodings(String s) {
{
if (s == null || s.length() == 0) if (s == null || s.length() == 0)
return 0; return 0;
int n = s.length(); int n = s.length();
@ -2200,8 +2178,7 @@ public int numDecodings(String s)
应该用动态规划求解,而不是深度优先搜索,深度优先搜索过于复杂,不是最优解。 应该用动态规划求解,而不是深度优先搜索,深度优先搜索过于复杂,不是最优解。
```java ```java
public int getMost(int[][] values) public int getMost(int[][] values) {
{
if (values == null || values.length == 0 || values[0].length == 0) if (values == null || values.length == 0 || values[0].length == 0)
return 0; return 0;
int n = values[0].length; int n = values[0].length;
@ -2224,8 +2201,7 @@ public int getMost(int[][] values)
## 解题思路 ## 解题思路
```java ```java
public int longestSubStringWithoutDuplication(String str) public int longestSubStringWithoutDuplication(String str) {
{
int curLen = 0; int curLen = 0;
int maxLen = 0; int maxLen = 0;
int[] preIndexs = new int[26]; int[] preIndexs = new int[26];
@ -2257,8 +2233,7 @@ public int longestSubStringWithoutDuplication(String str)
## 解题思路 ## 解题思路
```java ```java
public int GetUglyNumber_Solution(int N) public int GetUglyNumber_Solution(int N) {
{
if (N <= 6) if (N <= 6)
return N; return N;
int i2 = 0, i3 = 0, i5 = 0; int i2 = 0, i3 = 0, i5 = 0;
@ -2291,8 +2266,7 @@ public int GetUglyNumber_Solution(int N)
最直观的解法是使用 HashMap 对出现次数进行统计,但是考虑到要统计的字符范围有限,因此可以使用整型数组代替 HashMap。 最直观的解法是使用 HashMap 对出现次数进行统计,但是考虑到要统计的字符范围有限,因此可以使用整型数组代替 HashMap。
```java ```java
public int FirstNotRepeatingChar(String str) public int FirstNotRepeatingChar(String str) {
{
int[] cnts = new int[256]; int[] cnts = new int[256];
for (int i = 0; i < str.length(); i++) for (int i = 0; i < str.length(); i++)
cnts[str.charAt(i)]++; cnts[str.charAt(i)]++;
@ -2306,8 +2280,7 @@ public int FirstNotRepeatingChar(String str)
以上实现的空间复杂度还不是最优的。考虑到只需要找到只出现一次的字符,那么我们只需要统计的次数信息只有 0,1,更大,使用两个比特位就能存储这些信息。 以上实现的空间复杂度还不是最优的。考虑到只需要找到只出现一次的字符,那么我们只需要统计的次数信息只有 0,1,更大,使用两个比特位就能存储这些信息。
```java ```java
public int FirstNotRepeatingChar2(String str) public int FirstNotRepeatingChar2(String str) {
{
BitSet bs1 = new BitSet(256); BitSet bs1 = new BitSet(256);
BitSet bs2 = new BitSet(256); BitSet bs2 = new BitSet(256);
for (char c : str.toCharArray()) { for (char c : str.toCharArray()) {
@ -2339,15 +2312,13 @@ public int FirstNotRepeatingChar2(String str)
private long cnt = 0; private long cnt = 0;
private int[] tmp; // 在这里创建辅助数组,而不是在 merge() 递归函数中创建 private int[] tmp; // 在这里创建辅助数组,而不是在 merge() 递归函数中创建
public int InversePairs(int[] nums) public int InversePairs(int[] nums) {
{
tmp = new int[nums.length]; tmp = new int[nums.length];
mergeSort(nums, 0, nums.length - 1); mergeSort(nums, 0, nums.length - 1);
return (int) (cnt % 1000000007); return (int) (cnt % 1000000007);
} }
private void mergeSort(int[] nums, int l, int h) private void mergeSort(int[] nums, int l, int h) {
{
if (h - l < 1) if (h - l < 1)
return; return;
int m = l + (h - l) / 2; int m = l + (h - l) / 2;
@ -2356,8 +2327,7 @@ private void mergeSort(int[] nums, int l, int h)
merge(nums, l, m, h); merge(nums, l, m, h);
} }
private void merge(int[] nums, int l, int m, int h) private void merge(int[] nums, int l, int m, int h) {
{
int i = l, j = m + 1, k = l; int i = l, j = m + 1, k = l;
while (i <= m || j <= h) { while (i <= m || j <= h) {
if (i > m) if (i > m)
@ -2392,8 +2362,7 @@ private void merge(int[] nums, int l, int m, int h)
当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部重新开始访问链表 B同样地当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部重新开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。 当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部重新开始访问链表 B同样地当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部重新开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
```java ```java
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
{
ListNode l1 = pHead1, l2 = pHead2; ListNode l1 = pHead1, l2 = pHead2;
while (l1 != l2) { while (l1 != l2) {
l1 = (l1 == null) ? pHead2 : l1.next; l1 = (l1 == null) ? pHead2 : l1.next;
@ -2420,15 +2389,13 @@ Output:
## 解题思路 ## 解题思路
```java ```java
public int GetNumberOfK(int[] nums, int K) public int GetNumberOfK(int[] nums, int K) {
{
int first = binarySearch(nums, K); int first = binarySearch(nums, K);
int last = binarySearch(nums, K + 1); int last = binarySearch(nums, K + 1);
return (first == nums.length || nums[first] != K) ? 0 : last - first; return (first == nums.length || nums[first] != K) ? 0 : last - first;
} }
private int binarySearch(int[] nums, int K) private int binarySearch(int[] nums, int K) {
{
int l = 0, h = nums.length; int l = 0, h = nums.length;
while (l < h) { while (l < h) {
int m = l + (h - l) / 2; int m = l + (h - l) / 2;
@ -2453,14 +2420,12 @@ private int binarySearch(int[] nums, int K)
private TreeNode ret; private TreeNode ret;
private int cnt = 0; private int cnt = 0;
public TreeNode KthNode(TreeNode pRoot, int k) public TreeNode KthNode(TreeNode pRoot, int k) {
{
inOrder(pRoot, k); inOrder(pRoot, k);
return ret; return ret;
} }
private void inOrder(TreeNode root, int k) private void inOrder(TreeNode root, int k) {
{
if (root == null || cnt >= k) if (root == null || cnt >= k)
return; return;
inOrder(root.left, k); inOrder(root.left, k);
@ -2484,8 +2449,7 @@ private void inOrder(TreeNode root, int k)
## 解题思路 ## 解题思路
```java ```java
public int TreeDepth(TreeNode root) public int TreeDepth(TreeNode root) {
{
return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right)); return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right));
} }
``` ```
@ -2505,14 +2469,12 @@ public int TreeDepth(TreeNode root)
```java ```java
private boolean isBalanced = true; private boolean isBalanced = true;
public boolean IsBalanced_Solution(TreeNode root) public boolean IsBalanced_Solution(TreeNode root) {
{
height(root); height(root);
return isBalanced; return isBalanced;
} }
private int height(TreeNode root) private int height(TreeNode root) {
{
if (root == null || !isBalanced) if (root == null || !isBalanced)
return 0; return 0;
int left = height(root.left); int left = height(root.left);
@ -2538,8 +2500,7 @@ private int height(TreeNode root)
diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。
```java ```java
public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) {
{
int diff = 0; int diff = 0;
for (int num : nums) for (int num : nums)
diff ^= num; diff ^= num;
@ -2570,8 +2531,7 @@ public void FindNumsAppearOnce(int[] nums, int num1[], int num2[])
- 如果 sum < target移动较小的元素使 sum 变大一些 - 如果 sum < target移动较小的元素使 sum 变大一些
```java ```java
public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
{
int i = 0, j = array.length - 1; int i = 0, j = array.length - 1;
while (i < j) { while (i < j) {
int cur = array[i] + array[j]; int cur = array[i] + array[j];
@ -2604,8 +2564,7 @@ public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum)
## 解题思路 ## 解题思路
```java ```java
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
{
ArrayList<ArrayList<Integer>> ret = new ArrayList<>(); ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
int start = 1, end = 2; int start = 1, end = 2;
int curSum = 3; int curSum = 3;
@ -2648,8 +2607,7 @@ public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum)
正确的解法应该是和书上一样,先旋转每个单词,再旋转整个字符串。 正确的解法应该是和书上一样,先旋转每个单词,再旋转整个字符串。
```java ```java
public String ReverseSentence(String str) public String ReverseSentence(String str) {
{
int n = str.length(); int n = str.length();
char[] chars = str.toCharArray(); char[] chars = str.toCharArray();
int i = 0, j = 0; int i = 0, j = 0;
@ -2664,14 +2622,12 @@ public String ReverseSentence(String str)
return new String(chars); return new String(chars);
} }
private void reverse(char[] c, int i, int j) private void reverse(char[] c, int i, int j) {
{
while (i < j) while (i < j)
swap(c, i++, j--); swap(c, i++, j--);
} }
private void swap(char[] c, int i, int j) private void swap(char[] c, int i, int j) {
{
char t = c[i]; char t = c[i];
c[i] = c[j]; c[i] = c[j];
c[j] = t; c[j] = t;
@ -2691,8 +2647,7 @@ private void swap(char[] c, int i, int j)
先将 "abc" 和 "XYZdef" 分别翻转,得到 "cbafedZYX",然后再把整个字符串翻转得到 "XYZdefabc"。 先将 "abc" 和 "XYZdef" 分别翻转,得到 "cbafedZYX",然后再把整个字符串翻转得到 "XYZdefabc"。
```java ```java
public String LeftRotateString(String str, int n) public String LeftRotateString(String str, int n) {
{
if (n >= str.length()) if (n >= str.length())
return str; return str;
char[] chars = str.toCharArray(); char[] chars = str.toCharArray();
@ -2702,14 +2657,12 @@ public String LeftRotateString(String str, int n)
return new String(chars); return new String(chars);
} }
private void reverse(char[] chars, int i, int j) private void reverse(char[] chars, int i, int j) {
{
while (i < j) while (i < j)
swap(chars, i++, j--); swap(chars, i++, j--);
} }
private void swap(char[] chars, int i, int j) private void swap(char[] chars, int i, int j) {
{
char t = chars[i]; char t = chars[i];
chars[i] = chars[j]; chars[i] = chars[j];
chars[j] = t; chars[j] = t;
@ -2727,8 +2680,7 @@ private void swap(char[] chars, int i, int j)
## 解题思路 ## 解题思路
```java ```java
public ArrayList<Integer> maxInWindows(int[] num, int size) public ArrayList<Integer> maxInWindows(int[] num, int size) {
{
ArrayList<Integer> ret = new ArrayList<>(); ArrayList<Integer> ret = new ArrayList<>();
if (size > num.length || size < 1) if (size > num.length || size < 1)
return ret; return ret;
@ -2762,8 +2714,7 @@ public ArrayList<Integer> maxInWindows(int[] num, int size)
空间复杂度O(N<sup>2</sup>) 空间复杂度O(N<sup>2</sup>)
```java ```java
public List<Map.Entry<Integer, Double>> dicesSum(int n) public List<Map.Entry<Integer, Double>> dicesSum(int n) {
{
final int face = 6; final int face = 6;
final int pointNum = face * n; final int pointNum = face * n;
long[][] dp = new long[n + 1][pointNum + 1]; long[][] dp = new long[n + 1][pointNum + 1];
@ -2790,8 +2741,7 @@ public List<Map.Entry<Integer, Double>> dicesSum(int n)
空间复杂度O(N) 空间复杂度O(N)
```java ```java
public List<Map.Entry<Integer, Double>> dicesSum(int n) public List<Map.Entry<Integer, Double>> dicesSum(int n) {
{
final int face = 6; final int face = 6;
final int pointNum = face * n; final int pointNum = face * n;
long[][] dp = new long[2][pointNum + 1]; long[][] dp = new long[2][pointNum + 1];
@ -2829,8 +2779,7 @@ public List<Map.Entry<Integer, Double>> dicesSum(int n)
## 解题思路 ## 解题思路
```java ```java
public boolean isContinuous(int[] nums) public boolean isContinuous(int[] nums) {
{
if (nums.length < 5) if (nums.length < 5)
return false; return false;
Arrays.sort(nums); Arrays.sort(nums);
@ -2861,8 +2810,7 @@ public boolean isContinuous(int[] nums)
约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈所以最后需要对 n 取余。 约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈所以最后需要对 n 取余。
```java ```java
public int LastRemaining_Solution(int n, int m) public int LastRemaining_Solution(int n, int m) {
{
if (n == 0) /* 特殊输入的处理 */ if (n == 0) /* 特殊输入的处理 */
return -1; return -1;
if (n == 1) /* 返回条件 */ if (n == 1) /* 返回条件 */
@ -2884,8 +2832,7 @@ public int LastRemaining_Solution(int n, int m)
使用贪心策略,假设第 i 轮进行卖出操作,买入操作价格应该在 i 之前并且价格最低。 使用贪心策略,假设第 i 轮进行卖出操作,买入操作价格应该在 i 之前并且价格最低。
```java ```java
public int maxProfit(int[] prices) public int maxProfit(int[] prices) {
{
if (prices == null || prices.length == 0) if (prices == null || prices.length == 0)
return 0; return 0;
int soFarMin = prices[0]; int soFarMin = prices[0];
@ -2915,8 +2862,7 @@ public int maxProfit(int[] prices)
以下实现中,递归的返回条件为 n <= 0取非后就是 n > 0递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。 以下实现中,递归的返回条件为 n <= 0取非后就是 n > 0递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。
```java ```java
public int Sum_Solution(int n) public int Sum_Solution(int n) {
{
int sum = n; int sum = n;
boolean b = (n > 0) && ((sum += Sum_Solution(n - 1)) > 0); boolean b = (n > 0) && ((sum += Sum_Solution(n - 1)) > 0);
return sum; return sum;
@ -2938,8 +2884,7 @@ a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进
递归会终止的原因是 (a & b) << 1 最右边会多一个 0那么继续递归进位最右边的 0 会慢慢增多最后进位会变为 0递归终止 递归会终止的原因是 (a & b) << 1 最右边会多一个 0那么继续递归进位最右边的 0 会慢慢增多最后进位会变为 0递归终止
```java ```java
public int Add(int a, int b) public int Add(int a, int b) {
{
return b == 0 ? a : Add(a ^ b, (a & b) << 1); return b == 0 ? a : Add(a ^ b, (a & b) << 1);
} }
``` ```
@ -2955,8 +2900,7 @@ public int Add(int a, int b)
## 解题思路 ## 解题思路
```java ```java
public int[] multiply(int[] A) public int[] multiply(int[] A) {
{
int n = A.length; int n = A.length;
int[] B = new int[n]; int[] B = new int[n];
for (int i = 0, product = 1; i < n; product *= A[i], i++) /* 从左往右累乘 */ for (int i = 0, product = 1; i < n; product *= A[i], i++) /* 从左往右累乘 */
@ -2988,8 +2932,7 @@ Output:
## 解题思路 ## 解题思路
```java ```java
public int StrToInt(String str) public int StrToInt(String str) {
{
if (str == null || str.length() == 0) if (str == null || str.length() == 0)
return 0; return 0;
boolean isNegative = str.charAt(0) == '-'; boolean isNegative = str.charAt(0) == '-';

View File

@ -46,17 +46,17 @@
例如在注册流程中通常需要发送验证邮件来确保注册用户的身份合法,可以使用消息队列使发送验证邮件的操作异步处理,用户在填写完注册信息之后就可以完成注册,而将发送验证邮件这一消息发送到消息队列中。 例如在注册流程中通常需要发送验证邮件来确保注册用户的身份合法,可以使用消息队列使发送验证邮件的操作异步处理,用户在填写完注册信息之后就可以完成注册,而将发送验证邮件这一消息发送到消息队列中。
只有在业务流程允许异步处理的情况下才能这么做,例如上面的注册流程中,如果要求用户对验证邮件进行点击之后才能完成操作的话,就不能再使用消息队列。 只有在业务流程允许异步处理的情况下才能这么做,例如上面的注册流程中,如果要求用户对验证邮件进行点击之后才能完成注册的话,就不能再使用消息队列。
## 流量削锋 ## 流量削锋
在高并发的场景下,如果短时间有大量的请求会压垮服务器。 在高并发的场景下,如果短时间有大量的请求到达会压垮服务器。
可以将请求发送到消息队列中,服务器按照其处理能力从消息队列中订阅消息进行处理。 可以将请求发送到消息队列中,服务器按照其处理能力从消息队列中订阅消息进行处理。
## 应用解耦 ## 应用解耦
如果模块之间不直接进行调用,模块之间耦合度很低,那么修改一个模块或者新增一个模块对其它模块的影响会很小,从而实现可扩展性。 如果模块之间不直接进行调用,模块之间耦合度就会很低,那么修改一个模块或者新增一个模块对其它模块的影响会很小,从而实现可扩展性。
通过使用消息队列,一个模块只需要向消息队列中发送消息,其它模块可以选择性地从消息队列中订阅消息从而完成调用。 通过使用消息队列,一个模块只需要向消息队列中发送消息,其它模块可以选择性地从消息队列中订阅消息从而完成调用。

View File

@ -1623,10 +1623,10 @@ private List<Key> keys(Node x, Key l, Key h) {
## 2-3 查找树 ## 2-3 查找树
<div align="center"> <img src="../pics//ff396233-1bb1-4e74-8bc2-d7c90146f5dd.png" width="250"/> </div><br>
2-3 查找树引入了 2- 节点和 3- 节点,目的是为了让树平衡。一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。 2-3 查找树引入了 2- 节点和 3- 节点,目的是为了让树平衡。一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。
<div align="center"> <img src="../pics//ff396233-1bb1-4e74-8bc2-d7c90146f5dd.png" width="250"/> </div><br>
### 1. 插入操作 ### 1. 插入操作
插入操作和 BST 的插入操作有很大区别BST 的插入操作是先进行一次未命中的查找,然后再将节点插入到对应的空链接上。但是 2-3 查找树如果也这么做的话,那么就会破坏了平衡性。它是将新节点插入到叶子节点上。 插入操作和 BST 的插入操作有很大区别BST 的插入操作是先进行一次未命中的查找,然后再将节点插入到对应的空链接上。但是 2-3 查找树如果也这么做的话,那么就会破坏了平衡性。它是将新节点插入到叶子节点上。
@ -2042,24 +2042,26 @@ public class SparseVector {
## 汉诺塔 ## 汉诺塔
<div align="center"> <img src="../pics//54f1e052-0596-4b5e-833c-e80d75bf3f9b.png" width="300"/> </div><br>
这是一个经典的递归问题,分为三步求解: 这是一个经典的递归问题,分为三步求解:
1. 将 n-1 个圆盘从 from -> buffer - 将 n-1 个圆盘从 from -> buffer
2. 将 1 个圆盘从 from -> to
3. 将 n-1 个圆盘从 buffer -> to <div align="center"> <img src="../pics//8587132a-021d-4f1f-a8ec-5a9daa7157a7.png" width="300"/> </div><br>
- 将 1 个圆盘从 from -> to
<div align="center"> <img src="../pics//2861e923-4862-4526-881c-15529279d49c.png" width="300"/> </div><br>
- 将 n-1 个圆盘从 buffer -> to
<div align="center"> <img src="../pics//1c4e8185-8153-46b6-bd5a-288b15feeae6.png" width="300"/> </div><br>
如果只有一个圆盘,那么只需要进行一次移动操作。 如果只有一个圆盘,那么只需要进行一次移动操作。
从上面的讨论可以知道a<sub>n</sub> = 2 * a<sub>n-1</sub> + 1显然 a<sub>n</sub> = 2<sup>n</sup> - 1n 个圆盘需要移动 2<sup>n</sup> - 1 次。 从上面的讨论可以知道a<sub>n</sub> = 2 * a<sub>n-1</sub> + 1显然 a<sub>n</sub> = 2<sup>n</sup> - 1n 个圆盘需要移动 2<sup>n</sup> - 1 次。
<div align="center"> <img src="../pics//54f1e052-0596-4b5e-833c-e80d75bf3f9b.png" width="300"/> </div><br>
<div align="center"> <img src="../pics//8587132a-021d-4f1f-a8ec-5a9daa7157a7.png" width="300"/> </div><br>
<div align="center"> <img src="../pics//2861e923-4862-4526-881c-15529279d49c.png" width="300"/> </div><br>
<div align="center"> <img src="../pics//1c4e8185-8153-46b6-bd5a-288b15feeae6.png" width="300"/> </div><br>
```java ```java
public class Hanoi { public class Hanoi {
public static void move(int n, String from, String buffer, String to) { public static void move(int n, String from, String buffer, String to) {
@ -2105,7 +2107,7 @@ from H1 to H3
生成编码时,从根节点出发,向左遍历则添加二进制位 0向右则添加二进制位 1直到遍历到根节点根节点代表的字符的编码就是这个路径编码。 生成编码时,从根节点出发,向左遍历则添加二进制位 0向右则添加二进制位 1直到遍历到根节点根节点代表的字符的编码就是这个路径编码。
<div align="center"> <img src="../pics//3ff4f00a-2321-48fd-95f4-ce6001332151.png"/> </div><br> <div align="center"> <img src="../pics//3ff4f00a-2321-48fd-95f4-ce6001332151.png" width="400"/> </div><br>
```java ```java
public class Huffman { public class Huffman {

View File

@ -163,7 +163,11 @@ public class LRU<K, V> implements Iterable<K> {
## 分布式缓存 ## 分布式缓存
使用 Redis、Memcache 等分布式缓存将数据缓存在分布式缓存系统中。相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。不仅如此,服务器集群都可以访问分布式缓存,本地缓存需要在服务器集群之间进行同步,实现和性能开销上都非常大。 使用 Redis、Memcache 等分布式缓存将数据缓存在分布式缓存系统中。
相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。
不仅如此,服务器集群都可以访问分布式缓存。而本地缓存需要在服务器集群之间进行同步,实现和性能开销上都非常大。
## 数据库缓存 ## 数据库缓存
@ -171,7 +175,7 @@ MySQL 等数据库管理系统具有自己的查询缓存机制来提高 SQL 查
# 四、CDN # 四、CDN
内容分发网络Content distribution networkCDN是一种通过互连的网络系统利用更靠近用户的服务器更快更可靠地将 HTML、CSS、JavaScript、音乐、图片、视频等静态资源其它数据分发给用户。 内容分发网络Content distribution networkCDN是一种通过互连的网络系统利用更靠近用户的服务器更快更可靠地将 HTML、CSS、JavaScript、音乐、图片、视频等静态资源分发给用户。
CDN 主要有以下优点: CDN 主要有以下优点:
@ -190,7 +194,7 @@ CDN 主要有以下优点:
## 缓存穿透 ## 缓存穿透
指的是对某个一定不存在的数据进行请求,该请求将会穿透缓存到数据库。 指的是对某个一定不存在的数据进行请求,该请求将会穿透缓存到数据库。
解决方案: 解决方案:
@ -207,6 +211,7 @@ CDN 主要有以下优点:
- 为了防止缓存在同一时间大面积过期导致的缓存雪崩,可以通过观察用户行为,合理设置缓存过期时间来实现; - 为了防止缓存在同一时间大面积过期导致的缓存雪崩,可以通过观察用户行为,合理设置缓存过期时间来实现;
- 为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。 - 为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。
- 也可以在进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩。
## 缓存一致性 ## 缓存一致性
@ -258,7 +263,7 @@ Distributed Hash TableDHT 是一种哈希分布方式,其目的是为了
上面描述的一致性哈希存在数据分布不均匀的问题,节点存储的数据量有可能会存在很大的不同。 上面描述的一致性哈希存在数据分布不均匀的问题,节点存储的数据量有可能会存在很大的不同。
数据不均匀主要是因为节点在哈希环上分布的不均匀,这种情况在节点数量很少的情况下尤其明显。解决方式是通过增加虚拟节点,然后将虚拟节点映射到真实节点上。虚拟节点的数量比真实节点来得大,那么虚拟节点在哈希环上分布的均匀性就会比原来的真是节点,从而使得数据分布也更加均匀。 数据不均匀主要是因为节点在哈希环上分布的不均匀,这种情况在节点数量很少的情况下尤其明显。解决方式是通过增加虚拟节点,然后将虚拟节点映射到真实节点上。虚拟节点的数量比真实节点来得大,那么虚拟节点在哈希环上分布的均匀性就会比原来的真是节点,从而使得数据分布也更加均匀。
参考资料: 参考资料:

View File

@ -1851,11 +1851,7 @@ No gumball dispensed
### 与状态模式的比较 ### 与状态模式的比较
状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。 状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。
所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。 状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。
@ -1969,7 +1965,7 @@ public abstract class CaffeineBeverage {
``` ```
```java ```java
public class Coffee extends CaffeineBeverage{ public class Coffee extends CaffeineBeverage {
@Override @Override
void brew() { void brew() {
System.out.println("Coffee.brew"); System.out.println("Coffee.brew");
@ -1983,7 +1979,7 @@ public class Coffee extends CaffeineBeverage{
``` ```
```java ```java
public class Tea extends CaffeineBeverage{ public class Tea extends CaffeineBeverage {
@Override @Override
void brew() { void brew() {
System.out.println("Tea.brew"); System.out.println("Tea.brew");
@ -2238,7 +2234,7 @@ Number of items: 6
### 意图 ### 意图
使用什么都不做的空对象来代 NULL。 使用什么都不做的空对象来代 NULL。
一个方法返回 NULL意味着方法的调用端需要去检查返回值是否是 NULL这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值而直接使用返回的对象那么就有可能抛出空指针异常。 一个方法返回 NULL意味着方法的调用端需要去检查返回值是否是 NULL这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值而直接使用返回的对象那么就有可能抛出空指针异常。
@ -2393,7 +2389,7 @@ public abstract class TV {
``` ```
```java ```java
public class Sony extends TV{ public class Sony extends TV {
@Override @Override
public void on() { public void on() {
System.out.println("Sony.on()"); System.out.println("Sony.on()");
@ -2412,7 +2408,7 @@ public class Sony extends TV{
``` ```
```java ```java
public class RCA extends TV{ public class RCA extends TV {
@Override @Override
public void on() { public void on() {
System.out.println("RCA.on()"); System.out.println("RCA.on()");
@ -2551,9 +2547,6 @@ public abstract class Component {
``` ```
```java ```java
import java.util.ArrayList;
import java.util.List;
public class Composite extends Component { public class Composite extends Component {
private List<Component> child; private List<Component> child;
@ -2659,7 +2652,7 @@ Composite:root
### 类图 ### 类图
装饰者Decorator和具体组件ConcreteComponent都继承自组件Component具体组件的方法实现不需要依赖于其它对象而装饰者组合了一个组件这样它可以装饰其它装饰者或者具体组件。所谓装饰就是把这个装饰者套在被装饰上从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的这属于它的功能然后调用被装饰者的方法实现从而也保留了被装饰者的功能。可以看到具体组件应当是装饰层次的最低层因为只有具体组件的方法实现不需要依赖于其它对象。 装饰者Decorator和具体组件ConcreteComponent都继承自组件Component具体组件的方法实现不需要依赖于其它对象而装饰者组合了一个组件这样它可以装饰其它装饰者或者具体组件。所谓装饰就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。
<div align="center"> <img src="../pics//137c593d-0a9e-47b8-a9e6-b71f540b82dd.png"/> </div><br> <div align="center"> <img src="../pics//137c593d-0a9e-47b8-a9e6-b71f540b82dd.png"/> </div><br>
@ -2770,7 +2763,7 @@ public class Client {
### 实现 ### 实现
观看电影需要操作很多电器,使用外观模式可以实现一键看电影功能。 观看电影需要操作很多电器,使用外观模式实现一键看电影功能。
```java ```java
public class SubSystem { public class SubSystem {
@ -2811,7 +2804,7 @@ public class Client {
### 设计原则 ### 设计原则
最少知识原则:只和你的密友谈话。也就是客户对象所需要交互的对象应当尽可能少。 最少知识原则:只和你的密友谈话。也就是客户对象所需要交互的对象应当尽可能少。
## 6. 享元Flyweight ## 6. 享元Flyweight
@ -2822,8 +2815,8 @@ public class Client {
### 类图 ### 类图
- Flyweight享元对象 - Flyweight享元对象
- IntrinsicState内部状态相同的项元对象共享 - IntrinsicState内部状态享元对象共享内部状态
- ExtrinsicState外部状态 - ExtrinsicState外部状态,每个享元对象的外部状态不同
<div align="center"> <img src="../pics//d52270b4-9097-4667-9f18-f405fc661c99.png"/> </div><br> <div align="center"> <img src="../pics//d52270b4-9097-4667-9f18-f405fc661c99.png"/> </div><br>
@ -2854,8 +2847,6 @@ public class ConcreteFlyweight implements Flyweight {
``` ```
```java ```java
import java.util.HashMap;
public class FlyweightFactory { public class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap<>(); private HashMap<String, Flyweight> flyweights = new HashMap<>();