auto commit

This commit is contained in:
CyC2018
2020-11-17 00:32:18 +08:00
parent f5ad47b470
commit 7e61fc1360
380 changed files with 2371 additions and 46715 deletions

View File

@ -6,7 +6,7 @@
## 题目描述
求斐波那契数列的第 n n <= 39
求斐波那契数列的第 n n \<= 39
<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?f(n)=\left\{\begin{array}{rcl}0&&{n=0}\\1&&{n=1}\\f(n-1)+f(n-2)&&{n>1}\end{array}\right." class="mathjax-pic"/></div> <br> -->
@ -67,10 +67,3 @@ public class Solution {
}
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -40,10 +40,3 @@ public int RectCover(int n) {
return result;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -38,10 +38,3 @@ public int JumpFloor(int n) {
return result;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -58,10 +58,3 @@ public int JumpFloorII(int target) {
return (int) Math.pow(2, target - 1);
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -20,7 +20,7 @@
通过修改二分查找算法进行求解l 代表 lowm 代表 midh 代表 high
- nums[m] <= nums[h] 表示 [m, h] 区间内的数组是非递减数组[l, m] 区间内的数组是旋转数组此时令 h = m
- nums[m] \<= nums[h] 表示 [m, h] 区间内的数组是非递减数组[l, m] 区间内的数组是旋转数组此时令 h = m
- 否则 [m + 1, h] 区间内的数组是旋转数组 l = m + 1
```java
@ -65,10 +65,3 @@ private int minNumber(int[] nums, int l, int h) {
return nums[l];
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -62,10 +62,3 @@ private char[][] buildMatrix(char[] array) {
return matrix;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -56,10 +56,3 @@ private void initDigitSum() {
this.digitSum[i][j] = digitSumOne[i] + digitSumOne[j];
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -22,17 +22,17 @@ return 36 (10 = 3 + 3 + 4)
尽可能得多剪长度为 3 的绳子并且不允许有长度为 1 的绳子出现如果出现了就从已经切好长度为 3 的绳子中拿出一段与长度为 1 的绳子重新组合把它们切成两段长度为 2 的绳子以下为证明过程
将绳子拆成 1 n-1 1(n-1)-n=-1<0即拆开后的乘积一定更小所以不能出现长度为 1 的绳子
将绳子拆成 1 n-1 1(n-1)-n=-1\<0即拆开后的乘积一定更小所以不能出现长度为 1 的绳子
将绳子拆成 2 n-2 2(n-2)-n = n-4 n>=4 时这样拆开能得到的乘积会比不拆更大
将绳子拆成 2 n-2 2(n-2)-n = n-4 n\>=4 时这样拆开能得到的乘积会比不拆更大
将绳子拆成 3 n-3 3(n-3)-n = 2n-9 n>=5 时效果更好
将绳子拆成 3 n-3 3(n-3)-n = 2n-9 n\>=5 时效果更好
将绳子拆成 4 n-4因为 4=2\*2因此效果和拆成 2 一样
将绳子拆成 5 n-5因为 5=2+3 5<2\*3所以不能出现 5 的绳子而是尽可能拆成 2 3
将绳子拆成 5 n-5因为 5=2+3 5\<2\*3所以不能出现 5 的绳子而是尽可能拆成 2 3
将绳子拆成 6 n-6因为 6=3+3 6<3\*3所以不能出现 6 的绳子而是拆成 3 3这里 6 同样可以拆成 6=2+2+2但是 3(n - 3) - 2(n - 2) = n - 5 >= 0 n>=5 的情况下将绳子拆成 3 比拆成 2 效果更好
将绳子拆成 6 n-6因为 6=3+3 6\<3\*3所以不能出现 6 的绳子而是拆成 3 3这里 6 同样可以拆成 6=2+2+2但是 3(n - 3) - 2(n - 2) = n - 5 \>= 0 n\>=5 的情况下将绳子拆成 3 比拆成 2 效果更好
继续拆成更大的绳子可以发现都比拆成 2 3 的效果更差因此我们只考虑将绳子拆成 2 3并且优先拆成 3当拆到绳子长度 n 等于 4 也就是出现 3+1此时只能拆成 2+2
@ -65,10 +65,3 @@ public int integerBreak(int n) {
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -25,10 +25,3 @@ public int NumberOf1(int n) {
return cnt;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -44,10 +44,3 @@ private double pow(double x, int n) {
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -38,10 +38,3 @@ private void printNumber(char[] number) {
System.out.println();
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -35,10 +35,3 @@ public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
return head;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -23,10 +23,3 @@ public ListNode deleteDuplication(ListNode pHead) {
}
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -38,10 +38,3 @@ public boolean match(char[] str, char[] pattern) {
return dp[m][n];
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -47,10 +47,3 @@ public boolean isNumeric(char[] str) {
return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -60,10 +60,3 @@ private void swap(int[] nums, int i, int j) {
nums[j] = t;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -25,10 +25,3 @@ public ListNode FindKthToTail(ListNode head, int k) {
return P2;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -41,10 +41,3 @@ public ListNode EntryNodeOfLoop(ListNode pHead) {
return slow;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -34,10 +34,3 @@ public ListNode ReverseList(ListNode head) {
return newList.next;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -49,10 +49,3 @@ public ListNode Merge(ListNode list1, ListNode list2) {
return head.next;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -27,10 +27,3 @@ private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) {
return isSubtreeWithRoot(root1.left, root2.left) && isSubtreeWithRoot(root1.right, root2.right);
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -23,10 +23,3 @@ private void swap(TreeNode root) {
root.right = t;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -25,10 +25,3 @@ boolean isSymmetrical(TreeNode t1, TreeNode t2) {
return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left);
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -14,7 +14,7 @@
## 解题思路
一层一层从外到里打印观察可知每一层打印都有相同的处理步骤唯一不同的是上下左右的边界不同了因此使用四个变量 r1, r2, c1, c2 分别存储上下左右边界值从而定义当前最外层打印当前最外层的顺序从左到右打印最上一行->从上到下打印最右一行->从右到左打印最下一行->从下到上打印最左一行应当注意只有在 r1 != r2 时才打印最下一行也就是在当前最外层的行数大于 1 时才打印最下一行这是因为当前最外层只有一行时继续打印最下一行会导致重复打印打印最左一行也要做同样处理
一层一层从外到里打印观察可知每一层打印都有相同的处理步骤唯一不同的是上下左右的边界不同了因此使用四个变量 r1, r2, c1, c2 分别存储上下左右边界值从而定义当前最外层打印当前最外层的顺序从左到右打印最上一行-\>从上到下打印最右一行-\>从右到左打印最下一行-\>从下到上打印最左一行应当注意只有在 r1 != r2 时才打印最下一行也就是在当前最外层的行数大于 1 时才打印最下一行这是因为当前最外层只有一行时继续打印最下一行会导致重复打印打印最左一行也要做同样处理
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20201104010609223.png" width="500px"> </div><br>
@ -42,10 +42,3 @@ public ArrayList<Integer> printMatrix(int[][] matrix) {
return ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -49,10 +49,3 @@ private void swap(int[] nums, int i, int j) {
nums[j] = t;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -36,10 +36,3 @@ public int min() {
return minStack.peek();
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -30,10 +30,3 @@ public boolean IsPopOrder(int[] pushSequence, int[] popSequence) {
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -35,10 +35,3 @@ public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
return ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -30,10 +30,3 @@ ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
return ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -34,10 +34,3 @@ public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
return ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -32,10 +32,3 @@ private boolean verify(int[] sequence, int first, int last) {
return verify(sequence, first, cutIndex - 1) && verify(sequence, cutIndex, last - 1);
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -34,10 +34,3 @@ private void backtracking(TreeNode node, int target, ArrayList<Integer> path) {
path.remove(path.size() - 1);
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -65,10 +65,3 @@ public RandomListNode Clone(RandomListNode pHead) {
return pCloneHead;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -32,10 +32,3 @@ private void inOrder(TreeNode node) {
inOrder(node.right);
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -37,10 +37,3 @@ private TreeNode Deserialize() {
return t;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -38,10 +38,3 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) {
}
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -25,10 +25,3 @@ public int MoreThanHalfNum_Solution(int[] nums) {
return cnt > nums.length / 2 ? majority : 0;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -47,10 +47,3 @@ public boolean Find(int target, int[][] matrix) {
return false;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -15,7 +15,7 @@
应该使用大顶堆来维护最小堆而不能直接创建一个小顶堆并设置一个大小企图让小顶堆中的元素都是最小元素
Java PriorityQueue 实现了堆的能力PriorityQueue 默认是小顶堆可以在在初始化时使用 Lambda 表达式 (o1, o2) -> o2 - o1 来实现大顶堆其它语言也有类似的堆数据结构
Java PriorityQueue 实现了堆的能力PriorityQueue 默认是小顶堆可以在在初始化时使用 Lambda 表达式 (o1, o2) -\> o2 - o1 来实现大顶堆其它语言也有类似的堆数据结构
```java
public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
@ -83,10 +83,3 @@ private void swap(int[] nums, int i, int j) {
nums[j] = t;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -40,10 +40,3 @@ public Double GetMedian() {
return (double) right.peek();
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -29,10 +29,3 @@ public char FirstAppearingOnce() {
return queue.isEmpty() ? '#' : queue.peek();
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -22,10 +22,3 @@ public int FindGreatestSumOfSubArray(int[] nums) {
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -15,11 +15,4 @@ public int NumberOf1Between1AndN_Solution(int n) {
}
```
> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython)
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>
\> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython)

View File

@ -52,10 +52,3 @@ private int getDigitAtIndex(int index, int place) {
return number.charAt(count) - '0';
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,6 +1,8 @@
# 45. 把数组排成最小的数
[NowCoder](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github)
## 题目链接
[牛客网](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github)
## 题目描述
@ -8,7 +10,7 @@
## 解题思路
可以看成是一个排序问题在比较两个字符串 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
public String PrintMinNumber(int[] numbers) {
@ -25,10 +27,3 @@ public String PrintMinNumber(int[] numbers) {
return ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -29,10 +29,3 @@ public int numDecodings(String s) {
return dp[n];
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -33,10 +33,3 @@ public int getMost(int[][] values) {
return dp[n - 1];
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -27,10 +27,3 @@ public int longestSubStringWithoutDuplication(String str) {
return maxLen;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -28,10 +28,3 @@ public int GetUglyNumber_Solution(int N) {
return dp[N - 1];
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -23,7 +23,7 @@ Output:
P1 指向字符串原来的末尾位置P2 指向字符串现在的末尾位置P1 P2 从后向前遍历 P1 遍历到一个空格时就需要令 P2 指向的位置依次填充 02%注意是逆序的否则就填充上 P1 指向字符的值从后向前遍是为了在改变 P2 所指向的内容时不会影响到 P1 遍历原来字符串的内容
P2 遇到 P1 P2 <= P1或者遍历结束P1 < 0退出
P2 遇到 P1 P2 \<= P1或者遍历结束P1 \< 0退出
@ -50,10 +50,3 @@ public String replaceSpace(StringBuffer str) {
return str.toString();
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -51,10 +51,3 @@ public int FirstNotRepeatingChar2(String str) {
return -1;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -46,10 +46,3 @@ private void merge(int[] nums, int l, int m, int h) {
nums[k] = tmp[k];
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -22,10 +22,3 @@ public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
return l1;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -38,7 +38,7 @@ public int binarySearch(int[] nums, int K) {
}
```
但是在查找第一个位置时找到元素之后应该继续往前找也就是当 nums[m]>=k 在左区间继续查找左区间应该包含 m 位置
但是在查找第一个位置时找到元素之后应该继续往前找也就是当 nums[m]\>=k 在左区间继续查找左区间应该包含 m 位置
```java
private int binarySearch(int[] nums, int K) {
@ -71,10 +71,3 @@ nums = [2,2], k = 2
```
如果 h 的取值为 nums.length - 1那么在查找最后一个位置时binarySearch(nums, k + 1) - 1 = 1 - 1 = 0这是因为 binarySearch 只会返回 [0, nums.length - 1] 范围的值对于 binarySearch([2,2], 3) 我们希望返回 3 插入 nums 中的位置也就是数组最后一个位置再往后一个位置 nums.length所以我们需要将 h 取值为 nums.length从而使得 binarySearch 返回的区间更大能够覆盖 k 大于 nums 最后一个元素的情况
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -25,10 +25,3 @@ private void inOrder(TreeNode root, int k) {
inOrder(root.right, k);
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -15,10 +15,3 @@ public int TreeDepth(TreeNode root) {
return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right));
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -28,10 +28,3 @@ private int height(TreeNode root) {
return 1 + Math.max(left, right);
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -32,10 +32,3 @@ public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) {
}
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -13,8 +13,8 @@
使用双指针一个指针指向元素较小的值一个指针指向元素较大的值指向较小元素的指针从头向尾遍历指向较大元素的指针从尾向头遍历
- 如果两个指针指向元素的和 sum == target那么这两个元素即为所求
- 如果 sum > target移动较大的元素使 sum 变小一些
- 如果 sum < target移动较小的元素使 sum 变大一些
- 如果 sum \> target移动较大的元素使 sum 变小一些
- 如果 sum \< target移动较小的元素使 sum 变大一些
```java
public ArrayList<Integer> FindNumbersWithSum(int[] nums, int target) {
@ -31,10 +31,3 @@ public ArrayList<Integer> FindNumbersWithSum(int[] nums, int target) {
return new ArrayList<>();
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -41,10 +41,3 @@ public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
return ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -47,10 +47,3 @@ private void swap(char[] c, int i, int j) {
c[j] = t;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -41,10 +41,3 @@ private void swap(char[] chars, int i, int j) {
chars[j] = t;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -35,10 +35,3 @@ public ArrayList<Integer> maxInWindows(int[] num, int size) {
return ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -14,7 +14,7 @@
### 1. 使用递归
要逆序打印链表 1->2->33,2,1)可以先逆序打印链表 2->3(3,2)最后再打印第一个节点 1而链表 2->3 可以看成一个新的链表要逆序打印该链表可以继续使用求解函数也就是在求解函数中调用自己这就是递归函数
要逆序打印链表 1-\>2-\>33,2,1)可以先逆序打印链表 2-\>3(3,2)最后再打印第一个节点 1而链表 2-\>3 可以看成一个新的链表要逆序打印该链表可以继续使用求解函数也就是在求解函数中调用自己这就是递归函数
```java
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
@ -87,10 +87,3 @@ public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
return ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -72,10 +72,3 @@ public List<Map.Entry<Integer, Double>> dicesSum(int n) {
return ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -37,10 +37,3 @@ public boolean isContinuous(int[] nums) {
return cnt >= 0;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -21,10 +21,3 @@ public int LastRemaining_Solution(int n, int m) {
return (LastRemaining_Solution(n - 1, m) + m) % n;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -27,10 +27,3 @@ public int maxProfit(int[] prices) {
return maxProfit;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -14,7 +14,7 @@
条件与 && 具有短路原则即在第一个条件语句为 false 的情况下不会去执行第二个条件语句利用这一特性将递归的返回条件取非然后作为 && 的第一个条件语句递归的主体转换为第二个条件语句那么当递归的返回条件为 true 的情况下就不会执行递归的主体部分递归返回
本题的递归返回条件为 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
public int Sum_Solution(int n) {
@ -23,10 +23,3 @@ public int Sum_Solution(int n) {
return sum;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -10,19 +10,12 @@
## 解题思路
a ^ b 表示没有考虑进位的情况下两数的和(a & b) << 1 就是进位
a ^ b 表示没有考虑进位的情况下两数的和(a & b) \<\< 1 就是进位
递归会终止的原因是 (a & b) << 1 最右边会多一个 0那么继续递归进位最右边的 0 会慢慢增多最后进位会变为 0递归终止
递归会终止的原因是 (a & b) \<\< 1 最右边会多一个 0那么继续递归进位最右边的 0 会慢慢增多最后进位会变为 0递归终止
```java
public int Add(int a, int b) {
return b == 0 ? a : Add(a ^ b, (a & b) << 1);
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -24,10 +24,3 @@ public int[] multiply(int[] A) {
return B;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -37,10 +37,3 @@ public int StrToInt(String str) {
return isNegative ? -ret : ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -9,7 +9,7 @@
### 解题思路
在二叉查找树中两个节点 p, q 的公共祖先 root 满足 root.val >= p.val && root.val <= q.val
在二叉查找树中两个节点 p, q 的公共祖先 root 满足 root.val \>= p.val && root.val \<= q.val
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/047faac4-a368-4565-8331-2b66253080d3.jpg" width="250"/> </div><br>
@ -46,10 +46,3 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return left == null ? right : right == null ? left : root;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -39,10 +39,3 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL) {
return root;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -65,10 +65,3 @@ public TreeLinkNode GetNext(TreeLinkNode pNode) {
return null;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -33,10 +33,3 @@ public int pop() throws Exception {
return out.pop();
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,14 +1,10 @@
# Docker
<!-- GFM-TOC -->
* [解决的问题](#一解决的问题)
* [与虚拟机的比较](#二与虚拟机的比较)
* [优势](#三优势)
* [使用场景](#四使用场景)
* [镜像与容器](#五镜像与容器)
* [参考资料](#参考资料)
* [Docker](#docker)
<!-- GFM-TOC -->
# 解决的问题
## 解决的问题
由于不同的机器有不同的操作系统以及不同的库和组件在将一个应用部署到多台机器上需要进行大量的环境配置操作
@ -16,57 +12,57 @@ Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/011f3ef6-d824-4d43-8b2c-36dab8eaaa72-1.png" width="400px"/> </div><br>
# 与虚拟机的比较
## 与虚拟机的比较
虚拟机也是一种虚拟化技术它与 Docker 最大的区别在于它是通过模拟硬件并在硬件上安装操作系统来实现
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/be608a77-7b7f-4f8e-87cc-f2237270bf69.png" width="500"/> </div><br>
## 启动速度
### 启动速度
启动虚拟机需要先启动虚拟机的操作系统再启动应用这个过程非常慢
而启动 Docker 相当于启动宿主操作系统上的一个进程
## 占用资源
### 占用资源
虚拟机是一个完整的操作系统需要占用大量的磁盘内存和 CPU 资源一台机器只能开启几十个的虚拟机
Docker 只是一个进程只需要将应用以及相关的组件打包在运行时占用很少的资源一台机器可以开启成千上万个 Docker
# 优势
## 优势
除了启动速度快以及占用资源少之外Docker 具有以下优势
## 更容易迁移
### 更容易迁移
提供一致性的运行环境已经打包好的应用可以在不同的机器上进行迁移而不用担心环境变化导致无法运行
## 更容易维护
### 更容易维护
使用分层技术和镜像使得应用可以更容易复用重复的部分复用程度越高维护工作也越容易
## 更容易扩展
### 更容易扩展
可以使用基础镜像进一步扩展得到新的镜像并且官方和开源社区提供了大量的镜像通过扩展这些镜像可以非常容易得到我们想要的镜像
# 使用场景
## 使用场景
## 持续集成
### 持续集成
持续集成指的是频繁地将代码集成到主干上这样能够更快地发现错误
Docker 具有轻量级以及隔离性的特点在将代码集成到一个 Docker 中不会对其它 Docker 产生影响
## 提供可伸缩的云服务
### 提供可伸缩的云服务
根据应用的负载情况可以很容易地增加或者减少 Docker
## 搭建微服务架构
### 搭建微服务架构
Docker 轻量级的特点使得它很适合用于部署维护组合微服务
# 镜像与容器
## 镜像与容器
镜像是一种静态的结构可以看成面向对象里面的类而容器是镜像的一个实例
@ -76,7 +72,7 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/docker-filesystems-busyboxrw.png"/> </div><br>
# 参考资料
## 参考资料
- [DOCKER 101: INTRODUCTION TO DOCKER WEBINAR RECAP](https://blog.docker.com/2017/08/docker-101-introduction-docker-webinar-recap/)
- [Docker 入门教程](http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html)
@ -87,10 +83,3 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服
- [What is Docker](https://www.docker.com/what-docker)
- [持续集成是什么](http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html)
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,19 +1,21 @@
# Git
<!-- GFM-TOC -->
* [集中式与分布式](#集中式与分布式)
* [中心服务器](#中心服务器)
* [工作流](#工作流)
* [分支实现](#分支实现)
* [冲突](#冲突)
* [Fast forward](#fast-forward)
* [储藏Stashing](#储藏stashing)
* [SSH 传输设置](#ssh-传输设置)
* [.gitignore 文件](#gitignore-文件)
* [Git 命令一览](#git-命令一览)
* [参考资料](#参考资料)
* [Git](#git)
* [集中式与分布式](#集中式与分布式)
* [中心服务器](#中心服务器)
* [工作流](#工作流)
* [分支实现](#分支实现)
* [冲突](#冲突)
* [Fast forward](#fast-forward)
* [储藏Stashing](#储藏stashing)
* [SSH 传输设置](#ssh-传输设置)
* [.gitignore 文件](#gitignore-文件)
* [Git 命令一览](#git-命令一览)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 集中式与分布式
## 集中式与分布式
Git 属于分布式版本控制系统 SVN 属于集中式
@ -27,13 +29,13 @@ Git 属于分布式版本控制系统,而 SVN 属于集中式。
分布式版本控制新建分支合并分支操作速度非常快而集中式版本控制新建一个分支相当于复制一份完整代码
# 中心服务器
## 中心服务器
中心服务器用来交换每个用户的修改没有中心服务器也能工作但是中心服务器能够 24 小时保持开机状态这样就能更方便的交换修改
Github 就是一个中心服务器
# 工作流
## 工作流
新建一个仓库之后当前目录就成为了工作区工作区下有一个隐藏目录 .git它属于 Git 的版本库
@ -55,7 +57,7 @@ Git 的版本库有一个称为 Stage 的暂存区以及最后的 History 版本
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208200543923.png"/> </div><br>
# 分支实现
## 分支实现
使用指针将每个提交连接成一条时间线HEAD 指针指向当前分支指针
@ -73,13 +75,13 @@ Git 的版本库有一个称为 Stage 的暂存区以及最后的 History 版本
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208203010540.png"/> </div><br>
# 冲突
## 冲突
当两个分支都对同一个文件的同一行进行了修改在分支合并时就会产生冲突
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208203034705.png"/> </div><br>
Git 会使用 <<<<<<< ======= >>>>>>> 标记出不同分支的内容只需要把不同分支中冲突部分修改成一样就能解决冲突
Git 会使用 \<\<\<\<\<\<\< ======= \>\>\>\>\>\>\> 标记出不同分支的内容只需要把不同分支中冲突部分修改成一样就能解决冲突
```
<<<<<<< HEAD
@ -89,7 +91,7 @@ Creating a new branch is quick AND simple.
>>>>>>> feature1
```
# Fast forward
## Fast forward
"快进式合并"fast-farward merge会直接将 master 分支指向合并的分支这种模式下进行分支合并会丢失分支信息也就不能在分支历史上看出分支信息
@ -101,7 +103,7 @@ $ git merge --no-ff -m "merge with no-ff" dev
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208203639712.png"/> </div><br>
# 储藏Stashing
## 储藏Stashing
在一个分支上操作之后如果还没有将修改提交到分支上此时进行切换分支那么另一个分支上也能看到新的修改这是因为所有分支都共用一个工作区的缘故
@ -115,7 +117,7 @@ HEAD is now at 049d078 added the index file (To restore them type "git stash app
该功能可以用于 bug 分支的实现如果当前正在 dev 分支上进行开发但是此时 master 上有个 bug 需要修复但是 dev 分支上的开发还未完成不想立即提交在新建 bug 分支并切换到 bug 分支之前就需要使用 git stash dev 分支的未提交修改储藏起来
# SSH 传输设置
## SSH 传输设置
Git 仓库和 Github 中心仓库之间的传输是通过 SSH 加密
@ -127,7 +129,7 @@ $ ssh-keygen -t rsa -C "youremail@example.com"
然后把公钥 id_rsa.pub 的内容复制到 Github "Account settings" SSH Keys
# .gitignore 文件
## .gitignore 文件
忽略以下文件
@ -137,22 +139,15 @@ $ ssh-keygen -t rsa -C "youremail@example.com"
不需要全部自己编写可以到 [https://github.com/github/gitignore](https://github.com/github/gitignore) 中进行查询。
# Git 命令一览
## Git 命令一览
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7a29acce-f243-4914-9f00-f2988c528412.jpg" width=""> </div><br>
比较详细的地址http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf
# 参考资料
## 参考资料
- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)
- [图解 Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
- [廖雪峰 : Git 教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000)
- [Learn Git Branching](https://learngitbranching.js.org/)
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,64 +1,22 @@
# HTTP
<!-- GFM-TOC -->
* [ 基础概念](#-基础概念)
* [请求和响应报文](#请求和响应报文)
* [URL](#url)
* [HTTP 方法](#http-方法)
* [GET](#get)
* [HEAD](#head)
* [POST](#post)
* [PUT](#put)
* [PATCH](#patch)
* [DELETE](#delete)
* [OPTIONS](#options)
* [CONNECT](#connect)
* [TRACE](#trace)
* [HTTP 状态码](#三http-状态码)
* [1XX 信息](#1xx-信息)
* [2XX 成功](#2xx-成功)
* [3XX 重定向](#3xx-重定向)
* [4XX 客户端错误](#4xx-客户端错误)
* [5XX 服务器错误](#5xx-服务器错误)
* [HTTP 首部](#四http-首部)
* [通用首部字段](#通用首部字段)
* [请求首部字段](#请求首部字段)
* [响应首部字段](#响应首部字段)
* [实体首部字段](#实体首部字段)
* [具体应用](#五具体应用)
* [连接管理](#连接管理)
* [Cookie](#cookie)
* [缓存](#缓存)
* [内容协商](#内容协商)
* [内容编码](#内容编码)
* [范围请求](#范围请求)
* [分块传输编码](#分块传输编码)
* [多部分对象集合](#多部分对象集合)
* [虚拟主机](#虚拟主机)
* [通信数据转发](#通信数据转发)
* [HTTPS](#六https)
* [加密](#加密)
* [认证](#认证)
* [完整性保护](#完整性保护)
* [HTTPS 的缺点](#https-的缺点)
* [HTTP/2.0](#七http20)
* [HTTP/1.x 缺陷](#http1x-缺陷)
* [二进制分帧层](#二进制分帧层)
* [服务端推送](#服务端推送)
* [首部压缩](#首部压缩)
* [HTTP/1.1 新特性](#八http11-新特性)
* [GET POST 比较](#九get--post-比较)
* [作用](#作用)
* [参数](#参数)
* [安全](#安全)
* [幂等性](#幂等性)
* [可缓存](#可缓存)
* [XMLHttpRequest](#xmlhttprequest)
* [参考资料](#参考资料)
* [HTTP](#http)
* [ 基础概念](#-基础概念)
* [HTTP 方法](#二http-方法)
* [HTTP 状态码](#http-状态码)
* [HTTP 首部](#四http-首部)
* [具体应用](#五具体应用)
* [HTTPS](#六https)
* [HTTP/2.0](#七http20)
* [HTTP/1.1 新特性](#八http11-新特性)
* [GET POST 比较](#九get--post-比较)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 基础概念
## 基础概念
## 请求和响应报文
### 请求和响应报文
客户端发送一个请求报文给服务器服务器根据请求报文中的信息进行处理并将处理结果放入响应报文中返回给客户端
@ -120,7 +78,7 @@ X-Cache: HIT
```
## URL
### URL
http 使用 URL **U** niform **R**esource **L**ocator统一资源定位符来定位资源它可以认为是是 URI**U**niform **R**esource **I**dentifier统一资源标识符的一个子集URL URI 的基础上增加了定位能力URI 除了包含 URL 之外还包含 URNUniform Resource Name统一资源名称它知识用来定义一个资源的名称并不具备定位该资源的能力例如 urn:isbn:0451450523 用来定义一个书籍但是却没有表示怎么找到这本书
@ -131,35 +89,35 @@ http 使用 URL **U** niform **R**esource **L**ocator统一资源定位符
- [rfc26163.2.2 http URL](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.2)
- [What is the difference between a URI, a URL and a URN?](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn)
# HTTP 方法
## HTTP 方法
客户端发送的 **请求报文** 第一行为请求行包含了方法字段
## GET
### GET
> 获取资源
\> 获取资源
当前网络请求中绝大部分使用的是 GET 方法
## HEAD
### HEAD
> 获取报文首部
\> 获取报文首部
GET 方法类似但是不返回报文实体主体部分
主要用于确认 URL 的有效性以及资源更新的日期时间等
## POST
### POST
> 传输实体主体
\> 传输实体主体
POST 主要用来传输数据 GET 主要用来获取资源
更多 POST GET 的比较请见第九章
## PUT
### PUT
> 上传文件
\> 上传文件
由于自身不带验证机制任何人都可以上传文件因此存在安全性问题一般不使用该方法
@ -172,9 +130,9 @@ Content-length: 16
<p>New File</p>
```
## PATCH
### PATCH
> 对资源进行部分修改
\> 对资源进行部分修改
PUT 也可以用于修改资源但是只能完全替代原始资源PATCH 允许部分修改
@ -188,9 +146,9 @@ Content-Length: 100
[description of changes]
```
## DELETE
### DELETE
> 删除文件
\> 删除文件
PUT 功能相反并且同样不带验证机制
@ -198,17 +156,17 @@ Content-Length: 100
DELETE /file.html HTTP/1.1
```
## OPTIONS
### OPTIONS
> 查询支持的方法
\> 查询支持的方法
查询指定的 URL 能够支持的方法
会返回 `Allow: GET, POST, HEAD, OPTIONS` 这样的内容
## CONNECT
### CONNECT
> 要求在与代理服务器通信时建立隧道
\> 要求在与代理服务器通信时建立隧道
使用 SSLSecure Sockets Layer安全套接层 TLSTransport Layer Security传输层安全协议把通信内容加密后经网络隧道传输
@ -218,9 +176,9 @@ CONNECT www.example.com:443 HTTP/1.1
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/dc00f70e-c5c8-4d20-baf1-2d70014a97e3.jpg" width=""/> </div><br>
## TRACE
### TRACE
> 追踪路径
\> 追踪路径
服务器会将通信路径返回给客户端
@ -230,7 +188,7 @@ CONNECT www.example.com:443 HTTP/1.1
- [rfc26169 Method Definitions](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)
# HTTP 状态码
## HTTP 状态码
服务器返回的 **响应报文** 中第一行为状态行包含了状态码以及原因短语用来告知客户端请求的结果
@ -242,11 +200,11 @@ CONNECT www.example.com:443 HTTP/1.1
| 4XX | Client Error客户端错误状态码 | 服务器无法处理请求 |
| 5XX | Server Error服务器错误状态码 | 服务器处理请求出错 |
## 1XX 信息
### 1XX 信息
- **100 Continue** 表明到目前为止都很正常客户端可以继续发送请求或者忽略这个响应
## 2XX 成功
### 2XX 成功
- **200 OK**
@ -254,7 +212,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **206 Partial Content** 表示客户端进行了范围请求响应报文包含由 Content-Range 指定范围的实体内容
## 3XX 重定向
### 3XX 重定向
- **301 Moved Permanently** 永久性重定向
@ -268,7 +226,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **307 Temporary Redirect** 临时重定向 302 的含义类似但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法
## 4XX 客户端错误
### 4XX 客户端错误
- **400 Bad Request** 请求报文中存在语法错误
@ -278,19 +236,19 @@ CONNECT www.example.com:443 HTTP/1.1
- **404 Not Found**
## 5XX 服务器错误
### 5XX 服务器错误
- **500 Internal Server Error** 服务器正在执行请求时发生错误
- **503 Service Unavailable** 服务器暂时处于超负载或正在进行停机维护现在无法处理请求
# HTTP 首部
## HTTP 首部
4 种类型的首部字段通用首部字段请求首部字段响应首部字段和实体首部字段
各种首部字段及其含义如下不需要全记仅供查阅
## 通用首部字段
### 通用首部字段
| 首部字段名 | 说明 |
| :--: | :--: |
@ -304,7 +262,7 @@ CONNECT www.example.com:443 HTTP/1.1
| Via | 代理服务器的相关信息 |
| Warning | 错误通知 |
## 请求首部字段
### 请求首部字段
| 首部字段名 | 说明 |
| :--: | :--: |
@ -328,7 +286,7 @@ CONNECT www.example.com:443 HTTP/1.1
| TE | 传输编码的优先级 |
| User-Agent | HTTP 客户端程序的信息 |
## 响应首部字段
### 响应首部字段
| 首部字段名 | 说明 |
| :--: | :--: |
@ -342,7 +300,7 @@ CONNECT www.example.com:443 HTTP/1.1
| Vary | 代理服务器缓存的管理信息 |
| WWW-Authenticate | 服务器对客户端的认证信息 |
## 实体首部字段
### 实体首部字段
| 首部字段名 | 说明 |
| :--: | :--: |
@ -357,13 +315,13 @@ CONNECT www.example.com:443 HTTP/1.1
| Expires | 实体主体过期的日期时间 |
| Last-Modified | 资源的最后修改日期时间 |
# 具体应用
## 具体应用
## 连接管理
### 连接管理
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/HTTP1_x_Connections.png" width="800"/> </div><br>
### 1. 短连接与长连接
#### 1. 短连接与长连接
当浏览器访问一个包含多张图片的 HTML 页面时除了请求访问的 HTML 页面资源还会请求图片资源如果每进行一次 HTTP 通信就要新建一个 TCP 连接那么开销会很大
@ -372,13 +330,13 @@ CONNECT www.example.com:443 HTTP/1.1
- HTTP/1.1 开始默认是长连接的如果要断开连接需要由客户端或者服务器端提出断开使用 `Connection : close`
- HTTP/1.1 之前默认是短连接的如果需要使用长连接则使用 `Connection : Keep-Alive`
### 2. 流水线
#### 2. 流水线
默认情况下HTTP 请求是按顺序发出的下一个请求只有在当前请求收到响应之后才会被发出由于受到网络延迟和带宽的限制在下一个请求被发送到服务器之前可能需要等待很长时间
流水线是在同一条长连接上连续发出请求而不用等待响应返回这样可以减少延迟
## Cookie
### Cookie
HTTP 协议是无状态的主要是为了让 HTTP 协议尽可能简单使得它能够处理大量事务HTTP/1.1 引入 Cookie 来保存状态信息
@ -386,13 +344,13 @@ Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据
Cookie 曾一度用于客户端数据的存储因为当时并没有其它合适的存储办法而作为唯一的存储手段但现在随着现代浏览器开始支持各种各样的存储方式Cookie 渐渐被淘汰新的浏览器 API 已经允许开发者直接将数据存储到本地如使用 Web storage API本地存储和会话存储 IndexedDB
### 1. 用途
#### 1. 用途
- 会话状态管理如用户登录状态购物车游戏分数或其它需要记录的信息
- 个性化设置如用户自定义设置主题等
- 浏览器行为跟踪如跟踪分析用户行为等
### 2. 创建过程
#### 2. 创建过程
服务器发送的响应报文包含 Set-Cookie 首部字段客户端得到响应报文后把 Cookie 内容保存到浏览器中
@ -413,7 +371,7 @@ Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
```
### 3. 分类
#### 3. 分类
- 会话期 Cookie浏览器关闭之后它会被自动删除也就是说它仅在会话期内有效
- 持久性 Cookie指定过期时间Expires或有效期max-age之后就成为了持久性的 Cookie
@ -422,7 +380,7 @@ Cookie: yummy_cookie=choco; tasty_cookie=strawberry
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
```
### 4. 作用域
#### 4. 作用域
Domain 标识指定了哪些主机可以接受 Cookie如果不指定默认为当前文档的主机不包含子域名如果指定了 Domain则一般包含子域名例如如果设置 Domain=mozilla.org Cookie 也包含在子域名中 developer.mozilla.org
@ -432,7 +390,7 @@ Path 标识指定了主机下的哪些路径可以接受 Cookie该 URL 路径
- /docs/Web/
- /docs/Web/HTTP
### 5. JavaScript
#### 5. JavaScript
浏览器通过 `document.cookie` 属性可创建新的 Cookie也可通过该属性访问非 HttpOnly 标记的 Cookie
@ -442,7 +400,7 @@ document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
```
### 6. HttpOnly
#### 6. HttpOnly
标记为 HttpOnly Cookie 不能被 JavaScript 脚本调用跨站脚本攻击 (XSS) 常常使用 JavaScript `document.cookie` API 窃取用户的 Cookie 信息因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击
@ -450,11 +408,11 @@ console.log(document.cookie);
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
```
### 7. Secure
#### 7. Secure
标记为 Secure Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端但即便设置了 Secure 标记敏感信息也不应该通过 Cookie 传输因为 Cookie 有其固有的不安全性Secure 标记也无法提供确实的安全保障
### 8. Session
#### 8. Session
除了可以将用户信息通过 Cookie 存储在用户浏览器中也可以利用 Session 存储在服务器端存储在服务器端的信息更加安全
@ -469,29 +427,29 @@ Session 可以存储在服务器上的文件、数据库或者内存中。也可
应该注意 Session ID 的安全性问题不能让它被恶意攻击者轻易获取那么就不能产生一个容易被猜到的 Session ID 此外还需要经常重新生成 Session ID在对安全性要求极高的场景下例如转账等操作除了使用 Session 管理用户状态之外还需要对用户进行重新验证比如重新输入密码或者使用短信验证码等方式
### 9. 浏览器禁用 Cookie
#### 9. 浏览器禁用 Cookie
此时无法使用 Cookie 来保存用户信息只能使用 Session除此之外不能再将 Session ID 存放到 Cookie 而是使用 URL 重写技术 Session ID 作为 URL 的参数进行传递
### 10. Cookie Session 选择
#### 10. Cookie Session 选择
- Cookie 只能存储 ASCII 码字符串 Session 则可以存储任何类型的数据因此在考虑数据复杂性时首选 Session
- Cookie 存储在浏览器中容易被恶意查看如果非要将一些隐私数据存在 Cookie 可以将 Cookie 值进行加密然后在服务器进行解密
- 对于大型网站如果用户所有的信息都存储在 Session 那么开销是非常大的因此不建议将所有的用户信息都存储到 Session
## 缓存
### 缓存
### 1. 优点
#### 1. 优点
- 缓解服务器压力
- 降低客户端获取资源的延迟缓存通常位于内存中读取缓存的速度更快并且缓存服务器在地理位置上也有可能比源服务器来得近例如浏览器缓存
### 2. 实现方法
#### 2. 实现方法
- 让代理服务器进行缓存
- 让客户端浏览器进行缓存
### 3. Cache-Control
#### 3. Cache-Control
HTTP/1.1 通过 Cache-Control 首部字段来控制缓存
@ -544,7 +502,7 @@ Expires: Wed, 04 Jul 2012 08:26:05 GMT
- HTTP/1.1 会优先处理 max-age 指令
- HTTP/1.0 max-age 指令会被忽略掉
### 4. 缓存验证
#### 4. 缓存验证
需要先了解 ETag 首部字段的含义它是资源的唯一标识URL 不能唯一表示资源例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。
@ -568,11 +526,11 @@ Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
```
## 内容协商
### 内容协商
通过内容协商返回最合适的内容例如根据浏览器的默认语言选择返回中文界面还是英文界面
### 1. 类型
#### 1. 类型
**1.1 服务端驱动型**
@ -588,7 +546,7 @@ If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
服务器返回 300 Multiple Choices 或者 406 Not Acceptable客户端从中选出最合适的那个资源
### 2. Vary
#### 2. Vary
```html
Vary: Accept-Language
@ -598,7 +556,7 @@ Vary: Accept-Language
例如一个客户端发送了一个包含 Accept-Language 首部字段的请求之后源服务器返回的响应包含 `Vary: Accept-Language` 内容缓存服务器对这个响应进行缓存之后在客户端下一次访问同一个 URL 资源并且 Accept-Language 与缓存中的对应的值相同时才会返回该缓存
## 内容编码
### 内容编码
内容编码将实体主体进行压缩从而减少传输的数据量
@ -606,11 +564,11 @@ Vary: Accept-Language
浏览器发送 Accept-Encoding 首部其中包含有它所支持的压缩算法以及各自的优先级服务器则从中选择一种使用该算法对响应的消息主体进行压缩并且发送 Content-Encoding 首部来告知浏览器它选择了哪一种算法由于该内容协商过程是基于编码类型来选择资源的展现形式的响应报文的 Vary 首部字段至少要包含 Content-Encoding
## 范围请求
### 范围请求
如果网络出现中断服务器只发送了一部分数据范围请求可以使得客户端只请求服务器未发送的那部分数据从而避免服务器重新发送所有数据
### 1. Range
#### 1. Range
在请求报文中添加 Range 首部字段指定请求的范围
@ -630,7 +588,7 @@ Content-Length: 1024
(binary content)
```
### 2. Accept-Ranges
#### 2. Accept-Ranges
响应首部字段 Accept-Ranges 用于告知客户端是否能处理范围请求可以处理使用 bytes否则使用 none
@ -638,17 +596,17 @@ Content-Length: 1024
Accept-Ranges: bytes
```
### 3. 响应状态码
#### 3. 响应状态码
- 在请求成功的情况下服务器会返回 206 Partial Content 状态码
- 在请求的范围越界的情况下服务器会返回 416 Requested Range Not Satisfiable 状态码
- 在不支持范围请求的情况下服务器会返回 200 OK 状态码
## 分块传输编码
### 分块传输编码
Chunked Transfer Encoding可以把数据分割成多块让浏览器逐步显示页面
## 多部分对象集合
### 多部分对象集合
一份报文主体内可含有多种类型的实体同时发送每个部分之间用 boundary 字段定义的分隔符进行分隔每个部分都可以有首部字段
@ -669,13 +627,13 @@ Content-Type: text/plain
--AaB03x--
```
## 虚拟主机
### 虚拟主机
HTTP/1.1 使用虚拟主机技术使得一台服务器拥有多个域名并且在逻辑上可以看成多个服务器
## 通信数据转发
### 通信数据转发
### 1. 代理
#### 1. 代理
代理服务器接受客户端的请求并且转发给其它服务器
@ -696,15 +654,15 @@ HTTP/1.1 使用虚拟主机技术,使得一台服务器拥有多个域名,
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2d09a847-b854-439c-9198-b29c65810944.png" width=""/> </div><br>
### 2. 网关
#### 2. 网关
与代理服务器不同的是网关服务器会将 HTTP 转化为其它协议进行通信从而请求其它非 HTTP 服务器的服务
### 3. 隧道
#### 3. 隧道
使用 SSL 等加密手段在客户端和服务器之间建立一条安全的通信线路
# HTTPS
## HTTPS
HTTP 有以下安全性问题
@ -718,9 +676,9 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSLSecure Sockets Layer
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ssl-offloading.jpg" width="700"/> </div><br>
## 加密
### 加密
### 1. 对称密钥加密
#### 1. 对称密钥加密
对称密钥加密Symmetric-Key Encryption加密和解密使用同一密钥
@ -729,7 +687,7 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSLSecure Sockets Layer
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7fffa4b8-b36d-471f-ad0c-a88ee763bb76.png" width="600"/> </div><br>
### 2.非对称密钥加密
#### 2.非对称密钥加密
非对称密钥加密又称公开密钥加密Public-Key Encryption加密和解密使用不同的密钥
@ -742,7 +700,7 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSLSecure Sockets Layer
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/39ccb299-ee99-4dd1-b8b4-2f9ec9495cb4.png" width="600"/> </div><br>
### 3. HTTPS 采用的加密方式
#### 3. HTTPS 采用的加密方式
上面提到对称密钥加密方式的传输效率更高但是无法安全地将密钥 Secret Key 传输给通信方而非对称密钥加密方式可以保证传输的安全性因此我们可以利用非对称密钥加密方式将 Secret Key 传输给通信方HTTPS 采用混合的加密机制正是利用了上面提到的方案
@ -751,7 +709,7 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSLSecure Sockets Layer
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/How-HTTPS-Works.png" width="600"/> </div><br>
## 认证
### 认证
通过使用 **证书** 来对通信方进行认证
@ -763,7 +721,7 @@ HTTPS 并不是新协议,而是让 HTTP 先和 SSLSecure Sockets Layer
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2017-06-11-ca.png" width=""/> </div><br>
## 完整性保护
### 完整性保护
SSL 提供报文摘要功能来进行完整性保护
@ -771,14 +729,14 @@ HTTP 也提供了 MD5 报文摘要功能,但不是安全的。例如报文内
HTTPS 的报文摘要功能之所以安全是因为它结合了加密和认证这两个操作试想一下加密之后的报文遭到篡改之后也很难重新计算报文摘要因为无法轻易获取明文
## HTTPS 的缺点
### HTTPS 的缺点
- 因为需要进行加密解密等过程因此速度会更慢
- 需要支付证书授权的高额费用
# HTTP/2.0
## HTTP/2.0
## HTTP/1.x 缺陷
### HTTP/1.x 缺陷
HTTP/1.x 实现简单是以牺牲性能为代价的
@ -786,7 +744,7 @@ HTTP/1.x 实现简单是以牺牲性能为代价的:
- 不会压缩请求和响应首部从而导致不必要的网络流量
- 不支持有效的资源优先级致使底层 TCP 连接的利用率低下
## 二进制分帧层
### 二进制分帧层
HTTP/2.0 将报文分成 HEADERS 帧和 DATA 它们都是二进制格式的
@ -800,13 +758,13 @@ HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/af198da1-2480-4043-b07f-a3b91a88b815.png" width="600"/> </div><br>
## 服务端推送
### 服务端推送
HTTP/2.0 在客户端请求一个资源时会把相关的资源一起发送给客户端客户端就不需要再次发起请求了例如客户端请求 page.html 页面服务端就把 script.js style.css 等与之相关的资源一起发给客户端
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e3f1657c-80fc-4dfa-9643-bf51abd201c6.png" width="800"/> </div><br>
## 首部压缩
### 首部压缩
HTTP/1.1 的首部带有大量信息而且每次都要重复发送
@ -816,7 +774,7 @@ HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/_u4E0B_u8F7D.png" width="600"/> </div><br>
# HTTP/1.1 新特性
## HTTP/1.1 新特性
详细内容请见上文
@ -828,13 +786,13 @@ HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见
- 支持分块传输编码
- 新增缓存处理指令 max-age
# GET POST 比较
## GET POST 比较
## 作用
### 作用
GET 用于获取资源 POST 用于传输实体主体
## 参数
### 参数
GET POST 的请求都能使用额外的参数但是 GET 的参数是以查询字符串出现在 URL POST 的参数存储在实体主体中不能因为 POST 参数存储在实体主体中就认为它的安全性更高因为照样可以通过一些抓包工具Fiddler查看
@ -850,7 +808,7 @@ Host: w3schools.com
name1=value1&name2=value2
```
## 安全
### 安全
安全的 HTTP 方法不会改变服务器状态也就是说它只是可读的
@ -860,7 +818,7 @@ GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实
不安全的方法除了 POST 之外还有 PUTDELETE
## 幂等性
### 幂等性
幂等的 HTTP 方法同样的请求被执行一次与连续执行多次的效果是一样的服务器的状态也是一样的换句话说就是幂等方法不应该具有副作用统计用途除外
@ -893,7 +851,7 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404 as it just got deleted
DELETE /idX/delete HTTP/1.1 -> Returns 404
```
## 可缓存
### 可缓存
如果要对响应进行缓存需要满足以下条件
@ -901,16 +859,16 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
- 响应报文的状态码是可缓存的包括200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501
- 响应报文的 Cache-Control 首部字段没有指定不进行缓存
## XMLHttpRequest
### XMLHttpRequest
为了阐述 POST GET 的另一个区别需要先了解 XMLHttpRequest
> XMLHttpRequest 是一个 API它为客户端提供了在客户端和服务器之间传输数据的功能它提供了一个通过 URL 来获取数据的简单方式并且不会使整个页面刷新这使得网页只更新一部分页面而不会打扰到用户XMLHttpRequest AJAX 中被大量使用
\> XMLHttpRequest 是一个 API它为客户端提供了在客户端和服务器之间传输数据的功能它提供了一个通过 URL 来获取数据的简单方式并且不会使整个页面刷新这使得网页只更新一部分页面而不会打扰到用户XMLHttpRequest AJAX 中被大量使用
- 在使用 XMLHttpRequest POST 方法时浏览器会先发送 Header 再发送 Data但并不是所有浏览器会这么做例如火狐就不会
- GET 方法 Header Data 会一起发送
# 参考资料
## 参考资料
- 上野宣. 图解 HTTP[M]. 人民邮电出版社, 2014.
- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)
@ -939,10 +897,3 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
- [Symmetric vs. Asymmetric Encryption What are differences?](https://www.ssl2buy.com/wiki/symmetric-vs-asymmetric-encryption-what-are-differences)
- [Web 性能优化与 HTTP/2](https://www.kancloud.cn/digest/web-performance-http2)
- [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn)
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,37 +1,18 @@
# Java IO
<!-- GFM-TOC -->
* [概览](#一概览)
* [磁盘操作](#二磁盘操作)
* [字节操作](#三字节操作)
* [实现文件复制](#实现文件复制)
* [装饰者模式](#装饰者模式)
* [字符操作](#四字符操作)
* [编码与解码](#编码与解码)
* [String 的编码方式](#string-的编码方式)
* [Reader Writer](#reader--writer)
* [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容)
* [对象操作](#五对象操作)
* [序列化](#序列化)
* [Serializable](#serializable)
* [transient](#transient)
* [网络操作](#六网络操作)
* [InetAddress](#inetaddress)
* [URL](#url)
* [Sockets](#sockets)
* [Datagram](#datagram)
* [NIO](#七nio)
* [流与块](#流与块)
* [通道与缓冲区](#通道与缓冲区)
* [缓冲区状态变量](#缓冲区状态变量)
* [文件 NIO 实例](#文件-nio-实例)
* [选择器](#选择器)
* [套接字 NIO 实例](#套接字-nio-实例)
* [内存映射文件](#内存映射文件)
* [对比](#对比)
* [参考资料](#八参考资料)
* [Java IO](#java-io)
* [概览](#一概览)
* [磁盘操作](#二磁盘操作)
* [字节操作](#三字节操作)
* [字符操作](#四字符操作)
* [对象操作](#五对象操作)
* [网络操作](#六网络操作)
* [NIO](#七nio)
* [参考资料](#八参考资料)
<!-- GFM-TOC -->
# 概览
## 概览
Java I/O 大概可以分成以下几类
@ -42,7 +23,7 @@ Java 的 I/O 大概可以分成以下几类:
- 网络操作Socket
- 新的输入/输出NIO
# 磁盘操作
## 磁盘操作
File 类可以用于表示文件和目录的信息但是它不表示文件的内容
@ -65,9 +46,9 @@ public static void listAllFiles(File dir) {
Java7 开始可以使用 Paths Files 代替 File
# 字节操作
## 字节操作
## 实现文件复制
### 实现文件复制
```java
public static void copyFile(String src, String dist) throws IOException {
@ -89,7 +70,7 @@ public static void copyFile(String src, String dist) throws IOException {
}
```
## 装饰者模式
### 装饰者模式
Java I/O 使用了装饰者模式来实现 InputStream 为例
@ -108,9 +89,9 @@ BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStrea
DataInputStream 装饰者提供了对更多数据类型进行输入的操作比如 intdouble 等基本类型
# 字符操作
## 字符操作
## 编码与解码
### 编码与解码
编码就是把字符转换为字节而解码是把字节重新组合成字符
@ -124,7 +105,7 @@ UTF-16be 中的 be 指的是 Big Endian也就是大端。相应地也有 UTF-
Java 的内存编码使用双字节编码 UTF-16be这不是指 Java 只支持这一种编码方式而是说 char 这种类型使用 UTF-16be 进行编码char 类型占 16 也就是两个字节Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储
## String 的编码方式
### String 的编码方式
String 可以看成一个字符序列可以指定一个编码方式将它编码为字节序列也可以指定一个编码方式将一个字节序列解码为 String
@ -141,14 +122,14 @@ System.out.println(str2);
byte[] bytes = str1.getBytes();
```
## Reader Writer
### Reader Writer
不管是磁盘还是网络传输最小的存储单元都是字节而不是字符但是在程序中操作的通常是字符形式的数据因此需要提供对字符进行操作的方法
- InputStreamReader 实现从字节流解码成字符流
- OutputStreamWriter 实现字符流编码成为字节流
## 实现逐行输出文本文件的内容
### 实现逐行输出文本文件的内容
```java
public static void readFileContent(String filePath) throws IOException {
@ -168,9 +149,9 @@ public static void readFileContent(String filePath) throws IOException {
}
```
# 对象操作
## 对象操作
## 序列化
### 序列化
序列化就是将一个对象转换成字节序列方便存储和传输
@ -179,7 +160,7 @@ public static void readFileContent(String filePath) throws IOException {
不会对静态变量进行序列化因为序列化只是保存对象的状态静态变量属于类的状态
## Serializable
### Serializable
序列化的类需要实现 Serializable 接口它只是一个标准没有任何方法需要实现但是如果不去实现它的话而进行序列化会抛出异常
@ -216,7 +197,7 @@ private static class A implements Serializable {
}
```
## transient
### transient
transient 关键字可以使一些属性不会被序列化
@ -226,7 +207,7 @@ ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因
private transient Object[] elementData;
```
# 网络操作
## 网络操作
Java 中的网络支持
@ -235,7 +216,7 @@ Java 中的网络支持:
- Sockets使用 TCP 协议实现网络通信
- Datagram使用 UDP 协议实现网络通信
## InetAddress
### InetAddress
没有公有的构造函数只能通过静态方法来创建实例
@ -244,7 +225,7 @@ InetAddress.getByName(String host);
InetAddress.getByAddress(byte[] address);
```
## URL
### URL
可以直接从 URL 中读取字节流数据
@ -271,7 +252,7 @@ public static void main(String[] args) throws IOException {
}
```
## Sockets
### Sockets
- ServerSocket服务器端类
- Socket客户端类
@ -279,16 +260,16 @@ public static void main(String[] args) throws IOException {
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1e6affc4-18e5-4596-96ef-fb84c63bf88a.png" width="550px"> </div><br>
## Datagram
### Datagram
- DatagramSocket通信类
- DatagramPacket数据包类
# NIO
## NIO
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的弥补了原来的 I/O 的不足提供了高速的面向块的 I/O
## 流与块
### 流与块
I/O NIO 最重要的区别是数据打包和传输的方式I/O 以流的方式处理数据 NIO 以块的方式处理数据
@ -298,9 +279,9 @@ I/O 与 NIO 最重要的区别是数据打包和传输的方式I/O 以流的
I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重新实现了所以现在它可以利用 NIO 的一些特性例如java.io.\* 包中的一些类包含以块的形式读写数据的方法这使得即使在面向流的系统中处理速度也会更快
## 通道与缓冲区
### 通道与缓冲区
### 1. 通道
#### 1. 通道
通道 Channel 是对原 I/O 包中的流的模拟可以通过它读取和写入数据
@ -313,7 +294,7 @@ I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重
- SocketChannel通过 TCP 读写网络中数据
- ServerSocketChannel可以监听新进来的 TCP 连接对每一个新进来的连接都会创建一个 SocketChannel
### 2. 缓冲区
#### 2. 缓冲区
发送给一个通道的所有数据都必须首先放到缓冲区中同样地从通道中读取的任何数据都要先读到缓冲区中也就是说不会直接对通道进行读写数据而是要先经过缓冲区
@ -329,7 +310,7 @@ I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重
- FloatBuffer
- DoubleBuffer
## 缓冲区状态变量
### 缓冲区状态变量
- capacity最大容量
- position当前已经读写的字节数
@ -357,7 +338,7 @@ I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/67bf5487-c45d-49b6-b9c0-a058d8c68902.png"/> </div><br>
## 文件 NIO 实例
### 文件 NIO 实例
以下展示了使用 NIO 快速复制文件的实例
@ -401,7 +382,7 @@ public static void fastCopy(String src, String dist) throws IOException {
}
```
## 选择器
### 选择器
NIO 常常被叫做非阻塞 IO主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用
@ -415,13 +396,13 @@ NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/093f9e57-429c-413a-83ee-c689ba596cef.png" width="350px"> </div><br>
### 1. 创建选择器
#### 1. 创建选择器
```java
Selector selector = Selector.open();
```
### 2. 将通道注册到选择器上
#### 2. 将通道注册到选择器上
```java
ServerSocketChannel ssChannel = ServerSocketChannel.open();
@ -453,7 +434,7 @@ public static final int OP_ACCEPT = 1 << 4;
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
```
### 3. 监听事件
#### 3. 监听事件
```java
int num = selector.select();
@ -461,7 +442,7 @@ int num = selector.select();
使用 select() 来监听到达的事件它会一直阻塞直到有至少一个事件到达
### 4. 获取到达的事件
#### 4. 获取到达的事件
```java
Set<SelectionKey> keys = selector.selectedKeys();
@ -477,7 +458,7 @@ while (keyIterator.hasNext()) {
}
```
### 5. 事件循环
#### 5. 事件循环
因为一次 select() 调用不能处理完所有的事件并且服务器端有可能需要一直监听事件因此服务器端处理事件的代码一般会放在一个死循环内
@ -498,7 +479,7 @@ while (true) {
}
```
## 套接字 NIO 实例
### 套接字 NIO 实例
```java
public class NIOServer {
@ -587,7 +568,7 @@ public class NIOClient {
}
```
## 内存映射文件
### 内存映射文件
内存映射文件 I/O 是一种读和写文件数据的方法它可以比常规的基于流或者基于通道的 I/O 快得多
@ -599,14 +580,14 @@ public class NIOClient {
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
```
## 对比
### 对比
NIO 与普通 I/O 的区别主要有以下两点
- NIO 是非阻塞的
- NIO 面向块I/O 面向流
# 参考资料
## 参考资料
- Eckel B, 埃克尔, 昊鹏, . Java 编程思想 [M]. 机械工业出版社, 2002.
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
@ -618,10 +599,3 @@ NIO 与普通 I/O 的区别主要有以下两点:
- [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499)
- [Decorator Design Pattern](http://stg-tud.github.io/sedc/Lecture/ws13-14/5.3-Decorator.html#mode=document)
- [Socket Multicast](http://labojava.blogspot.com/2012/12/socket-multicast.html)
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,48 +1,24 @@
# Java 基础
<!-- GFM-TOC -->
* [数据类型](#一数据类型)
* [基本类型](#基本类型)
* [包装类型](#包装类型)
* [缓存池](#缓存池)
* [String](#二string)
* [概览](#概览)
* [不可变的好处](#不可变的好处)
* [String, StringBuffer and StringBuilder ](#string-stringbuffer-and-stringbuilder )
* [String Pool](#string-pool)
* [new String("abc")](#new-stringabc)
* [运算](#三运算)
* [参数传递](#参数传递)
* [float double](#float--double)
* [隐式类型转换](#隐式类型转换)
* [switch](#switch)
* [关键字](#四关键字)
* [final](#final)
* [static](#static)
* [Object 通用方法](#五object-通用方法)
* [概览](#概览)
* [equals()](#equals)
* [hashCode()](#hashcode)
* [toString()](#tostring)
* [clone()](#clone)
* [继承](#六继承)
* [访问权限](#访问权限)
* [抽象类与接口](#抽象类与接口)
* [super](#super)
* [重写与重载](#重写与重载)
* [反射](#七反射)
* [异常](#八异常)
* [泛型](#九泛型)
* [注解](#十注解)
* [十一特性](#十一特性)
* [Java 各版本的新特性](#java-各版本的新特性)
* [Java C++ 的区别](#java--c-的区别)
* [JRE or JDK](#jre-or-jdk)
* [参考资料](#参考资料)
* [Java 基础](#java-基础)
* [数据类型](#一数据类型)
* [String](#二string)
* [运算](#三运算)
* [关键字](#四关键字)
* [Object 通用方法](#五object-通用方法)
* [继承](#六继承)
* [反射](#七反射)
* [异常](#八异常)
* [泛型](#九泛型)
* [注解](#十注解)
* [十一特性](#十一特性)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 数据类型
## 数据类型
## 基本类型
### 基本类型
- byte/8
- char/16
@ -58,7 +34,7 @@ boolean 只有两个值true、false可以使用 1 bit 来存储,但是
- [Primitive Data Types](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
- [The Java® Virtual Machine Specification](https://docs.oracle.com/javase/specs/jvms/se8/jvms8.pdf)
## 包装类型
### 包装类型
基本类型都有对应的包装类型基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成
@ -69,7 +45,7 @@ int y = x; // 拆箱 调用了 X.intValue()
- [Autoboxing and Unboxing](https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html)
## 缓存池
### 缓存池
new Integer(123) Integer.valueOf(123) 的区别在于
@ -152,9 +128,9 @@ System.out.println(m == n); // true
[StackOverflow : Differences between new Integer(123), Integer.valueOf(123) and just 123
](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123)
# String
## String
## 概览
### 概览
String 被声明为 final因此它不可被继承(Integer 等包装类也不能被继承
@ -183,7 +159,7 @@ public final class String
value 数组被声明为 final这意味着 value 数组初始化之后就不能再引用其它数组并且 String 内部没有改变 value 数组的方法因此可以保证 String 不可变
## 不可变的好处
### 不可变的好处
**1. 可以缓存 hash **
@ -205,7 +181,7 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地
[Program Creek : Why String is immutable in Java?](https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/)
## String, StringBuffer and StringBuilder
### String, StringBuffer and StringBuilder
**1. 可变性**
@ -220,7 +196,7 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地
[StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder)
## String Pool
### String Pool
字符串常量池String Pool保存着所有字符串字面量literal strings这些字面量在编译时期就确定不仅如此还可以使用 String intern() 方法在运行过程将字符串添加到 String Pool
@ -250,7 +226,7 @@ System.out.println(s5 == s6); // true
- [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning)
- [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html)
## new String("abc")
### new String("abc")
使用这种方式一共会创建两个字符串对象前提是 String Pool 中还没有 "abc" 字符串对象
@ -304,9 +280,9 @@ public String(String original) {
}
```
# 运算
## 运算
## 参数传递
### 参数传递
Java 的参数是以值传递的形式传入方法中而不是引用传递
@ -374,7 +350,7 @@ public class PassByValueExample {
[StackOverflow: Is Java pass-by-reference or pass-by-value?](https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value)
## float double
### float double
Java 不能隐式执行向下转型因为这会使得精度降低
@ -390,7 +366,7 @@ Java 不能隐式执行向下转型,因为这会使得精度降低。
float f = 1.1f;
```
## 隐式类型转换
### 隐式类型转换
因为字面量 1 int 类型它比 short 类型精度要高因此不能隐式地将 int 类型向下转型为 short 类型
@ -414,7 +390,7 @@ s1 = (short) (s1 + 1);
[StackOverflow : Why don't Java's +=, -=, *=, /= compound assignment operators require casting?](https://stackoverflow.com/questions/8710619/why-dont-javas-compound-assignment-operators-require-casting)
## switch
### switch
Java 7 开始可以在 switch 条件判断语句中使用 String 对象
@ -447,9 +423,9 @@ switch 不支持 long是因为 switch 的设计初衷是对那些只有少数
[StackOverflow : Why can't your switch statement data type be long, Java?](https://stackoverflow.com/questions/2676210/why-cant-your-switch-statement-data-type-be-long-java)
# 关键字
## 关键字
## final
### final
**1. 数据**
@ -475,7 +451,7 @@ private 方法隐式地被指定为 final如果在子类中定义的方法和
声明类不允许被继承
## static
### static
**1. 静态变量**
@ -619,9 +595,9 @@ public InitialOrderTest() {
- 子类实例变量普通语句块
- 子类构造函数
# Object 通用方法
## Object 通用方法
## 概览
### 概览
```java
@ -648,7 +624,7 @@ public final void wait(long timeout, int nanos) throws InterruptedException
public final void wait() throws InterruptedException
```
## equals()
### equals()
**1. 等价关系**
@ -735,7 +711,7 @@ public class EqualExample {
}
```
## hashCode()
### hashCode()
hashCode() 返回哈希值 equals() 是用来判断两个对象是否等价等价的两个对象散列值一定相同但是散列值相同的两个对象不一定等价这是因为计算哈希值具有随机性两个值不同的对象可能计算出相同的哈希值
@ -757,7 +733,7 @@ System.out.println(set.size()); // 2
理想的哈希函数应当具有均匀性即不相等的对象应当均匀分布到所有可能的哈希值上这就要求了哈希函数要把所有域的值都考虑进来可以将每个域都当成 R 进制的某一位然后组成一个 R 进制的整数
R 一般取 31因为它是一个奇素数如果是偶数的话当出现乘法溢出信息就会丢失因为与 2 相乘相当于向左移一位最左边的位丢失并且一个数与 31 相乘可以转换成移位和减法`31*x == (x<<5)-x`编译器会自动进行这个优化
R 一般取 31因为它是一个奇素数如果是偶数的话当出现乘法溢出信息就会丢失因为与 2 相乘相当于向左移一位最左边的位丢失并且一个数与 31 相乘可以转换成移位和减法`31*x == (x\<\<5)-x`编译器会自动进行这个优化
```java
@Override
@ -770,7 +746,7 @@ public int hashCode() {
}
```
## toString()
### toString()
默认返回 ToStringExample@4554617c 这种形式其中 @ 后面的数值为散列码的无符号十六进制表示
@ -794,7 +770,7 @@ System.out.println(example.toString());
ToStringExample@4554617c
```
## clone()
### clone()
**1. cloneable**
@ -986,9 +962,9 @@ e1.set(2, 222);
System.out.println(e2.get(2)); // 2
```
# 继承
## 继承
## 访问权限
### 访问权限
Java 中有三个访问权限修饰符privateprotected 以及 public如果不加访问修饰符表示包级可见
@ -1049,7 +1025,7 @@ public class AccessWithInnerClassExample {
}
```
## 抽象类与接口
### 抽象类与接口
**1. 抽象类**
@ -1158,7 +1134,7 @@ System.out.println(InterfaceExample.x);
- [Java 9 Private Methods in Interfaces](https://www.journaldev.com/12850/java-9-private-methods-interfaces)
## super
### super
- 访问父类的构造函数可以使用 super() 函数访问父类的构造函数从而委托父类完成一些初始化的工作应该注意到子类一定会调用父类的构造函数来完成初始化工作一般是调用父类的默认构造函数如果子类需要调用父类其它构造函数那么就可以使用 super() 函数
- 访问父类的成员如果子类重写了父类的某个方法可以通过使用 super 关键字来引用父类的方法实现
@ -1210,7 +1186,7 @@ SuperExtendExample.func()
[Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html)
## 重写与重载
### 重写与重载
**1. 重写Override**
@ -1227,7 +1203,7 @@ SuperExtendExample.func()
下面的示例中SubClass SuperClass 的子类SubClass 重写了 SuperClass func() 方法其中
- 子类方法访问权限为 public大于父类的 protected
- 子类的返回类型为 ArrayList<Integer>是父类返回类型 List<Integer> 的子类
- 子类的返回类型为 ArrayList\<Integer\>是父类返回类型 List\<Integer\> 的子类
- 子类抛出的异常类型为 Exception是父类抛出异常 Throwable 的子类
- 子类重写方法使用 @Override 注解从而让编译器自动检查是否满足限制条件
@ -1342,7 +1318,7 @@ public static void main(String[] args) {
}
```
# 反射
## 反射
每个类都有一个 **Class** 对象包含了与类有关的信息当编译一个新类时会产生一个同名的 .class 文件该文件内容保存着 Class 对象
@ -1376,7 +1352,7 @@ Class 和 java.lang.reflect 一起对反射提供了支持java.lang.reflect
- [Trail: The Reflection API](https://docs.oracle.com/javase/tutorial/reflect/index.html)
- [深入解析 Java 反射1- 基础](http://www.sczyh30.com/posts/Java/java-reflection-1/)
# 异常
## 异常
Throwable 可以用来表示任何可以作为异常抛出的类分为两种 **Error** **Exception**其中 Error 用来表示 JVM 无法处理的错误Exception 分为两种
@ -1388,7 +1364,7 @@ Throwable 可以用来表示任何可以作为异常抛出的类,分为两种
- [Java 入门之异常处理](https://www.cnblogs.com/Blue-Keroro/p/8875898.html)
- [Java Exception Interview Questions and Answers](https://www.journaldev.com/2167/java-exception-interview-questions-and-answersl)
# 泛型
## 泛型
```java
public class Box<T> {
@ -1402,15 +1378,15 @@ public class Box<T> {
- [Java 泛型详解](http://www.importnew.com/24029.html)
- [10 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693)
# 注解
## 注解
Java 注解是附加在代码中的一些元信息用于一些工具在编译运行时进行解析和使用起到说明配置的功能注解不会也不能影响代码的实际逻辑仅仅起到辅助性的作用
[注解 Annotation 实现原理与自定义注解例子](https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html)
# 十一特性
## 十一特性
## Java 各版本的新特性
### Java 各版本的新特性
**New highlights in Java SE 8**
@ -1438,7 +1414,7 @@ Java 注解是附加在代码中的一些元信息,用于一些工具在编译
- [Difference between Java 1.8 and Java 1.7?](http://www.selfgrowth.com/articles/difference-between-java-18-and-java-17)
- [Java 8 特性](http://www.importnew.com/19345.html)
## Java C++ 的区别
### Java C++ 的区别
- Java 是纯粹的面向对象语言所有的对象都继承自 java.lang.ObjectC++ 为了兼容 C 即支持面向对象也支持面向过程
- Java 通过虚拟机从而实现跨平台特性但是 C++ 依赖于特定的平台
@ -1450,19 +1426,12 @@ Java 注解是附加在代码中的一些元信息,用于一些工具在编译
[What are the main differences between Java and C++?](http://cs-fundamentals.com/tech-interview/java/differences-between-java-and-cpp.php)
## JRE or JDK
### JRE or JDK
- JREJava Runtime EnvironmentJava 运行环境的简称 Java 的运行提供了所需的环境它是一个 JVM 程序主要包括了 JVM 的标准实现和一些 Java 基本类库
- JDKJava Development KitJava 开发工具包提供了 Java 的开发及运行环境JDK Java 开发的核心集成了 JRE 以及一些其它的工具比如编译 Java 源码的编译器 javac
# 参考资料
## 参考资料
- Eckel B. Java 编程思想[M]. 机械工业出版社, 2002.
- Bloch J. Effective java[M]. Addison-Wesley Professional, 2017.
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,32 +1,22 @@
# Java 容器
<!-- GFM-TOC -->
* [概览](#一概览)
* [Collection](#collection)
* [Map](#map)
* [容器中的设计模式](#二容器中的设计模式)
* [迭代器模式](#迭代器模式)
* [适配器模式](#适配器模式)
* [源码分析](#三源码分析)
* [ArrayList](#arraylist)
* [Vector](#vector)
* [CopyOnWriteArrayList](#copyonwritearraylist)
* [LinkedList](#linkedlist)
* [HashMap](#hashmap)
* [ConcurrentHashMap](#concurrenthashmap)
* [LinkedHashMap](#linkedhashmap)
* [WeakHashMap](#weakhashmap)
* [参考资料](#参考资料)
* [Java 容器](#java-容器)
* [概览](#一概览)
* [容器中的设计模式](#二容器中的设计模式)
* [源码分析](#三源码分析)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 概览
## 概览
容器主要包括 Collection Map 两种Collection 存储着对象的集合 Map 存储着键值对两个对象的映射表
## Collection
### Collection
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208220948084.png"/> </div><br>
### 1. Set
#### 1. Set
- TreeSet基于红黑树实现支持有序性操作例如根据一个范围查找元素的操作但是查找效率不如 HashSetHashSet 查找的时间复杂度为 O(1)TreeSet 则为 O(logN)
@ -34,7 +24,7 @@
- LinkedHashSet具有 HashSet 的查找效率并且内部使用双向链表维护元素的插入顺序
### 2. List
#### 2. List
- ArrayList基于动态数组实现支持随机访问
@ -42,13 +32,13 @@
- LinkedList基于双向链表实现只能顺序访问但是可以快速地在链表中间插入和删除元素不仅如此LinkedList 还可以用作栈队列和双向队列
### 3. Queue
#### 3. Queue
- LinkedList可以用它来实现双向队列
- PriorityQueue基于堆结构实现可以用它来实现优先队列
## Map
### Map
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20201101234335837.png"/> </div><br>
@ -61,9 +51,9 @@
- LinkedHashMap使用双向链表来维护元素的顺序顺序为插入顺序或者最近最少使用LRU顺序
# 容器中的设计模式
## 容器中的设计模式
## 迭代器模式
### 迭代器模式
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208225301973.png"/> </div><br>
@ -80,7 +70,7 @@ for (String item : list) {
}
```
## 适配器模式
### 适配器模式
java.util.Arrays#asList() 可以把数组类型转换为 List 类型
@ -102,16 +92,16 @@ List list = Arrays.asList(arr);
List list = Arrays.asList(1, 2, 3);
```
# 源码分析
## 源码分析
如果没有特别说明以下源码分析基于 JDK 1.8
IDEA double shift 调出 Search EveryWhere查找源码文件找到之后就可以阅读源码
## ArrayList
### ArrayList
### 1. 概览
#### 1. 概览
因为 ArrayList 是基于数组实现的所以支持快速随机访问RandomAccess 接口标识着该类支持快速随机访问
@ -128,9 +118,9 @@ private static final int DEFAULT_CAPACITY = 10;
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208232221265.png"/> </div><br>
### 2. 扩容
#### 2. 扩容
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够如果不够时需要使用 grow() 方法进行扩容新容量的大小为 `oldCapacity + (oldCapacity >> 1)`也就是旧容量的 1.5
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够如果不够时需要使用 grow() 方法进行扩容新容量的大小为 `oldCapacity + (oldCapacity \>\> 1)`也就是旧容量的 1.5
扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中这个操作代价很高因此最好在创建 ArrayList 对象时就指定大概的容量大小减少扩容操作的次数
@ -168,7 +158,7 @@ private void grow(int minCapacity) {
}
```
### 3. 删除元素
#### 3. 删除元素
需要调用 System.arraycopy() index+1 后面的元素都复制到 index 位置上该操作的时间复杂度为 O(N)可以看到 ArrayList 删除元素的代价是非常高的
@ -185,7 +175,7 @@ public E remove(int index) {
}
```
### 4. 序列化
#### 4. 序列化
ArrayList 基于数组实现并且具有动态扩容特性因此保存元素的数组不一定都会被使用那么就没必要全部进行序列化
@ -250,16 +240,16 @@ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list);
```
### 5. Fail-Fast
#### 5. Fail-Fast
modCount 用来记录 ArrayList 结构发生变化的次数结构发生变化是指添加或者删除至少一个元素的所有操作或者是调整内部数组的大小仅仅只是设置元素的值不算结构发生变化
在进行序列化或者迭代等操作时需要比较操作前后 modCount 是否改变如果改变了需要抛出 ConcurrentModificationException代码参考上节序列化中的 writeObject() 方法
## Vector
### Vector
### 1. 同步
#### 1. 同步
它的实现与 ArrayList 类似但是使用了 synchronized 进行同步
@ -279,7 +269,7 @@ public synchronized E get(int index) {
}
```
### 2. 扩容
#### 2. 扩容
Vector 的构造函数可以传入 capacityIncrement 参数它的作用是在扩容时使容量 capacity 增长 capacityIncrement如果这个参数的值小于等于 0扩容时每次都令 capacity 为原来的两倍
@ -320,12 +310,12 @@ public Vector() {
}
```
### 3. ArrayList 的比较
#### 3. ArrayList 的比较
- Vector 是同步的因此开销就比 ArrayList 要大访问速度更慢最好使用 ArrayList 而不是 Vector因为同步操作完全可以由程序员自己来控制
- Vector 每次扩容请求其大小的 2 也可以通过构造函数设置增长的容量 ArrayList 1.5
### 4. 替代方案
#### 4. 替代方案
可以使用 `Collections.synchronizedList();` 得到一个线程安全的 ArrayList
@ -340,9 +330,9 @@ List<String> synList = Collections.synchronizedList(list);
List<String> list = new CopyOnWriteArrayList<>();
```
## CopyOnWriteArrayList
### CopyOnWriteArrayList
### 1. 读写分离
#### 1. 读写分离
写操作在一个复制的数组上进行读操作还是在原始数组中进行读写分离互不影响
@ -378,7 +368,7 @@ private E get(Object[] a, int index) {
}
```
### 2. 适用场景
#### 2. 适用场景
CopyOnWriteArrayList 在写操作的同时允许读操作大大提高了读操作的性能因此很适合读多写少的应用场景
@ -389,9 +379,9 @@ CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读
所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景
## LinkedList
### LinkedList
### 1. 概览
#### 1. 概览
基于双向链表实现使用 Node 存储链表节点信息
@ -412,18 +402,18 @@ transient Node<E> last;
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208233940066.png"/> </div><br>
### 2. ArrayList 的比较
#### 2. ArrayList 的比较
ArrayList 基于动态数组实现LinkedList 基于双向链表实现ArrayList LinkedList 的区别可以归结为数组和链表的区别
- 数组支持随机访问但插入删除的代价很高需要移动大量元素
- 链表不支持随机访问但插入删除只需要改变指针
## HashMap
### HashMap
为了便于理解以下源码分析以 JDK 1.7 为主
### 1. 存储结构
#### 1. 存储结构
内部包含了一个 Entry 类型的数组 tableEntry 存储着键值对它包含了四个字段 next 字段我们可以看出 Entry 是一个链表即数组中的每个位置被当成一个桶一个桶存放一个链表HashMap 使用拉链法来解决冲突同一个链表中存放哈希值和散列桶取模运算结果相同的 Entry
@ -486,7 +476,7 @@ static class Entry<K,V> implements Map.Entry<K,V> {
}
```
### 2. 拉链法的工作原理
#### 2. 拉链法的工作原理
```java
HashMap<String, String> map = new HashMap<>();
@ -496,11 +486,11 @@ map.put("K3", "V3");
```
- 新建一个 HashMap默认大小为 16
- 插入 &lt;K1,V1> 键值对先计算 K1 hashCode 115使用除留余数法得到所在的桶下标 115%16=3
- 插入 &lt;K2,V2> 键值对先计算 K2 hashCode 118使用除留余数法得到所在的桶下标 118%16=6
- 插入 &lt;K3,V3> 键值对先计算 K3 hashCode 118使用除留余数法得到所在的桶下标 118%16=6插在 &lt;K2,V2> 前面
- 插入 &lt;K1,V1\> 键值对先计算 K1 hashCode 115使用除留余数法得到所在的桶下标 115%16=3
- 插入 &lt;K2,V2\> 键值对先计算 K2 hashCode 118使用除留余数法得到所在的桶下标 118%16=6
- 插入 &lt;K3,V3\> 键值对先计算 K3 hashCode 118使用除留余数法得到所在的桶下标 118%16=6插在 &lt;K2,V2\> 前面
应该注意到链表的插入是以头插法方式进行的例如上面的 &lt;K3,V3> 不是插在 &lt;K2,V2> 后面而是插入在链表头部
应该注意到链表的插入是以头插法方式进行的例如上面的 &lt;K3,V3\> 不是插在 &lt;K2,V2\> 后面而是插入在链表头部
查找需要分成两步进行
@ -509,7 +499,7 @@ map.put("K3", "V3");
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191208235258643.png"/> </div><br>
### 3. put 操作
#### 3. put 操作
```java
public V put(K key, V value) {
@ -588,7 +578,7 @@ Entry(int h, K k, V v, Entry<K,V> n) {
}
```
### 4. 确定桶下标
#### 4. 确定桶下标
很多操作都需要先确定一个键值对所在的桶下标
@ -624,7 +614,7 @@ public final int hashCode() {
**4.2 取模**
x = 1<<4 x 2 4 次方它具有以下性质
x = 1\<\<4 x 2 4 次方它具有以下性质
```
x : 00010000
@ -657,7 +647,7 @@ static int indexFor(int h, int length) {
}
```
### 5. 扩容-基本原理
#### 5. 扩容-基本原理
HashMap table 长度为 M需要存储的键值对数量为 N如果哈希函数满足均匀性的要求那么每条链表的长度大约为 N/M因此查找的复杂度为 O(N/M)
@ -736,7 +726,7 @@ void transfer(Entry[] newTable) {
}
```
### 6. 扩容-重新计算桶下标
#### 6. 扩容-重新计算桶下标
在进行扩容时需要把键值对重新计算桶下标从而放到对应的桶上在前面提到HashMap 使用 hash%capacity 来确定桶下标HashMap capacity 2 n 次方这一特点能够极大降低重新计算桶下标操作的复杂度
@ -752,7 +742,7 @@ new capacity : 00100000
- 0那么 hash%00010000 = hash%00100000桶位置和原来一致
- 1hash%00010000 = hash%00100000 + 16桶位置是原位置 + 16
### 7. 计算数组容量
#### 7. 计算数组容量
HashMap 构造函数允许用户传入的容量不是 2 n 次方因为它可以自动地将传入的容量转换为 2 n 次方
@ -785,20 +775,20 @@ static final int tableSizeFor(int cap) {
}
```
### 8. 链表转红黑树
#### 8. 链表转红黑树
JDK 1.8 开始一个桶存储的链表长度大于等于 8 时会将链表转换为红黑树
### 9. Hashtable 的比较
#### 9. Hashtable 的比较
- Hashtable 使用 synchronized 来进行同步
- HashMap 可以插入键为 null Entry
- HashMap 的迭代器是 fail-fast 迭代器
- HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的
## ConcurrentHashMap
### ConcurrentHashMap
### 1. 存储结构
#### 1. 存储结构
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/image-20191209001038024.png"/> </div><br>
@ -845,7 +835,7 @@ final Segment<K,V>[] segments;
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
```
### 2. size 操作
#### 2. size 操作
每个 Segment 维护了一个 count 变量来统计该 Segment 中的键值对个数
@ -918,7 +908,7 @@ public int size() {
}
```
### 3. JDK 1.8 的改动
#### 3. JDK 1.8 的改动
JDK 1.7 使用分段锁机制来实现并发更新操作核心类为 Segment它继承自重入锁 ReentrantLock并发度与 Segment 数量相等
@ -926,9 +916,9 @@ JDK 1.8 使用了 CAS 操作来支持更高的并发度,在 CAS 操作失败
并且 JDK 1.8 的实现也在链表过长时会转换为红黑树
## LinkedHashMap
### LinkedHashMap
### 存储结构
#### 存储结构
继承自 HashMap因此具有和 HashMap 一样的快速查找特性
@ -963,7 +953,7 @@ void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
```
### afterNodeAccess()
#### afterNodeAccess()
当一个节点被访问时如果 accessOrder true则会将该节点移到链表尾部也就是说指定为 LRU 顺序之后在每次访问一个节点时会将这个节点移到链表尾部保证链表尾部是最近访问的节点那么链表首部就是最近最久未使用的节点
@ -994,7 +984,7 @@ void afterNodeAccess(Node<K,V> e) { // move node to last
}
```
### afterNodeInsertion()
#### afterNodeInsertion()
put 等操作之后执行 removeEldestEntry() 方法返回 true 时会移除最晚的节点也就是链表首部节点 first
@ -1018,7 +1008,7 @@ protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
}
```
### LRU 缓存
#### LRU 缓存
以下是使用 LinkedHashMap 实现的一个 LRU 缓存
@ -1056,9 +1046,9 @@ public static void main(String[] args) {
[3, 1, 4]
```
## WeakHashMap
### WeakHashMap
### 存储结构
#### 存储结构
WeakHashMap Entry 继承自 WeakReference WeakReference 关联的对象在下一次垃圾回收时会被回收
@ -1068,7 +1058,7 @@ WeakHashMap 主要用来实现缓存,通过使用 WeakHashMap 来引用缓存
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
```
### ConcurrentCache
#### ConcurrentCache
Tomcat 中的 ConcurrentCache 使用了 WeakHashMap 来实现缓存功能
@ -1115,7 +1105,7 @@ public final class ConcurrentCache<K, V> {
```
# 参考资料
## 参考资料
- Eckel B. Java 编程思想 [M]. 机械工业出版社, 2002.
- [Java Collection Framework](https://www.w3resource.com/java-tutorial/java-collections.php)
@ -1129,10 +1119,3 @@ public final class ConcurrentCache<K, V> {
- [Java 集合细节asList 的缺陷](http://wiki.jikexueyuan.com/project/java-enhancement/java-thirtysix.html)
- [Java Collection Framework The LinkedList Class](http://javaconceptoftheday.com/java-collection-framework-linkedlist-class/)
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,66 +1,25 @@
# Java 并发
<!-- GFM-TOC -->
* [使用线程](#一使用线程)
* [实现 Runnable 接口](#实现-runnable-接口)
* [实现 Callable 接口](#实现-callable-接口)
* [继承 Thread ](#继承-thread-)
* [实现接口 VS 继承 Thread](#实现接口-vs-继承-thread)
* [基础线程机制](#二基础线程机制)
* [Executor](#executor)
* [Daemon](#daemon)
* [sleep()](#sleep)
* [yield()](#yield)
* [中断](#三中断)
* [InterruptedException](#interruptedexception)
* [interrupted()](#interrupted)
* [Executor 的中断操作](#executor-的中断操作)
* [互斥同步](#四互斥同步)
* [synchronized](#synchronized)
* [ReentrantLock](#reentrantlock)
* [比较](#比较)
* [使用选择](#使用选择)
* [线程之间的协作](#五线程之间的协作)
* [join()](#join)
* [wait() notify() notifyAll()](#wait-notify-notifyall)
* [await() signal() signalAll()](#await-signal-signalall)
* [线程状态](#六线程状态)
* [新建NEW](#新建new)
* [可运行RUNABLE](#可运行runable)
* [阻塞BLOCKED](#阻塞blocked)
* [无限期等待WAITING](#无限期等待waiting)
* [限期等待TIMED_WAITING](#限期等待timed_waiting)
* [死亡TERMINATED](#死亡terminated)
* [J.U.C - AQS](#七juc---aqs)
* [CountDownLatch](#countdownlatch)
* [CyclicBarrier](#cyclicbarrier)
* [Semaphore](#semaphore)
* [J.U.C - 其它组件](#八juc---其它组件)
* [FutureTask](#futuretask)
* [BlockingQueue](#blockingqueue)
* [ForkJoin](#forkjoin)
* [线程不安全示例](#九线程不安全示例)
* [Java 内存模型](#十java-内存模型)
* [主内存与工作内存](#主内存与工作内存)
* [内存间交互操作](#内存间交互操作)
* [内存模型三大特性](#内存模型三大特性)
* [先行发生原则](#先行发生原则)
* [十一线程安全](#十一线程安全)
* [不可变](#不可变)
* [互斥同步](#互斥同步)
* [非阻塞同步](#非阻塞同步)
* [无同步方案](#无同步方案)
* [十二锁优化](#十二锁优化)
* [自旋锁](#自旋锁)
* [锁消除](#锁消除)
* [锁粗化](#锁粗化)
* [轻量级锁](#轻量级锁)
* [偏向锁](#偏向锁)
* [十三多线程开发良好的实践](#十三多线程开发良好的实践)
* [参考资料](#参考资料)
* [Java 并发](#java-并发)
* [使用线程](#一使用线程)
* [基础线程机制](#二基础线程机制)
* [中断](#三中断)
* [互斥同步](#四互斥同步)
* [线程之间的协作](#五线程之间的协作)
* [线程状态](#六线程状态)
* [J.U.C - AQS](#七juc---aqs)
* [J.U.C - 其它组件](#八juc---其它组件)
* [线程不安全示例](#九线程不安全示例)
* [Java 内存模型](#十java-内存模型)
* [十一线程安全](#十一线程安全)
* [十二锁优化](#十二锁优化)
* [十三多线程开发良好的实践](#十三多线程开发良好的实践)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 使用线程
## 使用线程
有三种使用线程的方法
@ -70,7 +29,7 @@
实现 Runnable Callable 接口的类只能当做一个可以在线程中运行的任务不是真正意义上的线程因此最后还需要通过 Thread 来调用可以理解为任务是通过线程驱动从而执行的
## 实现 Runnable 接口
### 实现 Runnable 接口
需要实现接口中的 run() 方法
@ -93,7 +52,7 @@ public static void main(String[] args) {
}
```
## 实现 Callable 接口
### 实现 Callable 接口
Runnable 相比Callable 可以有返回值返回值通过 FutureTask 进行封装
@ -115,7 +74,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc
}
```
## 继承 Thread
### 继承 Thread
同样也是需要实现 run() 方法因为 Thread 类也实现了 Runable 接口
@ -136,16 +95,16 @@ public static void main(String[] args) {
}
```
## 实现接口 VS 继承 Thread
### 实现接口 VS 继承 Thread
实现接口会更好一些因为
- Java 不支持多重继承因此继承了 Thread 类就无法继承其它类但是可以实现多个接口
- 类可能只要求可执行就行继承整个 Thread 类开销过大
# 基础线程机制
## 基础线程机制
## Executor
### Executor
Executor 管理多个异步任务的执行而无需程序员显式地管理线程的生命周期这里的异步是指多个任务的执行互不干扰不需要进行同步操作
@ -165,7 +124,7 @@ public static void main(String[] args) {
}
```
## Daemon
### Daemon
守护线程是程序运行时在后台提供服务的线程不属于程序中不可或缺的部分
@ -182,7 +141,7 @@ public static void main(String[] args) {
}
```
## sleep()
### sleep()
Thread.sleep(millisec) 方法会休眠当前正在执行的线程millisec 单位为毫秒
@ -198,7 +157,7 @@ public void run() {
}
```
## yield()
### yield()
对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分可以切换给其它线程来执行该方法只是对线程调度器的一个建议而且也只是建议具有相同优先级的其它线程可以运行
@ -208,11 +167,11 @@ public void run() {
}
```
# 中断
## 中断
一个线程执行完毕之后会自动结束如果在运行过程中发生异常也会提前结束
## InterruptedException
### InterruptedException
通过调用一个线程的 interrupt() 来中断该线程如果该线程处于阻塞限期等待或者无限期等待状态那么就会抛出 InterruptedException从而提前结束该线程但是不能中断 I/O 阻塞和 synchronized 锁阻塞
@ -253,7 +212,7 @@ java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.run(Thread.java:745)
```
## interrupted()
### interrupted()
如果一个线程的 run() 方法执行一个无限循环并且没有执行 sleep() 等会抛出 InterruptedException 的操作那么调用线程的 interrupt() 方法就无法使线程提前结束
@ -286,7 +245,7 @@ public static void main(String[] args) throws InterruptedException {
Thread end
```
## Executor 的中断操作
### Executor 的中断操作
调用 Executor shutdown() 方法会等待线程都执行完毕之后再关闭但是如果调用的是 shutdownNow() 方法则相当于调用每个线程的 interrupt() 方法
@ -319,7 +278,7 @@ java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.run(Thread.java:745)
```
如果只想中断 Executor 中的一个线程可以通过使用 submit() 方法来提交一个线程它会返回一个 Future<?> 对象通过调用该对象的 cancel(true) 方法就可以中断线程
如果只想中断 Executor 中的一个线程可以通过使用 submit() 方法来提交一个线程它会返回一个 Future\<?\> 对象通过调用该对象的 cancel(true) 方法就可以中断线程
```java
Future<?> future = executorService.submit(() -> {
@ -328,11 +287,11 @@ Future<?> future = executorService.submit(() -> {
future.cancel(true);
```
# 互斥同步
## 互斥同步
Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问第一个是 JVM 实现的 synchronized而另一个是 JDK 实现的 ReentrantLock
## synchronized
### synchronized
**1. 同步一个代码块**
@ -450,7 +409,7 @@ public synchronized static void fun() {
作用于整个类
## ReentrantLock
### ReentrantLock
ReentrantLock java.util.concurrentJ.U.C包中的锁
@ -486,7 +445,7 @@ public static void main(String[] args) {
```
## 比较
### 比较
**1. 锁的实现**
@ -512,15 +471,15 @@ synchronized 中的锁是非公平的ReentrantLock 默认情况下也是非
一个 ReentrantLock 可以同时绑定多个 Condition 对象
## 使用选择
### 使用选择
除非需要使用 ReentrantLock 的高级功能否则优先使用 synchronized这是因为 synchronized JVM 实现的一种锁机制JVM 原生地支持它 ReentrantLock 不是所有的 JDK 版本都支持并且使用 synchronized 不用担心没有释放锁而导致死锁问题因为 JVM 会确保锁的释放
# 线程之间的协作
## 线程之间的协作
当多个线程可以一起工作去解决某个问题时如果某些部分必须在其它部分之前完成那么就需要对线程进行协调
## join()
### join()
在线程中调用另一个线程的 join() 方法会将当前线程挂起而不是忙等待直到目标线程结束
@ -576,7 +535,7 @@ A
B
```
## wait() notify() notifyAll()
### wait() notify() notifyAll()
调用 wait() 使得线程等待某个条件满足线程在等待时会被挂起当其他线程的运行使得这个条件满足时其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程
@ -624,7 +583,7 @@ after
- wait() Object 的方法 sleep() Thread 的静态方法
- wait() 会释放锁sleep() 不会
## await() signal() signalAll()
### await() signal() signalAll()
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调可以在 Condition 上调用 await() 方法使线程等待其它线程调用 signal() signalAll() 方法唤醒等待的线程
@ -676,23 +635,23 @@ before
after
```
# 线程状态
## 线程状态
一个线程只能处于一种状态并且这里的线程状态特指 Java 虚拟机的线程状态不能反映线程在特定操作系统下的状态
## 新建NEW
### 新建NEW
创建后尚未启动
## 可运行RUNABLE
### 可运行RUNABLE
正在 Java 虚拟机中运行但是在操作系统层面它可能处于运行状态也可能等待资源调度例如处理器资源资源调度完成就进入运行状态所以该状态的可运行是指可以被运行具体有没有运行要看底层操作系统的资源调度
## 阻塞BLOCKED
### 阻塞BLOCKED
请求获取 monitor lock 从而进入 synchronized 函数或者代码块但是其它线程已经占用了该 monitor lock所以出于阻塞状态要结束该状态进入从而 RUNABLE 需要其他线程释放 monitor lock
## 无限期等待WAITING
### 无限期等待WAITING
等待其它线程显式地唤醒
@ -704,7 +663,7 @@ after
| 没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
| LockSupport.park() 方法 | LockSupport.unpark(Thread) |
## 限期等待TIMED_WAITING
### 限期等待TIMED_WAITING
无需等待其它线程显式地唤醒在一定时间之后会被系统自动唤醒
@ -718,17 +677,17 @@ after
调用 Thread.sleep() 方法使线程进入限期等待状态时常常用使一个线程睡眠进行描述调用 Object.wait() 方法使线程进入限期等待或者无限期等待时常常用挂起一个线程进行描述睡眠和挂起是用来描述行为而阻塞和等待用来描述状态
## 死亡TERMINATED
### 死亡TERMINATED
可以是线程结束任务之后自己结束或者产生了异常而结束
[Java SE 9 Enum Thread.State](https://docs.oracle.com/javase/9/docs/api/java/lang/Thread.State.html)
# J.U.C - AQS
## J.U.C - AQS
java.util.concurrentJ.U.C大大提高了并发性能AQS 被认为是 J.U.C 的核心
## CountDownLatch
### CountDownLatch
用来控制一个或者多个线程等待多个线程
@ -760,7 +719,7 @@ public class CountdownLatchExample {
run..run..run..run..run..run..run..run..run..run..end
```
## CyclicBarrier
### CyclicBarrier
用来控制多个线程互相等待只有当多个线程都到达时这些线程才会继续执行
@ -812,7 +771,7 @@ public class CyclicBarrierExample {
before..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after..
```
## Semaphore
### Semaphore
Semaphore 类似于操作系统中的信号量可以控制对互斥资源的访问线程数
@ -847,11 +806,11 @@ public class SemaphoreExample {
2 1 2 2 2 2 2 1 2 2
```
# J.U.C - 其它组件
## J.U.C - 其它组件
## FutureTask
### FutureTask
在介绍 Callable 时我们知道它可以有返回值返回值通过 Future<V> 进行封装FutureTask 实现了 RunnableFuture 接口该接口继承自 Runnable Future<V> 接口这使得 FutureTask 既可以当做一个任务执行也可以有返回值
在介绍 Callable 时我们知道它可以有返回值返回值通过 Future\<V\> 进行封装FutureTask 实现了 RunnableFuture 接口该接口继承自 Runnable Future\<V\> 接口这使得 FutureTask 既可以当做一个任务执行也可以有返回值
```java
public class FutureTask<V> implements RunnableFuture<V>
@ -901,7 +860,7 @@ other task is running...
4950
```
## BlockingQueue
### BlockingQueue
java.util.concurrent.BlockingQueue 接口有以下阻塞队列的实现
@ -965,7 +924,7 @@ public static void main(String[] args) {
produce..produce..consume..consume..produce..consume..produce..consume..produce..consume..
```
## ForkJoin
### ForkJoin
主要用于并行计算中 MapReduce 原理类似都是把大的计算任务拆分成多个小任务并行计算
@ -1022,7 +981,7 @@ ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e42f188f-f4a9-4e6f-88fc-45f4682072fb.png" width="300px"> </div><br>
# 线程不安全示例
## 线程不安全示例
如果多个线程对同一个共享数据进行访问而不采取同步操作的话那么操作的结果是不一致的
@ -1065,11 +1024,11 @@ public static void main(String[] args) throws InterruptedException {
997
```
# Java 内存模型
## Java 内存模型
Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异以实现让 Java 程序在各种平台下都能达到一致的内存访问效果
## 主内存与工作内存
### 主内存与工作内存
处理器上的寄存器的读写的速度比内存快几个数量级为了解决这种速度矛盾在它们之间加入了高速缓存
@ -1083,7 +1042,7 @@ Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异,
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/15851555-5abc-497d-ad34-efed10f43a6b.png" width="600px"> </div><br>
## 内存间交互操作
### 内存间交互操作
Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作
@ -1098,9 +1057,9 @@ Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互
- lock作用于主内存的变量
- unlock
## 内存模型三大特性
### 内存模型三大特性
### 1. 原子性
#### 1. 原子性
Java 内存模型保证了 readloaduseassignstorewritelock unlock 操作具有原子性例如对一个 int 类型的变量执行 assign 赋值操作这个操作就是原子性的但是 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据longdouble的读写操作划分为两次 32 位的操作来进行 loadstoreread write 操作可以不具备原子性
@ -1192,7 +1151,7 @@ public static void main(String[] args) throws InterruptedException {
1000
```
### 2. 可见性
#### 2. 可见性
可见性指当一个线程修改了共享变量的值其它线程能够立即得知这个修改Java 内存模型是通过在变量修改后将新值同步回主内存在变量读取前从主内存刷新变量值来实现可见性的
@ -1204,7 +1163,7 @@ public static void main(String[] args) throws InterruptedException {
对前面的线程不安全示例中的 cnt 变量使用 volatile 修饰不能解决线程不安全问题因为 volatile 并不能保证操作的原子性
### 3. 有序性
#### 3. 有序性
有序性是指在本线程内观察所有操作都是有序的在一个线程观察另一个线程所有操作都是无序的无序是因为发生了指令重排序 Java 内存模型中允许编译器和处理器对指令进行重排序重排序过程不会影响到单线程程序的执行却会影响到多线程并发执行的正确性
@ -1212,75 +1171,75 @@ volatile 关键字通过添加内存屏障的方式来禁止指令重排,即
也可以通过 synchronized 来保证有序性它保证每个时刻只有一个线程执行同步代码相当于是让线程顺序执行同步代码
## 先行发生原则
### 先行发生原则
上面提到了可以用 volatile synchronized 来保证有序性除此之外JVM 还规定了先行发生原则让一个操作无需控制就能先于另一个操作完成
### 1. 单一线程原则
#### 1. 单一线程原则
> Single Thread rule
\> Single Thread rule
在一个线程内在程序前面的操作先行发生于后面的操作
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/874b3ff7-7c5c-4e7a-b8ab-a82a3e038d20.png" width="180px"> </div><br>
### 2. 管程锁定规则
#### 2. 管程锁定规则
> Monitor Lock Rule
\> Monitor Lock Rule
一个 unlock 操作先行发生于后面对同一个锁的 lock 操作
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/8996a537-7c4a-4ec8-a3b7-7ef1798eae26.png" width="350px"> </div><br>
### 3. volatile 变量规则
#### 3. volatile 变量规则
> Volatile Variable Rule
\> Volatile Variable Rule
对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/942f33c9-8ad9-4987-836f-007de4c21de0.png" width="400px"> </div><br>
### 4. 线程启动规则
#### 4. 线程启动规则
> Thread Start Rule
\> Thread Start Rule
Thread 对象的 start() 方法调用先行发生于此线程的每一个动作
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/6270c216-7ec0-4db7-94de-0003bce37cd2.png" width="380px"> </div><br>
### 5. 线程加入规则
#### 5. 线程加入规则
> Thread Join Rule
\> Thread Join Rule
Thread 对象的结束先行发生于 join() 方法返回
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/233f8d89-31d7-413f-9c02-042f19c46ba1.png" width="400px"> </div><br>
### 6. 线程中断规则
#### 6. 线程中断规则
> Thread Interruption Rule
\> Thread Interruption Rule
对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生可以通过 interrupted() 方法检测到是否有中断发生
### 7. 对象终结规则
#### 7. 对象终结规则
> Finalizer Rule
\> Finalizer Rule
一个对象的初始化完成构造函数执行结束先行发生于它的 finalize() 方法的开始
### 8. 传递性
#### 8. 传递性
> Transitivity
\> Transitivity
如果操作 A 先行发生于操作 B操作 B 先行发生于操作 C那么操作 A 先行发生于操作 C
# 十一线程安全
## 十一线程安全
多个线程不管以何种方式访问某个类并且在主调代码中不需要进行同步都能表现正确的行为
线程安全有以下几种实现方式
## 不可变
### 不可变
不可变Immutable的对象一定是线程安全的不需要再采取任何的线程安全保障措施只要一个不可变的对象被正确地构建出来永远也不会看到它在多个线程之中处于不一致的状态多线程环境下应当尽量使对象成为不可变来满足线程安全
@ -1317,11 +1276,11 @@ public V put(K key, V value) {
}
```
## 互斥同步
### 互斥同步
synchronized ReentrantLock
## 非阻塞同步
### 非阻塞同步
互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题因此这种同步也称为阻塞同步
@ -1329,11 +1288,11 @@ synchronized 和 ReentrantLock。
随着硬件指令集的发展我们可以使用基于冲突检测的乐观并发策略先进行操作如果没有其它线程争用共享数据那操作就成功了否则采取补偿措施不断地重试直到成功为止这种乐观的并发策略的许多实现都不需要将线程阻塞因此这种同步操作称为非阻塞同步
### 1. CAS
#### 1. CAS
乐观锁需要操作和冲突检测这两个步骤具备原子性这里就不能再使用互斥同步来保证了只能靠硬件来完成硬件支持的原子性操作最典型的是比较并交换Compare-and-SwapCASCAS 指令需要有 3 个操作数分别是内存地址 V旧的预期值 A 和新值 B当执行操作时只有当 V 的值等于 A才将 V 的值更新为 B
### 2. AtomicInteger
#### 2. AtomicInteger
J.U.C 包里面的整数原子类 AtomicInteger 的方法调用了 Unsafe 类的 CAS 操作
@ -1370,17 +1329,17 @@ public final int getAndAddInt(Object var1, long var2, int var4) {
}
```
### 3. ABA
#### 3. ABA
如果一个变量初次读取的时候是 A 它的值被改成了 B后来又被改回为 A CAS 操作就会误认为它从来没有被改变过
J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题它可以通过控制变量值的版本来保证 CAS 的正确性大部分情况下 ABA 问题不会影响程序并发的正确性如果需要解决 ABA 问题改用传统的互斥同步可能会比原子类更高效
## 无同步方案
### 无同步方案
要保证线程安全并不是一定就要进行同步如果一个方法本来就不涉及共享数据那它自然就无须任何同步措施去保证正确性
### 1. 栈封闭
#### 1. 栈封闭
多个线程访问同一个方法的局部变量时不会出现线程安全问题因为局部变量存储在虚拟机栈中属于线程私有的
@ -1411,7 +1370,7 @@ public static void main(String[] args) {
100
```
### 2. 线程本地存储Thread Local Storage
#### 2. 线程本地存储Thread Local Storage
如果一段代码中所需要的数据必须与其他代码共享那就看看这些共享数据的代码是否能保证在同一个线程中执行如果能保证我们就可以把共享数据的可见范围限制在同一个线程之内这样无须同步也能保证线程之间不出现数据争用的问题
@ -1482,7 +1441,7 @@ public class ThreadLocalExample1 {
ThreadLocal.ThreadLocalMap threadLocals = null;
```
当调用一个 ThreadLocal set(T value) 方法时先得到当前线程的 ThreadLocalMap 对象然后将 ThreadLocal->value 键值对插入到该 Map
当调用一个 ThreadLocal set(T value) 方法时先得到当前线程的 ThreadLocalMap 对象然后将 ThreadLocal-\>value 键值对插入到该 Map
```java
public void set(T value) {
@ -1517,17 +1476,17 @@ ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因
在一些场景 (尤其是使用线程池) 由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况应该尽可能在每次使用 ThreadLocal 后手动调用 remove()以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险
### 3. 可重入代码Reentrant Code
#### 3. 可重入代码Reentrant Code
这种代码也叫做纯代码Pure Code可以在代码执行的任何时刻中断它转而去执行另外一段代码包括递归调用它本身而在控制权返回后原来的程序不会出现任何错误
可重入代码有一些共同的特征例如不依赖存储在堆上的数据和公用的系统资源用到的状态量都由参数中传入不调用非可重入的方法等
# 十二锁优化
## 十二锁优化
这里的锁优化主要是指 JVM synchronized 的优化
## 自旋锁
### 自旋锁
互斥同步进入阻塞状态的开销都很大应该尽量避免在许多应用中共享数据的锁定状态只会持续很短的一段时间自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环自旋一段时间如果在这段时间内能获得锁就可以避免进入阻塞状态
@ -1535,7 +1494,7 @@ ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因
JDK 1.6 中引入了自适应的自旋锁自适应意味着自旋的次数不再固定了而是由前一次在同一个锁上的自旋次数及锁的拥有者的状态来决定
## 锁消除
### 锁消除
锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除
@ -1563,13 +1522,13 @@ public static String concatString(String s1, String s2, String s3) {
每个 append() 方法中都有一个同步块虚拟机观察变量 sb很快就会发现它的动态作用域被限制在 concatString() 方法内部也就是说sb 的所有引用永远不会逃逸到 concatString() 方法之外其他线程无法访问到它因此可以进行消除
## 锁粗化
### 锁粗化
如果一系列的连续操作都对同一个对象反复加锁和解锁频繁的加锁操作就会导致性能损耗
上一节的示例代码中连续的 append() 方法就属于这类情况如果虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁将会把加锁的范围扩展粗化到整个操作序列的外部对于上一节的示例代码就是扩展到第一个 append() 操作之前直至最后一个 append() 操作之后这样只需要加锁一次就可以了
## 轻量级锁
### 轻量级锁
JDK 1.6 引入了偏向锁和轻量级锁从而让锁拥有了四个状态无锁状态unlocked偏向锁状态biasble轻量级锁状态lightweight locked和重量级锁状态inflated
@ -1589,7 +1548,7 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
如果 CAS 操作失败了虚拟机首先会检查对象的 Mark Word 是否指向当前线程的虚拟机栈如果是的话说明当前线程已经拥有了这个锁对象那就可以直接进入同步块继续执行否则说明这个锁对象已经被其他线程线程抢占了如果有两条以上的线程争用同一个锁那轻量级锁就不再有效要膨胀为重量级锁
## 偏向锁
### 偏向锁
偏向锁的思想是偏向于让第一个获取锁对象的线程这个线程在之后获取该锁就不再需要进行同步操作甚至连 CAS 操作也不再需要
@ -1599,7 +1558,7 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/390c913b-5f31-444f-bbdb-2b88b688e7ce.jpg" width="600"/> </div><br>
# 十三多线程开发良好的实践
## 十三多线程开发良好的实践
- 给线程起个有意义的名字这样可以方便找 Bug
@ -1615,7 +1574,7 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
- 使用线程池而不是直接创建线程这是因为创建线程代价很高线程池可以有效地利用有限的线程来启动任务
# 参考资料
## 参考资料
- BruceEckel. Java 编程思想: 4 [M]. 机械工业出版社, 2007.
- 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.
@ -1632,10 +1591,3 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
- [JAVA FORK JOIN EXAMPLE](http://www.javacreed.com/java-fork-join-example/ "Java Fork Join Example")
- [聊聊并发Fork/Join 框架介绍](http://ifeve.com/talk-concurrency-forkjoin/)
- [Eliminating SynchronizationRelated Atomic Operations with Biased Locking and Bulk Rebiasing](http://www.oracle.com/technetwork/java/javase/tech/biasedlocking-oopsla2006-preso-150106.pdf)
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,44 +1,25 @@
# Java 虚拟机
<!-- GFM-TOC -->
* [运行时数据区域](#一运行时数据区域)
* [程序计数器](#程序计数器)
* [Java 虚拟机栈](#java-虚拟机栈)
* [本地方法栈](#本地方法栈)
* [](#)
* [方法区](#方法区)
* [运行时常量池](#运行时常量池)
* [直接内存](#直接内存)
* [垃圾收集](#二垃圾收集)
* [判断一个对象是否可被回收](#判断一个对象是否可被回收)
* [引用类型](#引用类型)
* [垃圾收集算法](#垃圾收集算法)
* [垃圾收集器](#垃圾收集器)
* [内存分配与回收策略](#三内存分配与回收策略)
* [Minor GC Full GC](#minor-gc--full-gc)
* [内存分配策略](#内存分配策略)
* [Full GC 的触发条件](#full-gc-的触发条件)
* [类加载机制](#四类加载机制)
* [类的生命周期](#类的生命周期)
* [类加载过程](#类加载过程)
* [类初始化时机](#类初始化时机)
* [类与类加载器](#类与类加载器)
* [类加载器分类](#类加载器分类)
* [双亲委派模型](#双亲委派模型)
* [自定义类加载器实现](#自定义类加载器实现)
* [参考资料](#参考资料)
* [Java 虚拟机](#java-虚拟机)
* [运行时数据区域](#一运行时数据区域)
* [垃圾收集](#二垃圾收集)
* [内存分配与回收策略](#三内存分配与回收策略)
* [类加载机制](#四类加载机制)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
本文大部分内容参考 **周志明深入理解 Java 虚拟机** 想要深入学习的话请看原书
# 运行时数据区域
## 运行时数据区域
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5778d113-8e13-4c53-b5bf-801e58080b97.png" width="400px"> </div><br>
## 程序计数器
### 程序计数器
记录正在执行的虚拟机字节码指令的地址如果正在执行的是本地方法则为空
## Java 虚拟机栈
### Java 虚拟机栈
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表操作数栈常量池引用等信息从方法调用直至执行完成的过程对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程
@ -55,7 +36,7 @@ java -Xss2M HackTheJava
- 当线程请求的栈深度超过最大值会抛出 StackOverflowError 异常
- 栈进行动态扩展时如果无法申请到足够内存会抛出 OutOfMemoryError 异常
## 本地方法栈
### 本地方法栈
本地方法栈与 Java 虚拟机栈类似它们之间的区别只不过是本地方法栈为本地方法服务
@ -63,7 +44,7 @@ java -Xss2M HackTheJava
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/66a6899d-c6b0-4a47-8569-9d08f0baf86c.png" width="300px"> </div><br>
##
###
所有对象都在这里分配内存是垃圾收集的主要区域"GC 堆"
@ -80,7 +61,7 @@ java -Xss2M HackTheJava
java -Xms1M -Xmx2M HackTheJava
```
## 方法区
### 方法区
用于存放已被加载的类信息常量静态变量即时编译器编译后的代码等数据
@ -92,7 +73,7 @@ HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永
方法区是一个 JVM 规范永久代与元空间都是其一种实现方式 JDK 1.8 之后原来永久代的数据被分到了堆和元空间中元空间存储类的元信息静态变量和常量池等放入堆中
## 运行时常量池
### 运行时常量池
运行时常量池是方法区的一部分
@ -100,17 +81,17 @@ Class 文件中的常量池(编译器生成的字面量和符号引用)会
除了在编译期生成的常量还允许动态生成例如 String 类的 intern()
## 直接内存
### 直接内存
JDK 1.4 中新引入了 NIO 它可以使用 Native 函数库直接分配堆外内存然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作这样能在一些场景中显著提高性能因为避免了在堆内存和堆外内存来回拷贝数据
# 垃圾收集
## 垃圾收集
垃圾收集主要是针对堆和方法区进行程序计数器虚拟机栈和本地方法栈这三个区域属于线程私有的只存在于线程的生命周期内线程结束之后就会消失因此不需要对这三个区域进行垃圾回收
## 判断一个对象是否可被回收
### 判断一个对象是否可被回收
### 1. 引用计数算法
#### 1. 引用计数算法
为对象添加一个引用计数器当对象增加一个引用时计数器加 1引用失效时计数器减 1引用计数为 0 的对象可被回收
@ -135,7 +116,7 @@ public class Test {
在上述代码中a b 引用的对象实例互相持有了对象的引用因此当我们把对 a 对象与 b 对象的引用去除之后由于两个对象还存在互相之间的引用导致两个 Test 对象无法被回收
### 2. 可达性分析算法
#### 2. 可达性分析算法
GC Roots 为起始点进行搜索可达的对象都是存活的不可达的对象可被回收
@ -149,7 +130,7 @@ Java 虚拟机使用该算法来判断对象是否可被回收GC Roots 一般
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/83d909d2-3858-4fe1-8ff4-16471db0b180.png" width="350px"> </div><br>
### 3. 方法区的回收
#### 3. 方法区的回收
因为方法区主要存放永久代对象而永久代对象的回收率比新生代低很多所以在方法区上进行回收性价比不高
@ -163,19 +144,19 @@ Java 虚拟机使用该算法来判断对象是否可被回收GC Roots 一般
- 加载该类的 ClassLoader 已经被回收
- 该类对应的 Class 对象没有在任何地方被引用也就无法在任何地方通过反射访问该类方法
### 4. finalize()
#### 4. finalize()
类似 C++ 的析构函数用于关闭外部资源但是 try-finally 等方式可以做得更好并且该方法运行代价很高不确定性大无法保证各个对象的调用顺序因此最好不要使用
当一个对象可被回收时如果需要执行该对象的 finalize() 方法那么就有可能在该方法中让对象重新被引用从而实现自救自救只能进行一次如果回收的对象之前调用了 finalize() 方法自救后面回收时不会再调用该方法
## 引用类型
### 引用类型
无论是通过引用计数算法判断对象的引用数量还是通过可达性分析算法判断对象是否可达判定对象是否可被回收都与引用有关
Java 提供了四种强度不同的引用类型
### 1. 强引用
#### 1. 强引用
被强引用关联的对象不会被回收
@ -185,7 +166,7 @@ Java 提供了四种强度不同的引用类型。
Object obj = new Object();
```
### 2. 软引用
#### 2. 软引用
被软引用关联的对象只有在内存不够的情况下才会被回收
@ -197,7 +178,7 @@ SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联
```
### 3. 弱引用
#### 3. 弱引用
被弱引用关联的对象一定会被回收也就是说它只能存活到下一次垃圾回收发生之前
@ -209,7 +190,7 @@ WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
```
### 4. 虚引用
#### 4. 虚引用
又称为幽灵引用或者幻影引用一个对象是否有虚引用的存在不会对其生存时间造成影响也无法通过虚引用得到一个对象
@ -223,9 +204,9 @@ PhantomReference<Object> pf = new PhantomReference<Object>(obj, null);
obj = null;
```
## 垃圾收集算法
### 垃圾收集算法
### 1. 标记 - 清除
#### 1. 标记 - 清除
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/005b481b-502b-4e3f-985d-d043c2b330aa.png" width="400px"> </div><br>
@ -240,7 +221,7 @@ obj = null;
- 标记和清除过程效率都不高
- 会产生大量不连续的内存碎片导致无法给大对象分配内存
### 2. 标记 - 整理
#### 2. 标记 - 整理
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ccd773a5-ad38-4022-895c-7ac318f31437.png" width="400px"> </div><br>
@ -254,7 +235,7 @@ obj = null;
- 需要移动大量对象处理效率比较低
### 3. 复制
#### 3. 复制
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b2b77b9e-958c-4016-8ae5-9c6edd83871e.png" width="400px"> </div><br>
@ -266,7 +247,7 @@ obj = null;
HotSpot 虚拟机的 Eden Survivor 大小比例默认为 8:1保证了内存的利用率达到 90%如果每次回收有多于 10% 的对象存活那么一块 Survivor 就不够用了此时需要依赖于老年代进行空间分配担保也就是借用老年代的空间存储放不下的对象
### 4. 分代收集
#### 4. 分代收集
现在的商业虚拟机采用分代收集算法它根据对象存活周期将内存划分为几块不同块采用适当的收集算法
@ -275,7 +256,7 @@ HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1保证了内
- 新生代使用复制算法
- 老年代使用标记 - 清除 或者 标记 - 整理 算法
## 垃圾收集器
### 垃圾收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c625baa0-dde6-449e-93df-c3a67f2f430f.jpg" width=""/> </div><br>
@ -284,7 +265,7 @@ HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1保证了内
- 单线程与多线程单线程指的是垃圾收集器只使用一个线程而多线程使用多个线程
- 串行与并行串行指的是垃圾收集器与用户程序交替执行这意味着在执行垃圾收集的时候需要停顿用户程序并行指的是垃圾收集器和用户程序同时执行除了 CMS G1 之外其它垃圾收集器都是以串行的方式执行
### 1. Serial 收集器
#### 1. Serial 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg" width=""/> </div><br>
@ -296,7 +277,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
它是 Client 场景下的默认新生代收集器因为在该场景下内存一般来说不会很大它收集一两百兆垃圾的停顿时间可以控制在一百多毫秒以内只要不是太频繁这点停顿时间是可以接受的
### 2. ParNew 收集器
#### 2. ParNew 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg" width=""/> </div><br>
@ -304,7 +285,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
它是 Server 场景下默认的新生代收集器除了性能原因外主要是因为除了 Serial 收集器只有它能与 CMS 收集器配合使用
### 3. Parallel Scavenge 收集器
#### 3. Parallel Scavenge 收集器
ParNew 一样是多线程收集器
@ -316,7 +297,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
可以通过一个开关参数打开 GC 自适应的调节策略GC Ergonomics就不需要手工指定新生代的大小-XmnEden Survivor 区的比例晋升老年代对象年龄等细节参数了虚拟机会根据当前系统的运行情况收集性能监控信息动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量
### 4. Serial Old 收集器
#### 4. Serial Old 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg" width=""/> </div><br>
@ -325,7 +306,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
- JDK 1.5 以及之前版本Parallel Old 诞生以前中与 Parallel Scavenge 收集器搭配使用
- 作为 CMS 收集器的后备预案在并发收集发生 Concurrent Mode Failure 时使用
### 5. Parallel Old 收集器
#### 5. Parallel Old 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/278fe431-af88-4a95-a895-9c3b80117de3.jpg" width=""/> </div><br>
@ -333,7 +314,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
在注重吞吐量以及 CPU 资源敏感的场合都可以优先考虑 Parallel Scavenge Parallel Old 收集器
### 6. CMS 收集器
#### 6. CMS 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/62e77997-6957-4b68-8d12-bfd609bb2c68.jpg" width=""/> </div><br>
@ -354,7 +335,7 @@ CMSConcurrent Mark SweepMark Sweep 指的是标记 - 清除算法。
- 无法处理浮动垃圾可能出现 Concurrent Mode Failure浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾这部分垃圾只能到下一次 GC 时才能进行回收由于浮动垃圾的存在因此需要预留出一部分内存意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收如果预留的内存不够存放浮动垃圾就会出现 Concurrent Mode Failure这时虚拟机将临时启用 Serial Old 来替代 CMS
- 标记 - 清除算法导致的空间碎片往往出现老年代空间剩余但无法找到足够大连续空间来分配当前对象不得不提前触发一次 Full GC
### 7. G1 收集器
#### 7. G1 收集器
G1Garbage-First它是一款面向服务端应用的垃圾收集器在多 CPU 和大内存的场景下有很好的性能HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器
@ -384,21 +365,21 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
- 空间整合整体来看是基于标记 - 整理算法实现的收集器从局部两个 Region 之间上来看是基于复制算法实现的这意味着运行期间不会产生内存空间碎片
- 可预测的停顿能让使用者明确指定在一个长度为 M 毫秒的时间片段内消耗在 GC 上的时间不得超过 N 毫秒
# 内存分配与回收策略
## 内存分配与回收策略
## Minor GC Full GC
### Minor GC Full GC
- Minor GC回收新生代因为新生代对象存活时间很短因此 Minor GC 会频繁执行执行的速度一般也会比较快
- Full GC回收老年代和新生代老年代对象其存活时间长因此 Full GC 很少执行执行速度会比 Minor GC 慢很多
## 内存分配策略
### 内存分配策略
### 1. 对象优先在 Eden 分配
#### 1. 对象优先在 Eden 分配
大多数情况下对象在新生代 Eden 上分配 Eden 空间不够时发起 Minor GC
### 2. 大对象直接进入老年代
#### 2. 大对象直接进入老年代
大对象是指需要连续内存空间的对象最典型的大对象是那种很长的字符串以及数组
@ -406,41 +387,41 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
-XX:PretenureSizeThreshold大于此值的对象直接在老年代分配避免在 Eden Survivor 之间的大量内存复制
### 3. 长期存活的对象进入老年代
#### 3. 长期存活的对象进入老年代
为对象定义年龄计数器对象在 Eden 出生并经过 Minor GC 依然存活将移动到 Survivor 年龄就增加 1 增加到一定年龄则移动到老年代中
-XX:MaxTenuringThreshold 用来定义年龄的阈值
### 4. 动态对象年龄判定
#### 4. 动态对象年龄判定
虚拟机并不是永远要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半则年龄大于或等于该年龄的对象可以直接进入老年代无需等到 MaxTenuringThreshold 中要求的年龄
### 5. 空间分配担保
#### 5. 空间分配担保
在发生 Minor GC 之前虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间如果条件成立的话那么 Minor GC 可以确认是安全的
如果不成立的话虚拟机会查看 HandlePromotionFailure 的值是否允许担保失败如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小如果大于将尝试着进行一次 Minor GC如果小于或者 HandlePromotionFailure 的值不允许冒险那么就要进行一次 Full GC
## Full GC 的触发条件
### Full GC 的触发条件
对于 Minor GC其触发条件非常简单 Eden 空间满时就将触发一次 Minor GC Full GC 则相对复杂有以下条件
### 1. 调用 System.gc()
#### 1. 调用 System.gc()
只是建议虚拟机执行 Full GC但是虚拟机不一定真正去执行不建议使用这种方式而是让虚拟机管理内存
### 2. 老年代空间不足
#### 2. 老年代空间不足
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代长期存活的对象进入老年代等
为了避免以上原因引起的 Full GC应当尽量不要创建过大的对象以及数组除此之外可以通过 -Xmn 虚拟机参数调大新生代的大小让对象尽量在新生代被回收掉不进入老年代还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄让对象在新生代多存活一段时间
### 3. 空间分配担保失败
#### 3. 空间分配担保失败
使用复制算法的 Minor GC 需要老年代的内存空间作担保如果担保失败会执行一次 Full GC具体内容请参考上面的第 5 小节
### 4. JDK 1.7 及以前的永久代空间不足
#### 4. JDK 1.7 及以前的永久代空间不足
JDK 1.7 及以前HotSpot 虚拟机中的方法区是用永久代实现的永久代中存放的为一些 Class 的信息常量静态变量等数据
@ -448,15 +429,15 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
为避免以上原因引起的 Full GC可采用的方法为增大永久代空间或转为使用 CMS GC
### 5. Concurrent Mode Failure
#### 5. Concurrent Mode Failure
执行 CMS GC 的过程中同时有对象要放入老年代而此时老年代空间不足可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足便会报 Concurrent Mode Failure 错误并触发 Full GC
# 类加载机制
## 类加载机制
类是在运行期间第一次使用时动态加载的而不是一次性加载所有类因为如果一次性加载那么会占用很多的内存
## 类的生命周期
### 类的生命周期
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/335fe19c-4a76-45ab-9320-88c90d6a0d7e.png" width="600px"> </div><br>
@ -470,11 +451,11 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
- 使用Using
- 卸载Unloading
## 类加载过程
### 类加载过程
包含了加载验证准备解析和初始化这 5 个阶段
### 1. 加载
#### 1. 加载
加载是类加载的一个阶段注意不要混淆
@ -492,11 +473,11 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
- 运行时计算生成例如动态代理技术 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 的代理类的二进制字节流
- 由其他文件生成例如由 JSP 文件生成对应的 Class
### 2. 验证
#### 2. 验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求并且不会危害虚拟机自身的安全
### 3. 准备
#### 3. 准备
类变量是被 static 修饰的变量准备阶段为类变量分配内存并设置初始值使用的是方法区的内存
@ -514,18 +495,18 @@ public static int value = 123;
public static final int value = 123;
```
### 4. 解析
#### 4. 解析
将常量池的符号引用替换为直接引用的过程
其中解析过程在某些情况下可以在初始化阶段之后再开始这是为了支持 Java 的动态绑定
### 5. 初始化
#### 5. 初始化
<div data="modify -->"></div>
初始化阶段才真正开始执行类中定义的 Java 程序代码初始化阶段是虚拟机执行类构造器 &lt;clinit>() 方法的过程在准备阶段类变量已经赋过一次系统要求的初始值而在初始化阶段根据程序员通过程序制定的主观计划去初始化类变量和其它资源
初始化阶段才真正开始执行类中定义的 Java 程序代码初始化阶段是虚拟机执行类构造器 &lt;clinit\>() 方法的过程在准备阶段类变量已经赋过一次系统要求的初始值而在初始化阶段根据程序员通过程序制定的主观计划去初始化类变量和其它资源
&lt;clinit>() 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的编译器收集的顺序由语句在源文件中出现的顺序决定特别注意的是静态语句块只能访问到定义在它之前的类变量定义在它之后的类变量只能赋值不能访问例如以下代码
&lt;clinit\>() 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的编译器收集的顺序由语句在源文件中出现的顺序决定特别注意的是静态语句块只能访问到定义在它之前的类变量定义在它之后的类变量只能赋值不能访问例如以下代码
```java
public class Test {
@ -537,7 +518,7 @@ public class Test {
}
```
由于父类的 &lt;clinit>() 方法先执行也就意味着父类中定义的静态语句块的执行要优先于子类例如以下代码
由于父类的 &lt;clinit\>() 方法先执行也就意味着父类中定义的静态语句块的执行要优先于子类例如以下代码
```java
static class Parent {
@ -556,13 +537,13 @@ public static void main(String[] args) {
}
```
接口中不可以使用静态语句块但仍然有类变量初始化的赋值操作因此接口与类一样都会生成 &lt;clinit>() 方法但接口与类不同的是执行接口的 &lt;clinit>() 方法不需要先执行父接口的 &lt;clinit>() 方法只有当父接口中定义的变量使用时父接口才会初始化另外接口的实现类在初始化时也一样不会执行接口的 &lt;clinit>() 方法
接口中不可以使用静态语句块但仍然有类变量初始化的赋值操作因此接口与类一样都会生成 &lt;clinit\>() 方法但接口与类不同的是执行接口的 &lt;clinit\>() 方法不需要先执行父接口的 &lt;clinit\>() 方法只有当父接口中定义的变量使用时父接口才会初始化另外接口的实现类在初始化时也一样不会执行接口的 &lt;clinit\>() 方法
虚拟机会保证一个类的 &lt;clinit>() 方法在多线程环境下被正确的加锁和同步如果多个线程同时初始化一个类只会有一个线程执行这个类的 &lt;clinit>() 方法其它线程都会阻塞等待直到活动线程执行 &lt;clinit>() 方法完毕如果在一个类的 &lt;clinit>() 方法中有耗时的操作就可能造成多个线程阻塞在实际过程中此种阻塞很隐蔽
虚拟机会保证一个类的 &lt;clinit\>() 方法在多线程环境下被正确的加锁和同步如果多个线程同时初始化一个类只会有一个线程执行这个类的 &lt;clinit\>() 方法其它线程都会阻塞等待直到活动线程执行 &lt;clinit\>() 方法完毕如果在一个类的 &lt;clinit\>() 方法中有耗时的操作就可能造成多个线程阻塞在实际过程中此种阻塞很隐蔽
## 类初始化时机
### 类初始化时机
### 1. 主动引用
#### 1. 主动引用
虚拟机规范中并没有强制约束何时进行加载但是规范严格规定了有且只有下列五种情况必须对类进行初始化加载验证准备都会随之发生
@ -576,7 +557,7 @@ public static void main(String[] args) {
- 当使用 JDK 1.7 的动态语言支持时如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄并且这个方法句柄所对应的类没有进行过初始化则需要先触发其初始化
### 2. 被动引用
#### 2. 被动引用
以上 5 种场景中的行为称为对一个类进行主动引用除此之外所有引用类的方式都不会触发初始化称为被动引用被动引用的常见例子包括
@ -598,13 +579,13 @@ SuperClass[] sca = new SuperClass[10];
System.out.println(ConstClass.HELLOWORLD);
```
## 类与类加载器
### 类与类加载器
两个类相等需要类本身相等并且使用同一个类加载器进行加载这是因为每一个类加载器都拥有一个独立的类名称空间
这里的相等包括类的 Class 对象的 equals() 方法isAssignableFrom() 方法isInstance() 方法的返回结果为 true也包括使用 instanceof 关键字做对象所属关系判定结果为 true
## 类加载器分类
### 类加载器分类
Java 虚拟机的角度来讲只存在以下两种不同的类加载器
@ -614,13 +595,13 @@ System.out.println(ConstClass.HELLOWORLD);
Java 开发人员的角度看类加载器可以划分得更细致一些
- 启动类加载器Bootstrap ClassLoader此类加载器负责将存放在 &lt;JRE_HOME>\lib 目录中的或者被 -Xbootclasspath 参数所指定的路径中的并且是虚拟机识别的仅按照文件名识别 rt.jar名字不符合的类库即使放在 lib 目录中也不会被加载类库加载到虚拟机内存中启动类加载器无法被 Java 程序直接引用用户在编写自定义类加载器时如果需要把加载请求委派给启动类加载器直接使用 null 代替即可
- 启动类加载器Bootstrap ClassLoader此类加载器负责将存放在 &lt;JRE_HOME\>\lib 目录中的或者被 -Xbootclasspath 参数所指定的路径中的并且是虚拟机识别的仅按照文件名识别 rt.jar名字不符合的类库即使放在 lib 目录中也不会被加载类库加载到虚拟机内存中启动类加载器无法被 Java 程序直接引用用户在编写自定义类加载器时如果需要把加载请求委派给启动类加载器直接使用 null 代替即可
- 扩展类加载器Extension ClassLoader这个类加载器是由 ExtClassLoadersun.misc.Launcher$ExtClassLoader实现的它负责将 &lt;JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中开发者可以直接使用扩展类加载器
- 扩展类加载器Extension ClassLoader这个类加载器是由 ExtClassLoadersun.misc.Launcher$ExtClassLoader实现的它负责将 &lt;JAVA_HOME\>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中开发者可以直接使用扩展类加载器
- 应用程序类加载器Application ClassLoader这个类加载器是由 AppClassLoadersun.misc.Launcher$AppClassLoader实现的由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值因此一般称为系统类加载器它负责加载用户类路径ClassPath上所指定的类库开发者可以直接使用这个类加载器如果应用程序中没有自定义过自己的类加载器一般情况下这个就是程序中默认的类加载器
## 双亲委派模型
### 双亲委派模型
应用程序是由三种类加载器互相配合从而实现类加载除此之外还可以加入自己定义的类加载器
@ -628,17 +609,17 @@ System.out.println(ConstClass.HELLOWORLD);
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/0dd2d40a-5b2b-4d45-b176-e75a4cd4bdbf.png" width="500px"> </div><br>
### 1. 工作过程
#### 1. 工作过程
一个类加载器首先将类加载请求转发到父类加载器只有当父类加载器无法完成时才尝试自己加载
### 2. 好处
#### 2. 好处
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系从而使得基础类得到统一
例如 java.lang.Object 存放在 rt.jar 如果编写另外一个 java.lang.Object 并放到 ClassPath 程序可以编译通过由于双亲委派模型的存在所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高这是因为 rt.jar 中的 Object 使用的是启动类加载器 ClassPath 中的 Object 使用的是应用程序类加载器rt.jar 中的 Object 优先级更高那么程序中所有的 Object 都是这个 Object
### 3. 实现
#### 3. 实现
以下是抽象类 java.lang.ClassLoader 的代码片段其中的 loadClass() 方法运行过程如下先检查类是否已经加载过如果没有则让父类加载器去加载当父类加载器加载失败时抛出 ClassNotFoundException此时尝试自己去加载
@ -686,7 +667,7 @@ public abstract class ClassLoader {
}
```
## 自定义类加载器实现
### 自定义类加载器实现
以下代码中的 FileSystemClassLoader 是自定义类加载器继承自 java.lang.ClassLoader用于加载文件系统上的类它首先根据类的全名在文件系统上查找类的字节代码文件.class 文件然后读取该文件内容最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例
@ -735,7 +716,7 @@ public class FileSystemClassLoader extends ClassLoader {
}
```
# 参考资料
## 参考资料
- 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.
- [Chapter 2. The Structure of the Java Virtual Machine](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4)
@ -751,10 +732,3 @@ public class FileSystemClassLoader extends ClassLoader {
- [深入探讨 Java 类加载器](https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html#code6)
- [Guide to WeakHashMap in Java](http://www.baeldung.com/java-weakhashmap)
- [Tomcat example source code file (ConcurrentCache.java)](https://alvinalexander.com/java/jwarehouse/apache-tomcat-6.0.16/java/org/apache/el/util/ConcurrentCache.java.shtml)
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,10 +1,12 @@
# Leetcode 题解 - 二分查找
<!-- GFM-TOC -->
* [1. 求开方](#1-求开方)
* [2. 大于给定元素的最小元素](#2-大于给定元素的最小元素)
* [3. 有序数组的 Single Element](#3-有序数组的-single-element)
* [4. 第一个错误的版本](#4-第一个错误的版本)
* [5. 旋转数组的最小数字](#5-旋转数组的最小数字)
* [6. 查找区间](#6-查找区间)
* [Leetcode 题解 - 二分查找](#leetcode-题解---二分查找)
* [1. 求开方](#1-求开方)
* [2. 大于给定元素的最小元素](#2-大于给定元素的最小元素)
* [3. 有序数组的 Single Element](#3-有序数组的-single-element)
* [4. 第一个错误的版本](#4-第一个错误的版本)
* [5. 旋转数组的最小数字](#5-旋转数组的最小数字)
* [6. 查找区间](#6-查找区间)
<!-- GFM-TOC -->
@ -75,12 +77,12 @@ public int binarySearch(int[] nums, int key) {
该实现和正常实现有以下不同
- h 的赋值表达式为 h = m
- 循环条件为 l < h
- 循环条件为 l \< h
- 最后返回 l 而不是 -1
nums[m] >= key 的情况下可以推导出最左 key 位于 [l, m] 区间中这是一个闭区间h 的赋值表达式为 h = m因为 m 位置也可能是解
nums[m] \>= key 的情况下可以推导出最左 key 位于 [l, m] 区间中这是一个闭区间h 的赋值表达式为 h = m因为 m 位置也可能是解
h 的赋值表达式为 h = m 的情况下如果循环条件为 l <= h那么会出现循环无法退出的情况因此循环条件只能是 l < h以下演示了循环条件为 l <= h 时循环无法退出的情况
h 的赋值表达式为 h = m 的情况下如果循环条件为 l \<= h那么会出现循环无法退出的情况因此循环条件只能是 l \< h以下演示了循环条件为 l \<= h 时循环无法退出的情况
```text
nums = {0, 1, 2}, key = 1
@ -94,7 +96,7 @@ l m h
当循环体退出时不表示没有查找到 key因此最后返回的结果不应该为 -1为了验证有没有查找到需要在调用端判断一下返回位置上的值和 key 是否相等
# 1. 求开方
## 1. 求开方
69\. Sqrt(x) (Easy)
@ -111,7 +113,7 @@ Explanation: The square root of 8 is 2.82842..., and since we want to return an
一个数 x 的开方 sqrt 一定在 0 \~ x 之间并且满足 sqrt == x / sqrt可以利用二分查找在 0 \~ x 之间查找 sqrt
对于 x = 8它的开方是 2.82842...最后应该返回 2 而不是 3在循环条件为 l <= h 并且循环退出时h 总是比 l 1也就是说 h = 2l = 3因此最后的返回值应该为 h 而不是 l
对于 x = 8它的开方是 2.82842...最后应该返回 2 而不是 3在循环条件为 l \<= h 并且循环退出时h 总是比 l 1也就是说 h = 2l = 3因此最后的返回值应该为 h 而不是 l
```java
public int mySqrt(int x) {
@ -134,7 +136,7 @@ public int mySqrt(int x) {
}
```
# 2. 大于给定元素的最小元素
## 2. 大于给定元素的最小元素
744\. Find Smallest Letter Greater Than Target (Easy)
@ -170,7 +172,7 @@ public char nextGreatestLetter(char[] letters, char target) {
}
```
# 3. 有序数组的 Single Element
## 3. 有序数组的 Single Element
540\. Single Element in a Sorted Array (Medium)
@ -185,11 +187,11 @@ Output: 2
要求以 O(logN) 时间复杂度进行求解因此不能遍历数组并进行异或操作来求解这么做的时间复杂度为 O(N)
index Single Element 在数组中的位置 index 之后数组中原来存在的成对状态被改变如果 m 为偶数并且 m + 1 < index那么 nums[m] == nums[m + 1]m + 1 >= index那么 nums[m] != nums[m + 1]
index Single Element 在数组中的位置 index 之后数组中原来存在的成对状态被改变如果 m 为偶数并且 m + 1 \< index那么 nums[m] == nums[m + 1]m + 1 \>= index那么 nums[m] != nums[m + 1]
从上面的规律可以知道如果 nums[m] == nums[m + 1]那么 index 所在的数组位置为 [m + 2, h]此时令 l = m + 2如果 nums[m] != nums[m + 1]那么 index 所在的数组位置为 [l, m]此时令 h = m
因为 h 的赋值表达式为 h = m那么循环条件也就只能使用 l < h 这种形式
因为 h 的赋值表达式为 h = m那么循环条件也就只能使用 l \< h 这种形式
```java
public int singleNonDuplicate(int[] nums) {
@ -209,7 +211,7 @@ public int singleNonDuplicate(int[] nums) {
}
```
# 4. 第一个错误的版本
## 4. 第一个错误的版本
278\. First Bad Version (Easy)
@ -219,7 +221,7 @@ public int singleNonDuplicate(int[] nums) {
如果第 m 个版本出错则表示第一个错误的版本在 [l, m] 之间 h = m否则第一个错误的版本在 [m + 1, h] 之间 l = m + 1
因为 h 的赋值表达式为 h = m因此循环条件为 l < h
因为 h 的赋值表达式为 h = m因此循环条件为 l \< h
```java
public int firstBadVersion(int n) {
@ -236,7 +238,7 @@ public int firstBadVersion(int n) {
}
```
# 5. 旋转数组的最小数字
## 5. 旋转数组的最小数字
153\. Find Minimum in Rotated Sorted Array (Medium)
@ -262,7 +264,7 @@ public int findMin(int[] nums) {
}
```
# 6. 查找区间
## 6. 查找区间
34\. Find First and Last Position of Element in Sorted Array
@ -312,10 +314,3 @@ nums = [2,2], target = 2
```
如果 h 的取值为 nums.length - 1那么 last = findFirst(nums, target + 1) - 1 = 1 - 1 = 0这是因为 findLeft 只会返回 [0, nums.length - 1] 范围的值对于 findFirst([2,2], 3) 我们希望返回 3 插入 nums 中的位置也就是数组最后一个位置再往后一个位置 nums.length所以我们需要将 h 取值为 nums.length从而使得 findFirst返回的区间更大能够覆盖 target 大于 nums 最后一个元素的情况
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,22 +1,24 @@
# Leetcode 题解 - 位运算
<!-- GFM-TOC -->
* [0. 原理](#0-原理)
* [1. 统计两个数的二进制表示有多少位不同](#1-统计两个数的二进制表示有多少位不同)
* [2. 数组中唯一一个不重复的元素](#2-数组中唯一一个不重复的元素)
* [3. 找出数组中缺失的那个数](#3-找出数组中缺失的那个数)
* [4. 数组中不重复的两个元素](#4-数组中不重复的两个元素)
* [5. 翻转一个数的比特位](#5-翻转一个数的比特位)
* [6. 不用额外变量交换两个整数](#6-不用额外变量交换两个整数)
* [7. 判断一个数是不是 2 n 次方](#7-判断一个数是不是-2--n-次方)
* [8. 判断一个数是不是 4 n 次方](#8--判断一个数是不是-4--n-次方)
* [9. 判断一个数的位级表示是否不会出现连续的 0 1](#9-判断一个数的位级表示是否不会出现连续的-0--1)
* [10. 一个数的补码](#10-一个数的补码)
* [11. 实现整数的加法](#11-实现整数的加法)
* [12. 字符串数组最大乘积](#12-字符串数组最大乘积)
* [13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数](#13-统计从-0-\~-n-每个数的二进制表示中-1-的个数)
* [Leetcode 题解 - 位运算](#leetcode-题解---位运算)
* [0. 原理](#0-原理)
* [1. 统计两个数的二进制表示有多少位不同](#1-统计两个数的二进制表示有多少位不同)
* [2. 数组中唯一一个不重复的元素](#2-数组中唯一一个不重复的元素)
* [3. 找出数组中缺失的那个数](#3-找出数组中缺失的那个数)
* [4. 数组中不重复的两个元素](#4-数组中不重复的两个元素)
* [5. 翻转一个数的比特位](#5-翻转一个数的比特位)
* [6. 不用额外变量交换两个整数](#6-不用额外变量交换两个整数)
* [7. 判断一个数是不是 2 n 次方](#7-判断一个数是不是-2--n-次方)
* [8. 判断一个数是不是 4 n 次方](#8--判断一个数是不是-4--n-次方)
* [9. 判断一个数的位级表示是否不会出现连续的 0 1](#9-判断一个数的位级表示是否不会出现连续的-0--1)
* [10. 求一个数的补码](#10-求一个数的补码)
* [11. 实现整数的加法](#11-实现整数的加法)
* [12. 字符串数组最大乘积](#12-字符串数组最大乘积)
* [13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数](#13-统计从-0-\~-n-每个数的二进制表示中-1-的个数)
<!-- GFM-TOC -->
# 0. 原理
## 0. 原理
**基本原理**
@ -76,7 +78,7 @@ n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1和 n&(n-1
**移位运算**
\>\> n 为算术右移相当于除以 2n例如 -7 \>\> 2 = -2
\\>\\> n 为算术右移相当于除以 2n例如 -7 \\>\\> 2 = -2
```
11111111111111111111111111111001 >> 2
@ -84,7 +86,7 @@ n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1和 n&(n-1
11111111111111111111111111111110
```
\>\>\> n 为无符号右移左边会补上 0例如 -7 \>\>\> 2 = 1073741822
\\>\\>\\> n 为无符号右移左边会补上 0例如 -7 \\>\\>\\> 2 = 1073741822
```
11111111111111111111111111111001 >>> 2
@ -92,7 +94,7 @@ n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1和 n&(n-1
00111111111111111111111111111111
```
<< n 为算术左移相当于乘以 2n-7 << 2 = -28
\<\< n 为算术左移相当于乘以 2n-7 \<\< 2 = -28
```
11111111111111111111111111111001 << 2
@ -104,11 +106,11 @@ n-(n&(-n)) 则可以去除 n 的位级表示中最低的那一位 1和 n&(n-1
要获取 111111111 0 取反即可\~0
要得到只有第 i 位为 1 mask 1 向左移动 i-1 位即可1<<(i-1) 例如 1<<4 得到只有第 5 位为 1 mask 00010000
要得到只有第 i 位为 1 mask 1 向左移动 i-1 位即可1\<\<(i-1) 例如 1\<\<4 得到只有第 5 位为 1 mask 00010000
要得到 1 i 位为 1 mask(1<<i)-1 即可例如将 (1<<4)-1 = 00010000-1 = 00001111
要得到 1 i 位为 1 mask(1\<\<i)-1 即可例如将 (1\<\<4)-1 = 00010000-1 = 00001111
要得到 1 i 位为 0 mask只需将 1 i 位为 1 mask 取反 \~((1<<i)-1)
要得到 1 i 位为 0 mask只需将 1 i 位为 1 mask 取反 \~((1\<\<i)-1)
**Java 中的位操作**
@ -118,7 +120,7 @@ static int Integer.highestOneBit(); // 获得最高位
static String toBinaryString(int i); // 转换为二进制表示的字符串
```
# 1. 统计两个数的二进制表示有多少位不同
## 1. 统计两个数的二进制表示有多少位不同
461. Hamming Distance (Easy)
@ -173,7 +175,7 @@ public int hammingDistance(int x, int y) {
}
```
# 2. 数组中唯一一个不重复的元素
## 2. 数组中唯一一个不重复的元素
136\. Single Number (Easy)
@ -194,7 +196,7 @@ public int singleNumber(int[] nums) {
}
```
# 3. 找出数组中缺失的那个数
## 3. 找出数组中缺失的那个数
268\. Missing Number (Easy)
@ -217,7 +219,7 @@ public int missingNumber(int[] nums) {
}
```
# 4. 数组中不重复的两个元素
## 4. 数组中不重复的两个元素
260\. Single Number III (Medium)
@ -243,7 +245,7 @@ public int[] singleNumber(int[] nums) {
}
```
# 5. 翻转一个数的比特位
## 5. 翻转一个数的比特位
190\. Reverse Bits (Easy)
@ -290,7 +292,7 @@ private int reverseByte(byte b) {
}
```
# 6. 不用额外变量交换两个整数
## 6. 不用额外变量交换两个整数
[程序员代码面试指南 P317](#)
@ -300,7 +302,7 @@ b = a ^ b;
a = a ^ b;
```
# 7. 判断一个数是不是 2 n 次方
## 7. 判断一个数是不是 2 n 次方
231\. Power of Two (Easy)
@ -322,7 +324,7 @@ public boolean isPowerOfTwo(int n) {
}
```
# 8. 判断一个数是不是 4 n 次方
## 8. 判断一个数是不是 4 n 次方
342\. Power of Four (Easy)
@ -344,7 +346,7 @@ public boolean isPowerOfFour(int num) {
}
```
# 9. 判断一个数的位级表示是否不会出现连续的 0 1
## 9. 判断一个数的位级表示是否不会出现连续的 0 1
693\. Binary Number with Alternating Bits (Easy)
@ -371,7 +373,7 @@ public boolean hasAlternatingBits(int n) {
}
```
# 10. 求一个数的补码
## 10. 求一个数的补码
476\. Number Complement (Easy)
@ -428,15 +430,15 @@ public int findComplement(int num) {
}
```
# 11. 实现整数的加法
## 11. 实现整数的加法
371\. Sum of Two Integers (Easy)
[Leetcode](https://leetcode.com/problems/sum-of-two-integers/description/) / [力扣](https://leetcode-cn.com/problems/sum-of-two-integers/description/)
a ^ b 表示没有考虑进位的情况下两数的和(a & b) << 1 就是进位
a ^ b 表示没有考虑进位的情况下两数的和(a & b) \<\< 1 就是进位
递归会终止的原因是 (a & b) << 1 最右边会多一个 0那么继续递归进位最右边的 0 会慢慢增多最后进位会变为 0递归终止
递归会终止的原因是 (a & b) \<\< 1 最右边会多一个 0那么继续递归进位最右边的 0 会慢慢增多最后进位会变为 0递归终止
```java
public int getSum(int a, int b) {
@ -444,7 +446,7 @@ public int getSum(int a, int b) {
}
```
# 12. 字符串数组最大乘积
## 12. 字符串数组最大乘积
318\. Maximum Product of Word Lengths (Medium)
@ -481,7 +483,7 @@ public int maxProduct(String[] words) {
}
```
# 13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数
## 13. 统计从 0 \~ n 每个数的二进制表示中 1 的个数
338\. Counting Bits (Medium)
@ -499,10 +501,3 @@ public int[] countBits(int num) {
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,10 +1,12 @@
# Leetcode 题解 - 分治
<!-- GFM-TOC -->
* [1. 给表达式加括号](#1-给表达式加括号)
* [2. 不同的二叉搜索树](#2-不同的二叉搜索树)
* [Leetcode 题解 - 分治](#leetcode-题解---分治)
* [1. 给表达式加括号](#1-给表达式加括号)
* [2. 不同的二叉搜索树](#2-不同的二叉搜索树)
<!-- GFM-TOC -->
# 1. 给表达式加括号
## 1. 给表达式加括号
241\. Different Ways to Add Parentheses (Medium)
@ -51,7 +53,7 @@ public List<Integer> diffWaysToCompute(String input) {
}
```
# 2. 不同的二叉搜索树
## 2. 不同的二叉搜索树
95\. Unique Binary Search Trees II (Medium)
@ -108,10 +110,3 @@ private List<TreeNode> generateSubtrees(int s, int e) {
return res;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,53 +1,53 @@
# Leetcode 题解 - 动态规划
<!-- GFM-TOC -->
* [斐波那契数列](#斐波那契数列)
* [1. 爬楼梯](#1-爬楼梯)
* [2. 强盗抢劫](#2-强盗抢劫)
* [3. 强盗在环形街区抢劫](#3-强盗在环形街区抢劫)
* [4. 信件错排](#4-信件错排)
* [5. 母牛生产](#5-母牛生产)
* [矩阵路径](#矩阵路径)
* [1. 矩阵的最小路径](#1-矩阵的最小路径)
* [2. 矩阵的路径](#2-矩阵的路径)
* [数组区间](#数组区间)
* [1. 数组区间](#1-数组区间)
* [2. 数组中等差递增子区间的个数](#2-数组中等差递增子区间的个数)
* [分割整数](#分割整)
* [1. 分割整数的最大乘积](#1-分割整数的最大乘积)
* [2. 按平方数来分割整数](#2-按平方数来分割整数)
* [3. 分割整数构成字母字符串](#3-分割整数构成字母字符串)
* [最长递增子序列](#最长递增子序列)
* [1. 最长递增子序列](#1-最长递增子序列)
* [2. 一组整数对能够构成的最长链](#2-一组整数对能够构成的最长链)
* [3. 最长摆动子序列](#3-最长摆动子序列)
* [最长公共子序列](#最长公共子序列)
* [1. 最长公共子序列](#1-最长公共子序列)
* [0-1 背包](#0-1-背包)
* [1. 划分数组为和相等的两部分](#1-划分数组为和相等的两部分)
* [2. 改变一组数的正负号使得它们的和为一给定数](#2-改变一组数的正负号使得它们的和为一给定数)
* [3. 01 字符构成最多的字符串](#3-01-字符构成最多的字符串)
* [4. 找零钱的最少硬币数](#4-找零钱的最少硬币数)
* [5. 找零钱的硬币数组合](#5-找零钱的硬币数组合)
* [6. 字符串按单词列表分割](#6-字符串按单词列表分割)
* [7. 组合总和](#7-组合总和)
* [股票交易](#股票交易)
* [1. 需要冷却期的股票交易](#1-需要冷却期的股票交易)
* [2. 需要交易费用的股票交易](#2-需要交易费用的股票交易)
* [3. 只能进行两次的股票交易](#3-只能进行两次的股票交易)
* [4. 只能进行 k 次的股票交易](#4-只能进行-k-次的股票交易)
* [字符串编辑](#字符串编辑)
* [1. 删除两个字符串的字符使它们相等](#1-删除两个字符串的字符使它们相等)
* [2. 编辑距离](#2-编辑距离)
* [3. 复制粘贴字符](#3-复制粘贴字符)
* [Leetcode 题解 - 动态规划](#leetcode-题解---动态规划)
* [斐波那契数列](#斐波那契数列)
* [1. 爬楼梯](#1-爬楼梯)
* [2. 强盗抢劫](#2-强盗抢劫)
* [3. 强盗在环形街区抢劫](#3-强盗在环形街区抢劫)
* [4. 信件错排](#4-信件错排)
* [5. 母牛生产](#5-母牛生产)
* [矩阵路径](#矩阵路径)
* [1. 矩阵的最小路径](#1-矩阵的最小路径)
* [2. 矩阵的总路径数](#2-矩阵的总路径数)
* [数组区间](#数组区间)
* [1. 数组区间和](#1-数组区间和)
* [2. 数组中等差递增子区间的个数](#2-数组中等差递增子区间的个)
* [分割整数](#分割整数)
* [1. 分割整数的最大乘积](#1-分割整数的最大乘积)
* [2. 按平方数来分割整数](#2-按平方数来分割整数)
* [3. 分割整数构成字母字符串](#3-分割整数构成字母字符串)
* [最长递增子序列](#最长递增子序列)
* [1. 最长递增子序列](#1-最长递增子序列)
* [2. 一组整数对能够构成的最长链](#2-一组整数对能够构成的最长链)
* [3. 最长摆动子序列](#3-最长摆动子序列)
* [最长公共子序列](#最长公共子序列)
* [1. 最长公共子序列](#1-最长公共子序列)
* [0-1 背包](#0-1-背包)
* [1. 划分数组为和相等的两部分](#1-划分数组为和相等的两部分)
* [2. 改变一组数的正负号使得它们的和为一给定数](#2-改变一组数的正负号使得它们的和为一给定数)
* [3. 01 字符构成最多的字符串](#3-01-字符构成最多的字符串)
* [4. 找零钱的最少硬币数](#4-找零钱的最少硬币数)
* [5. 找零钱的硬币数组合](#5-找零钱的硬币数组合)
* [6. 字符串按单词列表分割](#6-字符串按单词列表分割)
* [7. 组合总和](#7-组合总和)
* [股票交易](#股票交易)
* [1. 需要冷却期的股票交易](#1-需要冷却期的股票交易)
* [2. 需要交易费用的股票交易](#2-需要交易费用的股票交易)
* [3. 只能进行次的股票交易](#3-只能进行次的股票交易)
* [4. 只能进行 k 次的股票交易](#4-只能进行-k-次的股票交易)
* [字符串编辑](#字符串编辑)
* [1. 删除两个字符串的字符使它们相等](#1-删除两个字符串的字符使它们相等)
* [2. 编辑距离](#2-编辑距离)
* [3. 复制粘贴字符](#3-复制粘贴字符)
<!-- GFM-TOC -->
递归和动态规划都是将原问题拆成多个子问题然后求解他们之间最本质的区别是动态规划保存了子问题的解避免重复计算
# 斐波那契数列
## 斐波那契数列
## 1. 爬楼梯
### 1. 爬楼梯
70\. Climbing Stairs (Easy)
@ -80,7 +80,7 @@ public int climbStairs(int n) {
}
```
## 2. 强盗抢劫
### 2. 强盗抢劫
198\. House Robber (Easy)
@ -108,7 +108,7 @@ public int rob(int[] nums) {
}
```
## 3. 强盗在环形街区抢劫
### 3. 强盗在环形街区抢劫
213\. House Robber II (Medium)
@ -137,7 +137,7 @@ private int rob(int[] nums, int first, int last) {
}
```
## 4. 信件错排
### 4. 信件错排
题目描述 N 信封它们被打乱求错误装信方式的数量
@ -152,7 +152,7 @@ private int rob(int[] nums, int first, int last) {
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/da1f96b9-fd4d-44ca-8925-fb14c5733388.png" width="350px"> </div><br>
## 5. 母牛生产
### 5. 母牛生产
[程序员代码面试指南-P181](#)
@ -164,9 +164,9 @@ private int rob(int[] nums, int first, int last) {
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/879814ee-48b5-4bcb-86f5-dcc400cb81ad.png" width="250px"> </div><br>
# 矩阵路径
## 矩阵路径
## 1. 矩阵的最小路径和
### 1. 矩阵的最小路径和
64\. Minimum Path Sum (Medium)
@ -204,7 +204,7 @@ public int minPathSum(int[][] grid) {
}
```
## 2. 矩阵的总路径数
### 2. 矩阵的总路径数
62\. Unique Paths (Medium)
@ -241,9 +241,9 @@ public int uniquePaths(int m, int n) {
}
```
# 数组区间
## 数组区间
## 1. 数组区间和
### 1. 数组区间和
303\. Range Sum Query - Immutable (Easy)
@ -277,7 +277,7 @@ class NumArray {
}
```
## 2. 数组中等差递增子区间的个数
### 2. 数组中等差递增子区间的个数
413\. Arithmetic Slices (Medium)
@ -336,9 +336,9 @@ public int numberOfArithmeticSlices(int[] A) {
}
```
# 分割整数
## 分割整数
## 1. 分割整数的最大乘积
### 1. 分割整数的最大乘积
343\. Integer Break (Medim)
@ -359,7 +359,7 @@ public int integerBreak(int n) {
}
```
## 2. 按平方数来分割整数
### 2. 按平方数来分割整数
279\. Perfect Squares(Medium)
@ -397,7 +397,7 @@ private List<Integer> generateSquareList(int n) {
}
```
## 3. 分割整数构成字母字符串
### 3. 分割整数构成字母字符串
91\. Decode Ways (Medium)
@ -431,7 +431,7 @@ public int numDecodings(String s) {
}
```
# 最长递增子序列
## 最长递增子序列
已知一个序列 {S<sub>1</sub>, S<sub>2</sub>,...,S<sub>n</sub>}取出若干数组成新的序列 {S<sub>i1</sub>, S<sub>i2</sub>,..., S<sub>im</sub>}其中 i1i2 ... im 保持递增即新序列中各个数仍然保持原数列中的先后顺序称新序列为原序列的一个 **子序列**
@ -447,7 +447,7 @@ public int numDecodings(String s) {
对于一个长度为 N 的序列最长递增子序列并不一定会以 S<sub>N</sub> 为结尾因此 dp[N] 不是序列的最长递增子序列的长度需要遍历 dp 数组找出最大值才是所要的结果max{ dp[i] | 1 <= i <= N} 即为所求
## 1. 最长递增子序列
### 1. 最长递增子序列
300\. Longest Increasing Subsequence (Medium)
@ -485,7 +485,7 @@ return ret;
定义一个 tails 数组其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素对于一个元素 x
- 如果它大于 tails 数组所有的值那么把它添加到 tails 后面表示最长递增子序列长度加 1
- 如果 tails[i-1] < x <= tails[i]那么更新 tails[i] = x
- 如果 tails[i-1] \< x \<= tails[i]那么更新 tails[i] = x
例如对于数组 [4,3,6,5]
@ -531,7 +531,7 @@ private int binarySearch(int[] tails, int len, int key) {
}
```
## 2. 一组整数对能够构成的最长链
### 2. 一组整数对能够构成的最长链
646\. Maximum Length of Pair Chain (Medium)
@ -543,7 +543,7 @@ Output: 2
Explanation: The longest chain is [1,2] -> [3,4]
```
题目描述对于 (a, b) (c, d) 如果 b < c则它们可以构成一条链
题目描述对于 (a, b) (c, d) 如果 b \< c则它们可以构成一条链
```java
public int findLongestChain(int[][] pairs) {
@ -565,7 +565,7 @@ public int findLongestChain(int[][] pairs) {
}
```
## 3. 最长摆动子序列
### 3. 最长摆动子序列
376\. Wiggle Subsequence (Medium)
@ -603,7 +603,7 @@ public int wiggleMaxLength(int[] nums) {
}
```
# 最长公共子序列
## 最长公共子序列
对于两个子序列 S1 S2找出它们最长的公共子序列
@ -626,7 +626,7 @@ public int wiggleMaxLength(int[] nums) {
- 在最长递增子序列中dp[i] 表示以 S<sub>i</sub> 为结尾的最长递增子序列长度子序列必须包含 S<sub>i</sub> 在最长公共子序列中dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度不一定包含 S1<sub>i</sub> S2<sub>j</sub>
- 在求最终解时最长公共子序列中 dp[N][M] 就是最终解而最长递增子序列中 dp[N] 不是最终解因为以 S<sub>N</sub> 为结尾的最长递增子序列不一定是整个序列最长递增子序列需要遍历一遍 dp 数组找到最大者
## 1. 最长公共子序列
### 1. 最长公共子序列
1143\. Longest Common Subsequence
@ -649,7 +649,7 @@ public int wiggleMaxLength(int[] nums) {
}
```
# 0-1 背包
## 0-1 背包
有一个容量为 N 的背包要用这个背包装下物品的价值最大这些物品有两个属性体积 w 和价值 v
@ -730,7 +730,7 @@ public int knapsack(int W, int N, int[] weights, int[] values) {
- 其它物品之间相互约束或者依赖
## 1. 划分数组为和相等的两部分
### 1. 划分数组为和相等的两部分
416\. Partition Equal Subset Sum (Medium)
@ -772,7 +772,7 @@ private int computeArraySum(int[] nums) {
}
```
## 2. 改变一组数的正负号使得它们的和为一给定数
### 2. 改变一组数的正负号使得它们的和为一给定数
494\. Target Sum (Medium)
@ -846,7 +846,7 @@ private int findTargetSumWays(int[] nums, int start, int S) {
}
```
## 3. 01 字符构成最多的字符串
### 3. 01 字符构成最多的字符串
474\. Ones and Zeroes (Medium)
@ -886,7 +886,7 @@ public int findMaxForm(String[] strs, int m, int n) {
}
```
## 4. 找零钱的最少硬币数
### 4. 找零钱的最少硬币数
322\. Coin Change (Medium)
@ -930,7 +930,7 @@ public int coinChange(int[] coins, int amount) {
}
```
## 5. 找零钱的硬币数组合
### 5. 找零钱的硬币数组合
518\. Coin Change 2 (Medium)
@ -964,7 +964,7 @@ public int change(int amount, int[] coins) {
}
```
## 6. 字符串按单词列表分割
### 6. 字符串按单词列表分割
139\. Word Break (Medium)
@ -1003,7 +1003,7 @@ public boolean wordBreak(String s, List<String> wordDict) {
}
```
## 7. 组合总和
### 7. 组合总和
377\. Combination Sum IV (Medium)
@ -1046,9 +1046,9 @@ public int combinationSum4(int[] nums, int target) {
}
```
# 股票交易
## 股票交易
## 1. 需要冷却期的股票交易
### 1. 需要冷却期的股票交易
309\. Best Time to Buy and Sell Stock with Cooldown(Medium)
@ -1080,7 +1080,7 @@ public int maxProfit(int[] prices) {
}
```
## 2. 需要交易费用的股票交易
### 2. 需要交易费用的股票交易
714\. Best Time to Buy and Sell Stock with Transaction Fee (Medium)
@ -1121,7 +1121,7 @@ public int maxProfit(int[] prices, int fee) {
```
## 3. 只能进行两次的股票交易
### 3. 只能进行两次的股票交易
123\. Best Time to Buy and Sell Stock III (Hard)
@ -1149,7 +1149,7 @@ public int maxProfit(int[] prices) {
}
```
## 4. 只能进行 k 次的股票交易
### 4. 只能进行 k 次的股票交易
188\. Best Time to Buy and Sell Stock IV (Hard)
@ -1179,9 +1179,9 @@ public int maxProfit(int k, int[] prices) {
}
```
# 字符串编辑
## 字符串编辑
## 1. 删除两个字符串的字符使它们相等
### 1. 删除两个字符串的字符使它们相等
583\. Delete Operation for Two Strings (Medium)
@ -1212,7 +1212,7 @@ public int minDistance(String word1, String word2) {
}
```
## 2. 编辑距离
### 2. 编辑距离
72\. Edit Distance (Hard)
@ -1267,7 +1267,7 @@ public int minDistance(String word1, String word2) {
}
```
## 3. 复制粘贴字符
### 3. 复制粘贴字符
650\. 2 Keys Keyboard (Medium)
@ -1311,10 +1311,3 @@ public int minSteps(int n) {
return dp[n];
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,17 +1,19 @@
# Leetcode 题解 - 双指针
<!-- GFM-TOC -->
* [1. 有序数组的 Two Sum](#1-有序数组的-two-sum)
* [2. 两数平方和](#2-两数平方和)
* [3. 反转字符串中的元音字符](#3-反转字符串中的元音字符)
* [4. 回文字符串](#4-回文字符串)
* [5. 归并两个有序数组](#5-归并两个有序数组)
* [6. 判断链表是否存在环](#6-判断链表是否存在环)
* [7. 最长子序列](#7-最长子序列)
* [Leetcode 题解 - 双指针](#leetcode-题解---双指针)
* [1. 有序数组的 Two Sum](#1-有序数组的-two-sum)
* [2. 两数平方和](#2-两数平方和)
* [3. 反转字符串中的元音字符](#3-反转字符串中的元音字符)
* [4. 回文字符串](#4-回文字符串)
* [5. 归并两个有序数组](#5-归并两个有序数组)
* [6. 判断链表是否存在环](#6-判断链表是否存在环)
* [7. 最长子序列](#7-最长子序列)
<!-- GFM-TOC -->
双指针主要用于遍历数组两个指针指向不同的元素从而协同完成任务
# 1. 有序数组的 Two Sum
## 1. 有序数组的 Two Sum
167\. Two Sum II - Input array is sorted (Easy)
@ -27,8 +29,8 @@ Output: index1=1, index2=2
使用双指针一个指针指向值较小的元素一个指针指向值较大的元素指向较小元素的指针从头向尾遍历指向较大元素的指针从尾向头遍历
- 如果两个指针指向元素的和 sum == target那么得到要求的结果
- 如果 sum > target移动较大的元素使 sum 变小一些
- 如果 sum < target移动较小的元素使 sum 变大一些
- 如果 sum \> target移动较大的元素使 sum 变小一些
- 如果 sum \< target移动较小的元素使 sum 变大一些
数组中的元素最多遍历一次时间复杂度为 O(N)只使用了两个额外变量空间复杂度为 O(1)
@ -52,7 +54,7 @@ public int[] twoSum(int[] numbers, int target) {
}
```
# 2. 两数平方和
## 2. 两数平方和
633\. Sum of Square Numbers (Easy)
@ -92,7 +94,7 @@ Explanation: 1 * 1 + 2 * 2 = 5
}
```
# 3. 反转字符串中的元音字符
## 3. 反转字符串中的元音字符
345\. Reverse Vowels of a String (Easy)
@ -137,7 +139,7 @@ public String reverseVowels(String s) {
}
```
# 4. 回文字符串
## 4. 回文字符串
680\. Valid Palindrome II (Easy)
@ -185,7 +187,7 @@ private boolean isPalindrome(String s, int i, int j) {
}
```
# 5. 归并两个有序数组
## 5. 归并两个有序数组
88\. Merge Sorted Array (Easy)
@ -221,7 +223,7 @@ public void merge(int[] nums1, int m, int[] nums2, int n) {
}
```
# 6. 判断链表是否存在环
## 6. 判断链表是否存在环
141\. Linked List Cycle (Easy)
@ -246,7 +248,7 @@ public boolean hasCycle(ListNode head) {
}
```
# 7. 最长子序列
## 7. 最长子序列
524\. Longest Word in Dictionary through Deleting (Medium)
@ -290,10 +292,3 @@ private boolean isSubstr(String s, String target) {
return j == target.length();
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,8 +1,10 @@
# Leetcode 题解 - 哈希表
<!-- GFM-TOC -->
* [1. 数组中两个数的和为给定值](#1-数组中两个数的和为给定值)
* [2. 判断数组是否含有重复元素](#2-判断数组是否含有重复元素)
* [3. 最长和谐序列](#3-最长和谐序列)
* [4. 最长连续序列](#4-最长连续序列)
* [Leetcode 题解 - 哈希表](#leetcode-题解---哈希表)
* [1. 数组中两个数的和为给定值](#1-数组中两个数的和为给定值)
* [2. 判断数组是否含有重复元素](#2-判断数组是否含有重复元素)
* [3. 最长和谐序列](#3-最长和谐序列)
* [4. 最长连续序列](#4-最长连续序列)
<!-- GFM-TOC -->
@ -15,7 +17,7 @@
[Leetcode](https://leetcode.com/problems/encode-and-decode-tinyurl/description/),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url也可以根据简化的 url 得到原始 url 从而定位到正确的资源<E8B584>) / [力扣](https://leetcode-cn.com/problems/encode-and-decode-tinyurl/description/),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url也可以根据简化的 url 得到原始 url 从而定位到正确的资源<E8B584>)
# 1. 数组中两个数的和为给定值
## 1. 数组中两个数的和为给定值
1\. Two Sum (Easy)
@ -39,7 +41,7 @@ public int[] twoSum(int[] nums, int target) {
}
```
# 2. 判断数组是否含有重复元素
## 2. 判断数组是否含有重复元素
217\. Contains Duplicate (Easy)
@ -55,7 +57,7 @@ public boolean containsDuplicate(int[] nums) {
}
```
# 3. 最长和谐序列
## 3. 最长和谐序列
594\. Longest Harmonious Subsequence (Easy)
@ -85,7 +87,7 @@ public int findLHS(int[] nums) {
}
```
# 4. 最长连续序列
## 4. 最长连续序列
128\. Longest Consecutive Sequence (Hard)
@ -131,10 +133,3 @@ private int maxCount(Map<Integer, Integer> countForNum) {
return max;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,19 +1,21 @@
# Leetcode 题解 -
<!-- GFM-TOC -->
* [二分图](#二分)
* [1. 判断是否为二分图](#1-判断是否为二分图)
* [拓扑排序](#拓扑排序)
* [1. 课程安排的合法性](#1-课程安排的合法性)
* [2. 课程安排的顺序](#2-课程安排的顺序)
* [并查集](#并查集)
* [1. 冗余连接](#1-冗余连接)
* [Leetcode 题解 - ](#leetcode-题解---)
* [二分图](#二分图)
* [1. 判断是否为二分图](#1-判断是否为二分图)
* [拓扑排序](#拓扑排序)
* [1. 课程安排的合法性](#1-课程安排的合法性)
* [2. 课程安排的顺序](#2-课程安排的顺序)
* [并查集](#并查集)
* [1. 冗余连接](#1-冗余连接)
<!-- GFM-TOC -->
# 二分图
## 二分图
如果可以用两种颜色对图中的节点进行着色并且保证相邻的节点颜色不同那么这个图就是二分图
## 1. 判断是否为二分图
### 1. 判断是否为二分图
785\. Is Graph Bipartite? (Medium)
@ -70,11 +72,11 @@ private boolean isBipartite(int curNode, int curColor, int[] colors, int[][] gra
}
```
# 拓扑排序
## 拓扑排序
常用于在具有先序关系的任务规划中
## 1. 课程安排的合法性
### 1. 课程安排的合法性
207\. Course Schedule (Medium)
@ -134,7 +136,7 @@ private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked,
}
```
## 2. 课程安排的顺序
### 2. 课程安排的顺序
210\. Course Schedule II (Medium)
@ -147,7 +149,7 @@ There are a total of 4 courses to take. To take course 3 you should have finishe
使用 DFS 来实现拓扑排序使用一个栈存储后序遍历结果这个栈的逆序结果就是拓扑排序结果
证明对于任何先序关系v->w后序遍历结果可以保证 w 先进入栈中因此栈的逆序结果中 v 会在 w 之前
证明对于任何先序关系v-\>w后序遍历结果可以保证 w 先进入栈中因此栈的逆序结果中 v 会在 w 之前
```java
public int[] findOrder(int numCourses, int[][] prerequisites) {
@ -195,11 +197,11 @@ private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked, List<Int
}
```
# 并查集
## 并查集
并查集可以动态地连通两个点并且可以非常快速地判断两个点是否连通
## 1. 冗余连接
### 1. 冗余连接
684\. Redundant Connection (Medium)
@ -263,10 +265,3 @@ private class UF {
}
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,17 +1,19 @@
# Leetcode 题解 - 字符串
<!-- GFM-TOC -->
* [1. 字符串循环移位包含](#1-字符串循环移位包含)
* [2. 字符串循环移位](#2-字符串循环移位)
* [3. 字符串中单词的翻转](#3-字符串中单词的翻转)
* [4. 两个字符串包含的字符是否完全相同](#4-两个字符串包含的字符是否完全相同)
* [5. 计算一组字符集合可以组成的回文字符串的最大长度](#5-计算一组字符集合可以组成的回文字符串的最大长度)
* [6. 字符串同构](#6-字符串同构)
* [7. 回文子字符串个数](#7-回文子字符串个数)
* [8. 判断一个整数是否是回文数](#8-判断一个整数是否是回文数)
* [9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数](#9-统计二进制字符串中连续-1-和连续-0-数量相同的子字符串个)
* [Leetcode 题解 - 字符串](#leetcode-题解---字符串)
* [1. 字符串循环移位包含](#1-字符串循环移位包含)
* [2. 字符串循环移位](#2-字符串循环移位)
* [3. 字符串中单词的翻转](#3-字符串中单词的翻转)
* [4. 两个字符串包含的字符是否完全相同](#4-两个字符串包含的字符是否完全相同)
* [5. 计算一组字符集合可以组成的回文字符串的最大长度](#5-计算一组字符集合可以组成的回文字符串的最大长度)
* [6. 字符串同构](#6-字符串同构)
* [7. 回文子字符串个](#7-回文子字符串个)
* [8. 判断一个整数是否是回文数](#8-判断一个整数是否是回文)
* [9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数](#9-统计二进制字符串中连续-1-和连续-0-数量相同的子字符串个数)
<!-- GFM-TOC -->
# 1. 字符串循环移位包含
## 1. 字符串循环移位包含
[编程之美 3.1](#)
@ -24,7 +26,7 @@ Return : true
s1 进行循环移位的结果是 s1s1 的子字符串因此只要判断 s2 是否是 s1s1 的子字符串即可
# 2. 字符串循环移位
## 2. 字符串循环移位
[编程之美 2.17](#)
@ -37,7 +39,7 @@ Return "123abcd"
abcd123 中的 abcd 123 单独翻转得到 dcba321然后对整个字符串进行翻转得到 123abcd
# 3. 字符串中单词的翻转
## 3. 字符串中单词的翻转
[程序员代码面试指南](#)
@ -48,7 +50,7 @@ Return "student a am I"
将每个单词翻转然后将整个字符串翻转
# 4. 两个字符串包含的字符是否完全相同
## 4. 两个字符串包含的字符是否完全相同
242\. Valid Anagram (Easy)
@ -81,7 +83,7 @@ public boolean isAnagram(String s, String t) {
}
```
# 5. 计算一组字符集合可以组成的回文字符串的最大长度
## 5. 计算一组字符集合可以组成的回文字符串的最大长度
409\. Longest Palindrome (Easy)
@ -114,7 +116,7 @@ public int longestPalindrome(String s) {
}
```
# 6. 字符串同构
## 6. 字符串同构
205\. Isomorphic Strings (Easy)
@ -144,7 +146,7 @@ public boolean isIsomorphic(String s, String t) {
}
```
# 7. 回文子字符串个数
## 7. 回文子字符串个数
647\. Palindromic Substrings (Medium)
@ -178,7 +180,7 @@ private void extendSubstrings(String s, int start, int end) {
}
```
# 8. 判断一个整数是否是回文数
## 8. 判断一个整数是否是回文数
9\. Palindrome Number (Easy)
@ -205,7 +207,7 @@ public boolean isPalindrome(int x) {
}
```
# 9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数
## 9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数
696\. Count Binary Substrings (Easy)
@ -235,10 +237,3 @@ public int countBinarySubstrings(String s) {
return count;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,22 +1,24 @@
# Leetcode 题解 - 排序
<!-- GFM-TOC -->
* [快速选择](#快速选择)
* [](#)
* [1. Kth Element](#1-kth-element)
* [桶排序](#桶排序)
* [1. 出现频率最多的 k 个元素](#1-出现频率最多的-k-个元素)
* [2. 按照字符出现次数对字符串排序](#2-按照字符出现次数对字符串排序)
* [荷兰国旗问题](#荷兰国旗问题)
* [1. 按颜色进行排序](#1-按颜色进行排序)
* [Leetcode 题解 - 排序](#leetcode-题解---排序)
* [快速选择](#快速选择)
* [](#)
* [1. Kth Element](#1-kth-element)
* [桶排序](#桶排序)
* [1. 出现频率最多的 k 个元素](#1-出现频率最多的-k-个元素)
* [2. 按照字符出现次数对字符串排序](#2-按照字符出现次数对字符串排序)
* [荷兰国旗问题](#荷兰国旗问题)
* [1. 按颜色进行排序](#1-按颜色进行排序)
<!-- GFM-TOC -->
# 快速选择
## 快速选择
用于求解 **Kth Element** 问题也就是第 K 个元素的问题
可以使用快速排序的 partition() 进行实现需要先打乱数组否则最坏情况下时间复杂度为 O(N<sup>2</sup>)
#
##
用于求解 **TopK Elements** 问题也就是 K 个最小元素的问题使用最小堆来实现 TopK 问题最小堆使用大顶堆来实现大顶堆的堆顶元素为当前堆的最大元素实现过程不断地往大顶堆中插入新元素当堆中元素的数量大于 k 移除堆顶元素也就是当前堆中最大的元素剩下的元素都为当前添加过的元素中最小的 K 个元素插入和移除堆顶元素的时间复杂度都为 log<sub>2</sub>N
@ -26,7 +28,7 @@
可以看到快速选择和堆排序都可以求解 Kth Element TopK Elements 问题
## 1. Kth Element
### 1. Kth Element
215\. Kth Largest Element in an Array (Medium)
@ -102,9 +104,9 @@ private void swap(int[] a, int i, int j) {
}
```
# 桶排序
## 桶排序
## 1. 出现频率最多的 k 个元素
### 1. 出现频率最多的 k 个元素
347\. Top K Frequent Elements (Medium)
@ -151,7 +153,7 @@ public int[] topKFrequent(int[] nums, int k) {
}
```
## 2. 按照字符出现次数对字符串排序
### 2. 按照字符出现次数对字符串排序
451\. Sort Characters By Frequency (Medium)
@ -198,7 +200,7 @@ public String frequencySort(String s) {
}
```
# 荷兰国旗问题
## 荷兰国旗问题
荷兰国旗包含三种颜色
@ -207,7 +209,7 @@ public String frequencySort(String s) {
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/7a3215ec-6fb7-4935-8b0d-cb408208f7cb.png"/> </div><br>
## 1. 按颜色进行排序
### 1. 按颜色进行排序
75\. Sort Colors (Medium)
@ -240,10 +242,3 @@ private void swap(int[] nums, int i, int j) {
nums[j] = t;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,36 +1,38 @@
# Leetcode 题解 - 搜索
<!-- GFM-TOC -->
* [BFS](#bfs)
* [1. 计算在网格中从原点到特定点的最短路径长度](#1-计算在网格中从原点到特定点的最短路径长度)
* [2. 组成整数的最小平方数数量](#2-组成整数的最小平方数数量)
* [3. 最短单词路径](#3-最短单词路径)
* [DFS](#dfs)
* [1. 查找最大的连通面积](#1-查找最大的连通面积)
* [2. 矩阵中的连通分量数目](#2-矩阵中的连通分量数目)
* [3. 好友关系的连通分量数目](#3-好友关系的连通分量数目)
* [4. 填充封闭区域](#4-填充封闭区域)
* [5. 能到达的太平洋和大西洋的区域](#5-能到达的太平洋和大西洋的区域)
* [Backtracking](#backtracking)
* [1. 数字键盘组合](#1-数字键盘组合)
* [2. IP 地址划分](#2-ip-地址划分)
* [3. 在矩阵中寻找字符串](#3-在矩阵中寻找字符串)
* [4. 输出二叉树中所有从根到叶子的路径](#4-输出二叉树中所有从根到叶子的路径)
* [5. 排列](#5-排列)
* [6. 含有相同元素求排列](#6-含有相同元素求排列)
* [7. 组合](#7-组合)
* [8. 组合求和](#8-组合求和)
* [9. 含有相同元素的组合求和](#9-含有相同元素的组合求和)
* [10. 1-9 数字的组合求和](#10-1-9-数字的组合求和)
* [11. 子集](#11-子集)
* [12. 含有相同元素求子集](#12-含有相同元素求子集)
* [13. 分割字符串使得每个部分都是回文数](#13-分割字符串使得每个部分都是回文数)
* [14. ](#14-)
* [15. N 皇后](#15-n-皇后)
* [Leetcode 题解 - 搜索](#leetcode-题解---搜索)
* [BFS](#bfs)
* [1. 计算在网格中从原点到特定点的最短路径长度](#1-计算在网格中从原点到特定点的最短路径长度)
* [2. 组成整数的最小平方数数量](#2-组成整数的最小平方数数量)
* [3. 最短单词路径](#3-最短单词路径)
* [DFS](#dfs)
* [1. 查找最大的连通面积](#1-查找最大的连通面积)
* [2. 矩阵中的连通分量数目](#2-矩阵中的连通分量数目)
* [3. 好友关系的连通分量数目](#3-好友关系的连通分量数目)
* [4. 填充封闭区域](#4-填充封闭区域)
* [5. 能到达的太平洋和大西洋的区域](#5-能到达的太平洋和大西洋的区域)
* [Backtracking](#backtracking)
* [1. 数字键盘组合](#1-数字键盘组合)
* [2. IP 地址划分](#2-ip-地址划分)
* [3. 在矩阵中寻找字符串](#3-在矩阵中寻找字符串)
* [4. 输出二叉树中所有从根到叶子的路径](#4-输出二叉树中所有从根到叶子的路径)
* [5. 排列](#5-排列)
* [6. 含有相同元素求排列](#6-含有相同元素求排列)
* [7. 组合](#7-组合)
* [8. 组合求和](#8-组合求和)
* [9. 含有相同元素的组合求和](#9-含有相同元素的组合求和)
* [10. 1-9 数字的组合求和](#10-1-9-数字的组合求和)
* [11. 子集](#11-子集)
* [12. 含有相同元素求子集](#12-含有相同元素求子集)
* [13. 分割字符串使得每个部分都是回文](#13-分割字符串使得每个部分都是回文)
* [14. 数独](#14-数独)
* [15. N 皇后](#15-n-皇后)
<!-- GFM-TOC -->
深度优先搜索和广度优先搜索广泛运用于树和图中但是它们的应用远远不止如此
# BFS
## BFS
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/95903878-725b-4ed9-bded-bc4aae0792a9.jpg"/> </div><br>
@ -38,19 +40,19 @@
第一层
- 0 -> {6,2,1,5}
- 0 -\> {6,2,1,5}
第二层
- 6 -> {4}
- 2 -> {}
- 1 -> {}
- 5 -> {3}
- 6 -\> {4}
- 2 -\> {}
- 1 -\> {}
- 5 -\> {3}
第三层
- 4 -> {}
- 3 -> {}
- 4 -\> {}
- 3 -\> {}
每一层遍历的节点都与根节点距离相同 d<sub>i</sub> 表示第 i 个节点与根节点的距离推导出一个结论对于先遍历的节点 i 与后遍历的节点 j d<sub>i</sub> <= d<sub>j</sub>利用这个结论可以求解最短路径等 **最优解** 问题第一次遍历到目的节点其所经过的路径为最短路径应该注意的是使用 BFS 只能求解无权图的最短路径无权图是指从一个节点到另一个节点的代价都记为 1
@ -59,7 +61,7 @@
- 队列用来存储每一轮遍历得到的节点
- 标记对于遍历过的节点应该将它标记防止重复遍历
## 1. 计算在网格中从原点到特定点的最短路径长度
### 1. 计算在网格中从原点到特定点的最短路径长度
1091\. Shortest Path in Binary Matrix(Medium)
@ -110,7 +112,7 @@ public int shortestPathBinaryMatrix(int[][] grids) {
}
```
## 2. 组成整数的最小平方数数量
### 2. 组成整数的最小平方数数量
279\. Perfect Squares (Medium)
@ -175,7 +177,7 @@ private List<Integer> generateSquares(int n) {
}
```
## 3. 最短单词路径
### 3. 最短单词路径
127\. Word Ladder (Medium)
@ -273,7 +275,7 @@ private int getShortestPath(List<Integer>[] graphic, int start, int end) {
}
```
# DFS
## DFS
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/74dc31eb-6baa-47ea-ab1c-d27a0ca35093.png"/> </div><br>
@ -288,7 +290,7 @@ private int getShortestPath(List<Integer>[] graphic, int start, int end) {
- 用栈来保存当前节点信息当遍历新节点返回时能够继续遍历当前节点可以使用递归栈
- 标记 BFS 一样同样需要对已经遍历过的节点进行标记
## 1. 查找最大的连通面积
### 1. 查找最大的连通面积
695\. Max Area of Island (Medium)
@ -337,7 +339,7 @@ private int dfs(int[][] grid, int r, int c) {
}
```
## 2. 矩阵中的连通分量数目
### 2. 矩阵中的连通分量数目
200\. Number of Islands (Medium)
@ -388,7 +390,7 @@ private void dfs(char[][] grid, int i, int j) {
}
```
## 3. 好友关系的连通分量数目
### 3. 好友关系的连通分量数目
547\. Friend Circles (Medium)
@ -434,7 +436,7 @@ private void dfs(int[][] M, int i, boolean[] hasVisited) {
}
```
## 4. 填充封闭区域
### 4. 填充封闭区域
130\. Surrounded Regions (Medium)
@ -501,7 +503,7 @@ private void dfs(char[][] board, int r, int c) {
}
```
## 5. 能到达的太平洋和大西洋的区域
### 5. 能到达的太平洋和大西洋的区域
417\. Pacific Atlantic Water Flow (Medium)
@ -579,7 +581,7 @@ private void dfs(int r, int c, boolean[][] canReach) {
}
```
# Backtracking
## Backtracking
Backtracking回溯属于 DFS
@ -591,7 +593,7 @@ Backtracking回溯属于 DFS。
- 在访问一个新元素进入新的递归调用时需要将新元素标记为已经访问这样才能在继续递归调用时不用重复访问该元素
- 但是在递归返回时需要将元素标记为未访问因为只需要保证在一个递归链中不同时访问一个元素可以访问已经访问过但是不在当前递归链中的元素
## 1. 数字键盘组合
### 1. 数字键盘组合
17\. Letter Combinations of a Phone Number (Medium)
@ -631,7 +633,7 @@ private void doCombination(StringBuilder prefix, List<String> combinations, fina
}
```
## 2. IP 地址划分
### 2. IP 地址划分
93\. Restore IP Addresses(Medium)
@ -674,7 +676,7 @@ private void doRestore(int k, StringBuilder tempAddress, List<String> addresses,
}
```
## 3. 在矩阵中寻找字符串
### 3. 在矩阵中寻找字符串
79\. Word Search (Medium)
@ -745,7 +747,7 @@ private boolean backtracking(int curLen, int r, int c, boolean[][] visited, fina
}
```
## 4. 输出二叉树中所有从根到叶子的路径
### 4. 输出二叉树中所有从根到叶子的路径
257\. Binary Tree Paths (Easy)
@ -805,7 +807,7 @@ private String buildPath(List<Integer> values) {
}
```
## 5. 排列
### 5. 排列
46\. Permutations (Medium)
@ -850,7 +852,7 @@ private void backtracking(List<Integer> permuteList, List<List<Integer>> permute
}
```
## 6. 含有相同元素求排列
### 6. 含有相同元素求排列
47\. Permutations II (Medium)
@ -897,7 +899,7 @@ private void backtracking(List<Integer> permuteList, List<List<Integer>> permute
}
```
## 7. 组合
### 7. 组合
77\. Combinations (Medium)
@ -936,7 +938,7 @@ private void backtracking(List<Integer> combineList, List<List<Integer>> combina
}
```
## 8. 组合求和
### 8. 组合求和
39\. Combination Sum (Medium)
@ -972,7 +974,7 @@ private void backtracking(List<Integer> tempCombination, List<List<Integer>> com
}
```
## 9. 含有相同元素的组合求和
### 9. 含有相同元素的组合求和
40\. Combination Sum II (Medium)
@ -1019,7 +1021,7 @@ private void backtracking(List<Integer> tempCombination, List<List<Integer>> com
}
```
## 10. 1-9 数字的组合求和
### 10. 1-9 数字的组合求和
216\. Combination Sum III (Medium)
@ -1061,7 +1063,7 @@ private void backtracking(int k, int n, int start,
}
```
## 11. 子集
### 11. 子集
78\. Subsets (Medium)
@ -1094,7 +1096,7 @@ private void backtracking(int start, List<Integer> tempSubset, List<List<Integer
}
```
## 12. 含有相同元素求子集
### 12. 含有相同元素求子集
90\. Subsets II (Medium)
@ -1146,7 +1148,7 @@ private void backtracking(int start, List<Integer> tempSubset, List<List<Integer
}
```
## 13. 分割字符串使得每个部分都是回文数
### 13. 分割字符串使得每个部分都是回文数
131\. Palindrome Partitioning (Medium)
@ -1194,7 +1196,7 @@ private boolean isPalindrome(String s, int begin, int end) {
}
```
## 14. 数独
### 14. 数独
37\. Sudoku Solver (Hard)
@ -1253,7 +1255,7 @@ private int cubeNum(int i, int j) {
}
```
## 15. N 皇后
### 15. N 皇后
51\. N-Queens (Hard)
@ -1320,10 +1322,3 @@ private void backtracking(int row) {
}
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,50 +1,52 @@
# Leetcode 题解 - 数学
<!-- GFM-TOC -->
* [素数分解](#素数分解)
* [整除](#整除)
* [最大公约数最小公倍数](#最大公约数最小公倍数)
* [1. 生成素数序列](#1-生成素数序列)
* [2. 最大公约数](#2-最大公约数)
* [3. 使用位操作和减法求解最大公约数](#3-使用位操作和减法求解最大公约数)
* [进制转换](#进制转换)
* [1. 7 进制](#1-7-进制)
* [2. 16 进制](#2-16-进制)
* [3. 26 进制](#3-26-进制)
* [阶乘](#阶乘)
* [1. 统计阶乘尾部有多少个 0](#1-统计阶乘尾部有多少个-0)
* [字符串加法减法](#字符串加法减法)
* [1. 二进制加](#1-二进制加)
* [2. 字符串加法](#2-字符串加法)
* [相遇问题](#相遇问题)
* [1. 改变数组元素使所有的数组元素都相等](#1-改变数组元素使所有的数组元素都相等)
* [多数投票问题](#多数投票问题)
* [1. 数组中出现次数多于 n / 2 的元素](#1-数组中出现次数多于-n--2-的元素)
* [其它](#其它)
* [1. 平方数](#1-平方数)
* [2. 3 n 次方](#2-3--n-次方)
* [3. 乘积数组](#3-乘积数组)
* [4. 找出数组中的乘积最大的三个数](#4-找出数组中的乘积最大的三个数)
* [Leetcode 题解 - 数学](#leetcode-题解---数学)
* [素数分解](#素数分解)
* [整除](#整除)
* [最大公约数最小公倍数](#最大公约数最小公倍数)
* [1. 生成素数序列](#1-生成素数序列)
* [2. 最大公约数](#2-最大公约数)
* [3. 使用位操作和减法求解最大公约数](#3-使用位操作和减法求解最大公约数)
* [进制转换](#进制转换)
* [1. 7 进制](#1-7-进制)
* [2. 16 进制](#2-16-进制)
* [3. 26 进制](#3-26-进制)
* [阶乘](#阶乘)
* [1. 统计阶乘尾部有多少个 0](#1-统计阶乘尾部有多少个-0)
* [字符串加法减](#字符串加法减)
* [1. 二进制加法](#1-二进制加法)
* [2. 字符串加法](#2-字符串加法)
* [相遇问题](#相遇问题)
* [1. 改变数组元素使所有的数组元素都相等](#1-改变数组元素使所有的数组元素都相等)
* [多数投票问题](#多数投票问题)
* [1. 数组中出现次数多于 n / 2 的元素](#1-数组中出现次数多于-n--2-的元素)
* [其它](#其它)
* [1. 平方数](#1-平方数)
* [2. 3 n 次方](#2-3--n-次方)
* [3. 乘积数组](#3-乘积数组)
* [4. 找出数组中的乘积最大的三个数](#4-找出数组中的乘积最大的三个数)
<!-- GFM-TOC -->
# 素数分解
## 素数分解
每一个数都可以分解成素数的乘积例如 84 = 2<sup>2</sup> \* 3<sup>1</sup> \* 5<sup>0</sup> \* 7<sup>1</sup> \* 11<sup>0</sup> \* 13<sup>0</sup> \* 17<sup>0</sup> \*
# 整除
## 整除
x = 2<sup>m0</sup> \* 3<sup>m1</sup> \* 5<sup>m2</sup> \* 7<sup>m3</sup> \* 11<sup>m4</sup> \*
y = 2<sup>n0</sup> \* 3<sup>n1</sup> \* 5<sup>n2</sup> \* 7<sup>n3</sup> \* 11<sup>n4</sup> \*
如果 x 整除 yy mod x == 0则对于所有 imi <= ni
如果 x 整除 yy mod x == 0则对于所有 imi \<= ni
# 最大公约数最小公倍数
## 最大公约数最小公倍数
x y 的最大公约数为gcd(x,y) = 2<sup>min(m0,n0)</sup> \* 3<sup>min(m1,n1)</sup> \* 5<sup>min(m2,n2)</sup> \* ...
x y 的最小公倍数为lcm(x,y) = 2<sup>max(m0,n0)</sup> \* 3<sup>max(m1,n1)</sup> \* 5<sup>max(m2,n2)</sup> \* ...
## 1. 生成素数序列
### 1. 生成素数序列
204\. Count Primes (Easy)
@ -70,7 +72,7 @@ public int countPrimes(int n) {
}
```
## 2. 最大公约数
### 2. 最大公约数
```java
int gcd(int a, int b) {
@ -86,7 +88,7 @@ int lcm(int a, int b) {
}
```
## 3. 使用位操作和减法求解最大公约数
### 3. 使用位操作和减法求解最大公约数
[编程之美2.7](#)
@ -120,9 +122,9 @@ public int gcd(int a, int b) {
}
```
# 进制转换
## 进制转换
## 1. 7 进制
### 1. 7 进制
504\. Base 7 (Easy)
@ -155,7 +157,7 @@ public String convertToBase7(int num) {
}
```
## 2. 16 进制
### 2. 16 进制
405\. Convert a Number to Hexadecimal (Easy)
@ -190,7 +192,7 @@ public String toHex(int num) {
}
```
## 3. 26 进制
### 3. 26 进制
168\. Excel Sheet Column Title (Easy)
@ -218,9 +220,9 @@ public String convertToTitle(int n) {
}
```
# 阶乘
## 阶乘
## 1. 统计阶乘尾部有多少个 0
### 1. 统计阶乘尾部有多少个 0
172\. Factorial Trailing Zeroes (Easy)
@ -238,9 +240,9 @@ public int trailingZeroes(int n) {
如果统计的是 N! 的二进制表示中最低位 1 的位置只要统计有多少个 2 即可该题目出自 [编程之美2.2](#) 和求解有多少个 5 一样2 的个数为 N/2 + N/2<sup>2</sup> + N/2<sup>3</sup> + ...
# 字符串加法减法
## 字符串加法减法
## 1. 二进制加法
### 1. 二进制加法
67\. Add Binary (Easy)
@ -270,7 +272,7 @@ public String addBinary(String a, String b) {
}
```
## 2. 字符串加法
### 2. 字符串加法
415\. Add Strings (Easy)
@ -292,9 +294,9 @@ public String addStrings(String num1, String num2) {
}
```
# 相遇问题
## 相遇问题
## 1. 改变数组元素使所有的数组元素都相等
### 1. 改变数组元素使所有的数组元素都相等
462\. Minimum Moves to Equal Array Elements II (Medium)
@ -317,7 +319,7 @@ Only two moves are needed (remember each move increments or decrements one eleme
这是个典型的相遇问题移动距离最小的方式是所有元素都移动到中位数理由如下
m 为中位数a b m 两边的两个元素 b > a要使 a b 相等它们总共移动的次数为 b - a这个值等于 (b - m) + (m - a)也就是把这两个数移动到中位数的移动次数
m 为中位数a b m 两边的两个元素 b \> a要使 a b 相等它们总共移动的次数为 b - a这个值等于 (b - m) + (m - a)也就是把这两个数移动到中位数的移动次数
设数组长度为 N则可以找到 N/2 a b 的组合使它们都移动到 m 的位置
@ -390,9 +392,9 @@ private void swap(int[] nums, int i, int j) {
}
```
# 多数投票问题
## 多数投票问题
## 1. 数组中出现次数多于 n / 2 的元素
### 1. 数组中出现次数多于 n / 2 的元素
169\. Majority Element (Easy)
@ -420,9 +422,9 @@ public int majorityElement(int[] nums) {
}
```
# 其它
## 其它
## 1. 平方数
### 1. 平方数
367\. Valid Perfect Square (Easy)
@ -450,7 +452,7 @@ public boolean isPerfectSquare(int num) {
}
```
## 2. 3 n 次方
### 2. 3 n 次方
326\. Power of Three (Easy)
@ -462,7 +464,7 @@ public boolean isPowerOfThree(int n) {
}
```
## 3. 乘积数组
### 3. 乘积数组
238\. Product of Array Except Self (Medium)
@ -495,7 +497,7 @@ public int[] productExceptSelf(int[] nums) {
}
```
## 4. 找出数组中的乘积最大的三个数
### 4. 找出数组中的乘积最大的三个数
628\. Maximum Product of Three Numbers (Easy)
@ -531,10 +533,3 @@ public int maximumProduct(int[] nums) {
return Math.max(max1*max2*max3, max1*min1*min2);
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,20 +1,22 @@
# Leetcode 题解 - 数组与矩阵
<!-- GFM-TOC -->
* [1. 把数组中的 0 移到末尾](#1-把数组中的-0-移到末尾)
* [2. 改变矩阵维度](#2-改变矩阵维度)
* [3. 找出数组中最长的连续 1](#3-找出数组中最长的连续-1)
* [4. 有序矩阵查](#4-有序矩阵查)
* [5. 有序矩阵 Kth Element](#5-有序矩阵-kth-element)
* [6. 一个数组元素在 [1, n] 之间其中一个数被替换为另一个数找出重复的数和丢失的数](#6-一个数组元素在-[1-n]-之间其中一个数被替换为另一个数找出重复的数和丢失的数)
* [7. 找出数组中重复的数数组值 [1, n] 之间](#7-找出数组中重复的数数组值在-[1-n]-之间)
* [8. 数组相邻差值的个数](#8-数组相邻差值的个数)
* [9. 数组的度](#9-数组的度)
* [10. 对角元素相等的矩阵](#10-对角元素相等的矩阵)
* [11. 嵌套数组](#11-嵌套数组)
* [12. 分隔数组](#12-分隔数组)
* [Leetcode 题解 - 数组与矩阵](#leetcode-题解---数组与矩阵)
* [1. 把数组中的 0 移到末尾](#1-把数组中的-0-移到末尾)
* [2. 改变矩阵维度](#2-改变矩阵维度)
* [3. 出数组中最长的连续 1](#3-出数组中最长的连续-1)
* [4. 有序矩阵查找](#4-有序矩阵查找)
* [5. 有序矩阵的 Kth Element](#5-有序矩阵的-kth-element)
* [6. 一个数组元素 [1, n] 之间其中一个数被替换为另一个数找出重复的数和丢失的数](#6-一个数组元素在-[1-n]-之间其中一个数被替换为另一个数找出重复的数和丢失的数)
* [7. 找出数组中重复的数数组值在 [1, n] 之间](#7-找出数组中重复的数数组值在-[1-n]-之间)
* [8. 数组相邻差值的个数](#8-数组相邻差值的个数)
* [9. 数组的度](#9-数组的度)
* [10. 对角元素相等的矩阵](#10-对角元素相等的矩阵)
* [11. 嵌套数组](#11-嵌套数组)
* [12. 分隔数组](#12-分隔数组)
<!-- GFM-TOC -->
# 1. 把数组中的 0 移到末尾
## 1. 把数组中的 0 移到末尾
283\. Move Zeroes (Easy)
@ -38,7 +40,7 @@ public void moveZeroes(int[] nums) {
}
```
# 2. 改变矩阵维度
## 2. 改变矩阵维度
566\. Reshape the Matrix (Easy)
@ -76,7 +78,7 @@ public int[][] matrixReshape(int[][] nums, int r, int c) {
}
```
# 3. 找出数组中最长的连续 1
## 3. 找出数组中最长的连续 1
485\. Max Consecutive Ones (Easy)
@ -93,7 +95,7 @@ public int findMaxConsecutiveOnes(int[] nums) {
}
```
# 4. 有序矩阵查找
## 4. 有序矩阵查找
240\. Search a 2D Matrix II (Medium)
@ -121,7 +123,7 @@ public boolean searchMatrix(int[][] matrix, int target) {
}
```
# 5. 有序矩阵的 Kth Element
## 5. 有序矩阵的 Kth Element
378\. Kth Smallest Element in a Sorted Matrix ((Medium))
@ -189,7 +191,7 @@ class Tuple implements Comparable<Tuple> {
}
```
# 6. 一个数组元素在 [1, n] 之间其中一个数被替换为另一个数找出重复的数和丢失的数
## 6. 一个数组元素在 [1, n] 之间其中一个数被替换为另一个数找出重复的数和丢失的数
645\. Set Mismatch (Easy)
@ -231,7 +233,7 @@ private void swap(int[] nums, int i, int j) {
}
```
# 7. 找出数组中重复的数数组值在 [1, n] 之间
## 7. 找出数组中重复的数数组值在 [1, n] 之间
287\. Find the Duplicate Number (Medium)
@ -275,7 +277,7 @@ public int findDuplicate(int[] nums) {
}
```
# 8. 数组相邻差值的个数
## 8. 数组相邻差值的个数
667\. Beautiful Arrangement II (Medium)
@ -305,7 +307,7 @@ public int[] constructArray(int n, int k) {
}
```
# 9. 数组的度
## 9. 数组的度
697\. Degree of an Array (Easy)
@ -346,7 +348,7 @@ public int findShortestSubArray(int[] nums) {
}
```
# 10. 对角元素相等的矩阵
## 10. 对角元素相等的矩阵
766\. Toeplitz Matrix (Easy)
@ -386,7 +388,7 @@ private boolean check(int[][] matrix, int expectValue, int row, int col) {
}
```
# 11. 嵌套数组
## 11. 嵌套数组
565\. Array Nesting (Medium)
@ -422,7 +424,7 @@ public int arrayNesting(int[] nums) {
}
```
# 12. 分隔数组
## 12. 分隔数组
769\. Max Chunks To Make Sorted (Medium)
@ -450,10 +452,3 @@ public int maxChunksToSorted(int[] arr) {
return ret;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,14 +1,16 @@
# Leetcode 题解 - 栈和队列
<!-- GFM-TOC -->
* [1. 用栈实现队列](#1-用栈实现队列)
* [2. 用队列实现栈](#2-用队列实现栈)
* [3. 最小值](#3-最小值)
* [4. 用栈实现括号匹配](#4-用栈实现括号匹配)
* [5. 数组中元素与下一个比它大的元素之间的距离](#5-数组中元素与下一个比它大的元素之间的距离)
* [6. 循环数组中比当前元素大的下一个元素](#6-循环数组中比当前元素大的下一个元素)
* [Leetcode 题解 - 栈和队列](#leetcode-题解---栈和队列)
* [1. 栈实现队列](#1-栈实现队列)
* [2. 用队列实现](#2-用队列实现)
* [3. 最小值栈](#3-最小值栈)
* [4. 用栈实现括号匹配](#4-用栈实现括号匹配)
* [5. 数组中元素与下一个比它大的元素之间的距离](#5-数组中元素与下一个比它大的元素之间的距离)
* [6. 循环数组中比当前元素大的下一个元素](#6-循环数组中比当前元素大的下一个元素)
<!-- GFM-TOC -->
# 1. 用栈实现队列
## 1. 用栈实现队列
232\. Implement Queue using Stacks (Easy)
@ -50,7 +52,7 @@ class MyQueue {
}
```
# 2. 用队列实现栈
## 2. 用队列实现栈
225\. Implement Stack using Queues (Easy)
@ -89,7 +91,7 @@ class MyStack {
}
```
# 3. 最小值栈
## 3. 最小值栈
155\. Min Stack (Easy)
@ -132,7 +134,7 @@ class MinStack {
对于实现最小值队列问题可以先将队列使用栈来实现然后就将问题转换为最小值栈这个问题出现在 编程之美3.7
# 4. 用栈实现括号匹配
## 4. 用栈实现括号匹配
20\. Valid Parentheses (Easy)
@ -167,7 +169,7 @@ public boolean isValid(String s) {
}
```
# 5. 数组中元素与下一个比它大的元素之间的距离
## 5. 数组中元素与下一个比它大的元素之间的距离
739\. Daily Temperatures (Medium)
@ -196,7 +198,7 @@ public int[] dailyTemperatures(int[] temperatures) {
}
```
# 6. 循环数组中比当前元素大的下一个元素
## 6. 循环数组中比当前元素大的下一个元素
503\. Next Greater Element II (Medium)
@ -230,10 +232,3 @@ public int[] nextGreaterElements(int[] nums) {
return next;
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,48 +1,50 @@
# Leetcode 题解 -
<!-- GFM-TOC -->
* [递归](#递归)
* [1. 树的高度](#1-树的高度)
* [2. 平衡](#2-平衡)
* [3. 两节点的最长路径](#3-两节点的最长路径)
* [4. 翻转树](#4-翻转树)
* [5. 归并两棵](#5-归并两棵)
* [6. 判断路径和是否等于一个数](#6-判断路径和是否等于一个数)
* [7. 统计路径和等于一个数的路径数量](#7-统计路径和等于一个数的路径数量)
* [8. 子树](#8-子树)
* [9. 的对称](#9-的对称)
* [10. 最小路径](#10-最小路径)
* [11. 统计左叶子节点的和](#11-统计左叶子节点的和)
* [12. 相同节点值的最大路径长度](#12-相同节点值的最大路径长度)
* [13. 间隔遍历](#13-间隔遍历)
* [14. 找出二叉树中第二小的节点](#14-找出二叉树中第二小的节点)
* [层次遍历](#层次遍历)
* [1. 一棵树每层节点的平均数](#1-一棵树每层节点的平均数)
* [2. 得到左下角的节点](#2-得到左下角的节点)
* [前中后序遍历](#前中后序遍历)
* [1. 非递归实现二叉树的前序遍历](#1-非递归实现二叉树的前序遍历)
* [2. 非递归实现二叉树的序遍历](#2-非递归实现二叉树的序遍历)
* [3. 非递归实现二叉树的序遍历](#3-非递归实现二叉树的序遍历)
* [BST](#bst)
* [1. 修剪二叉查找树](#1-修剪二叉查找树)
* [2. 寻找二叉查找树的第 k 个元素](#2-寻找二叉查找树的第-k-个元素)
* [3. 二叉查找树每个节点的值都加上比它大的节点的值](#3-二叉查找树每个节点的值都加上比它大的节点的值)
* [4. 二叉查找树的最近公共祖先](#4-二叉查找树的最近公共祖先)
* [5. 二叉树的最近公共祖先](#5-二叉树的最近公共祖先)
* [6. 从有序数组中构造二叉查找树](#6-从有序数组中构造二叉查找树)
* [7. 根据有序链表构造平衡的二叉查找树](#7-根据有序链表构造平衡的二叉查找树)
* [8. 在二叉查找树中寻找两个节点使它们的和为一个给定值](#8-在二叉查找树中寻找两个节点使它们的和为一个给定值)
* [9. 在二叉查找树中找两个节点之差的最小绝对](#9-在二叉查找树中找两个节点之差的最小绝对)
* [10. 寻找二叉查找树中出现次数最多的](#10-寻找二叉查找树中出现次数最多的)
* [Trie](#trie)
* [1. 实现一个 Trie](#1-实现一个-trie)
* [2. 实现一个 Trie用来求前缀和](#2-实现一个-trie用来求前缀和)
* [Leetcode 题解 - ](#leetcode-题解---)
* [递归](#递归)
* [1. 的高度](#1-的高度)
* [2. 平衡树](#2-平衡树)
* [3. 两节点的最长路径](#3-两节点的最长路径)
* [4. 翻转](#4-翻转)
* [5. 归并两棵树](#5-归并两棵树)
* [6. 判断路径和是否等于一个数](#6-判断路径和是否等于一个数)
* [7. 统计路径和等于一个数的路径数量](#7-统计路径和等于一个数的路径数量)
* [8. ](#8-)
* [9. 树的对称](#9-树的对称)
* [10. 最小路径](#10-最小路径)
* [11. 统计左叶子节点的和](#11-统计左叶子节点的和)
* [12. 相同节点值的最大路径长度](#12-相同节点值的最大路径长度)
* [13. 间隔遍历](#13-间隔遍历)
* [14. 找出二叉树中第二小的节点](#14-找出二叉树中第二小的节点)
* [层次遍历](#层次遍历)
* [1. 一棵树每层节点的平均数](#1-一棵树每层节点的平均数)
* [2. 得到左下角的节点](#2-得到左下角的节点)
* [前中后序遍历](#前中后序遍历)
* [1. 非递归实现二叉树的序遍历](#1-非递归实现二叉树的序遍历)
* [2. 非递归实现二叉树的序遍历](#2-非递归实现二叉树的序遍历)
* [3. 非递归实现二叉树的中序遍历](#3-非递归实现二叉树的中序遍历)
* [BST](#bst)
* [1. 修剪二叉查找树](#1-修剪二叉查找树)
* [2. 寻找二叉查找树的第 k 个元素](#2-寻找二叉查找树的第-k-个元素)
* [3. 二叉查找树每个节点的值都加上比它大的节点的值](#3-二叉查找树每个节点的值都加上比它大的节点的值)
* [4. 二叉查找树的最近公共祖先](#4-二叉查找树的最近公共祖先)
* [5. 二叉树的最近公共祖先](#5-二叉树的最近公共祖先)
* [6. 从有序数组中构造二叉查找树](#6-从有序数组中构造二叉查找树)
* [7. 根据有序链表构造平衡的二叉查找树](#7-根据有序链表构造平衡的二叉查找树)
* [8. 在二叉查找树中找两个节点使它们的和为一个给定](#8-在二叉查找树中找两个节点使它们的和为一个给定)
* [9. 二叉查找树中查找两个节点之差的最小绝对](#9-二叉查找树中查找两个节点之差的最小绝对)
* [10. 寻找二叉查找树中出现次数最多的值](#10-寻找二叉查找树中出现次数最多的值)
* [Trie](#trie)
* [1. 实现一个 Trie](#1-实现一个-trie)
* [2. 实现一个 Trie用来求前缀和](#2-实现一个-trie用来求前缀和)
<!-- GFM-TOC -->
# 递归
## 递归
一棵树要么是空树要么有两个指针每个指针指向一棵树树是一种递归结构很多树的问题可以使用递归来处理
## 1. 树的高度
### 1. 树的高度
104\. Maximum Depth of Binary Tree (Easy)
@ -55,7 +57,7 @@ public int maxDepth(TreeNode root) {
}
```
## 2. 平衡树
### 2. 平衡树
110\. Balanced Binary Tree (Easy)
@ -88,7 +90,7 @@ public int maxDepth(TreeNode root) {
}
```
## 3. 两节点的最长路径
### 3. 两节点的最长路径
543\. Diameter of Binary Tree (Easy)
@ -123,7 +125,7 @@ private int depth(TreeNode root) {
}
```
## 4. 翻转树
### 4. 翻转树
226\. Invert Binary Tree (Easy)
@ -139,7 +141,7 @@ public TreeNode invertTree(TreeNode root) {
}
```
## 5. 归并两棵树
### 5. 归并两棵树
617\. Merge Two Binary Trees (Easy)
@ -174,7 +176,7 @@ public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
}
```
## 6. 判断路径和是否等于一个数
### 6. 判断路径和是否等于一个数
Leetcdoe : 112. Path Sum (Easy)
@ -204,7 +206,7 @@ public boolean hasPathSum(TreeNode root, int sum) {
}
```
## 7. 统计路径和等于一个数的路径数量
### 7. 统计路径和等于一个数的路径数量
437\. Path Sum III (Easy)
@ -246,7 +248,7 @@ private int pathSumStartWithRoot(TreeNode root, int sum) {
}
```
## 8. 子树
### 8. 子树
572\. Subtree of Another Tree (Easy)
@ -299,7 +301,7 @@ private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
}
```
## 9. 树的对称
### 9. 树的对称
101\. Symmetric Tree (Easy)
@ -327,7 +329,7 @@ private boolean isSymmetric(TreeNode t1, TreeNode t2) {
}
```
## 10. 最小路径
### 10. 最小路径
111\. Minimum Depth of Binary Tree (Easy)
@ -345,7 +347,7 @@ public int minDepth(TreeNode root) {
}
```
## 11. 统计左叶子节点的和
### 11. 统计左叶子节点的和
404\. Sum of Left Leaves (Easy)
@ -374,7 +376,7 @@ private boolean isLeaf(TreeNode node){
}
```
## 12. 相同节点值的最大路径长度
### 12. 相同节点值的最大路径长度
687\. Longest Univalue Path (Easy)
@ -409,7 +411,7 @@ private int dfs(TreeNode root){
}
```
## 13. 间隔遍历
### 13. 间隔遍历
337\. House Robber III (Medium)
@ -435,7 +437,7 @@ public int rob(TreeNode root) {
}
```
## 14. 找出二叉树中第二小的节点
### 14. 找出二叉树中第二小的节点
671\. Second Minimum Node In a Binary Tree (Easy)
@ -468,11 +470,11 @@ public int findSecondMinimumValue(TreeNode root) {
}
```
# 层次遍历
## 层次遍历
使用 BFS 进行层次遍历不需要使用两个队列来分别存储当前层的节点和下一层的节点因为在开始遍历一层的节点时当前队列中的节点数就是当前层的节点数只要控制遍历这么多节点数就能保证这次遍历的都是当前层的节点
## 1. 一棵树每层节点的平均数
### 1. 一棵树每层节点的平均数
637\. Average of Levels in Binary Tree (Easy)
@ -499,7 +501,7 @@ public List<Double> averageOfLevels(TreeNode root) {
}
```
## 2. 得到左下角的节点
### 2. 得到左下角的节点
513\. Find Bottom Left Tree Value (Easy)
@ -533,7 +535,7 @@ public int findBottomLeftValue(TreeNode root) {
}
```
# 前中后序遍历
## 前中后序遍历
```html
1
@ -582,7 +584,7 @@ void dfs(TreeNode root) {
}
```
## 1. 非递归实现二叉树的前序遍历
### 1. 非递归实现二叉树的前序遍历
144\. Binary Tree Preorder Traversal (Medium)
@ -604,13 +606,13 @@ public List<Integer> preorderTraversal(TreeNode root) {
}
```
## 2. 非递归实现二叉树的后序遍历
### 2. 非递归实现二叉树的后序遍历
145\. Binary Tree Postorder Traversal (Medium)
[Leetcode](https://leetcode.com/problems/binary-tree-postorder-traversal/description/) / [力扣](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/description/)
前序遍历为 root -> left -> right后序遍历为 left -> right -> root可以修改前序遍历成为 root -> right -> left那么这个顺序就和后序遍历正好相反
前序遍历为 root -\> left -\> right后序遍历为 left -\> right -\> root可以修改前序遍历成为 root -\> right -\> left那么这个顺序就和后序遍历正好相反
```java
public List<Integer> postorderTraversal(TreeNode root) {
@ -629,7 +631,7 @@ public List<Integer> postorderTraversal(TreeNode root) {
}
```
## 3. 非递归实现二叉树的中序遍历
### 3. 非递归实现二叉树的中序遍历
94\. Binary Tree Inorder Traversal (Medium)
@ -654,13 +656,13 @@ public List<Integer> inorderTraversal(TreeNode root) {
}
```
# BST
## BST
二叉查找树BST根节点大于等于左子树所有节点小于等于右子树所有节点
二叉查找树中序遍历有序
## 1. 修剪二叉查找树
### 1. 修剪二叉查找树
669\. Trim a Binary Search Tree (Easy)
@ -702,7 +704,7 @@ public TreeNode trimBST(TreeNode root, int L, int R) {
}
```
## 2. 寻找二叉查找树的第 k 个元素
### 2. 寻找二叉查找树的第 k 个元素
230\. Kth Smallest Element in a BST (Medium)
@ -748,7 +750,7 @@ private int count(TreeNode node) {
}
```
## 3. 把二叉查找树每个节点的值都加上比它大的节点的值
### 3. 把二叉查找树每个节点的值都加上比它大的节点的值
Convert BST to Greater Tree (Easy)
@ -787,7 +789,7 @@ private void traver(TreeNode node) {
}
```
## 4. 二叉查找树的最近公共祖先
### 4. 二叉查找树的最近公共祖先
235\. Lowest Common Ancestor of a Binary Search Tree (Easy)
@ -813,7 +815,7 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
}
```
## 5. 二叉树的最近公共祖先
### 5. 二叉树的最近公共祖先
236\. Lowest Common Ancestor of a Binary Tree (Medium)
@ -840,7 +842,7 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
}
```
## 6. 从有序数组中构造二叉查找树
### 6. 从有序数组中构造二叉查找树
108\. Convert Sorted Array to Binary Search Tree (Easy)
@ -861,7 +863,7 @@ private TreeNode toBST(int[] nums, int sIdx, int eIdx){
}
```
## 7. 根据有序链表构造平衡的二叉查找树
### 7. 根据有序链表构造平衡的二叉查找树
109\. Convert Sorted List to Binary Search Tree (Medium)
@ -904,7 +906,7 @@ private ListNode preMid(ListNode head) {
}
```
## 8. 在二叉查找树中寻找两个节点使它们的和为一个给定值
### 8. 在二叉查找树中寻找两个节点使它们的和为一个给定值
653\. Two Sum IV - Input is a BST (Easy)
@ -950,7 +952,7 @@ private void inOrder(TreeNode root, List<Integer> nums) {
}
```
## 9. 在二叉查找树中查找两个节点之差的最小绝对值
### 9. 在二叉查找树中查找两个节点之差的最小绝对值
530\. Minimum Absolute Difference in BST (Easy)
@ -990,7 +992,7 @@ private void inOrder(TreeNode node) {
}
```
## 10. 寻找二叉查找树中出现次数最多的值
### 10. 寻找二叉查找树中出现次数最多的值
501\. Find Mode in Binary Search Tree (Easy)
@ -1043,13 +1045,13 @@ private void inOrder(TreeNode node, List<Integer> nums) {
}
```
# Trie
## Trie
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5c638d59-d4ae-4ba4-ad44-80bdc30f38dd.jpg"/> </div><br>
Trie又称前缀树或字典树用于判断字符串是否存在或者是否具有某种字符串前缀
## 1. 实现一个 Trie
### 1. 实现一个 Trie
208\. Implement Trie (Prefix Tree) (Medium)
@ -1113,7 +1115,7 @@ class Trie {
}
```
## 2. 实现一个 Trie用来求前缀和
### 2. 实现一个 Trie用来求前缀和
677\. Map Sum Pairs (Medium)
@ -1180,10 +1182,3 @@ class MapSum {
}
```
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -1,8 +1,10 @@
# 前言
# Leetcode 题解
## 前言
本文从 Leetcode 中精选大概 200 左右的题目去除了某些繁杂但是没有多少算法思想的题目同时保留了面试中经常被问到的经典题目
# 算法思想
## 算法思想
- [双指针](Leetcode%20题解%20-%20双指针.md)
- [排序](Leetcode%20题解%20-%20排序.md)
@ -13,7 +15,7 @@
- [动态规划](Leetcode%20题解%20-%20动态规划.md)
- [数学](Leetcode%20题解%20-%20数学.md)
# 数据结构相关
## 数据结构相关
- [链表](Leetcode%20题解%20-%20链表.md)
- [](Leetcode%20题解%20-%20树.md)
@ -24,8 +26,7 @@
- [](Leetcode%20题解%20-%20图.md)
- [位运算](Leetcode%20题解%20-%20位运算.md)
# 参考资料
## 参考资料
- Leetcode
- Weiss M A, 冯舜玺. 数据结构与算法分析C 语言描述[J]. 2004.
@ -33,10 +34,3 @@
- 何海涛, 软件工程师. 剑指 Offer: 名企面试官精讲典型编程题[M]. 电子工业出版社, 2014.
- 编程之美小组. 编程之美[M]. 电子工业出版社, 2008.
- 左程云. 程序员代码面试指南[M]. 电子工业出版社, 2015.
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

View File

@ -31,10 +31,3 @@
- 何海涛, 软件工程师. 剑指 Offer: 名企面试官精讲典型编程题[M]. 电子工业出版社, 2014.
- 编程之美小组. 编程之美[M]. 电子工业出版社, 2008.
- 左程云. 程序员代码面试指南[M]. 电子工业出版社, 2015.
<div align="center"><img width="320px" src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/githubio/公众号二维码-2.png"></img></div>

Some files were not shown because too many files have changed in this diff Show More