auto commit
This commit is contained in:
parent
0d34f6ec14
commit
9a4f6ea86c
|
@ -45,7 +45,7 @@
|
|||
* [二进制分帧层](#二进制分帧层)
|
||||
* [服务端推送](#服务端推送)
|
||||
* [首部压缩](#首部压缩)
|
||||
* [八、GET 和 POST 的区别](#八get-和-post-的区别)
|
||||
* [八、GET 和 POST 比较](#八get-和-post-比较)
|
||||
* [作用](#作用)
|
||||
* [参数](#参数)
|
||||
* [安全](#安全)
|
||||
|
@ -61,13 +61,13 @@
|
|||
|
||||
## URL
|
||||
|
||||
- URI(Uniform Resource Identifier,统一资源标识符)
|
||||
- URL(Uniform Resource Locator,统一资源定位符)
|
||||
- URN(Uniform Resource Name,统一资源名称),例如 urn:isbn:0-486-27557-4。
|
||||
|
||||
URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
|
||||
|
||||
<div align="center"> <img src="../pics//f716427a-94f2-4875-9c86-98793cf5dcc3.jpg" width="400"/> </div><br>
|
||||
- URI(Uniform Resource Identifier,统一资源标识符)
|
||||
- URL(Uniform Resource Locator,统一资源定位符)
|
||||
- URN(Uniform 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** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
|
||||
|
||||
- **206 Partial Content** :表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。
|
||||
- **206 Partial Content** :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。
|
||||
|
||||
## 3XX 重定向
|
||||
|
||||
|
@ -219,7 +219,7 @@ CONNECT www.example.com:443 HTTP/1.1
|
|||
|
||||
- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
|
||||
|
||||
- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由。
|
||||
- **403 Forbidden** :请求被拒绝。
|
||||
|
||||
- **404 Not Found**
|
||||
|
||||
|
@ -331,7 +331,7 @@ Set-Cookie: tasty_cookie=strawberry
|
|||
[page content]
|
||||
```
|
||||
|
||||
客户端之后对同一个服务器发送请求时,会从浏览器中读出 Cookie 信息通过 Cookie 请求首部字段发送给服务器。
|
||||
客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。
|
||||
|
||||
```html
|
||||
GET /sample_page.html HTTP/1.1
|
||||
|
@ -382,9 +382,9 @@ Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径
|
|||
|
||||
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
|
||||
|
||||
Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在内存型数据库中,比如 Redis,效率会更高。
|
||||
Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。
|
||||
|
||||
使用 Session 维护用户登录的过程如下:
|
||||
使用 Session 维护用户登录状态的过程如下:
|
||||
|
||||
- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
|
||||
- 服务器验证该用户名和密码;
|
||||
|
@ -499,7 +499,7 @@ If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
|
|||
|
||||
### 1. 短连接与长连接
|
||||
|
||||
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。
|
||||
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
|
||||
|
||||
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
|
||||
|
||||
|
@ -688,7 +688,7 @@ HTTPs 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)
|
|||
|
||||
### 3. HTTPs 采用的加密方式
|
||||
|
||||
HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证安全性,之后使用对称密钥加密进行通信来保证效率。(下图中的 Session Key 就是对称密钥)
|
||||
HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率。(下图中的 Session Key 就是对称密钥)
|
||||
|
||||
<div align="center"> <img src="../pics//How-HTTPS-Works.png" width="600"/> </div><br>
|
||||
|
||||
|
@ -717,7 +717,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
|
|||
## HTTPs 的缺点
|
||||
|
||||
- 因为需要进行加密解密等过程,因此速度会更慢;
|
||||
- 需要支付证书授权的高费用。
|
||||
- 需要支付证书授权的高额费用。
|
||||
|
||||
## 配置 HTTPs
|
||||
|
||||
|
@ -727,7 +727,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
|
|||
|
||||
## 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>
|
||||
|
||||
# 八、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)
|
||||
- [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn)
|
||||
- [htmlspecialchars](http://php.net/manual/zh/function.htmlspecialchars.php)
|
||||
- [Difference between file URI and URL in java](http://java2db.com/java-io/how-to-get-and-the-difference-between-file-uri-and-url-in-java)
|
||||
- [How to Fix SQL Injection Using Java PreparedStatement & CallableStatement](https://software-security.sans.org/developer-how-to/fix-sql-injection-in-java-using-prepared-callable-statement)
|
||||
- [浅谈 HTTP 中 Get 与 Post 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html)
|
||||
- [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/)
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
## Collection
|
||||
|
||||
<div align="center"> <img src="../pics//NP4z3i8m38Ntd28NQ4_0KCJ2q044Oez.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png"/> </div><br>
|
||||
|
||||
### 1. Set
|
||||
|
||||
|
@ -129,12 +129,67 @@ private static final int DEFAULT_CAPACITY = 10;
|
|||
|
||||
ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
|
||||
|
||||
保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
|
||||
保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。
|
||||
|
||||
```java
|
||||
transient Object[] elementData; // non-private to simplify nested class access
|
||||
```
|
||||
|
||||
ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
|
||||
|
||||
```java
|
||||
private void readObject(java.io.ObjectInputStream s)
|
||||
throws java.io.IOException, ClassNotFoundException {
|
||||
elementData = EMPTY_ELEMENTDATA;
|
||||
|
||||
// Read in size, and any hidden stuff
|
||||
s.defaultReadObject();
|
||||
|
||||
// Read in capacity
|
||||
s.readInt(); // ignored
|
||||
|
||||
if (size > 0) {
|
||||
// be like clone(), allocate array based upon size not capacity
|
||||
ensureCapacityInternal(size);
|
||||
|
||||
Object[] a = elementData;
|
||||
// Read in all elements in the proper order.
|
||||
for (int i=0; i<size; i++) {
|
||||
a[i] = s.readObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
private void writeObject(java.io.ObjectOutputStream s)
|
||||
throws java.io.IOException{
|
||||
// Write out element count, and any hidden stuff
|
||||
int expectedModCount = modCount;
|
||||
s.defaultWriteObject();
|
||||
|
||||
// Write out size as capacity for behavioural compatibility with clone()
|
||||
s.writeInt(size);
|
||||
|
||||
// Write out all elements in the proper order.
|
||||
for (int i=0; i<size; i++) {
|
||||
s.writeObject(elementData[i]);
|
||||
}
|
||||
|
||||
if (modCount != expectedModCount) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。
|
||||
|
||||
```java
|
||||
ArrayList list = new ArrayList();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
|
||||
oos.writeObject(list);
|
||||
```
|
||||
|
||||
### 3. 扩容
|
||||
|
||||
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity >> 1)`,也就是旧容量的 1.5 倍。
|
||||
|
@ -1080,7 +1135,7 @@ Set <|.. LinkedHashSet
|
|||
SortSet <|.. TreeSet
|
||||
List <|.. ArrayList
|
||||
List <|.. Vector
|
||||
List <|.. LinkeList
|
||||
List <|.. LinkedList
|
||||
Queue <|.. LinkedList
|
||||
Queue <|.. PriorityQueue
|
||||
|
||||
|
|
2931
notes/Leetcode 题解.md
2931
notes/Leetcode 题解.md
File diff suppressed because it is too large
Load Diff
|
@ -461,7 +461,7 @@ Employee 表:
|
|||
+----+-------+--------+-----------+
|
||||
```
|
||||
|
||||
查找所有员工,他们的薪资大于其经理薪资。
|
||||
查找薪资大于其经理薪资的员工信息。
|
||||
|
||||
## SQL Schema
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
@ -1217,8 +1218,7 @@ public ListNode ReverseList(ListNode head) {
|
|||
### 递归
|
||||
|
||||
```java
|
||||
public ListNode Merge(ListNode list1, ListNode list2)
|
||||
{
|
||||
public ListNode Merge(ListNode list1, ListNode list2) {
|
||||
if (list1 == null)
|
||||
return list2;
|
||||
if (list2 == null)
|
||||
|
@ -1236,8 +1236,7 @@ public ListNode Merge(ListNode list1, ListNode list2)
|
|||
### 迭代
|
||||
|
||||
```java
|
||||
public ListNode Merge(ListNode list1, ListNode list2)
|
||||
{
|
||||
public ListNode Merge(ListNode list1, ListNode list2) {
|
||||
ListNode head = new ListNode(-1);
|
||||
ListNode cur = head;
|
||||
while (list1 != null && list2 != null) {
|
||||
|
@ -1269,15 +1268,13 @@ public ListNode Merge(ListNode list1, ListNode list2)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public boolean HasSubtree(TreeNode root1, TreeNode root2)
|
||||
{
|
||||
public boolean HasSubtree(TreeNode root1, TreeNode root2) {
|
||||
if (root1 == null || root2 == null)
|
||||
return false;
|
||||
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)
|
||||
return true;
|
||||
if (root1 == null)
|
||||
|
@ -1298,9 +1295,10 @@ private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2)
|
|||
|
||||
## 解题思路
|
||||
|
||||
### 递归
|
||||
|
||||
```java
|
||||
public void Mirror(TreeNode root)
|
||||
{
|
||||
public void Mirror(TreeNode root) {
|
||||
if (root == null)
|
||||
return;
|
||||
swap(root);
|
||||
|
@ -1308,14 +1306,36 @@ public void Mirror(TreeNode root)
|
|||
Mirror(root.right);
|
||||
}
|
||||
|
||||
private void swap(TreeNode root)
|
||||
{
|
||||
private void swap(TreeNode root) {
|
||||
TreeNode t = root.left;
|
||||
root.left = root.right;
|
||||
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 对称的二叉树
|
||||
|
||||
[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
|
||||
boolean isSymmetrical(TreeNode pRoot)
|
||||
{
|
||||
boolean isSymmetrical(TreeNode pRoot) {
|
||||
if (pRoot == null)
|
||||
return true;
|
||||
return isSymmetrical(pRoot.left, pRoot.right);
|
||||
}
|
||||
|
||||
boolean isSymmetrical(TreeNode t1, TreeNode t2)
|
||||
{
|
||||
boolean isSymmetrical(TreeNode t1, TreeNode t2) {
|
||||
if (t1 == null && t2 == null)
|
||||
return true;
|
||||
if (t1 == null || t2 == null)
|
||||
|
@ -1359,8 +1377,7 @@ boolean isSymmetrical(TreeNode t1, TreeNode t2)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public ArrayList<Integer> printMatrix(int[][] matrix)
|
||||
{
|
||||
public ArrayList<Integer> printMatrix(int[][] matrix) {
|
||||
ArrayList<Integer> ret = new ArrayList<>();
|
||||
int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1;
|
||||
while (r1 <= r2 && c1 <= c2) {
|
||||
|
@ -1394,25 +1411,21 @@ public ArrayList<Integer> printMatrix(int[][] matrix)
|
|||
private Stack<Integer> dataStack = new Stack<>();
|
||||
private Stack<Integer> minStack = new Stack<>();
|
||||
|
||||
public void push(int node)
|
||||
{
|
||||
public void push(int node) {
|
||||
dataStack.push(node);
|
||||
minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(), node));
|
||||
}
|
||||
|
||||
public void pop()
|
||||
{
|
||||
public void pop() {
|
||||
dataStack.pop();
|
||||
minStack.pop();
|
||||
}
|
||||
|
||||
public int top()
|
||||
{
|
||||
public int top() {
|
||||
return dataStack.peek();
|
||||
}
|
||||
|
||||
public int min()
|
||||
{
|
||||
public int min() {
|
||||
return minStack.peek();
|
||||
}
|
||||
```
|
||||
|
@ -1430,8 +1443,7 @@ public int min()
|
|||
使用一个栈来模拟压入弹出操作。
|
||||
|
||||
```java
|
||||
public boolean IsPopOrder(int[] pushSequence, int[] popSequence)
|
||||
{
|
||||
public boolean IsPopOrder(int[] pushSequence, int[] popSequence) {
|
||||
int n = pushSequence.length;
|
||||
Stack<Integer> stack = new Stack<>();
|
||||
for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) {
|
||||
|
@ -1464,8 +1476,7 @@ public boolean IsPopOrder(int[] pushSequence, int[] popSequence)
|
|||
不需要使用两个队列分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
|
||||
|
||||
```java
|
||||
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root)
|
||||
{
|
||||
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
|
||||
Queue<TreeNode> queue = new LinkedList<>();
|
||||
ArrayList<Integer> ret = new ArrayList<>();
|
||||
queue.add(root);
|
||||
|
@ -1495,8 +1506,7 @@ public ArrayList<Integer> PrintFromTopToBottom(TreeNode root)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot)
|
||||
{
|
||||
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
|
||||
ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
|
||||
Queue<TreeNode> queue = new LinkedList<>();
|
||||
queue.add(pRoot);
|
||||
|
@ -1529,8 +1539,7 @@ ArrayList<ArrayList<Integer>> Print(TreeNode pRoot)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot)
|
||||
{
|
||||
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
|
||||
ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
|
||||
Queue<TreeNode> queue = new LinkedList<>();
|
||||
queue.add(pRoot);
|
||||
|
@ -1571,15 +1580,13 @@ public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public boolean VerifySquenceOfBST(int[] sequence)
|
||||
{
|
||||
public boolean VerifySquenceOfBST(int[] sequence) {
|
||||
if (sequence == null || sequence.length == 0)
|
||||
return false;
|
||||
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)
|
||||
return true;
|
||||
int rootVal = sequence[last];
|
||||
|
@ -1610,14 +1617,12 @@ private boolean verify(int[] sequence, int first, int last)
|
|||
```java
|
||||
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<>());
|
||||
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)
|
||||
return;
|
||||
path.add(node.val);
|
||||
|
@ -1641,8 +1646,7 @@ private void backtracking(TreeNode node, int target, ArrayList<Integer> path)
|
|||
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。
|
||||
|
||||
```java
|
||||
public class RandomListNode
|
||||
{
|
||||
public class RandomListNode {
|
||||
int label;
|
||||
RandomListNode next = 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>
|
||||
|
||||
```java
|
||||
public RandomListNode Clone(RandomListNode pHead)
|
||||
{
|
||||
public RandomListNode Clone(RandomListNode pHead) {
|
||||
if (pHead == null)
|
||||
return null;
|
||||
// 插入新节点
|
||||
|
@ -1718,14 +1721,12 @@ public RandomListNode Clone(RandomListNode pHead)
|
|||
private TreeNode pre = null;
|
||||
private TreeNode head = null;
|
||||
|
||||
public TreeNode Convert(TreeNode root)
|
||||
{
|
||||
public TreeNode Convert(TreeNode root) {
|
||||
inOrder(root);
|
||||
return head;
|
||||
}
|
||||
|
||||
private void inOrder(TreeNode node)
|
||||
{
|
||||
private void inOrder(TreeNode node) {
|
||||
if (node == null)
|
||||
return;
|
||||
inOrder(node.left);
|
||||
|
@ -1752,21 +1753,18 @@ private void inOrder(TreeNode node)
|
|||
```java
|
||||
private String deserializeStr;
|
||||
|
||||
public String Serialize(TreeNode root)
|
||||
{
|
||||
public String Serialize(TreeNode root) {
|
||||
if (root == null)
|
||||
return "#";
|
||||
return root.val + " " + Serialize(root.left) + " " + Serialize(root.right);
|
||||
}
|
||||
|
||||
public TreeNode Deserialize(String str)
|
||||
{
|
||||
public TreeNode Deserialize(String str) {
|
||||
deserializeStr = str;
|
||||
return Deserialize();
|
||||
}
|
||||
|
||||
private TreeNode Deserialize()
|
||||
{
|
||||
private TreeNode Deserialize() {
|
||||
if (deserializeStr.length() == 0)
|
||||
return null;
|
||||
int index = deserializeStr.indexOf(" ");
|
||||
|
@ -1795,8 +1793,7 @@ private TreeNode Deserialize()
|
|||
```java
|
||||
private ArrayList<String> ret = new ArrayList<>();
|
||||
|
||||
public ArrayList<String> Permutation(String str)
|
||||
{
|
||||
public ArrayList<String> Permutation(String str) {
|
||||
if (str.length() == 0)
|
||||
return ret;
|
||||
char[] chars = str.toCharArray();
|
||||
|
@ -1805,8 +1802,7 @@ public ArrayList<String> Permutation(String str)
|
|||
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) {
|
||||
ret.add(s.toString());
|
||||
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。
|
||||
|
||||
```java
|
||||
public int MoreThanHalfNum_Solution(int[] nums)
|
||||
{
|
||||
public int MoreThanHalfNum_Solution(int[] nums) {
|
||||
int majority = nums[0];
|
||||
for (int i = 1, cnt = 1; i < nums.length; i++) {
|
||||
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 个元素的算法称为快速选择算法。
|
||||
|
||||
```java
|
||||
public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k)
|
||||
{
|
||||
public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
|
||||
ArrayList<Integer> ret = new ArrayList<>();
|
||||
if (k > nums.length || k <= 0)
|
||||
return ret;
|
||||
|
@ -1880,8 +1874,7 @@ public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k)
|
|||
return ret;
|
||||
}
|
||||
|
||||
public void findKthSmallest(int[] nums, int k)
|
||||
{
|
||||
public void findKthSmallest(int[] nums, int k) {
|
||||
int l = 0, h = nums.length - 1;
|
||||
while (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 i = l, j = h + 1;
|
||||
while (true) {
|
||||
|
@ -1909,8 +1901,7 @@ private int partition(int[] nums, int l, int h)
|
|||
return j;
|
||||
}
|
||||
|
||||
private void swap(int[] nums, int i, int j)
|
||||
{
|
||||
private void swap(int[] nums, int i, int j) {
|
||||
int t = nums[i];
|
||||
nums[i] = nums[j];
|
||||
nums[j] = t;
|
||||
|
@ -1927,8 +1918,7 @@ private void swap(int[] nums, int i, int j)
|
|||
维护一个大小为 K 的最小堆过程如下:在添加一个元素之后,如果大顶堆的大小大于 K,那么需要将大顶堆的堆顶元素去除。
|
||||
|
||||
```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)
|
||||
return new ArrayList<>();
|
||||
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
|
||||
|
@ -1959,8 +1949,7 @@ private PriorityQueue<Integer> right = new PriorityQueue<>();
|
|||
/* 当前数据流读入的元素个数 */
|
||||
private int N = 0;
|
||||
|
||||
public void Insert(Integer val)
|
||||
{
|
||||
public void Insert(Integer val) {
|
||||
/* 插入要保证两个堆存于平衡状态 */
|
||||
if (N % 2 == 0) {
|
||||
/* N 为偶数的情况下插入到右半边。
|
||||
|
@ -1975,8 +1964,7 @@ public void Insert(Integer val)
|
|||
N++;
|
||||
}
|
||||
|
||||
public Double GetMedian()
|
||||
{
|
||||
public Double GetMedian() {
|
||||
if (N % 2 == 0)
|
||||
return (left.peek() + right.peek()) / 2.0;
|
||||
else
|
||||
|
@ -1998,16 +1986,14 @@ public Double GetMedian()
|
|||
private int[] cnts = new int[256];
|
||||
private Queue<Character> queue = new LinkedList<>();
|
||||
|
||||
public void Insert(char ch)
|
||||
{
|
||||
public void Insert(char ch) {
|
||||
cnts[ch]++;
|
||||
queue.add(ch);
|
||||
while (!queue.isEmpty() && cnts[queue.peek()] > 1)
|
||||
queue.poll();
|
||||
}
|
||||
|
||||
public char FirstAppearingOnce()
|
||||
{
|
||||
public char FirstAppearingOnce() {
|
||||
return queue.isEmpty() ? '#' : queue.peek();
|
||||
}
|
||||
```
|
||||
|
@ -2023,8 +2009,7 @@ public char FirstAppearingOnce()
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public int FindGreatestSumOfSubArray(int[] nums)
|
||||
{
|
||||
public int FindGreatestSumOfSubArray(int[] nums) {
|
||||
if (nums == null || nums.length == 0)
|
||||
return 0;
|
||||
int greatestSum = Integer.MIN_VALUE;
|
||||
|
@ -2044,8 +2029,7 @@ public int FindGreatestSumOfSubArray(int[] nums)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public int NumberOf1Between1AndN_Solution(int n)
|
||||
{
|
||||
public int NumberOf1Between1AndN_Solution(int n) {
|
||||
int cnt = 0;
|
||||
for (int m = 1; m <= n; m *= 10) {
|
||||
int a = n / m, b = n % m;
|
||||
|
@ -2066,8 +2050,7 @@ public int NumberOf1Between1AndN_Solution(int n)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public int getDigitAtIndex(int index)
|
||||
{
|
||||
public int getDigitAtIndex(int index) {
|
||||
if (index < 0)
|
||||
return -1;
|
||||
int place = 1; // 1 表示个位,2 表示 十位...
|
||||
|
@ -2085,8 +2068,7 @@ public int getDigitAtIndex(int index)
|
|||
* place 位数的数字组成的字符串长度
|
||||
* 10, 90, 900, ...
|
||||
*/
|
||||
private int getAmountOfPlace(int place)
|
||||
{
|
||||
private int getAmountOfPlace(int place) {
|
||||
if (place == 1)
|
||||
return 10;
|
||||
return (int) Math.pow(10, place - 1) * 9;
|
||||
|
@ -2096,8 +2078,7 @@ private int getAmountOfPlace(int place)
|
|||
* place 位数的起始数字
|
||||
* 0, 10, 100, ...
|
||||
*/
|
||||
private int getBeginNumberOfPlace(int place)
|
||||
{
|
||||
private int getBeginNumberOfPlace(int place) {
|
||||
if (place == 1)
|
||||
return 0;
|
||||
return (int) Math.pow(10, place - 1);
|
||||
|
@ -2106,8 +2087,7 @@ private int getBeginNumberOfPlace(int place)
|
|||
/**
|
||||
* 在 place 位数组成的字符串中,第 index 个数
|
||||
*/
|
||||
private int getDigitAtIndex(int index, int place)
|
||||
{
|
||||
private int getDigitAtIndex(int index, int place) {
|
||||
int beginNumber = getBeginNumberOfPlace(place);
|
||||
int shiftNumber = index / place;
|
||||
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 排在前面。
|
||||
|
||||
```java
|
||||
public String PrintMinNumber(int[] numbers)
|
||||
{
|
||||
public String PrintMinNumber(int[] numbers) {
|
||||
if (numbers == null || numbers.length == 0)
|
||||
return "";
|
||||
int n = numbers.length;
|
||||
|
@ -2156,8 +2135,7 @@ public String PrintMinNumber(int[] numbers)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public int numDecodings(String s)
|
||||
{
|
||||
public int numDecodings(String s) {
|
||||
if (s == null || s.length() == 0)
|
||||
return 0;
|
||||
int n = s.length();
|
||||
|
@ -2200,8 +2178,7 @@ public int numDecodings(String s)
|
|||
应该用动态规划求解,而不是深度优先搜索,深度优先搜索过于复杂,不是最优解。
|
||||
|
||||
```java
|
||||
public int getMost(int[][] values)
|
||||
{
|
||||
public int getMost(int[][] values) {
|
||||
if (values == null || values.length == 0 || values[0].length == 0)
|
||||
return 0;
|
||||
int n = values[0].length;
|
||||
|
@ -2224,8 +2201,7 @@ public int getMost(int[][] values)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public int longestSubStringWithoutDuplication(String str)
|
||||
{
|
||||
public int longestSubStringWithoutDuplication(String str) {
|
||||
int curLen = 0;
|
||||
int maxLen = 0;
|
||||
int[] preIndexs = new int[26];
|
||||
|
@ -2257,8 +2233,7 @@ public int longestSubStringWithoutDuplication(String str)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public int GetUglyNumber_Solution(int N)
|
||||
{
|
||||
public int GetUglyNumber_Solution(int N) {
|
||||
if (N <= 6)
|
||||
return N;
|
||||
int i2 = 0, i3 = 0, i5 = 0;
|
||||
|
@ -2291,8 +2266,7 @@ public int GetUglyNumber_Solution(int N)
|
|||
最直观的解法是使用 HashMap 对出现次数进行统计,但是考虑到要统计的字符范围有限,因此可以使用整型数组代替 HashMap。
|
||||
|
||||
```java
|
||||
public int FirstNotRepeatingChar(String str)
|
||||
{
|
||||
public int FirstNotRepeatingChar(String str) {
|
||||
int[] cnts = new int[256];
|
||||
for (int i = 0; i < str.length(); i++)
|
||||
cnts[str.charAt(i)]++;
|
||||
|
@ -2306,8 +2280,7 @@ public int FirstNotRepeatingChar(String str)
|
|||
以上实现的空间复杂度还不是最优的。考虑到只需要找到只出现一次的字符,那么我们只需要统计的次数信息只有 0,1,更大,使用两个比特位就能存储这些信息。
|
||||
|
||||
```java
|
||||
public int FirstNotRepeatingChar2(String str)
|
||||
{
|
||||
public int FirstNotRepeatingChar2(String str) {
|
||||
BitSet bs1 = new BitSet(256);
|
||||
BitSet bs2 = new BitSet(256);
|
||||
for (char c : str.toCharArray()) {
|
||||
|
@ -2339,15 +2312,13 @@ public int FirstNotRepeatingChar2(String str)
|
|||
private long cnt = 0;
|
||||
private int[] tmp; // 在这里创建辅助数组,而不是在 merge() 递归函数中创建
|
||||
|
||||
public int InversePairs(int[] nums)
|
||||
{
|
||||
public int InversePairs(int[] nums) {
|
||||
tmp = new int[nums.length];
|
||||
mergeSort(nums, 0, nums.length - 1);
|
||||
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)
|
||||
return;
|
||||
int m = l + (h - l) / 2;
|
||||
|
@ -2356,8 +2327,7 @@ private void mergeSort(int[] nums, int l, int 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;
|
||||
while (i <= m || j <= h) {
|
||||
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 两个链表的指针能同时访问到交点。
|
||||
|
||||
```java
|
||||
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2)
|
||||
{
|
||||
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
|
||||
ListNode l1 = pHead1, l2 = pHead2;
|
||||
while (l1 != l2) {
|
||||
l1 = (l1 == null) ? pHead2 : l1.next;
|
||||
|
@ -2420,15 +2389,13 @@ Output:
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public int GetNumberOfK(int[] nums, int K)
|
||||
{
|
||||
public int GetNumberOfK(int[] nums, int K) {
|
||||
int first = binarySearch(nums, K);
|
||||
int last = binarySearch(nums, K + 1);
|
||||
return (first == nums.length || nums[first] != K) ? 0 : last - first;
|
||||
}
|
||||
|
||||
private int binarySearch(int[] nums, int K)
|
||||
{
|
||||
private int binarySearch(int[] nums, int K) {
|
||||
int l = 0, h = nums.length;
|
||||
while (l < h) {
|
||||
int m = l + (h - l) / 2;
|
||||
|
@ -2453,14 +2420,12 @@ private int binarySearch(int[] nums, int K)
|
|||
private TreeNode ret;
|
||||
private int cnt = 0;
|
||||
|
||||
public TreeNode KthNode(TreeNode pRoot, int k)
|
||||
{
|
||||
public TreeNode KthNode(TreeNode pRoot, int k) {
|
||||
inOrder(pRoot, k);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void inOrder(TreeNode root, int k)
|
||||
{
|
||||
private void inOrder(TreeNode root, int k) {
|
||||
if (root == null || cnt >= k)
|
||||
return;
|
||||
inOrder(root.left, k);
|
||||
|
@ -2484,8 +2449,7 @@ private void inOrder(TreeNode root, int k)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public int TreeDepth(TreeNode root)
|
||||
{
|
||||
public int TreeDepth(TreeNode root) {
|
||||
return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right));
|
||||
}
|
||||
```
|
||||
|
@ -2505,14 +2469,12 @@ public int TreeDepth(TreeNode root)
|
|||
```java
|
||||
private boolean isBalanced = true;
|
||||
|
||||
public boolean IsBalanced_Solution(TreeNode root)
|
||||
{
|
||||
public boolean IsBalanced_Solution(TreeNode root) {
|
||||
height(root);
|
||||
return isBalanced;
|
||||
}
|
||||
|
||||
private int height(TreeNode root)
|
||||
{
|
||||
private int height(TreeNode root) {
|
||||
if (root == null || !isBalanced)
|
||||
return 0;
|
||||
int left = height(root.left);
|
||||
|
@ -2538,8 +2500,7 @@ private int height(TreeNode root)
|
|||
diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。
|
||||
|
||||
```java
|
||||
public void FindNumsAppearOnce(int[] nums, int num1[], int num2[])
|
||||
{
|
||||
public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) {
|
||||
int diff = 0;
|
||||
for (int num : nums)
|
||||
diff ^= num;
|
||||
|
@ -2570,8 +2531,7 @@ public void FindNumsAppearOnce(int[] nums, int num1[], int num2[])
|
|||
- 如果 sum < target,移动较小的元素,使 sum 变大一些。
|
||||
|
||||
```java
|
||||
public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum)
|
||||
{
|
||||
public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
|
||||
int i = 0, j = array.length - 1;
|
||||
while (i < j) {
|
||||
int cur = array[i] + array[j];
|
||||
|
@ -2604,8 +2564,7 @@ public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum)
|
||||
{
|
||||
public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
|
||||
ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
|
||||
int start = 1, end = 2;
|
||||
int curSum = 3;
|
||||
|
@ -2648,8 +2607,7 @@ public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum)
|
|||
正确的解法应该是和书上一样,先旋转每个单词,再旋转整个字符串。
|
||||
|
||||
```java
|
||||
public String ReverseSentence(String str)
|
||||
{
|
||||
public String ReverseSentence(String str) {
|
||||
int n = str.length();
|
||||
char[] chars = str.toCharArray();
|
||||
int i = 0, j = 0;
|
||||
|
@ -2664,14 +2622,12 @@ public String ReverseSentence(String str)
|
|||
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)
|
||||
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];
|
||||
c[i] = c[j];
|
||||
c[j] = t;
|
||||
|
@ -2691,8 +2647,7 @@ private void swap(char[] c, int i, int j)
|
|||
先将 "abc" 和 "XYZdef" 分别翻转,得到 "cbafedZYX",然后再把整个字符串翻转得到 "XYZdefabc"。
|
||||
|
||||
```java
|
||||
public String LeftRotateString(String str, int n)
|
||||
{
|
||||
public String LeftRotateString(String str, int n) {
|
||||
if (n >= str.length())
|
||||
return str;
|
||||
char[] chars = str.toCharArray();
|
||||
|
@ -2702,14 +2657,12 @@ public String LeftRotateString(String str, int n)
|
|||
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)
|
||||
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];
|
||||
chars[i] = chars[j];
|
||||
chars[j] = t;
|
||||
|
@ -2727,8 +2680,7 @@ private void swap(char[] chars, int i, int j)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public ArrayList<Integer> maxInWindows(int[] num, int size)
|
||||
{
|
||||
public ArrayList<Integer> maxInWindows(int[] num, int size) {
|
||||
ArrayList<Integer> ret = new ArrayList<>();
|
||||
if (size > num.length || size < 1)
|
||||
return ret;
|
||||
|
@ -2762,8 +2714,7 @@ public ArrayList<Integer> maxInWindows(int[] num, int size)
|
|||
空间复杂度:O(N<sup>2</sup>)
|
||||
|
||||
```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 pointNum = face * n;
|
||||
long[][] dp = new long[n + 1][pointNum + 1];
|
||||
|
@ -2790,8 +2741,7 @@ public List<Map.Entry<Integer, Double>> dicesSum(int n)
|
|||
空间复杂度:O(N)
|
||||
|
||||
```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 pointNum = face * n;
|
||||
long[][] dp = new long[2][pointNum + 1];
|
||||
|
@ -2829,8 +2779,7 @@ public List<Map.Entry<Integer, Double>> dicesSum(int n)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public boolean isContinuous(int[] nums)
|
||||
{
|
||||
public boolean isContinuous(int[] nums) {
|
||||
if (nums.length < 5)
|
||||
return false;
|
||||
Arrays.sort(nums);
|
||||
|
@ -2861,8 +2810,7 @@ public boolean isContinuous(int[] nums)
|
|||
约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈,所以最后需要对 n 取余。
|
||||
|
||||
```java
|
||||
public int LastRemaining_Solution(int n, int m)
|
||||
{
|
||||
public int LastRemaining_Solution(int n, int m) {
|
||||
if (n == 0) /* 特殊输入的处理 */
|
||||
return -1;
|
||||
if (n == 1) /* 返回条件 */
|
||||
|
@ -2884,8 +2832,7 @@ public int LastRemaining_Solution(int n, int m)
|
|||
使用贪心策略,假设第 i 轮进行卖出操作,买入操作价格应该在 i 之前并且价格最低。
|
||||
|
||||
```java
|
||||
public int maxProfit(int[] prices)
|
||||
{
|
||||
public int maxProfit(int[] prices) {
|
||||
if (prices == null || prices.length == 0)
|
||||
return 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。
|
||||
|
||||
```java
|
||||
public int Sum_Solution(int n)
|
||||
{
|
||||
public int Sum_Solution(int n) {
|
||||
int sum = n;
|
||||
boolean b = (n > 0) && ((sum += Sum_Solution(n - 1)) > 0);
|
||||
return sum;
|
||||
|
@ -2938,8 +2884,7 @@ a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进
|
|||
递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
@ -2955,8 +2900,7 @@ public int Add(int a, int b)
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public int[] multiply(int[] A)
|
||||
{
|
||||
public int[] multiply(int[] A) {
|
||||
int n = A.length;
|
||||
int[] B = new int[n];
|
||||
for (int i = 0, product = 1; i < n; product *= A[i], i++) /* 从左往右累乘 */
|
||||
|
@ -2988,8 +2932,7 @@ Output:
|
|||
## 解题思路
|
||||
|
||||
```java
|
||||
public int StrToInt(String str)
|
||||
{
|
||||
public int StrToInt(String str) {
|
||||
if (str == null || str.length() == 0)
|
||||
return 0;
|
||||
boolean isNegative = str.charAt(0) == '-';
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
## 危害
|
||||
|
||||
- 窃取用户的 Cookie 值
|
||||
- 窃取用户的 Cookie
|
||||
- 伪造虚假的输入表单骗取个人信息
|
||||
- 显示伪造的文章或者图片
|
||||
|
||||
|
@ -47,7 +47,7 @@
|
|||
|
||||
富文本编辑器允许用户输入 HTML 代码,就不能简单地将 `<` 等字符进行过滤了,极大地提高了 XSS 攻击的可能性。
|
||||
|
||||
富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,可以定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。
|
||||
富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,通过定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。
|
||||
|
||||
以下例子中,form 和 script 等标签都被转义,而 h 和 p 等标签将会保留。
|
||||
|
||||
|
@ -131,7 +131,7 @@ http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
|
|||
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">。
|
||||
```
|
||||
|
||||
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
|
||||
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 美元。
|
||||
|
||||
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
|
||||
|
||||
|
@ -153,8 +153,6 @@ Referer 首部字段位于 HTTP 报文中,用于标识请求来源的地址。
|
|||
|
||||
因为 CSRF 攻击是在用户无意识的情况下发生的,所以要求用户输入验证码可以让用户知道自己正在做的操作。
|
||||
|
||||
也可以要求用户输入验证码来进行校验。
|
||||
|
||||
# 三、SQL 注入攻击
|
||||
|
||||
## 概念
|
||||
|
|
|
@ -383,13 +383,13 @@ MVCC 不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题
|
|||
|
||||
## Record Locks
|
||||
|
||||
锁定的对象是记录的索引,而不是记录本身。
|
||||
锁定一个记录上的索引,而不是记录本身。
|
||||
|
||||
如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚集索引,因此 Record Locks 依然可以使用。
|
||||
|
||||
## Gap Locks
|
||||
|
||||
锁定一个范围内的索引,例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。
|
||||
锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。
|
||||
|
||||
```sql
|
||||
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
|
||||
|
@ -397,28 +397,14 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
|
|||
|
||||
## Next-Key Locks
|
||||
|
||||
它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录,也锁定范围内的索引。在 user 中有以下记录:
|
||||
它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录上的索引,也锁定范围内的索引。例如一个索引包含以下值:10, 11, 13, and 20,那么就需要锁定以下区间:
|
||||
|
||||
```sql
|
||||
| id | last_name | first_name | age |
|
||||
|------|-------------|--------------|-------|
|
||||
| 4 | stark | tony | 21 |
|
||||
| 1 | tom | hiddleston | 30 |
|
||||
| 3 | morgan | freeman | 40 |
|
||||
| 5 | jeff | dean | 50 |
|
||||
| 2 | donald | trump | 80 |
|
||||
+------|-------------|--------------|-------+
|
||||
```
|
||||
|
||||
那么就需要锁定以下范围:
|
||||
|
||||
```sql
|
||||
(-∞, 21]
|
||||
(21, 30]
|
||||
(30, 40]
|
||||
(40, 50]
|
||||
(50, 80]
|
||||
(80, ∞)
|
||||
(negative infinity, 10]
|
||||
(10, 11]
|
||||
(11, 13]
|
||||
(13, 20]
|
||||
(20, positive infinity)
|
||||
```
|
||||
|
||||
# 七、关系数据库设计理论
|
||||
|
|
30
notes/算法.md
30
notes/算法.md
|
@ -1623,10 +1623,10 @@ private List<Key> keys(Node x, Key l, Key h) {
|
|||
|
||||
## 2-3 查找树
|
||||
|
||||
<div align="center"> <img src="../pics//ff396233-1bb1-4e74-8bc2-d7c90146f5dd.png" width="250"/> </div><br>
|
||||
|
||||
2-3 查找树引入了 2- 节点和 3- 节点,目的是为了让树平衡。一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。
|
||||
|
||||
<div align="center"> <img src="../pics//ff396233-1bb1-4e74-8bc2-d7c90146f5dd.png" width="250"/> </div><br>
|
||||
|
||||
### 1. 插入操作
|
||||
|
||||
插入操作和 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
|
||||
2. 将 1 个圆盘从 from -> to
|
||||
3. 将 n-1 个圆盘从 buffer -> to
|
||||
- 将 n-1 个圆盘从 from -> buffer
|
||||
|
||||
<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> - 1,n 个圆盘需要移动 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
|
||||
public class Hanoi {
|
||||
public static void move(int n, String from, String buffer, String to) {
|
||||
|
@ -2105,7 +2107,7 @@ from H1 to H3
|
|||
|
||||
生成编码时,从根节点出发,向左遍历则添加二进制位 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
|
||||
public class Huffman {
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
实现可扩展主要有两种方式:
|
||||
|
||||
- 使用消息队列进行解耦,应用之间通过消息传递的方式进行通信;
|
||||
- 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以用过调用可复用的服务来实现业务逻辑,对其它产品没有影响。
|
||||
- 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以通过调用可复用的服务来实现业务逻辑,对其它产品没有影响。
|
||||
|
||||
# 四、可用性
|
||||
|
||||
|
|
|
@ -98,7 +98,9 @@ public static synchronized Singleton getUniqueInstance() {
|
|||
|
||||
(三)饿汉式-线程安全
|
||||
|
||||
线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的优势。
|
||||
线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。
|
||||
|
||||
但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
|
||||
|
||||
```java
|
||||
private static Singleton uniqueInstance = new Singleton();
|
||||
|
@ -106,7 +108,7 @@ private static Singleton uniqueInstance = new Singleton();
|
|||
|
||||
(四)双重校验锁-线程安全
|
||||
|
||||
uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行。也就是说,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
|
||||
uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
|
||||
|
||||
双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
|
||||
|
||||
|
@ -131,7 +133,7 @@ public class Singleton {
|
|||
}
|
||||
```
|
||||
|
||||
考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,也就是说会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。
|
||||
考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,那么就会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。
|
||||
|
||||
```java
|
||||
if (uniqueInstance == null) {
|
||||
|
@ -157,7 +159,7 @@ uniqueInstance 采用 volatile 关键字修饰也是很有必要的。`uniqueIns
|
|||
|
||||
这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。
|
||||
|
||||
```source-java
|
||||
```java
|
||||
public class Singleton {
|
||||
|
||||
private Singleton() {
|
||||
|
@ -299,7 +301,7 @@ public class Client {
|
|||
|
||||
### 意图
|
||||
|
||||
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化推迟到子类。
|
||||
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
|
||||
|
||||
### 类图
|
||||
|
||||
|
@ -1851,11 +1853,7 @@ No gumball dispensed
|
|||
|
||||
### 与状态模式的比较
|
||||
|
||||
状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。
|
||||
|
||||
但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。
|
||||
|
||||
所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
|
||||
状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
|
||||
|
||||
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。
|
||||
|
||||
|
@ -1969,7 +1967,7 @@ public abstract class CaffeineBeverage {
|
|||
```
|
||||
|
||||
```java
|
||||
public class Coffee extends CaffeineBeverage{
|
||||
public class Coffee extends CaffeineBeverage {
|
||||
@Override
|
||||
void brew() {
|
||||
System.out.println("Coffee.brew");
|
||||
|
@ -1983,7 +1981,7 @@ public class Coffee extends CaffeineBeverage{
|
|||
```
|
||||
|
||||
```java
|
||||
public class Tea extends CaffeineBeverage{
|
||||
public class Tea extends CaffeineBeverage {
|
||||
@Override
|
||||
void brew() {
|
||||
System.out.println("Tea.brew");
|
||||
|
@ -2238,7 +2236,7 @@ Number of items: 6
|
|||
|
||||
### 意图
|
||||
|
||||
使用什么都不做的空对象来替代 NULL。
|
||||
使用什么都不做的空对象来代替 NULL。
|
||||
|
||||
一个方法返回 NULL,意味着方法的调用端需要去检查返回值是否是 NULL,这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值,而直接使用返回的对象,那么就有可能抛出空指针异常。
|
||||
|
||||
|
@ -2393,7 +2391,7 @@ public abstract class TV {
|
|||
```
|
||||
|
||||
```java
|
||||
public class Sony extends TV{
|
||||
public class Sony extends TV {
|
||||
@Override
|
||||
public void on() {
|
||||
System.out.println("Sony.on()");
|
||||
|
@ -2412,7 +2410,7 @@ public class Sony extends TV{
|
|||
```
|
||||
|
||||
```java
|
||||
public class RCA extends TV{
|
||||
public class RCA extends TV {
|
||||
@Override
|
||||
public void on() {
|
||||
System.out.println("RCA.on()");
|
||||
|
@ -2551,9 +2549,6 @@ public abstract class Component {
|
|||
```
|
||||
|
||||
```java
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Composite extends Component {
|
||||
|
||||
private List<Component> child;
|
||||
|
@ -2659,7 +2654,7 @@ Composite:root
|
|||
|
||||
### 类图
|
||||
|
||||
装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。
|
||||
装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。
|
||||
|
||||
<div align="center"> <img src="../pics//137c593d-0a9e-47b8-a9e6-b71f540b82dd.png"/> </div><br>
|
||||
|
||||
|
@ -2770,7 +2765,7 @@ public class Client {
|
|||
|
||||
### 实现
|
||||
|
||||
观看电影需要操作很多电器,使用外观模式可以实现一键看电影功能。
|
||||
观看电影需要操作很多电器,使用外观模式实现一键看电影功能。
|
||||
|
||||
```java
|
||||
public class SubSystem {
|
||||
|
@ -2811,7 +2806,7 @@ public class Client {
|
|||
|
||||
### 设计原则
|
||||
|
||||
最少知识原则:只和你的密友谈话。也就是客户对象所需要交互的对象应当尽可能少。
|
||||
最少知识原则:只和你的密友谈话。也就是说客户对象所需要交互的对象应当尽可能少。
|
||||
|
||||
## 6. 享元(Flyweight)
|
||||
|
||||
|
@ -2822,8 +2817,8 @@ public class Client {
|
|||
### 类图
|
||||
|
||||
- Flyweight:享元对象
|
||||
- IntrinsicState:内部状态,相同的项元对象共享
|
||||
- ExtrinsicState:外部状态
|
||||
- IntrinsicState:内部状态,享元对象共享内部状态
|
||||
- ExtrinsicState:外部状态,每个享元对象的外部状态不同
|
||||
|
||||
<div align="center"> <img src="../pics//d52270b4-9097-4667-9f18-f405fc661c99.png"/> </div><br>
|
||||
|
||||
|
@ -2854,8 +2849,6 @@ public class ConcreteFlyweight implements Flyweight {
|
|||
```
|
||||
|
||||
```java
|
||||
import java.util.HashMap;
|
||||
|
||||
public class FlyweightFactory {
|
||||
|
||||
private HashMap<String, Flyweight> flyweights = new HashMap<>();
|
||||
|
|
BIN
pics/VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png
Normal file
BIN
pics/VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Loading…
Reference in New Issue
Block a user