diff --git a/docs/README.md b/docs/README.md index 972f95b7c684d5f363f43bdd1e936c1ac4530550..756b1422354386434ad225edff2f992f084d043f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,61 +1,2 @@ -- [Github](https://github.com/CyC2018/CS-Notes) +# 😃 该网站已迁移至 >>> [www.cyc2018.xyz](http://www.cyc2018.xyz) -## ✏️ 算法 - -- [剑指 Offer 题解](notes/剑指%20Offer%20题解%20-%20目录2.md)
-- [Leetcode 题解](notes/Leetcode%20题解%20-%20目录1.md)
-- [算法](notes/算法%20-%20目录1.md)
- -## 💻 操作系统 - -- [计算机操作系统](notes/计算机操作系统%20-%20目录1.md)
-- [Linux](notes/Linux.md) - -## ☁️ 网络 - -- [计算机网络](notes/计算机网络%20-%20目录1.md)
-- [HTTP](notes/HTTP.md)
-- [Socket](notes/Socket.md) - -## 💾 数据库 - -- [数据库系统原理](notes/数据库系统原理.md)
-- [SQL 语法](notes/SQL%20语法.md)
-- [SQL 练习](notes/SQL%20练习.md)
-- [MySQL](notes/MySQL.md)
-- [Redis](notes/Redis.md) - -## ☕️ Java - -- [Java 基础](notes/Java%20基础.md)
-- [Java 容器](notes/Java%20容器.md)
-- [Java 并发](notes/Java%20并发.md)
-- [Java 虚拟机](notes/Java%20虚拟机.md)
-- [Java I/O](notes/Java%20IO.md) - -## 💡 系统设计 - -- [系统设计基础](notes/系统设计基础.md)
-- [分布式](notes/分布式.md)
-- [集群](notes/集群.md)
-- [攻击技术](notes/攻击技术.md)
-- [缓存](notes/缓存.md)
-- [消息队列](notes/消息队列.md) - -## 🎨 面向对象 - -- [设计模式](notes/设计模式%20-%20目录1.md)
-- [面向对象思想](notes/面向对象思想.md) - -## 🔧 工具 - -- [Git](notes/Git.md)
-- [Docker](notes/Docker.md)
-- [正则表达式](notes/正则表达式.md)
-- [构建工具](notes/构建工具.md) - - diff --git a/docs/_404.md b/docs/_404.md new file mode 100644 index 0000000000000000000000000000000000000000..831dac82e5668ffea7dc7e94ada626703145857f --- /dev/null +++ b/docs/_404.md @@ -0,0 +1 @@ +# 😃 该网站已迁移至 >>> [www.cyc2018.xyz](http://www.cyc2018.xyz) \ No newline at end of file diff --git a/docs/_coverpage.md b/docs/_coverpage.md index 630c61377664fb13e2816205a82578acc3bbd509..df3eaa882aaa0ed7946acc93034aa39800e3f78b 100644 --- a/docs/_coverpage.md +++ b/docs/_coverpage.md @@ -7,5 +7,5 @@ [![stars](https://badgen.net/github/stars/CyC2018/CS-Notes?icon=github&color=4ab8a1)](https://github.com/CyC2018/CS-Notes) [![forks](https://badgen.net/github/forks/CyC2018/CS-Notes?icon=github&color=4ab8a1)](https://github.com/CyC2018/CS-Notes) -[开始阅读](README.md) +[开始阅读](http://www.cyc2018.xyz) diff --git a/docs/index.html b/docs/index.html index cffaa7c0a459a520409b9821a89236fa7508aef0..c810d4c9882069a33972d289bce4e338c6e8a67f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -422,7 +422,8 @@ depth: 6 }, // subMaxLevel: 2, - coverpage: true + coverpage: true, + notFoundPage: true } diff --git "a/docs/notes/10.1 \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" "b/docs/notes/10.1 \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" deleted file mode 100644 index 2d4326fe974f843bab564f4a011f2488cfabc6b9..0000000000000000000000000000000000000000 --- "a/docs/notes/10.1 \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" +++ /dev/null @@ -1,76 +0,0 @@ -# 10.1 斐波那契数列 - -## 题目链接 - -[NowCoder](https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -求斐波那契数列的第 n 项,n <= 39。 - - - -

- -## 解题思路 - -如果使用递归求解,会重复计算一些子问题。例如,计算 f(4) 需要计算 f(3) 和 f(2),计算 f(3) 需要计算 f(2) 和 f(1),可以看到 f(2) 被重复计算了。 - -

- -递归是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,从而避免重复求解子问题。 - -```java -public int Fibonacci(int n) { - if (n <= 1) - return n; - int[] fib = new int[n + 1]; - fib[1] = 1; - for (int i = 2; i <= n; i++) - fib[i] = fib[i - 1] + fib[i - 2]; - return fib[n]; -} -``` - -考虑到第 i 项只与第 i-1 和第 i-2 项有关,因此只需要存储前两项的值就能求解第 i 项,从而将空间复杂度由 O(N) 降低为 O(1)。 - -```java -public int Fibonacci(int n) { - if (n <= 1) - return n; - int pre2 = 0, pre1 = 1; - int fib = 0; - for (int i = 2; i <= n; i++) { - fib = pre2 + pre1; - pre2 = pre1; - pre1 = fib; - } - return fib; -} -``` - -由于待求解的 n 小于 40,因此可以将前 40 项的结果先进行计算,之后就能以 O(1) 时间复杂度得到第 n 项的值。 - -```java -public class Solution { - - private int[] fib = new int[40]; - - public Solution() { - fib[1] = 1; - for (int i = 2; i < fib.length; i++) - fib[i] = fib[i - 1] + fib[i - 2]; - } - - public int Fibonacci(int n) { - return fib[n]; - } -} -``` - - - - - - -
diff --git "a/docs/notes/10.2 \347\237\251\345\275\242\350\246\206\347\233\226.md" "b/docs/notes/10.2 \347\237\251\345\275\242\350\246\206\347\233\226.md" deleted file mode 100644 index ecc7a64651a674ed2ef7d8ccf160d3e8b87ab29c..0000000000000000000000000000000000000000 --- "a/docs/notes/10.2 \347\237\251\345\275\242\350\246\206\347\233\226.md" +++ /dev/null @@ -1,49 +0,0 @@ -# 10.2 矩形覆盖 - -## 题目链接 - -[NowCoder](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -我们可以用 2\*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2\*1 的小矩形无重叠地覆盖一个 2\*n 的大矩形,总共有多少种方法? - -

- -## 解题思路 - -当 n 为 1 时,只有一种覆盖方法: - -

- -当 n 为 2 时,有两种覆盖方法: - -

- -要覆盖 2\*n 的大矩形,可以先覆盖 2\*1 的矩形,再覆盖 2\*(n-1) 的矩形;或者先覆盖 2\*2 的矩形,再覆盖 2\*(n-2) 的矩形。而覆盖 2\*(n-1) 和 2\*(n-2) 的矩形可以看成子问题。该问题的递推公式如下: - - - -

- -```java -public int RectCover(int n) { - if (n <= 2) - return n; - int pre2 = 1, pre1 = 2; - int result = 0; - for (int i = 3; i <= n; i++) { - result = pre2 + pre1; - pre2 = pre1; - pre1 = result; - } - return result; -} -``` - - - - - - -
diff --git "a/docs/notes/10.3 \350\267\263\345\217\260\351\230\266.md" "b/docs/notes/10.3 \350\267\263\345\217\260\351\230\266.md" deleted file mode 100644 index bd89c6fd9bf7aa8c377b263c0505bac19d5a2e42..0000000000000000000000000000000000000000 --- "a/docs/notes/10.3 \350\267\263\345\217\260\351\230\266.md" +++ /dev/null @@ -1,47 +0,0 @@ -# 10.3 跳台阶 - -## 题目链接 - -[NowCoder](https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=13&tqId=11161&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 - -

- -## 解题思路 - -当 n = 1 时,只有一种跳法: - -

- -当 n = 2 时,有两种跳法: - -

- -跳 n 阶台阶,可以先跳 1 阶台阶,再跳 n-1 阶台阶;或者先跳 2 阶台阶,再跳 n-2 阶台阶。而 n-1 和 n-2 阶台阶的跳法可以看成子问题,该问题的递推公式为: - -

- -```java -public int JumpFloor(int n) { - if (n <= 2) - return n; - int pre2 = 1, pre1 = 2; - int result = 0; - for (int i = 2; i < n; i++) { - result = pre2 + pre1; - pre2 = pre1; - pre1 = result; - } - return result; -} -``` - - - - - - -
diff --git "a/docs/notes/10.4 \345\217\230\346\200\201\350\267\263\345\217\260\351\230\266.md" "b/docs/notes/10.4 \345\217\230\346\200\201\350\267\263\345\217\260\351\230\266.md" deleted file mode 100644 index 0e779d00114bdc58b23ecf891b12e98331b73219..0000000000000000000000000000000000000000 --- "a/docs/notes/10.4 \345\217\230\346\200\201\350\267\263\345\217\260\351\230\266.md" +++ /dev/null @@ -1,67 +0,0 @@ -# 10.4 变态跳台阶 - -## 题目链接 - -[NowCoder](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级... 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 - -

- -## 解题思路 - -### 动态规划 - -```java -public int JumpFloorII(int target) { - int[] dp = new int[target]; - Arrays.fill(dp, 1); - for (int i = 1; i < target; i++) - for (int j = 0; j < i; j++) - dp[i] += dp[j]; - return dp[target - 1]; -} -``` - -### 数学推导 - -跳上 n-1 级台阶,可以从 n-2 级跳 1 级上去,也可以从 n-3 级跳 2 级上去...,那么 - -``` -f(n-1) = f(n-2) + f(n-3) + ... + f(0) -``` - -同样,跳上 n 级台阶,可以从 n-1 级跳 1 级上去,也可以从 n-2 级跳 2 级上去... ,那么 - -``` -f(n) = f(n-1) + f(n-2) + ... + f(0) -``` - -综上可得 - -``` -f(n) - f(n-1) = f(n-1) -``` - -即 - -``` -f(n) = 2*f(n-1) -``` - -所以 f(n) 是一个等比数列 - -```source-java -public int JumpFloorII(int target) { - return (int) Math.pow(2, target - 1); -} -``` - - - - - - -
diff --git "a/docs/notes/11. \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.md" "b/docs/notes/11. \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.md" deleted file mode 100644 index c1022565d822b70280c487952ec92e32b8adba89..0000000000000000000000000000000000000000 --- "a/docs/notes/11. \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.md" +++ /dev/null @@ -1,74 +0,0 @@ -# 11. 旋转数组的最小数字 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba?tpId=13&tqId=11159&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 - -

- -## 解题思路 - -将旋转数组对半分可以得到一个包含最小元素的新旋转数组,以及一个非递减排序的数组。新的旋转数组的长度是原数组的一半,从而将问题规模减少了一半,这种折半性质的算法的时间复杂度为 O(log2N)。 - -

- -此时问题的关键在于确定对半分得到的两个数组哪一个是旋转数组,哪一个是非递减数组。我们很容易知道非递减数组的第一个元素一定小于等于最后一个元素。 - -通过修改二分查找算法进行求解(l 代表 low,m 代表 mid,h 代表 high): - -- 当 nums[m] <= nums[h] 时,表示 [m, h] 区间内的数组是非递减数组,[l, m] 区间内的数组是旋转数组,此时令 h = m; -- 否则 [m + 1, h] 区间内的数组是旋转数组,令 l = m + 1。 - -```java -public int minNumberInRotateArray(int[] nums) { - if (nums.length == 0) - return 0; - int l = 0, h = nums.length - 1; - while (l < h) { - int m = l + (h - l) / 2; - if (nums[m] <= nums[h]) - h = m; - else - l = m + 1; - } - return nums[l]; -} -``` - -如果数组元素允许重复,会出现一个特殊的情况:nums[l] == nums[m] == nums[h],此时无法确定解在哪个区间,需要切换到顺序查找。例如对于数组 {1,1,1,0,1},l、m 和 h 指向的数都为 1,此时无法知道最小数字 0 在哪个区间。 - -```java -public int minNumberInRotateArray(int[] nums) { - if (nums.length == 0) - return 0; - int l = 0, h = nums.length - 1; - while (l < h) { - int m = l + (h - l) / 2; - if (nums[l] == nums[m] && nums[m] == nums[h]) - return minNumber(nums, l, h); - else if (nums[m] <= nums[h]) - h = m; - else - l = m + 1; - } - return nums[l]; -} - -private int minNumber(int[] nums, int l, int h) { - for (int i = l; i < h; i++) - if (nums[i] > nums[i + 1]) - return nums[i + 1]; - return nums[l]; -} -``` - - - - - - -
diff --git "a/docs/notes/12. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" "b/docs/notes/12. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" deleted file mode 100644 index 217f56967e8d2c1d8f3d994dbeeb7ac690957841..0000000000000000000000000000000000000000 --- "a/docs/notes/12. \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" +++ /dev/null @@ -1,71 +0,0 @@ -# 12. 矩阵中的路径 - -[NowCoder](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向上下左右移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 - -例如下面的矩阵包含了一条 bfce 路径。 - -

- -## 解题思路 - -使用回溯法(backtracking)进行求解,它是一种暴力搜索方法,通过搜索所有可能的结果来求解问题。回溯法在一次搜索结束时需要进行回溯(回退),将这一次搜索过程中设置的状态进行清除,从而开始一次新的搜索过程。例如下图示例中,从 f 开始,下一步有 4 种搜索可能,如果先搜索 b,需要将 b 标记为已经使用,防止重复使用。在这一次搜索结束之后,需要将 b 的已经使用状态清除,并搜索 c。 - -

- -本题的输入是数组而不是矩阵(二维数组),因此需要先将数组转换成矩阵。 - -```java -private final static int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; -private int rows; -private int cols; - -public boolean hasPath(char[] array, int rows, int cols, char[] str) { - if (rows == 0 || cols == 0) return false; - this.rows = rows; - this.cols = cols; - boolean[][] marked = new boolean[rows][cols]; - char[][] matrix = buildMatrix(array); - for (int i = 0; i < rows; i++) - for (int j = 0; j < cols; j++) - if (backtracking(matrix, str, marked, 0, i, j)) - return true; - - return false; -} - -private boolean backtracking(char[][] matrix, char[] str, - boolean[][] marked, int pathLen, int r, int c) { - - if (pathLen == str.length) return true; - if (r < 0 || r >= rows || c < 0 || c >= cols - || matrix[r][c] != str[pathLen] || marked[r][c]) { - - return false; - } - marked[r][c] = true; - for (int[] n : next) - if (backtracking(matrix, str, marked, pathLen + 1, r + n[0], c + n[1])) - return true; - marked[r][c] = false; - return false; -} - -private char[][] buildMatrix(char[] array) { - char[][] matrix = new char[rows][cols]; - for (int r = 0, idx = 0; r < rows; r++) - for (int c = 0; c < cols; c++) - matrix[r][c] = array[idx++]; - return matrix; -} -``` - - - - - - -
diff --git "a/docs/notes/13. \346\234\272\345\231\250\344\272\272\347\232\204\350\277\220\345\212\250\350\214\203\345\233\264.md" "b/docs/notes/13. \346\234\272\345\231\250\344\272\272\347\232\204\350\277\220\345\212\250\350\214\203\345\233\264.md" deleted file mode 100644 index 50477b073d86fffc90497656cb82f0ca787278aa..0000000000000000000000000000000000000000 --- "a/docs/notes/13. \346\234\272\345\231\250\344\272\272\347\232\204\350\277\220\345\212\250\350\214\203\345\233\264.md" +++ /dev/null @@ -1,65 +0,0 @@ -# 13. 机器人的运动范围 - -[NowCoder](https://www.nowcoder.com/practice/6e5207314b5241fb83f2329e89fdecc8?tpId=13&tqId=11219&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。 - -例如,当 k 为 18 时,机器人能够进入方格 (35,37),因为 3+5+3+7=18。但是,它不能进入方格 (35,38),因为 3+5+3+8=19。请问该机器人能够达到多少个格子? - -## 解题思路 - -使用深度优先搜索(Depth First Search,DFS)方法进行求解。回溯是深度优先搜索的一种特例,它在一次搜索过程中需要设置一些本次搜索过程的局部状态,并在本次搜索结束之后清除状态。而普通的深度优先搜索并不需要使用这些局部状态,虽然还是有可能设置一些全局状态。 - -```java -private static final int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; -private int cnt = 0; -private int rows; -private int cols; -private int threshold; -private int[][] digitSum; - -public int movingCount(int threshold, int rows, int cols) { - this.rows = rows; - this.cols = cols; - this.threshold = threshold; - initDigitSum(); - boolean[][] marked = new boolean[rows][cols]; - dfs(marked, 0, 0); - return cnt; -} - -private void dfs(boolean[][] marked, int r, int c) { - if (r < 0 || r >= rows || c < 0 || c >= cols || marked[r][c]) - return; - marked[r][c] = true; - if (this.digitSum[r][c] > this.threshold) - return; - cnt++; - for (int[] n : next) - dfs(marked, r + n[0], c + n[1]); -} - -private void initDigitSum() { - int[] digitSumOne = new int[Math.max(rows, cols)]; - for (int i = 0; i < digitSumOne.length; i++) { - int n = i; - while (n > 0) { - digitSumOne[i] += n % 10; - n /= 10; - } - } - this.digitSum = new int[rows][cols]; - for (int i = 0; i < this.rows; i++) - for (int j = 0; j < this.cols; j++) - this.digitSum[i][j] = digitSumOne[i] + digitSumOne[j]; -} -``` - - - - - - -
diff --git "a/docs/notes/14. \345\211\252\347\273\263\345\255\220.md" "b/docs/notes/14. \345\211\252\347\273\263\345\255\220.md" deleted file mode 100644 index 853f42e4858bc755ef0d59e82a33989fc85be4b2..0000000000000000000000000000000000000000 --- "a/docs/notes/14. \345\211\252\347\273\263\345\255\220.md" +++ /dev/null @@ -1,74 +0,0 @@ -# 14. 剪绳子 - -## 题目链接 - -[Leetcode](https://leetcode.com/problems/integer-break/description/) - -## 题目描述 - -把一根绳子剪成多段,并且使得每段的长度乘积最大。 - -```html -n = 2 -return 1 (2 = 1 + 1) - -n = 10 -return 36 (10 = 3 + 3 + 4) -``` - -## 解题思路 - -### 贪心 - -尽可能得多剪长度为 3 的绳子,并且不允许有长度为 1 的绳子出现。如果出现了,就从已经切好长度为 3 的绳子中拿出一段与长度为 1 的绳子重新组合,把它们切成两段长度为 2 的绳子。以下为证明过程。 - -将绳子拆成 1 和 n-1,则 1(n-1)-n=-1<0,即拆开后的乘积一定更小,所以不能出现长度为 1 的绳子。 - -将绳子拆成 2 和 n-2,则 2(n-2)-n = n-4,在 n>=4 时这样拆开能得到的乘积会比不拆更大。 - -将绳子拆成 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。 - -将绳子拆成 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。 - -```java -public int integerBreak(int n) { - if (n < 2) - return 0; - if (n == 2) - return 1; - if (n == 3) - return 2; - int timesOf3 = n / 3; - if (n - timesOf3 * 3 == 1) - timesOf3--; - int timesOf2 = (n - timesOf3 * 3) / 2; - return (int) (Math.pow(3, timesOf3)) * (int) (Math.pow(2, timesOf2)); -} -``` - -### 动态规划 - -```java -public int integerBreak(int n) { - int[] dp = new int[n + 1]; - dp[1] = 1; - for (int i = 2; i <= n; i++) - for (int j = 1; j < i; j++) - dp[i] = Math.max(dp[i], Math.max(j * (i - j), dp[j] * (i - j))); - return dp[n]; -} -``` - - - - - - - -
diff --git "a/docs/notes/15. \344\272\214\350\277\233\345\210\266\344\270\255 1 \347\232\204\344\270\252\346\225\260.md" "b/docs/notes/15. \344\272\214\350\277\233\345\210\266\344\270\255 1 \347\232\204\344\270\252\346\225\260.md" deleted file mode 100644 index d3532e14cab592fc21b4a57fa0f5d1c1d18c9db0..0000000000000000000000000000000000000000 --- "a/docs/notes/15. \344\272\214\350\277\233\345\210\266\344\270\255 1 \347\232\204\344\270\252\346\225\260.md" +++ /dev/null @@ -1,34 +0,0 @@ -# 15. 二进制中 1 的个数 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/8ee967e43c2c4ec193b040ea7fbb10b8?tpId=13&tqId=11164&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -输入一个整数,输出该数二进制表示中 1 的个数。 - -### 解题思路 - -n&(n-1) 位运算可以将 n 的位级表示中最低的那一位 1 设置为 0。不断将 1 设置为 0,直到 n 为 0。时间复杂度:O(M),其中 M 表示 1 的个数。 - -

- - -```java -public int NumberOf1(int n) { - int cnt = 0; - while (n != 0) { - cnt++; - n &= (n - 1); - } - return cnt; -} -``` - - - - - - -
diff --git "a/docs/notes/16. \346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271.md" "b/docs/notes/16. \346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271.md" deleted file mode 100644 index e8e579fc0b601db807c683d4f70e3c306378e224..0000000000000000000000000000000000000000 --- "a/docs/notes/16. \346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271.md" +++ /dev/null @@ -1,53 +0,0 @@ -# 16. 数值的整数次方 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&tqId=11165&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -给定一个 double 类型的浮点数 x和 int 类型的整数 n,求 x 的 n 次方。 - -## 解题思路 - - - -最直观的解法是将 x 重复乘 n 次,x\*x\*x...\*x,那么时间复杂度为 O(N)。因为乘法是可交换的,所以可以将上述操作拆开成两半 (x\*x..\*x)\* (x\*x..\*x),两半的计算是一样的,因此只需要计算一次。而且对于新拆开的计算,又可以继续拆开。这就是分治思想,将原问题的规模拆成多个规模较小的子问题,最后子问题的解合并起来。 - -本题中子问题是 xn/2,在将子问题合并时将子问题的解乘于自身相乘即可。但如果 n 不为偶数,那么拆成两半还会剩下一个 x,在将子问题合并时还需要需要多乘于一个 x。 - - - -

- - -因为 (x\*x)n/2 可以通过递归求解,并且每次递归 n 都减小一半,因此整个算法的时间复杂度为 O(logN)。 - -```java -public double Power(double x, int n) { - boolean isNegative = false; - if (n < 0) { - n = -n; - isNegative = true; - } - double res = pow(x, n); - return isNegative ? 1 / res : res; -} - -private double pow(double x, int n) { - if (n == 0) return 1; - if (n == 1) return x; - double res = pow(x, n / 2); - res = res * res; - if (n % 2 != 0) res *= x; - return res; -} -``` - - - - - - - -
diff --git "a/docs/notes/17. \346\211\223\345\215\260\344\273\216 1 \345\210\260\346\234\200\345\244\247\347\232\204 n \344\275\215\346\225\260.md" "b/docs/notes/17. \346\211\223\345\215\260\344\273\216 1 \345\210\260\346\234\200\345\244\247\347\232\204 n \344\275\215\346\225\260.md" deleted file mode 100644 index 28ad68e536bf9aa9fe096c1acc8d85c2abf2d86e..0000000000000000000000000000000000000000 --- "a/docs/notes/17. \346\211\223\345\215\260\344\273\216 1 \345\210\260\346\234\200\345\244\247\347\232\204 n \344\275\215\346\225\260.md" +++ /dev/null @@ -1,47 +0,0 @@ -# 17. 打印从 1 到最大的 n 位数 - -## 题目描述 - -输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数即 999。 - -## 解题思路 - -由于 n 可能会非常大,因此不能直接用 int 表示数字,而是用 char 数组进行存储。 - -使用回溯法得到所有的数。 - -```java -public void print1ToMaxOfNDigits(int n) { - if (n <= 0) - return; - char[] number = new char[n]; - print1ToMaxOfNDigits(number, 0); -} - -private void print1ToMaxOfNDigits(char[] number, int digit) { - if (digit == number.length) { - printNumber(number); - return; - } - for (int i = 0; i < 10; i++) { - number[digit] = (char) (i + '0'); - print1ToMaxOfNDigits(number, digit + 1); - } -} - -private void printNumber(char[] number) { - int index = 0; - while (index < number.length && number[index] == '0') - index++; - while (index < number.length) - System.out.print(number[index++]); - System.out.println(); -} -``` - - - - - - -
diff --git "a/docs/notes/18.1 \345\234\250 O(1) \346\227\266\351\227\264\345\206\205\345\210\240\351\231\244\351\223\276\350\241\250\350\212\202\347\202\271.md" "b/docs/notes/18.1 \345\234\250 O(1) \346\227\266\351\227\264\345\206\205\345\210\240\351\231\244\351\223\276\350\241\250\350\212\202\347\202\271.md" deleted file mode 100644 index 8cd0a5eee641c2b498b343f51ca89f36cfa166e0..0000000000000000000000000000000000000000 --- "a/docs/notes/18.1 \345\234\250 O(1) \346\227\266\351\227\264\345\206\205\345\210\240\351\231\244\351\223\276\350\241\250\350\212\202\347\202\271.md" +++ /dev/null @@ -1,44 +0,0 @@ -# 18.1 在 O(1) 时间内删除链表节点 - -## 解题思路 - -① 如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,然后令该节点指向下下个节点,再删除下一个节点,时间复杂度为 O(1)。 - -

- -② 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向 null,时间复杂度为 O(N)。 - -

- -综上,如果进行 N 次操作,那么大约需要操作节点的次数为 N-1+N=2N-1,其中 N-1 表示 N-1 个不是尾节点的每个节点以 O(1) 的时间复杂度操作节点的总次数,N 表示 1 个尾节点以 O(N) 的时间复杂度操作节点的总次数。(2N-1)/N \~ 2,因此该算法的平均时间复杂度为 O(1)。 - -```java -public ListNode deleteNode(ListNode head, ListNode tobeDelete) { - if (head == null || tobeDelete == null) - return null; - if (tobeDelete.next != null) { - // 要删除的节点不是尾节点 - ListNode next = tobeDelete.next; - tobeDelete.val = next.val; - tobeDelete.next = next.next; - } else { - if (head == tobeDelete) - // 只有一个节点 - head = null; - else { - ListNode cur = head; - while (cur.next != tobeDelete) - cur = cur.next; - cur.next = null; - } - } - return head; -} -``` - - - - - - -
diff --git "a/docs/notes/18.2 \345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\351\207\215\345\244\215\347\232\204\347\273\223\347\202\271.md" "b/docs/notes/18.2 \345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\351\207\215\345\244\215\347\232\204\347\273\223\347\202\271.md" deleted file mode 100644 index 555a075436647ff71634368fd3f09602d06ceeae..0000000000000000000000000000000000000000 --- "a/docs/notes/18.2 \345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\351\207\215\345\244\215\347\232\204\347\273\223\347\202\271.md" +++ /dev/null @@ -1,32 +0,0 @@ -# 18.2 删除链表中重复的结点 - -[NowCoder](https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -

- -## 解题描述 - -```java -public ListNode deleteDuplication(ListNode pHead) { - if (pHead == null || pHead.next == null) - return pHead; - ListNode next = pHead.next; - if (pHead.val == next.val) { - while (next != null && pHead.val == next.val) - next = next.next; - return deleteDuplication(next); - } else { - pHead.next = deleteDuplication(pHead.next); - return pHead; - } -} -``` - - - - - - -
diff --git "a/docs/notes/19. \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215.md" "b/docs/notes/19. \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215.md" deleted file mode 100644 index c8af6f8351e5e8bafe3fd7a6b01fa3c16c3cbfd2..0000000000000000000000000000000000000000 --- "a/docs/notes/19. \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215.md" +++ /dev/null @@ -1,47 +0,0 @@ -# 19. 正则表达式匹配 - -[NowCoder](https://www.nowcoder.com/practice/45327ae22b7b413ea21df13ee7d6429c?tpId=13&tqId=11205&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -请实现一个函数用来匹配包括 '.' 和 '\*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而 '\*' 表示它前面的字符可以出现任意次(包含 0 次)。 - -在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 "aaa" 与模式 "a.a" 和 "ab\*ac\*a" 匹配,但是与 "aa.a" 和 "ab\*a" 均不匹配。 - -## 解题思路 - -应该注意到,'.' 是用来当做一个任意字符,而 '\*' 是用来重复前面的字符。这两个的作用不同,不能把 '.' 的作用和 '\*' 进行类比,从而把它当成重复前面字符一次。 - -```java -public boolean match(char[] str, char[] pattern) { - - int m = str.length, n = pattern.length; - boolean[][] dp = new boolean[m + 1][n + 1]; - - dp[0][0] = true; - for (int i = 1; i <= n; i++) - if (pattern[i - 1] == '*') - dp[0][i] = dp[0][i - 2]; - - for (int i = 1; i <= m; i++) - for (int j = 1; j <= n; j++) - if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') - dp[i][j] = dp[i - 1][j - 1]; - else if (pattern[j - 1] == '*') - if (pattern[j - 2] == str[i - 1] || pattern[j - 2] == '.') { - dp[i][j] |= dp[i][j - 1]; // a* counts as single a - dp[i][j] |= dp[i - 1][j]; // a* counts as multiple a - dp[i][j] |= dp[i][j - 2]; // a* counts as empty - } else - dp[i][j] = dp[i][j - 2]; // a* only counts as empty - - return dp[m][n]; -} -``` - - - - - - -
diff --git "a/docs/notes/20. \350\241\250\347\244\272\346\225\260\345\200\274\347\232\204\345\255\227\347\254\246\344\270\262.md" "b/docs/notes/20. \350\241\250\347\244\272\346\225\260\345\200\274\347\232\204\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 8afe4f9126e88670bdaa0123f549b3e16756deab..0000000000000000000000000000000000000000 --- "a/docs/notes/20. \350\241\250\347\244\272\346\225\260\345\200\274\347\232\204\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,56 +0,0 @@ -# 20. 表示数值的字符串 - -[NowCoder](https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -``` -true - -"+100" -"5e2" -"-123" -"3.1416" -"-1E-16" -``` - -``` -false - -"12e" -"1a3.14" -"1.2.3" -"+-5" -"12e+4.3" -``` - - -## 解题思路 - -使用正则表达式进行匹配。 - -```html -[] : 字符集合 -() : 分组 -? : 重复 0 ~ 1 次 -+ : 重复 1 ~ n 次 -* : 重复 0 ~ n 次 -. : 任意字符 -\\. : 转义后的 . -\\d : 数字 -``` - -```java -public boolean isNumeric(char[] str) { - if (str == null || str.length == 0) - return false; - return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?"); -} -``` - - - - - - -
diff --git "a/docs/notes/21. \350\260\203\346\225\264\346\225\260\347\273\204\351\241\272\345\272\217\344\275\277\345\245\207\346\225\260\344\275\215\344\272\216\345\201\266\346\225\260\345\211\215\351\235\242.md" "b/docs/notes/21. \350\260\203\346\225\264\346\225\260\347\273\204\351\241\272\345\272\217\344\275\277\345\245\207\346\225\260\344\275\215\344\272\216\345\201\266\346\225\260\345\211\215\351\235\242.md" deleted file mode 100644 index 2c965dab745308c8e2611d2e2f19704bb0c9fbec..0000000000000000000000000000000000000000 --- "a/docs/notes/21. \350\260\203\346\225\264\346\225\260\347\273\204\351\241\272\345\272\217\344\275\277\345\245\207\346\225\260\344\275\215\344\272\216\345\201\266\346\225\260\345\211\215\351\235\242.md" +++ /dev/null @@ -1,69 +0,0 @@ -# 21. 调整数组顺序使奇数位于偶数前面 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/beb5aa231adc45b2a5dcc5b62c93f593?tpId=13&tqId=11166&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -需要保证奇数和奇数,偶数和偶数之间的相对位置不变,这和书本不太一样。例如对于 [1,2,3,4,5],调整后得到 [1,3,5,2,4],而不能是 {5,1,3,4,2} 这种相对位置改变的结果。 - -

- -## 解题思路 - -方法一:创建一个新数组,时间复杂度 O(N),空间复杂度 O(N)。 - -```java -public void reOrderArray(int[] nums) { - // 奇数个数 - int oddCnt = 0; - for (int x : nums) - if (!isEven(x)) - oddCnt++; - int[] copy = nums.clone(); - int i = 0, j = oddCnt; - for (int num : copy) { - if (num % 2 == 1) - nums[i++] = num; - else - nums[j++] = num; - } -} - -private boolean isEven(int x) { - return x % 2 == 0; -} -``` - -方法二:使用冒泡思想,每次都将当前偶数上浮到当前最右边。时间复杂度 O(N2),空间复杂度 O(1),时间换空间。 - -```java -public void reOrderArray(int[] nums) { - int N = nums.length; - for (int i = N - 1; i > 0; i--) { - for (int j = 0; j < i; j++) { - if (isEven(nums[j]) && !isEven(nums[j + 1])) { - swap(nums, j, j + 1); - } - } - } -} - -private boolean isEven(int x) { - return x % 2 == 0; -} - -private void swap(int[] nums, int i, int j) { - int t = nums[i]; - nums[i] = nums[j]; - nums[j] = t; -} -``` - - - - - - -
diff --git "a/docs/notes/22. \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254 K \344\270\252\347\273\223\347\202\271.md" "b/docs/notes/22. \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254 K \344\270\252\347\273\223\347\202\271.md" deleted file mode 100644 index 209dc0e98e1d4b3cd061dbf6388bae5cbcf93bd6..0000000000000000000000000000000000000000 --- "a/docs/notes/22. \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254 K \344\270\252\347\273\223\347\202\271.md" +++ /dev/null @@ -1,34 +0,0 @@ -# 22. 链表中倒数第 K 个结点 - -[NowCoder](https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 解题思路 - -设链表的长度为 N。设置两个指针 P1 和 P2,先让 P1 移动 K 个节点,则还有 N - K 个节点可以移动。此时让 P1 和 P2 同时移动,可以知道当 P1 移动到链表结尾时,P2 移动到第 N - K 个节点处,该位置就是倒数第 K 个节点。 - -

- -```java -public ListNode FindKthToTail(ListNode head, int k) { - if (head == null) - return null; - ListNode P1 = head; - while (P1 != null && k-- > 0) - P1 = P1.next; - if (k > 0) - return null; - ListNode P2 = head; - while (P1 != null) { - P1 = P1.next; - P2 = P2.next; - } - return P2; -} -``` - - - - - - -
diff --git "a/docs/notes/23. \351\223\276\350\241\250\344\270\255\347\216\257\347\232\204\345\205\245\345\217\243\347\273\223\347\202\271.md" "b/docs/notes/23. \351\223\276\350\241\250\344\270\255\347\216\257\347\232\204\345\205\245\345\217\243\347\273\223\347\202\271.md" deleted file mode 100644 index aea40f9a71f54c01e8743cfa9ebd589b7b26a416..0000000000000000000000000000000000000000 --- "a/docs/notes/23. \351\223\276\350\241\250\344\270\255\347\216\257\347\232\204\345\205\245\345\217\243\347\273\223\347\202\271.md" +++ /dev/null @@ -1,50 +0,0 @@ -# 23. 链表中环的入口结点 - -[NowCoder](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -一个链表中包含环,请找出该链表的环的入口结点。要求不能使用额外的空间。 - -## 解题思路 - -使用双指针,一个快指针 fast 每次移动两个节点,一个慢指针 slow 每次移动一个节点。因为存在环,所以两个指针必定相遇在环中的某个节点上。 - -假设环入口节点为 y1,相遇所在节点为 z1。 - -假设快指针 fast 在圈内绕了 N 圈,则总路径长度为 x+Ny+(N-1)z。z 为 (N-1) 倍是因为快慢指针最后已经在 z1 节点相遇了,后面就不需要再走了。 - -而慢指针 slow 总路径长度为 x+y。 - -因为快指针是慢指针的两倍,因此 x+Ny+(N-1)z = 2(x+y)。 - -我们要找的是环入口节点 y1,也可以看成寻找长度 x 的值,因此我们先将上面的等值分解为和 x 有关:x=(N-2)y+(N-1)z。 - -上面的等值没有很强的规律,但是我们可以发现 y+z 就是圆环的总长度,因此我们将上面的等式再分解:x=(N-2)(y+z)+z。这个等式左边是从起点x1 到环入口节点 y1 的长度,而右边是在圆环中走过 (N-2) 圈,再从相遇点 z1 再走过长度为 z 的长度。此时我们可以发现如果让两个指针同时从起点 x1 和相遇点 z1 开始,每次只走过一个距离,那么最后他们会在环入口节点相遇。 - -

- -```java -public ListNode EntryNodeOfLoop(ListNode pHead) { - if (pHead == null || pHead.next == null) - return null; - ListNode slow = pHead, fast = pHead; - do { - fast = fast.next.next; - slow = slow.next; - } while (slow != fast); - fast = pHead; - while (slow != fast) { - slow = slow.next; - fast = fast.next; - } - return slow; -} -``` - - - - - - -
diff --git "a/docs/notes/24. \345\217\215\350\275\254\351\223\276\350\241\250.md" "b/docs/notes/24. \345\217\215\350\275\254\351\223\276\350\241\250.md" deleted file mode 100644 index 8d48a3583f1d697514c4cb7c22626c644933a56e..0000000000000000000000000000000000000000 --- "a/docs/notes/24. \345\217\215\350\275\254\351\223\276\350\241\250.md" +++ /dev/null @@ -1,43 +0,0 @@ -# 24. 反转链表 - -[NowCoder](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 解题思路 - -### 递归 - -```java -public ListNode ReverseList(ListNode head) { - if (head == null || head.next == null) - return head; - ListNode next = head.next; - head.next = null; - ListNode newHead = ReverseList(next); - next.next = head; - return newHead; -} -``` - -### 迭代 - -使用头插法。 - -```java -public ListNode ReverseList(ListNode head) { - ListNode newList = new ListNode(-1); - while (head != null) { - ListNode next = head.next; - head.next = newList.next; - newList.next = head; - head = next; - } - return newList.next; -} -``` - - - - - - -
diff --git "a/docs/notes/25. \345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.md" "b/docs/notes/25. \345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.md" deleted file mode 100644 index b3b5b4df990680401225cf82b8a0e9d53a9bdcb9..0000000000000000000000000000000000000000 --- "a/docs/notes/25. \345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.md" +++ /dev/null @@ -1,58 +0,0 @@ -# 25. 合并两个排序的链表 - -[NowCoder](https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -

- -## 解题思路 - -### 递归 - -```java -public ListNode Merge(ListNode list1, ListNode list2) { - if (list1 == null) - return list2; - if (list2 == null) - return list1; - if (list1.val <= list2.val) { - list1.next = Merge(list1.next, list2); - return list1; - } else { - list2.next = Merge(list1, list2.next); - return list2; - } -} -``` - -### 迭代 - -```java -public ListNode Merge(ListNode list1, ListNode list2) { - ListNode head = new ListNode(-1); - ListNode cur = head; - while (list1 != null && list2 != null) { - if (list1.val <= list2.val) { - cur.next = list1; - list1 = list1.next; - } else { - cur.next = list2; - list2 = list2.next; - } - cur = cur.next; - } - if (list1 != null) - cur.next = list1; - if (list2 != null) - cur.next = list2; - return head.next; -} -``` - - - - - - -
diff --git "a/docs/notes/26. \346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.md" "b/docs/notes/26. \346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.md" deleted file mode 100644 index c5c31e5063c677e3a593cead41ed88efc3b0bc12..0000000000000000000000000000000000000000 --- "a/docs/notes/26. \346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.md" +++ /dev/null @@ -1,36 +0,0 @@ -# 26. 树的子结构 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&tqId=11170&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -

- -## 解题思路 - -```java -public boolean HasSubtree(TreeNode root1, TreeNode root2) { - if (root1 == null || root2 == null) - return false; - return isSubtreeWithRoot(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2); -} - -private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) { - if (root2 == null) - return true; - if (root1 == null) - return false; - if (root1.val != root2.val) - return false; - return isSubtreeWithRoot(root1.left, root2.left) && isSubtreeWithRoot(root1.right, root2.right); -} -``` - - - - - - -
diff --git "a/docs/notes/27. \344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.md" "b/docs/notes/27. \344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.md" deleted file mode 100644 index abe1c395409efd1a2d1eecf15a715520c4af36d6..0000000000000000000000000000000000000000 --- "a/docs/notes/27. \344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.md" +++ /dev/null @@ -1,32 +0,0 @@ -# 27. 二叉树的镜像 - -[NowCoder](https://www.nowcoder.com/practice/564f4c26aa584921bc75623e48ca3011?tpId=13&tqId=11171&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -

- -## 解题思路 - -```java -public void Mirror(TreeNode root) { - if (root == null) - return; - swap(root); - Mirror(root.left); - Mirror(root.right); -} - -private void swap(TreeNode root) { - TreeNode t = root.left; - root.left = root.right; - root.right = t; -} -``` - - - - - - -
diff --git "a/docs/notes/28. \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" "b/docs/notes/28. \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 94d658aafbcad24c7f7111054ac0889ff16e8cd8..0000000000000000000000000000000000000000 --- "a/docs/notes/28. \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,34 +0,0 @@ -# 28. 对称的二叉树 - -[NowCoder](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -

- -## 解题思路 - -```java -boolean isSymmetrical(TreeNode pRoot) { - if (pRoot == null) - return true; - return isSymmetrical(pRoot.left, pRoot.right); -} - -boolean isSymmetrical(TreeNode t1, TreeNode t2) { - if (t1 == null && t2 == null) - return true; - if (t1 == null || t2 == null) - return false; - if (t1.val != t2.val) - return false; - return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left); -} -``` - - - - - - -
diff --git "a/docs/notes/29. \351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.md" "b/docs/notes/29. \351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.md" deleted file mode 100644 index ba9a3bdbcde39ed3872170b52cbdf6df8941143a..0000000000000000000000000000000000000000 --- "a/docs/notes/29. \351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.md" +++ /dev/null @@ -1,51 +0,0 @@ -# 29. 顺时针打印矩阵 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a?tpId=13&tqId=11172&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -按顺时针的方向,从外到里打印矩阵的值。下图的矩阵打印结果为:1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10 - -

- - - -## 解题思路 - -一层一层从外到里打印,观察可知每一层打印都有相同的处理步骤,唯一不同的是上下左右的边界不同了。因此使用四个变量 r1, r2, c1, c2 分别存储上下左右边界值,从而定义当前最外层。打印当前最外层的顺序:从左到右打印最上一行->从上到下打印最右一行->从右到左打印最下一行->从下到上打印最左一行。应当注意只有在 r1 != r2 时才打印最下一行,也就是在当前最外层的行数大于 1 时才打印最下一行,这是因为当前最外层只有一行时,继续打印最下一行,会导致重复打印。打印最左一行也要做同样处理。 - -

- -```java -public ArrayList printMatrix(int[][] matrix) { - ArrayList ret = new ArrayList<>(); - int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1; - while (r1 <= r2 && c1 <= c2) { - // 上 - for (int i = c1; i <= c2; i++) - ret.add(matrix[r1][i]); - // 右 - for (int i = r1 + 1; i <= r2; i++) - ret.add(matrix[i][c2]); - if (r1 != r2) - // 下 - for (int i = c2 - 1; i >= c1; i--) - ret.add(matrix[r2][i]); - if (c1 != c2) - // 左 - for (int i = r2 - 1; i > r1; i--) - ret.add(matrix[i][c1]); - r1++; r2--; c1++; c2--; - } - return ret; -} -``` - - - - - - -
diff --git "a/docs/notes/3. \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" "b/docs/notes/3. \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index aec6d1d969ef806124799a3bba8e0e3821c62194..0000000000000000000000000000000000000000 --- "a/docs/notes/3. \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,58 +0,0 @@ -# 3. 数组中重复的数字 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 - -```html -Input: -{2, 3, 1, 0, 2, 5} - -Output: -2 -``` - -## 解题思路 - -要求时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。 - -对于这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上进行求解。在调整过程中,如果第 i 位置上已经有一个值为 i 的元素,就可以知道 i 值重复。 - -以 (2, 3, 1, 0, 2, 5) 为例,遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复: - -

- - -```java -public boolean duplicate(int[] nums, int length, int[] duplication) { - if (nums == null || length <= 0) - return false; - for (int i = 0; i < length; i++) { - while (nums[i] != i) { - if (nums[i] == nums[nums[i]]) { - duplication[0] = nums[i]; - return true; - } - swap(nums, i, nums[i]); - } - } - return false; -} - -private void swap(int[] nums, int i, int j) { - int t = nums[i]; - nums[i] = nums[j]; - nums[j] = t; -} -``` - - - - - - -
diff --git "a/docs/notes/30. \345\214\205\345\220\253 min \345\207\275\346\225\260\347\232\204\346\240\210.md" "b/docs/notes/30. \345\214\205\345\220\253 min \345\207\275\346\225\260\347\232\204\346\240\210.md" deleted file mode 100644 index c1cf7e22f35d684fe33b2ecc03074b9926972c69..0000000000000000000000000000000000000000 --- "a/docs/notes/30. \345\214\205\345\220\253 min \345\207\275\346\225\260\347\232\204\346\240\210.md" +++ /dev/null @@ -1,45 +0,0 @@ -# 30. 包含 min 函数的栈 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/4c776177d2c04c2494f2555c9fcc1e49?tpId=13&tqId=11173&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -实现一个包含 min() 函数的栈,该方法返回当前栈中最小的值。 - -## 解题思路 - -使用一个额外的 minStack,栈顶元素为当前栈中最小的值。在对栈进行 push 入栈和 pop 出栈操作时,同样需要对 minStack 进行入栈出栈操作,从而使 minStack 栈顶元素一直为当前栈中最小的值。在进行 push 操作时,需要比较入栈元素和当前栈中最小值,将值较小的元素 push 到 minStack 中。 - -

- -```java -private Stack dataStack = new Stack<>(); -private Stack minStack = new Stack<>(); - -public void push(int node) { - dataStack.push(node); - minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(), node)); -} - -public void pop() { - dataStack.pop(); - minStack.pop(); -} - -public int top() { - return dataStack.peek(); -} - -public int min() { - return minStack.peek(); -} -``` - - - - - - -
diff --git "a/docs/notes/31. \346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.md" "b/docs/notes/31. \346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.md" deleted file mode 100644 index 7384a5b45c6c9c2210e77a7c0332da959d06a4e3..0000000000000000000000000000000000000000 --- "a/docs/notes/31. \346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.md" +++ /dev/null @@ -1,39 +0,0 @@ -# 31. 栈的压入、弹出序列 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&tqId=11174&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。 - -例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。 - -## 解题思路 - -使用一个栈来模拟压入弹出操作。每次入栈一个元素后,都要判断一下栈顶元素是不是当前出栈序列 popSequence 的第一个元素,如果是的话则执行出栈操作并将 popSequence 往后移一位,继续进行判断。 - -```java -public boolean IsPopOrder(int[] pushSequence, int[] popSequence) { - int n = pushSequence.length; - Stack stack = new Stack<>(); - for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) { - stack.push(pushSequence[pushIndex]); - while (popIndex < n && !stack.isEmpty() - && stack.peek() == popSequence[popIndex]) { - stack.pop(); - popIndex++; - } - } - return stack.isEmpty(); -} -``` - - - - - - - -
diff --git "a/docs/notes/32.1 \344\273\216\344\270\212\345\276\200\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" "b/docs/notes/32.1 \344\273\216\344\270\212\345\276\200\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 6c963b15912f6215dec574f5d26d3bfdfabc7289..0000000000000000000000000000000000000000 --- "a/docs/notes/32.1 \344\273\216\344\270\212\345\276\200\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,44 +0,0 @@ -# 32.1 从上往下打印二叉树 - -[NowCoder](https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -从上往下打印出二叉树的每个节点,同层节点从左至右打印。 - -例如,以下二叉树层次遍历的结果为:1,2,3,4,5,6,7 - -

- -## 解题思路 - -使用队列来进行层次遍历。 - -不需要使用两个队列分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。 - -```java -public ArrayList PrintFromTopToBottom(TreeNode root) { - Queue queue = new LinkedList<>(); - ArrayList ret = new ArrayList<>(); - queue.add(root); - while (!queue.isEmpty()) { - int cnt = queue.size(); - while (cnt-- > 0) { - TreeNode t = queue.poll(); - if (t == null) - continue; - ret.add(t.val); - queue.add(t.left); - queue.add(t.right); - } - } - return ret; -} -``` - - - - - - -
diff --git "a/docs/notes/32.2 \346\212\212\344\272\214\345\217\211\346\240\221\346\211\223\345\215\260\346\210\220\345\244\232\350\241\214.md" "b/docs/notes/32.2 \346\212\212\344\272\214\345\217\211\346\240\221\346\211\223\345\215\260\346\210\220\345\244\232\350\241\214.md" deleted file mode 100644 index 3c7df0ab63d87edc2ec77b0e44eda1f0c8912d12..0000000000000000000000000000000000000000 --- "a/docs/notes/32.2 \346\212\212\344\272\214\345\217\211\346\240\221\346\211\223\345\215\260\346\210\220\345\244\232\350\241\214.md" +++ /dev/null @@ -1,39 +0,0 @@ -# 32.2 把二叉树打印成多行 - -[NowCoder](https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -和上题几乎一样。 - -## 解题思路 - -```java -ArrayList> Print(TreeNode pRoot) { - ArrayList> ret = new ArrayList<>(); - Queue queue = new LinkedList<>(); - queue.add(pRoot); - while (!queue.isEmpty()) { - ArrayList list = new ArrayList<>(); - int cnt = queue.size(); - while (cnt-- > 0) { - TreeNode node = queue.poll(); - if (node == null) - continue; - list.add(node.val); - queue.add(node.left); - queue.add(node.right); - } - if (list.size() != 0) - ret.add(list); - } - return ret; -} -``` - - - - - - -
diff --git "a/docs/notes/32.3 \346\214\211\344\271\213\345\255\227\345\275\242\351\241\272\345\272\217\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" "b/docs/notes/32.3 \346\214\211\344\271\213\345\255\227\345\275\242\351\241\272\345\272\217\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index a960778e29e3a27000be63866c3e1d6486acbe2c..0000000000000000000000000000000000000000 --- "a/docs/notes/32.3 \346\214\211\344\271\213\345\255\227\345\275\242\351\241\272\345\272\217\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,43 +0,0 @@ -# 32.3 按之字形顺序打印二叉树 - -[NowCoder](https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&tqId=11212&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。 - -## 解题思路 - -```java -public ArrayList> Print(TreeNode pRoot) { - ArrayList> ret = new ArrayList<>(); - Queue queue = new LinkedList<>(); - queue.add(pRoot); - boolean reverse = false; - while (!queue.isEmpty()) { - ArrayList list = new ArrayList<>(); - int cnt = queue.size(); - while (cnt-- > 0) { - TreeNode node = queue.poll(); - if (node == null) - continue; - list.add(node.val); - queue.add(node.left); - queue.add(node.right); - } - if (reverse) - Collections.reverse(list); - reverse = !reverse; - if (list.size() != 0) - ret.add(list); - } - return ret; -} -``` - - - - - - -
diff --git "a/docs/notes/33. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.md" "b/docs/notes/33. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.md" deleted file mode 100644 index 6741f31e22b565c1dfedf40bdfd53cb69fa36054..0000000000000000000000000000000000000000 --- "a/docs/notes/33. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.md" +++ /dev/null @@ -1,41 +0,0 @@ -# 33. 二叉搜索树的后序遍历序列 - -[NowCoder](https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&tqId=11176&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。假设输入的数组的任意两个数字都互不相同。 - -例如,下图是后序遍历序列 1,3,2 所对应的二叉搜索树。 - -

- -## 解题思路 - -```java -public boolean VerifySquenceOfBST(int[] sequence) { - if (sequence == null || sequence.length == 0) - return false; - return verify(sequence, 0, sequence.length - 1); -} - -private boolean verify(int[] sequence, int first, int last) { - if (last - first <= 1) - return true; - int rootVal = sequence[last]; - int cutIndex = first; - while (cutIndex < last && sequence[cutIndex] <= rootVal) - cutIndex++; - for (int i = cutIndex; i < last; i++) - if (sequence[i] < rootVal) - return false; - return verify(sequence, first, cutIndex - 1) && verify(sequence, cutIndex, last - 1); -} -``` - - - - - - -
diff --git "a/docs/notes/34. \344\272\214\345\217\211\346\240\221\344\270\255\345\222\214\344\270\272\346\237\220\344\270\200\345\200\274\347\232\204\350\267\257\345\276\204.md" "b/docs/notes/34. \344\272\214\345\217\211\346\240\221\344\270\255\345\222\214\344\270\272\346\237\220\344\270\200\345\200\274\347\232\204\350\267\257\345\276\204.md" deleted file mode 100644 index 95629ac44277a3ec56c15d97fe2ed65ec4af0e4e..0000000000000000000000000000000000000000 --- "a/docs/notes/34. \344\272\214\345\217\211\346\240\221\344\270\255\345\222\214\344\270\272\346\237\220\344\270\200\345\200\274\347\232\204\350\267\257\345\276\204.md" +++ /dev/null @@ -1,43 +0,0 @@ -# 34. 二叉树中和为某一值的路径 - -[NowCoder](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 - -下图的二叉树有两条和为 22 的路径:10, 5, 7 和 10, 12 - -

- -## 解题思路 - -```java -private ArrayList> ret = new ArrayList<>(); - -public ArrayList> FindPath(TreeNode root, int target) { - backtracking(root, target, new ArrayList<>()); - return ret; -} - -private void backtracking(TreeNode node, int target, ArrayList path) { - if (node == null) - return; - path.add(node.val); - target -= node.val; - if (target == 0 && node.left == null && node.right == null) { - ret.add(new ArrayList<>(path)); - } else { - backtracking(node.left, target, path); - backtracking(node.right, target, path); - } - path.remove(path.size() - 1); -} -``` - - - - - - -
diff --git "a/docs/notes/35. \345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" "b/docs/notes/35. \345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" deleted file mode 100644 index 1f382d6b05f867d8422efc6da95487ea7b4abbf0..0000000000000000000000000000000000000000 --- "a/docs/notes/35. \345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" +++ /dev/null @@ -1,74 +0,0 @@ -# 35. 复杂链表的复制 - -[NowCoder](https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&tqId=11178&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。 - -```java -public class RandomListNode { - int label; - RandomListNode next = null; - RandomListNode random = null; - - RandomListNode(int label) { - this.label = label; - } -} -``` - -

- -## 解题思路 - -第一步,在每个节点的后面插入复制的节点。 - -

- -第二步,对复制节点的 random 链接进行赋值。 - -

- -第三步,拆分。 - -

- -```java -public RandomListNode Clone(RandomListNode pHead) { - if (pHead == null) - return null; - // 插入新节点 - RandomListNode cur = pHead; - while (cur != null) { - RandomListNode clone = new RandomListNode(cur.label); - clone.next = cur.next; - cur.next = clone; - cur = clone.next; - } - // 建立 random 链接 - cur = pHead; - while (cur != null) { - RandomListNode clone = cur.next; - if (cur.random != null) - clone.random = cur.random.next; - cur = clone.next; - } - // 拆分 - cur = pHead; - RandomListNode pCloneHead = pHead.next; - while (cur.next != null) { - RandomListNode next = cur.next; - cur.next = next.next; - cur = next; - } - return pCloneHead; -} -``` - - - - - - -
diff --git "a/docs/notes/36. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" "b/docs/notes/36. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" deleted file mode 100644 index 5ab2372adb10551ec612460e61bc78756e81b77f..0000000000000000000000000000000000000000 --- "a/docs/notes/36. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" +++ /dev/null @@ -1,41 +0,0 @@ -# 36. 二叉搜索树与双向链表 - -[NowCoder](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 - -

- -## 解题思路 - -```java -private TreeNode pre = null; -private TreeNode head = null; - -public TreeNode Convert(TreeNode root) { - inOrder(root); - return head; -} - -private void inOrder(TreeNode node) { - if (node == null) - return; - inOrder(node.left); - node.left = pre; - if (pre != null) - pre.right = node; - pre = node; - if (head == null) - head = node; - inOrder(node.right); -} -``` - - - - - - -
diff --git "a/docs/notes/37. \345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" "b/docs/notes/37. \345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index 6a20d5069114f9583d3a910e3b359c08db83f939..0000000000000000000000000000000000000000 --- "a/docs/notes/37. \345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,46 +0,0 @@ -# 37. 序列化二叉树 - -[NowCoder](https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -请实现两个函数,分别用来序列化和反序列化二叉树。 - -## 解题思路 - -```java -private String deserializeStr; - -public String Serialize(TreeNode root) { - if (root == null) - return "#"; - return root.val + " " + Serialize(root.left) + " " + Serialize(root.right); -} - -public TreeNode Deserialize(String str) { - deserializeStr = str; - return Deserialize(); -} - -private TreeNode Deserialize() { - if (deserializeStr.length() == 0) - return null; - int index = deserializeStr.indexOf(" "); - String node = index == -1 ? deserializeStr : deserializeStr.substring(0, index); - deserializeStr = index == -1 ? "" : deserializeStr.substring(index + 1); - if (node.equals("#")) - return null; - int val = Integer.valueOf(node); - TreeNode t = new TreeNode(val); - t.left = Deserialize(); - t.right = Deserialize(); - return t; -} -``` - - - - - - -
diff --git "a/docs/notes/38. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" "b/docs/notes/38. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" deleted file mode 100644 index fa93aaaffcf3f6e3f2c28b4756a58c68f8b2233a..0000000000000000000000000000000000000000 --- "a/docs/notes/38. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" +++ /dev/null @@ -1,47 +0,0 @@ -# 38. 字符串的排列 - -[NowCoder](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串 abc,则打印出由字符 a, b, c 所能排列出来的所有字符串 abc, acb, bac, bca, cab 和 cba。 - -## 解题思路 - -```java -private ArrayList ret = new ArrayList<>(); - -public ArrayList Permutation(String str) { - if (str.length() == 0) - return ret; - char[] chars = str.toCharArray(); - Arrays.sort(chars); - backtracking(chars, new boolean[chars.length], new StringBuilder()); - return ret; -} - -private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) { - if (s.length() == chars.length) { - ret.add(s.toString()); - return; - } - for (int i = 0; i < chars.length; i++) { - if (hasUsed[i]) - continue; - if (i != 0 && chars[i] == chars[i - 1] && !hasUsed[i - 1]) /* 保证不重复 */ - continue; - hasUsed[i] = true; - s.append(chars[i]); - backtracking(chars, hasUsed, s); - s.deleteCharAt(s.length() - 1); - hasUsed[i] = false; - } -} -``` - - - - - - -
diff --git "a/docs/notes/39. \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" "b/docs/notes/39. \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index c6f119d17eafe7a0a6a480996267ae9670e8c893..0000000000000000000000000000000000000000 --- "a/docs/notes/39. \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,34 +0,0 @@ -# 39. 数组中出现次数超过一半的数字 - -[NowCoder](https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&tqId=11181&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 解题思路 - -多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(N)。 - -使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素相等时,令 cnt++,否则令 cnt--。如果前面查找了 i 个元素,且 cnt == 0,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,因此继续查找就能找出 majority。 - -```java -public int MoreThanHalfNum_Solution(int[] nums) { - int majority = nums[0]; - for (int i = 1, cnt = 1; i < nums.length; i++) { - cnt = nums[i] == majority ? cnt + 1 : cnt - 1; - if (cnt == 0) { - majority = nums[i]; - cnt = 1; - } - } - int cnt = 0; - for (int val : nums) - if (val == majority) - cnt++; - return cnt > nums.length / 2 ? majority : 0; -} -``` - - - - - - -
diff --git "a/docs/notes/4. \344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.md" "b/docs/notes/4. \344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.md" deleted file mode 100644 index a8887f628162a38bc0862b88f5de9545db1b73da..0000000000000000000000000000000000000000 --- "a/docs/notes/4. \344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.md" +++ /dev/null @@ -1,56 +0,0 @@ -# 4. 二维数组中的查找 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -给定一个二维数组,其每一行从左到右递增排序,从上到下也是递增排序。给定一个数,判断这个数是否在该二维数组中。 - -```html -Consider the following matrix: -[ - [1, 4, 7, 11, 15], - [2, 5, 8, 12, 19], - [3, 6, 9, 16, 22], - [10, 13, 14, 17, 24], - [18, 21, 23, 26, 30] -] - -Given target = 5, return true. -Given target = 20, return false. -``` - -## 解题思路 - -要求时间复杂度 O(M + N),空间复杂度 O(1)。其中 M 为行数,N 为 列数。 - -该二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来快速地缩小查找区间,每次减少一行或者一列的元素。当前元素的查找区间为左下角的所有元素。 - -

- -```java -public boolean Find(int target, int[][] matrix) { - if (matrix == null || matrix.length == 0 || matrix[0].length == 0) - return false; - int rows = matrix.length, cols = matrix[0].length; - int r = 0, c = cols - 1; // 从右上角开始 - while (r <= rows - 1 && c >= 0) { - if (target == matrix[r][c]) - return true; - else if (target > matrix[r][c]) - r++; - else - c--; - } - return false; -} -``` - - - - - - -
diff --git "a/docs/notes/40. \346\234\200\345\260\217\347\232\204 K \344\270\252\346\225\260.md" "b/docs/notes/40. \346\234\200\345\260\217\347\232\204 K \344\270\252\346\225\260.md" deleted file mode 100644 index cd474ecff2195cdac3d3285da3720d8c8ff96dd4..0000000000000000000000000000000000000000 --- "a/docs/notes/40. \346\234\200\345\260\217\347\232\204 K \344\270\252\346\225\260.md" +++ /dev/null @@ -1,92 +0,0 @@ -# 40. 最小的 K 个数 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 解题思路 - -### 大小为 K 的最小堆 - -- 复杂度:O(NlogK) + O(K) -- 特别适合处理海量数据 - -维护一个大小为 K 的最小堆过程如下:使用大顶堆。在添加一个元素之后,如果大顶堆的大小大于 K,那么将大顶堆的堆顶元素去除,也就是将当前堆中值最大的元素去除,从而使得留在堆中的元素都比被去除的元素来得小。 - -应该使用大顶堆来维护最小堆,而不能直接创建一个小顶堆并设置一个大小,企图让小顶堆中的元素都是最小元素。 - -Java 的 PriorityQueue 实现了堆的能力,PriorityQueue 默认是小顶堆,可以在在初始化时使用 Lambda 表达式 (o1, o2) -> o2 - o1 来实现大顶堆。其它语言也有类似的堆数据结构。 - -```java -public ArrayList GetLeastNumbers_Solution(int[] nums, int k) { - if (k > nums.length || k <= 0) - return new ArrayList<>(); - PriorityQueue maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1); - for (int num : nums) { - maxHeap.add(num); - if (maxHeap.size() > k) - maxHeap.poll(); - } - return new ArrayList<>(maxHeap); -} -``` - -### 快速选择 - -- 复杂度:O(N) + O(1) -- 只有当允许修改数组元素时才可以使用 - -快速排序的 partition() 方法,会返回一个整数 j 使得 a[l..j-1] 小于等于 a[j],且 a[j+1..h] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素。可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。 - -```java -public ArrayList GetLeastNumbers_Solution(int[] nums, int k) { - ArrayList ret = new ArrayList<>(); - if (k > nums.length || k <= 0) - return ret; - findKthSmallest(nums, k - 1); - /* findKthSmallest 会改变数组,使得前 k 个数都是最小的 k 个数 */ - for (int i = 0; i < k; i++) - ret.add(nums[i]); - return ret; -} - -public void findKthSmallest(int[] nums, int k) { - int l = 0, h = nums.length - 1; - while (l < h) { - int j = partition(nums, l, h); - if (j == k) - break; - if (j > k) - h = j - 1; - else - l = j + 1; - } -} - -private int partition(int[] nums, int l, int h) { - int p = nums[l]; /* 切分元素 */ - int i = l, j = h + 1; - while (true) { - while (i != h && nums[++i] < p) ; - while (j != l && nums[--j] > p) ; - if (i >= j) - break; - swap(nums, i, j); - } - swap(nums, l, j); - return j; -} - -private void swap(int[] nums, int i, int j) { - int t = nums[i]; - nums[i] = nums[j]; - nums[j] = t; -} -``` - - - - - - -
diff --git "a/docs/notes/41.1 \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\344\270\255\344\275\215\346\225\260.md" "b/docs/notes/41.1 \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\344\270\255\344\275\215\346\225\260.md" deleted file mode 100644 index aaeee54dc02cbf0d3a7e37d4f6c194b757294a9c..0000000000000000000000000000000000000000 --- "a/docs/notes/41.1 \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\344\270\255\344\275\215\346\225\260.md" +++ /dev/null @@ -1,49 +0,0 @@ -# 41.1 数据流中的中位数 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&tqId=11216&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 - -## 解题思路 - -```java -/* 大顶堆,存储左半边元素 */ -private PriorityQueue left = new PriorityQueue<>((o1, o2) -> o2 - o1); -/* 小顶堆,存储右半边元素,并且右半边元素都大于左半边 */ -private PriorityQueue right = new PriorityQueue<>(); -/* 当前数据流读入的元素个数 */ -private int N = 0; - -public void Insert(Integer val) { - /* 插入要保证两个堆存于平衡状态 */ - if (N % 2 == 0) { - /* N 为偶数的情况下插入到右半边。 - * 因为右半边元素都要大于左半边,但是新插入的元素不一定比左半边元素来的大, - * 因此需要先将元素插入左半边,然后利用左半边为大顶堆的特点,取出堆顶元素即为最大元素,此时插入右半边 */ - left.add(val); - right.add(left.poll()); - } else { - right.add(val); - left.add(right.poll()); - } - N++; -} - -public Double GetMedian() { - if (N % 2 == 0) - return (left.peek() + right.peek()) / 2.0; - else - return (double) right.peek(); -} -``` - - - - - - -
diff --git "a/docs/notes/41.2 \345\255\227\347\254\246\346\265\201\344\270\255\347\254\254\344\270\200\344\270\252\344\270\215\351\207\215\345\244\215\347\232\204\345\255\227\347\254\246.md" "b/docs/notes/41.2 \345\255\227\347\254\246\346\265\201\344\270\255\347\254\254\344\270\200\344\270\252\344\270\215\351\207\215\345\244\215\347\232\204\345\255\227\347\254\246.md" deleted file mode 100644 index 51e34915de158e91ac2ea7a049ed1a133cc5e097..0000000000000000000000000000000000000000 --- "a/docs/notes/41.2 \345\255\227\347\254\246\346\265\201\344\270\255\347\254\254\344\270\200\344\270\252\344\270\215\351\207\215\345\244\215\347\232\204\345\255\227\347\254\246.md" +++ /dev/null @@ -1,38 +0,0 @@ -# 41.2 字符流中第一个不重复的字符 - -## 题目描述 - -[牛客网](https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "go" 时,第一个只出现一次的字符是 "g"。当从该字符流中读出前六个字符“google" 时,第一个只出现一次的字符是 "l"。 - -## 解题思路 - -使用统计数组来统计每个字符出现的次数,本题涉及到的字符为都为 ASCII 码,因此使用一个大小为 128 的整型数组就能完成次数统计任务。 - -使用队列来存储到达的字符,并在每次有新的字符从字符流到达时移除队列头部那些出现次数不再是一次的元素。因为队列是先进先出顺序,因此队列头部的元素为第一次只出现一次的字符。 - -```java -private int[] cnts = new int[128]; -private Queue queue = new LinkedList<>(); - -public void Insert(char ch) { - cnts[ch]++; - queue.add(ch); - while (!queue.isEmpty() && cnts[queue.peek()] > 1) - queue.poll(); -} - -public char FirstAppearingOnce() { - return queue.isEmpty() ? '#' : queue.peek(); -} -``` - - - - - - -
diff --git "a/docs/notes/42. \350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" "b/docs/notes/42. \350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" deleted file mode 100644 index f53ca04753966367cd2d0dd8ca0976eae6e512f0..0000000000000000000000000000000000000000 --- "a/docs/notes/42. \350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" +++ /dev/null @@ -1,31 +0,0 @@ -# 42. 连续子数组的最大和 - -[NowCoder](https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&tqId=11183&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -{6, -3, -2, 7, -15, 1, 2, 2},连续子数组的最大和为 8(从第 0 个开始,到第 3 个为止)。 - -## 解题思路 - -```java -public int FindGreatestSumOfSubArray(int[] nums) { - if (nums == null || nums.length == 0) - return 0; - int greatestSum = Integer.MIN_VALUE; - int sum = 0; - for (int val : nums) { - sum = sum <= 0 ? val : sum + val; - greatestSum = Math.max(greatestSum, sum); - } - return greatestSum; -} -``` - - - - - - - -
diff --git "a/docs/notes/43. \344\273\216 1 \345\210\260 n \346\225\264\346\225\260\344\270\255 1 \345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" "b/docs/notes/43. \344\273\216 1 \345\210\260 n \346\225\264\346\225\260\344\270\255 1 \345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" deleted file mode 100644 index df296e438ca1b00eb50709b7e97ef2bea8c29846..0000000000000000000000000000000000000000 --- "a/docs/notes/43. \344\273\216 1 \345\210\260 n \346\225\264\346\225\260\344\270\255 1 \345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" +++ /dev/null @@ -1,25 +0,0 @@ -# 43. 从 1 到 n 整数中 1 出现的次数 - -[NowCoder](https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&tqId=11184&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 解题思路 - -```java -public int NumberOf1Between1AndN_Solution(int n) { - int cnt = 0; - for (int m = 1; m <= n; m *= 10) { - int a = n / m, b = n % m; - cnt += (a + 8) / 10 * m + (a % 10 == 1 ? b + 1 : 0); - } - return cnt; -} -``` - -> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython) - - - - - - -
diff --git "a/docs/notes/44. \346\225\260\345\255\227\345\272\217\345\210\227\344\270\255\347\232\204\346\237\220\344\270\200\344\275\215\346\225\260\345\255\227.md" "b/docs/notes/44. \346\225\260\345\255\227\345\272\217\345\210\227\344\270\255\347\232\204\346\237\220\344\270\200\344\275\215\346\225\260\345\255\227.md" deleted file mode 100644 index 16dde5c402e2c7d491b0fb75eadd82a37ba73df0..0000000000000000000000000000000000000000 --- "a/docs/notes/44. \346\225\260\345\255\227\345\272\217\345\210\227\344\270\255\347\232\204\346\237\220\344\270\200\344\275\215\346\225\260\345\255\227.md" +++ /dev/null @@ -1,61 +0,0 @@ -# 44. 数字序列中的某一位数字 - -## 题目描述 - -数字以 0123456789101112131415... 的格式序列化到一个字符串中,求这个字符串的第 index 位。 - -## 解题思路 - -```java -public int getDigitAtIndex(int index) { - if (index < 0) - return -1; - int place = 1; // 1 表示个位,2 表示 十位... - while (true) { - int amount = getAmountOfPlace(place); - int totalAmount = amount * place; - if (index < totalAmount) - return getDigitAtIndex(index, place); - index -= totalAmount; - place++; - } -} - -/** - * place 位数的数字组成的字符串长度 - * 10, 90, 900, ... - */ -private int getAmountOfPlace(int place) { - if (place == 1) - return 10; - return (int) Math.pow(10, place - 1) * 9; -} - -/** - * place 位数的起始数字 - * 0, 10, 100, ... - */ -private int getBeginNumberOfPlace(int place) { - if (place == 1) - return 0; - return (int) Math.pow(10, place - 1); -} - -/** - * 在 place 位数组成的字符串中,第 index 个数 - */ -private int getDigitAtIndex(int index, int place) { - int beginNumber = getBeginNumberOfPlace(place); - int shiftNumber = index / place; - String number = (beginNumber + shiftNumber) + ""; - int count = index % place; - return number.charAt(count) - '0'; -} -``` - - - - - - -
diff --git "a/docs/notes/45. \346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.md" "b/docs/notes/45. \346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.md" deleted file mode 100644 index f64fa518b0f359e3fb42f0ad3bacc8f4cd924bb4..0000000000000000000000000000000000000000 --- "a/docs/notes/45. \346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.md" +++ /dev/null @@ -1,34 +0,0 @@ -# 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) - -## 题目描述 - -输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组 {3,32,321},则打印出这三个数字能排成的最小数字为 321323。 - -## 解题思路 - -可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 < S2+S1,那么应该把 S1 排在前面,否则应该把 S2 排在前面。 - -```java -public String PrintMinNumber(int[] numbers) { - if (numbers == null || numbers.length == 0) - return ""; - int n = numbers.length; - String[] nums = new String[n]; - for (int i = 0; i < n; i++) - nums[i] = numbers[i] + ""; - Arrays.sort(nums, (s1, s2) -> (s1 + s2).compareTo(s2 + s1)); - String ret = ""; - for (String str : nums) - ret += str; - return ret; -} -``` - - - - - - -
diff --git "a/docs/notes/46. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.md" "b/docs/notes/46. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index d5aaa6d31f7c562ac3584a85599ff8af09522332..0000000000000000000000000000000000000000 --- "a/docs/notes/46. \346\212\212\346\225\260\345\255\227\347\277\273\350\257\221\346\210\220\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,38 +0,0 @@ -# 46. 把数字翻译成字符串 - -[Leetcode](https://leetcode.com/problems/decode-ways/description/) - -## 题目描述 - -给定一个数字,按照如下规则翻译成字符串:1 翻译成“a”,2 翻译成“b”... 26 翻译成“z”。一个数字有多种翻译可能,例如 12258 一共有 5 种,分别是 abbeh,lbeh,aveh,abyh,lyh。实现一个函数,用来计算一个数字有多少种不同的翻译方法。 - -## 解题思路 - -```java -public int numDecodings(String s) { - if (s == null || s.length() == 0) - return 0; - int n = s.length(); - int[] dp = new int[n + 1]; - dp[0] = 1; - dp[1] = s.charAt(0) == '0' ? 0 : 1; - for (int i = 2; i <= n; i++) { - int one = Integer.valueOf(s.substring(i - 1, i)); - if (one != 0) - dp[i] += dp[i - 1]; - if (s.charAt(i - 2) == '0') - continue; - int two = Integer.valueOf(s.substring(i - 2, i)); - if (two <= 26) - dp[i] += dp[i - 2]; - } - return dp[n]; -} -``` - - - - - - -
diff --git "a/docs/notes/47. \347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.md" "b/docs/notes/47. \347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.md" deleted file mode 100644 index 66be428c7b158b1d3cde2e96bd8cc09e26b72bfc..0000000000000000000000000000000000000000 --- "a/docs/notes/47. \347\244\274\347\211\251\347\232\204\346\234\200\345\244\247\344\273\267\345\200\274.md" +++ /dev/null @@ -1,42 +0,0 @@ -# 47. 礼物的最大价值 - -[NowCoder](https://www.nowcoder.com/questionTerminal/72a99e28381a407991f2c96d8cb238ab) - -## 题目描述 - -在一个 m\*n 的棋盘的每一个格都放有一个礼物,每个礼物都有一定价值(大于 0)。从左上角开始拿礼物,每次向右或向下移动一格,直到右下角结束。给定一个棋盘,求拿到礼物的最大价值。例如,对于如下棋盘 - -``` -1 10 3 8 -12 2 9 6 -5 7 4 11 -3 7 16 5 -``` - -礼物的最大价值为 1+12+5+7+7+16+5=53。 - -## 解题思路 - -应该用动态规划求解,而不是深度优先搜索,深度优先搜索过于复杂,不是最优解。 - -```java -public int getMost(int[][] values) { - if (values == null || values.length == 0 || values[0].length == 0) - return 0; - int n = values[0].length; - int[] dp = new int[n]; - for (int[] value : values) { - dp[0] += value[0]; - for (int i = 1; i < n; i++) - dp[i] = Math.max(dp[i], dp[i - 1]) + value[i]; - } - return dp[n - 1]; -} -``` - - - - - - -
diff --git "a/docs/notes/48. \346\234\200\351\225\277\344\270\215\345\220\253\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" "b/docs/notes/48. \346\234\200\351\225\277\344\270\215\345\220\253\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 6923678c4214fb06babcac076e60a14778a6340b..0000000000000000000000000000000000000000 --- "a/docs/notes/48. \346\234\200\351\225\277\344\270\215\345\220\253\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,36 +0,0 @@ -# 48. 最长不含重复字符的子字符串 - -## 题目描述 - -输入一个字符串(只包含 a\~z 的字符),求其最长不含重复字符的子字符串的长度。例如对于 arabcacfr,最长不含重复字符的子字符串为 acfr,长度为 4。 - -## 解题思路 - -```java -public int longestSubStringWithoutDuplication(String str) { - int curLen = 0; - int maxLen = 0; - int[] preIndexs = new int[26]; - Arrays.fill(preIndexs, -1); - for (int curI = 0; curI < str.length(); curI++) { - int c = str.charAt(curI) - 'a'; - int preI = preIndexs[c]; - if (preI == -1 || curI - preI > curLen) { - curLen++; - } else { - maxLen = Math.max(maxLen, curLen); - curLen = curI - preI; - } - preIndexs[c] = curI; - } - maxLen = Math.max(maxLen, curLen); - return maxLen; -} -``` - - - - - - -
diff --git "a/docs/notes/49. \344\270\221\346\225\260.md" "b/docs/notes/49. \344\270\221\346\225\260.md" deleted file mode 100644 index fe4e11506c4d3827908feae756b61515b40ee598..0000000000000000000000000000000000000000 --- "a/docs/notes/49. \344\270\221\346\225\260.md" +++ /dev/null @@ -1,37 +0,0 @@ -# 49. 丑数 - -[NowCoder](https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b?tpId=13&tqId=11186&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含因子 7。习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。 - -## 解题思路 - -```java -public int GetUglyNumber_Solution(int N) { - if (N <= 6) - return N; - int i2 = 0, i3 = 0, i5 = 0; - int[] dp = new int[N]; - dp[0] = 1; - for (int i = 1; i < N; i++) { - int next2 = dp[i2] * 2, next3 = dp[i3] * 3, next5 = dp[i5] * 5; - dp[i] = Math.min(next2, Math.min(next3, next5)); - if (dp[i] == next2) - i2++; - if (dp[i] == next3) - i3++; - if (dp[i] == next5) - i5++; - } - return dp[N - 1]; -} -``` - - - - - - -
diff --git "a/docs/notes/5. \346\233\277\346\215\242\347\251\272\346\240\274.md" "b/docs/notes/5. \346\233\277\346\215\242\347\251\272\346\240\274.md" deleted file mode 100644 index efe83db60c32b9b8dc918f45dff3511ef64722f2..0000000000000000000000000000000000000000 --- "a/docs/notes/5. \346\233\277\346\215\242\347\251\272\346\240\274.md" +++ /dev/null @@ -1,59 +0,0 @@ -# 5. 替换空格 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - - -将一个字符串中的空格替换成 "%20"。 - -```text -Input: -"A B" - -Output: -"A%20B" -``` - -## 解题思路 - -① 在字符串尾部填充任意字符,使得字符串的长度等于替换之后的长度。因为一个空格要替换成三个字符(%20),所以当遍历到一个空格时,需要在尾部填充两个任意字符。 - -② 令 P1 指向字符串原来的末尾位置,P2 指向字符串现在的末尾位置。P1 和 P2 从后向前遍历,当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。 - -③ 当 P2 遇到 P1 时(P2 <= P1),或者遍历结束(P1 < 0),退出。 - - - -

- -```java -public String replaceSpace(StringBuffer str) { - int P1 = str.length() - 1; - for (int i = 0; i <= P1; i++) - if (str.charAt(i) == ' ') - str.append(" "); - - int P2 = str.length() - 1; - while (P1 >= 0 && P2 > P1) { - char c = str.charAt(P1--); - if (c == ' ') { - str.setCharAt(P2--, '0'); - str.setCharAt(P2--, '2'); - str.setCharAt(P2--, '%'); - } else { - str.setCharAt(P2--, c); - } - } - return str.toString(); -} -``` - - - - - - -
diff --git "a/docs/notes/50. \347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246\344\275\215\347\275\256.md" "b/docs/notes/50. \347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246\344\275\215\347\275\256.md" deleted file mode 100644 index db74dd8b28abcf4eda43550c7ffa40b710350785..0000000000000000000000000000000000000000 --- "a/docs/notes/50. \347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246\344\275\215\347\275\256.md" +++ /dev/null @@ -1,60 +0,0 @@ -# 50. 第一个只出现一次的字符位置 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/1c82e8cf713b4bbeb2a5b31cf5b0417c?tpId=13&tqId=11187&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -在一个字符串中找到第一个只出现一次的字符,并返回它的位置。字符串只包含 ASCII 码字符。 - -``` -Input: abacc -Output: b -``` - -## 解题思路 - -最直观的解法是使用 HashMap 对出现次数进行统计:字符做为 key,出现次数作为 value,遍历字符串每次都将 key 对应的 value 加 1。最后再遍历这个 HashMap 就可以找出出现次数为 1 的字符。 - -考虑到要统计的字符范围有限,也可以使用整型数组代替 HashMap。ASCII 码只有 128 个字符,因此可以使用长度为 128 的整型数组来存储每个字符出现的次数。 - -```java -public int FirstNotRepeatingChar(String str) { - int[] cnts = new int[128]; - for (int i = 0; i < str.length(); i++) - cnts[str.charAt(i)]++; - for (int i = 0; i < str.length(); i++) - if (cnts[str.charAt(i)] == 1) - return i; - return -1; -} -``` - -以上实现的空间复杂度还不是最优的。考虑到只需要找到只出现一次的字符,那么需要统计的次数信息只有 0,1,更大,使用两个比特位就能存储这些信息。 - -```java -public int FirstNotRepeatingChar2(String str) { - BitSet bs1 = new BitSet(128); - BitSet bs2 = new BitSet(128); - for (char c : str.toCharArray()) { - if (!bs1.get(c) && !bs2.get(c)) - bs1.set(c); // 0 0 -> 0 1 - else if (bs1.get(c) && !bs2.get(c)) - bs2.set(c); // 0 1 -> 1 1 - } - for (int i = 0; i < str.length(); i++) { - char c = str.charAt(i); - if (bs1.get(c) && !bs2.get(c)) // 0 1 - return i; - } - return -1; -} -``` - - - - - - -
diff --git "a/docs/notes/51. \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" "b/docs/notes/51. \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" deleted file mode 100644 index 674c84c3b9e01e1995fe3fcede4f9d641c512936..0000000000000000000000000000000000000000 --- "a/docs/notes/51. \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" +++ /dev/null @@ -1,55 +0,0 @@ -# 51. 数组中的逆序对 - -[NowCoder](https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。 - -## 解题思路 - -```java -private long cnt = 0; -private int[] tmp; // 在这里声明辅助数组,而不是在 merge() 递归函数中声明 - -public int InversePairs(int[] nums) { - tmp = new int[nums.length]; - mergeSort(nums, 0, nums.length - 1); - return (int) (cnt % 1000000007); -} - -private void mergeSort(int[] nums, int l, int h) { - if (h - l < 1) - return; - int m = l + (h - l) / 2; - mergeSort(nums, l, m); - mergeSort(nums, m + 1, h); - merge(nums, l, m, h); -} - -private void merge(int[] nums, int l, int m, int h) { - int i = l, j = m + 1, k = l; - while (i <= m || j <= h) { - if (i > m) - tmp[k] = nums[j++]; - else if (j > h) - tmp[k] = nums[i++]; - else if (nums[i] <= nums[j]) - tmp[k] = nums[i++]; - else { - tmp[k] = nums[j++]; - this.cnt += m - i + 1; // nums[i] > nums[j],说明 nums[i...mid] 都大于 nums[j] - } - k++; - } - for (k = l; k <= h; k++) - nums[k] = tmp[k]; -} -``` - - - - - - -
diff --git "a/docs/notes/52. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" "b/docs/notes/52. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" deleted file mode 100644 index 371e83419d190dcb570f2f6026f1e1fbc17ce422..0000000000000000000000000000000000000000 --- "a/docs/notes/52. \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" +++ /dev/null @@ -1,31 +0,0 @@ -# 52. 两个链表的第一个公共结点 - -[NowCoder](https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&tqId=11189&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -

- -## 解题思路 - -设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 - -当访问链表 A 的指针访问到链表尾部时,令它从链表 B 的头部重新开始访问链表 B;同样地,当访问链表 B 的指针访问到链表尾部时,令它从链表 A 的头部重新开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。 - -```java -public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { - ListNode l1 = pHead1, l2 = pHead2; - while (l1 != l2) { - l1 = (l1 == null) ? pHead2 : l1.next; - l2 = (l2 == null) ? pHead1 : l2.next; - } - return l1; -} -``` - - - - - - -
diff --git "a/docs/notes/53. \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" "b/docs/notes/53. \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" deleted file mode 100644 index 94681b387aff421af89bc2000b473afd0870fce4..0000000000000000000000000000000000000000 --- "a/docs/notes/53. \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" +++ /dev/null @@ -1,80 +0,0 @@ -# 53. 数字在排序数组中出现的次数 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/70610bf967994b22bb1c26f9ae901fa2?tpId=13&tqId=11190&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -```html -Input: -nums = 1, 2, 3, 3, 3, 3, 4, 6 -K = 3 - -Output: -4 -``` - -## 解题思路 - -只要能找出给定的数字 k 在有序数组第一个位置和最后一个位置,就能知道该数字出现的次数。 - -先考虑如何实现寻找数字在有序数组的第一个位置。正常的二分查找如下,在查找到给定元素 k 之后,立即返回当前索引下标。 - -```java -public int binarySearch(int[] nums, int K) { - int l = 0, h = nums.length - 1; - while (l <= h) { - int m = l + (h - l) / 2; - if (nums[m] == K) { - return m; - } else if (nums[m] > K) { - h = m - 1; - } else { - l = m + 1; - } - } - return -1; -} -``` - -但是在查找第一个位置时,找到元素之后应该继续往前找。也就是当 nums[m]>=k 时,在左区间继续查找,左区间应该包含 m 位置。 - -```java -private int binarySearch(int[] nums, int K) { - int l = 0, h = nums.length; - while (l < h) { - int m = l + (h - l) / 2; - if (nums[m] >= K) - h = m; - else - l = m + 1; - } - return l; -} -``` - -查找最后一个位置可以转换成寻找 k+1 的第一个位置,并再往前移动一个位置。 - -```java -public int GetNumberOfK(int[] nums, int K) { - int first = binarySearch(nums, K); - int last = binarySearch(nums, K + 1); - return (first == nums.length || nums[first] != K) ? 0 : last - first; -} -``` - -需要注意以上实现的查找第一个位置的 binarySearch 方法,h 的初始值为 nums.length,而不是 nums.length - 1。先看以下示例: - -``` -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 最后一个元素的情况。 - - - - - - -
diff --git "a/docs/notes/54. \344\272\214\345\217\211\346\237\245\346\211\276\346\240\221\347\232\204\347\254\254 K \344\270\252\347\273\223\347\202\271.md" "b/docs/notes/54. \344\272\214\345\217\211\346\237\245\346\211\276\346\240\221\347\232\204\347\254\254 K \344\270\252\347\273\223\347\202\271.md" deleted file mode 100644 index 511e25d27253733be43a702575ccce6477bdf924..0000000000000000000000000000000000000000 --- "a/docs/notes/54. \344\272\214\345\217\211\346\237\245\346\211\276\346\240\221\347\232\204\347\254\254 K \344\270\252\347\273\223\347\202\271.md" +++ /dev/null @@ -1,34 +0,0 @@ -# 54. 二叉查找树的第 K 个结点 - -[NowCoder](https://www.nowcoder.com/practice/ef068f602dde4d28aab2b210e859150a?tpId=13&tqId=11215&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 解题思路 - -利用二叉查找树中序遍历有序的特点。 - -```java -private TreeNode ret; -private int cnt = 0; - -public TreeNode KthNode(TreeNode pRoot, int k) { - inOrder(pRoot, k); - return ret; -} - -private void inOrder(TreeNode root, int k) { - if (root == null || cnt >= k) - return; - inOrder(root.left, k); - cnt++; - if (cnt == k) - ret = root; - inOrder(root.right, k); -} -``` - - - - - - -
diff --git "a/docs/notes/55.1 \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" "b/docs/notes/55.1 \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" deleted file mode 100644 index 0aeb411e27b9f5cd61711309025d4c9f2d14580a..0000000000000000000000000000000000000000 --- "a/docs/notes/55.1 \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" +++ /dev/null @@ -1,24 +0,0 @@ -# 55.1 二叉树的深度 - -[NowCoder](https://www.nowcoder.com/practice/435fb86331474282a3499955f0a41e8b?tpId=13&tqId=11191&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 - -

- -## 解题思路 - -```java -public int TreeDepth(TreeNode root) { - return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right)); -} -``` - - - - - - -
diff --git "a/docs/notes/55.2 \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" "b/docs/notes/55.2 \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index d2cdf538f8f7bf2a00175520feece0a5e348c899..0000000000000000000000000000000000000000 --- "a/docs/notes/55.2 \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,37 +0,0 @@ -# 55.2 平衡二叉树 - -[NowCoder](https://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId=13&tqId=11192&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -平衡二叉树左右子树高度差不超过 1。 - -

- -## 解题思路 - -```java -private boolean isBalanced = true; - -public boolean IsBalanced_Solution(TreeNode root) { - height(root); - return isBalanced; -} - -private int height(TreeNode root) { - if (root == null || !isBalanced) - return 0; - int left = height(root.left); - int right = height(root.right); - if (Math.abs(left - right) > 1) - isBalanced = false; - return 1 + Math.max(left, right); -} -``` - - - - - - -
diff --git "a/docs/notes/56. \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" "b/docs/notes/56. \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" deleted file mode 100644 index c42219eee9c1a57633a9a5f1ef33033be378546c..0000000000000000000000000000000000000000 --- "a/docs/notes/56. \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" +++ /dev/null @@ -1,41 +0,0 @@ -# 56. 数组中只出现一次的数字 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/e02fdb54d7524710a7d664d082bb7811?tpId=13&tqId=11193&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -一个整型数组里除了两个数字之外,其他的数字都出现了两次,找出这两个数。 - -## 解题思路 - -两个相等的元素异或的结果为 0,而 0 与任意数 x 异或的结果都为 x。 - -对本题给的数组的所有元素执行异或操作,得到的是两个不存在重复的元素异或的结果。例如对于数组 [x,x,y,y,z,k],x^x^y^y^z^k = 0^y^y^z^k = y^y^z^k = 0^z^k = z^k。 - -两个不相等的元素在位级表示上一定会有所不同,因此这两个元素异或得到的结果 diff 一定不为 0。位运算 diff & -diff 能得到 diff 位级表示中最右侧为 1 的位,令 diff = diff & -diff。将 diff 作为区分两个元素的依据,一定有一个元素对 diff 进行异或的结果为 0,另一个结果非 0。设不相等的两个元素分别为 z 和 k,遍历数组所有元素,判断元素与 diff 的异或结果是否为 0,如果是的话将元素与 z 进行异或并赋值给 z,否则与 k 进行异或并赋值给 k。数组中相等的元素一定会同时与 z 或者与 k 进行异或操作,而不是一个与 z 进行异或,一个与 k 进行异或。而且这些相等的元素异或的结果为 0,因此最后 z 和 k 只是不相等的两个元素与 0 异或的结果,也就是不相等两个元素本身。 - -下面的解法中,num1 和 num2 数组的第一个元素是用来保持返回值的... 实际开发中不推荐这种返回值的方式。 - -```java -public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) { - int diff = 0; - for (int num : nums) - diff ^= num; - diff &= -diff; - for (int num : nums) { - if ((num & diff) == 0) - num1[0] ^= num; - else - num2[0] ^= num; - } -} -``` - - - - - - -
diff --git "a/docs/notes/57.1 \345\222\214\344\270\272 S \347\232\204\344\270\244\344\270\252\346\225\260\345\255\227.md" "b/docs/notes/57.1 \345\222\214\344\270\272 S \347\232\204\344\270\244\344\270\252\346\225\260\345\255\227.md" deleted file mode 100644 index 01ee3b426a245ac24604e943e1391da17925fcd6..0000000000000000000000000000000000000000 --- "a/docs/notes/57.1 \345\222\214\344\270\272 S \347\232\204\344\270\244\344\270\252\346\225\260\345\255\227.md" +++ /dev/null @@ -1,40 +0,0 @@ -# 57.1 和为 S 的两个数字 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/390da4f7a00f44bea7c2f3d19491311b?tpId=13&tqId=11195&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -在有序数组中找出两个数,使得和为给定的数 S。如果有多对数字的和等于 S,输出两个数的乘积最小的。 - -## 解题思路 - -使用双指针,一个指针指向元素较小的值,一个指针指向元素较大的值。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。 - -- 如果两个指针指向元素的和 sum == target,那么这两个元素即为所求。 -- 如果 sum > target,移动较大的元素,使 sum 变小一些; -- 如果 sum < target,移动较小的元素,使 sum 变大一些。 - -```java -public ArrayList FindNumbersWithSum(int[] nums, int target) { - int i = 0, j = nums.length - 1; - while (i < j) { - int cur = nums[i] + array[j]; - if (cur == target) - return new ArrayList<>(Arrays.asList(nums[i], nums[j])); - if (cur < target) - i++; - else - j--; - } - return new ArrayList<>(); -} -``` - - - - - - -
diff --git "a/docs/notes/57.2 \345\222\214\344\270\272 S \347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" "b/docs/notes/57.2 \345\222\214\344\270\272 S \347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" deleted file mode 100644 index 5d9f112284c6d7fdf4eeb2ce9026e14a0c678e4e..0000000000000000000000000000000000000000 --- "a/docs/notes/57.2 \345\222\214\344\270\272 S \347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" +++ /dev/null @@ -1,50 +0,0 @@ -# 57.2 和为 S 的连续正数序列 - -## 题目描述 - -[牛客网](https://www.nowcoder.com/practice/c451a3fd84b64cb19485dad758a55ebe?tpId=13&tqId=11194&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -输出所有和为 S 的连续正数序列。例如和为 100 的连续序列有: - -``` -[9, 10, 11, 12, 13, 14, 15, 16] -[18, 19, 20, 21, 22]。 -``` - -## 解题思路 - -```java -public ArrayList> FindContinuousSequence(int sum) { - ArrayList> ret = new ArrayList<>(); - int start = 1, end = 2; - int curSum = 3; - while (end < sum) { - if (curSum > sum) { - curSum -= start; - start++; - } else if (curSum < sum) { - end++; - curSum += end; - } else { - ArrayList list = new ArrayList<>(); - for (int i = start; i <= end; i++) - list.add(i); - ret.add(list); - curSum -= start; - start++; - end++; - curSum += end; - } - } - return ret; -} -``` - - - - - - -
diff --git "a/docs/notes/58.1 \347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217\345\210\227.md" "b/docs/notes/58.1 \347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217\345\210\227.md" deleted file mode 100644 index b221a77b3987bd86ee75c37c65db4fe7435c1357..0000000000000000000000000000000000000000 --- "a/docs/notes/58.1 \347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217\345\210\227.md" +++ /dev/null @@ -1,56 +0,0 @@ -# 58.1 翻转单词顺序列 - -## 题目描述 - -[牛客网](https://www.nowcoder.com/practice/3194a4f4cf814f63919d0790578d51f3?tpId=13&tqId=11197&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -```html -Input: -"I am a student." - -Output: -"student. a am I" -``` - -## 解题思路 - -先旋转每个单词,再旋转整个字符串。 - -题目应该有一个隐含条件,就是不能用额外的空间。虽然 Java 的题目输入参数为 String 类型,需要先创建一个字符数组使得空间复杂度为 O(N),但是正确的参数类型应该和原书一样,为字符数组,并且只能使用该字符数组的空间。任何使用了额外空间的解法在面试时都会大打折扣,包括递归解法。 - -```java -public String ReverseSentence(String str) { - int n = str.length(); - char[] chars = str.toCharArray(); - int i = 0, j = 0; - while (j <= n) { - if (j == n || chars[j] == ' ') { - reverse(chars, i, j - 1); - i = j + 1; - } - j++; - } - reverse(chars, 0, n - 1); - return new String(chars); -} - -private void reverse(char[] c, int i, int j) { - while (i < j) - swap(c, i++, j--); -} - -private void swap(char[] c, int i, int j) { - char t = c[i]; - c[i] = c[j]; - c[j] = t; -} -``` - - - - - - -
diff --git "a/docs/notes/58.2 \345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" "b/docs/notes/58.2 \345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 77b6db6443c6c69a0da92b67769e8ad89b25f351..0000000000000000000000000000000000000000 --- "a/docs/notes/58.2 \345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,50 +0,0 @@ -# 58.2 左旋转字符串 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/12d959b108cb42b1ab72cef4d36af5ec?tpId=13&tqId=11196&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -```html -Input: -S="abcXYZdef" -K=3 - -Output: -"XYZdefabc" -``` - -## 解题思路 - -先将 "abc" 和 "XYZdef" 分别翻转,得到 "cbafedZYX",然后再把整个字符串翻转得到 "XYZdefabc"。 - -```java -public String LeftRotateString(String str, int n) { - if (n >= str.length()) - return str; - char[] chars = str.toCharArray(); - reverse(chars, 0, n - 1); - reverse(chars, n, chars.length - 1); - reverse(chars, 0, chars.length - 1); - return new String(chars); -} - -private void reverse(char[] chars, int i, int j) { - while (i < j) - swap(chars, i++, j--); -} - -private void swap(char[] chars, int i, int j) { - char t = chars[i]; - chars[i] = chars[j]; - chars[j] = t; -} -``` - - - - - - -
diff --git "a/docs/notes/59. \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" "b/docs/notes/59. \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" deleted file mode 100644 index 2862511e70544a5545162d12f7356b8a2e78132c..0000000000000000000000000000000000000000 --- "a/docs/notes/59. \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" +++ /dev/null @@ -1,44 +0,0 @@ -# 59. 滑动窗口的最大值 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/1624bc35a45c42c0bc17d17fa0cba788?tpId=13&tqId=11217&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。 - -例如,如果输入数组 {2, 3, 4, 2, 6, 2, 5, 1} 及滑动窗口的大小 3,那么一共存在 6 个滑动窗口,他们的最大值分别为 {4, 4, 6, 6, 6, 5}。 - -

- -## 解题思路 - -维护一个大小为窗口大小的大顶堆,顶堆元素则为当前窗口的最大值。 - -假设窗口的大小为 M,数组的长度为 N。在窗口向右移动时,需要先在堆中删除离开窗口的元素,并将新到达的元素添加到堆中,这两个操作的时间复杂度都为 log2M,因此算法的时间复杂度为 O(Nlog2M),空间复杂度为 O(M)。 - -```java -public ArrayList maxInWindows(int[] num, int size) { - ArrayList ret = new ArrayList<>(); - if (size > num.length || size < 1) - return ret; - PriorityQueue heap = new PriorityQueue<>((o1, o2) -> o2 - o1); /* 大顶堆 */ - for (int i = 0; i < size; i++) - heap.add(num[i]); - ret.add(heap.peek()); - for (int i = 0, j = i + size; j < num.length; i++, j++) { /* 维护一个大小为 size 的大顶堆 */ - heap.remove(num[i]); - heap.add(num[j]); - ret.add(heap.peek()); - } - return ret; -} -``` - - - - - - -
diff --git "a/docs/notes/6. \344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.md" "b/docs/notes/6. \344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.md" deleted file mode 100644 index fb78ed03b95827477caf82b4e1a54c29e100df31..0000000000000000000000000000000000000000 --- "a/docs/notes/6. \344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.md" +++ /dev/null @@ -1,96 +0,0 @@ -# 6. 从尾到头打印链表 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -从尾到头反过来打印出每个结点的值。 - -

- -## 解题思路 - -### 1. 使用递归 - -要逆序打印链表 1->2->3(3,2,1),可以先逆序打印链表 2->3(3,2),最后再打印第一个节点 1。而链表 2->3 可以看成一个新的链表,要逆序打印该链表可以继续使用求解函数,也就是在求解函数中调用自己,这就是递归函数。 - -```java -public ArrayList printListFromTailToHead(ListNode listNode) { - ArrayList ret = new ArrayList<>(); - if (listNode != null) { - ret.addAll(printListFromTailToHead(listNode.next)); - ret.add(listNode.val); - } - return ret; -} -``` - -### 2. 使用头插法 - -头插法顾名思义是将节点插入到头部:在遍历原始链表时,将当前节点插入新链表的头部,使其成为第一个节点。 - -链表的操作需要维护后继关系,例如在某个节点 node1 之后插入一个节点 node2,我们可以通过修改后继关系来实现: - -```java -node3 = node1.next; -node2.next = node3; -node1.next = node2; -``` - -

- - - -为了能将一个节点插入头部,我们引入了一个叫头结点的辅助节点,该节点不存储值,只是为了方便进行插入操作。不要将头结点与第一个节点混起来,第一个节点是链表中第一个真正存储值的节点。 - -

- -```java -public ArrayList printListFromTailToHead(ListNode listNode) { - // 头插法构建逆序链表 - ListNode head = new ListNode(-1); - while (listNode != null) { - ListNode memo = listNode.next; - listNode.next = head.next; - head.next = listNode; - listNode = memo; - } - // 构建 ArrayList - ArrayList ret = new ArrayList<>(); - head = head.next; - while (head != null) { - ret.add(head.val); - head = head.next; - } - return ret; -} -``` - -### 3. 使用栈 - -栈具有后进先出的特点,在遍历链表时将值按顺序放入栈中,最后出栈的顺序即为逆序。 - -

- -```java -public ArrayList printListFromTailToHead(ListNode listNode) { - Stack stack = new Stack<>(); - while (listNode != null) { - stack.add(listNode.val); - listNode = listNode.next; - } - ArrayList ret = new ArrayList<>(); - while (!stack.isEmpty()) - ret.add(stack.pop()); - return ret; -} -``` - - - - - - -
diff --git "a/docs/notes/60. n \344\270\252\351\252\260\345\255\220\347\232\204\347\202\271\346\225\260.md" "b/docs/notes/60. n \344\270\252\351\252\260\345\255\220\347\232\204\347\202\271\346\225\260.md" deleted file mode 100644 index d298cc9af6de2c1ebeb9070df0e48c8b79c609fc..0000000000000000000000000000000000000000 --- "a/docs/notes/60. n \344\270\252\351\252\260\345\255\220\347\232\204\347\202\271\346\225\260.md" +++ /dev/null @@ -1,81 +0,0 @@ -# 60. n 个骰子的点数 - -## 题目链接 - -[Lintcode](https://www.lintcode.com/en/problem/dices-sum/) - -## 题目描述 - -把 n 个骰子扔在地上,求点数和为 s 的概率。 - -

- -## 解题思路 - -### 动态规划 - -使用一个二维数组 dp 存储点数出现的次数,其中 dp\[i]\[j] 表示前 i 个骰子产生点数 j 的次数。 - -空间复杂度:O(N2) - -```java -public List> dicesSum(int n) { - final int face = 6; - final int pointNum = face * n; - long[][] dp = new long[n + 1][pointNum + 1]; - - for (int i = 1; i <= face; i++) - dp[1][i] = 1; - - for (int i = 2; i <= n; i++) - for (int j = i; j <= pointNum; j++) /* 使用 i 个骰子最小点数为 i */ - for (int k = 1; k <= face && k <= j; k++) - dp[i][j] += dp[i - 1][j - k]; - - final double totalNum = Math.pow(6, n); - List> ret = new ArrayList<>(); - for (int i = n; i <= pointNum; i++) - ret.add(new AbstractMap.SimpleEntry<>(i, dp[n][i] / totalNum)); - - return ret; -} -``` - -### 动态规划 + 旋转数组 - -空间复杂度:O(N) - -```java -public List> dicesSum(int n) { - final int face = 6; - final int pointNum = face * n; - long[][] dp = new long[2][pointNum + 1]; - - for (int i = 1; i <= face; i++) - dp[0][i] = 1; - - int flag = 1; /* 旋转标记 */ - for (int i = 2; i <= n; i++, flag = 1 - flag) { - for (int j = 0; j <= pointNum; j++) - dp[flag][j] = 0; /* 旋转数组清零 */ - - for (int j = i; j <= pointNum; j++) - for (int k = 1; k <= face && k <= j; k++) - dp[flag][j] += dp[1 - flag][j - k]; - } - - final double totalNum = Math.pow(6, n); - List> ret = new ArrayList<>(); - for (int i = n; i <= pointNum; i++) - ret.add(new AbstractMap.SimpleEntry<>(i, dp[1 - flag][i] / totalNum)); - - return ret; -} -``` - - - - - - -
diff --git "a/docs/notes/61. \346\211\221\345\205\213\347\211\214\351\241\272\345\255\220.md" "b/docs/notes/61. \346\211\221\345\205\213\347\211\214\351\241\272\345\255\220.md" deleted file mode 100644 index 9fde275c1f8b675d9b6dadb0616f28c8e308aa1c..0000000000000000000000000000000000000000 --- "a/docs/notes/61. \346\211\221\345\205\213\347\211\214\351\241\272\345\255\220.md" +++ /dev/null @@ -1,46 +0,0 @@ -# 61. 扑克牌顺子 - -## 题目链接 - -[NowCoder](https://www.nowcoder.com/practice/762836f4d43d43ca9deb273b3de8e1f4?tpId=13&tqId=11198&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -五张牌,其中大小鬼为癞子,牌面为 0。判断这五张牌是否能组成顺子。 - -

- - -## 解题思路 - -```java -public boolean isContinuous(int[] nums) { - - if (nums.length < 5) - return false; - - Arrays.sort(nums); - - // 统计癞子数量 - int cnt = 0; - for (int num : nums) - if (num == 0) - cnt++; - - // 使用癞子去补全不连续的顺子 - for (int i = cnt; i < nums.length - 1; i++) { - if (nums[i + 1] == nums[i]) - return false; - cnt -= nums[i + 1] - nums[i] - 1; - } - - return cnt >= 0; -} -``` - - - - - - -
diff --git "a/docs/notes/62. \345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260.md" "b/docs/notes/62. \345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260.md" deleted file mode 100644 index e99e8486dcf686c02f5eecb431190ba34b42e6f5..0000000000000000000000000000000000000000 --- "a/docs/notes/62. \345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260.md" +++ /dev/null @@ -1,30 +0,0 @@ -# 62. 圆圈中最后剩下的数 - -## 题目链接 - -[NowCoder](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -让小朋友们围成一个大圈。然后,随机指定一个数 m,让编号为 0 的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续 0...m-1 报数 .... 这样下去 .... 直到剩下最后一个小朋友,可以不用表演。 - -## 解题思路 - -约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈,所以最后需要对 n 取余。 - -```java -public int LastRemaining_Solution(int n, int m) { - if (n == 0) /* 特殊输入的处理 */ - return -1; - if (n == 1) /* 递归返回条件 */ - return 0; - return (LastRemaining_Solution(n - 1, m) + m) % n; -} -``` - - - - - - -
diff --git "a/docs/notes/63. \350\202\241\347\245\250\347\232\204\346\234\200\345\244\247\345\210\251\346\266\246.md" "b/docs/notes/63. \350\202\241\347\245\250\347\232\204\346\234\200\345\244\247\345\210\251\346\266\246.md" deleted file mode 100644 index 442f0df0143bc896d8384d770ff70dfe9131c479..0000000000000000000000000000000000000000 --- "a/docs/notes/63. \350\202\241\347\245\250\347\232\204\346\234\200\345\244\247\345\210\251\346\266\246.md" +++ /dev/null @@ -1,36 +0,0 @@ -# 63. 股票的最大利润 - -## 题目链接 - -[Leetcode:121. Best Time to Buy and Sell Stock ](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/) - -## 题目描述 - -可以有一次买入和一次卖出,买入必须在前。求最大收益。 - -

- -## 解题思路 - -使用贪心策略,假设第 i 轮进行卖出操作,买入操作价格应该在 i 之前并且价格最低。因此在遍历数组时记录当前最低的买入价格,并且尝试将每个位置都作为卖出价格,取收益最大的即可。 - -```java -public int maxProfit(int[] prices) { - if (prices == null || prices.length == 0) - return 0; - int soFarMin = prices[0]; - int maxProfit = 0; - for (int i = 1; i < prices.length; i++) { - soFarMin = Math.min(soFarMin, prices[i]); - maxProfit = Math.max(maxProfit, prices[i] - soFarMin); - } - return maxProfit; -} -``` - - - - - - -
diff --git "a/docs/notes/64. \346\261\202 1+2+3+...+n.md" "b/docs/notes/64. \346\261\202 1+2+3+...+n.md" deleted file mode 100644 index 77cc5303fd0922ad3e1d990dfb94bfe461ffc7a1..0000000000000000000000000000000000000000 --- "a/docs/notes/64. \346\261\202 1+2+3+...+n.md" +++ /dev/null @@ -1,32 +0,0 @@ -# 64. 求 1+2+3+...+n - -## 题目链接 - -[NowCoder](https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句 A ? B : C。 - -## 解题思路 - -使用递归解法最重要的是指定返回条件,但是本题无法直接使用 if 语句来指定返回条件。 - -条件与 && 具有短路原则,即在第一个条件语句为 false 的情况下不会去执行第二个条件语句。利用这一特性,将递归的返回条件取非然后作为 && 的第一个条件语句,递归的主体转换为第二个条件语句,那么当递归的返回条件为 true 的情况下就不会执行递归的主体部分,递归返回。 - -本题的递归返回条件为 n <= 0,取非后就是 n > 0;递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。 - -```java -public int Sum_Solution(int n) { - int sum = n; - boolean b = (n > 0) && ((sum += Sum_Solution(n - 1)) > 0); - return sum; -} -``` - - - - - - -
diff --git "a/docs/notes/65. \344\270\215\347\224\250\345\212\240\345\207\217\344\271\230\351\231\244\345\201\232\345\212\240\346\263\225.md" "b/docs/notes/65. \344\270\215\347\224\250\345\212\240\345\207\217\344\271\230\351\231\244\345\201\232\345\212\240\346\263\225.md" deleted file mode 100644 index 1afc54278668190864a688927d9c288c5aaddd9d..0000000000000000000000000000000000000000 --- "a/docs/notes/65. \344\270\215\347\224\250\345\212\240\345\207\217\344\271\230\351\231\244\345\201\232\345\212\240\346\263\225.md" +++ /dev/null @@ -1,28 +0,0 @@ -# 65. 不用加减乘除做加法 - -## 题目链接 - -[NowCoder](https://www.nowcoder.com/practice/59ac416b4b944300b617d4f7f111b215?tpId=13&tqId=11201&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -写一个函数,求两个整数之和,要求不得使用 +、-、\*、/ 四则运算符号。 - -## 解题思路 - -a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。 - -递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 - -```java -public int Add(int a, int b) { - return b == 0 ? a : Add(a ^ b, (a & b) << 1); -} -``` - - - - - - -
diff --git "a/docs/notes/66. \346\236\204\345\273\272\344\271\230\347\247\257\346\225\260\347\273\204.md" "b/docs/notes/66. \346\236\204\345\273\272\344\271\230\347\247\257\346\225\260\347\273\204.md" deleted file mode 100644 index 8317ca2a0dc82f6e8331859d2d2e5ad2ed8e9cfd..0000000000000000000000000000000000000000 --- "a/docs/notes/66. \346\236\204\345\273\272\344\271\230\347\247\257\346\225\260\347\273\204.md" +++ /dev/null @@ -1,33 +0,0 @@ -# 66. 构建乘积数组 - -## 题目链接 - -[NowCoder](https://www.nowcoder.com/practice/94a4d381a68b47b7a8bed86f2975db46?tpId=13&tqId=11204&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -给定一个数组 A[0, 1,..., n-1],请构建一个数组 B[0, 1,..., n-1],其中 B 中的元素 B[i]=A[0]\*A[1]\*...\*A[i-1]\*A[i+1]\*...\*A[n-1]。要求不能使用除法。 - -

- - -## 解题思路 - -```java -public int[] multiply(int[] A) { - int n = A.length; - int[] B = new int[n]; - for (int i = 0, product = 1; i < n; product *= A[i], i++) /* 从左往右累乘 */ - B[i] = product; - for (int i = n - 1, product = 1; i >= 0; product *= A[i], i--) /* 从右往左累乘 */ - B[i] *= product; - return B; -} -``` - - - - - - -
diff --git "a/docs/notes/67. \346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.md" "b/docs/notes/67. \346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.md" deleted file mode 100644 index db736fa84690165cbb311efca4fa83fc5799da0d..0000000000000000000000000000000000000000 --- "a/docs/notes/67. \346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.md" +++ /dev/null @@ -1,46 +0,0 @@ -# 67. 把字符串转换成整数 - -## 题目链接 - -[NowCoder](https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&tqId=11202&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -将一个字符串转换成一个整数,字符串不是一个合法的数值则返回 0,要求不能使用字符串转换整数的库函数。 - -```html -Iuput: -+2147483647 -1a33 - -Output: -2147483647 -0 -``` - -## 解题思路 - -```java -public int StrToInt(String str) { - if (str == null || str.length() == 0) - return 0; - boolean isNegative = str.charAt(0) == '-'; - int ret = 0; - for (int i = 0; i < str.length(); i++) { - char c = str.charAt(i); - if (i == 0 && (c == '+' || c == '-')) /* 符号判定 */ - continue; - if (c < '0' || c > '9') /* 非法输入 */ - return 0; - ret = ret * 10 + (c - '0'); - } - return isNegative ? -ret : ret; -} -``` - - - - - - -
diff --git "a/docs/notes/68. \346\240\221\344\270\255\344\270\244\344\270\252\350\212\202\347\202\271\347\232\204\346\234\200\344\275\216\345\205\254\345\205\261\347\245\226\345\205\210.md" "b/docs/notes/68. \346\240\221\344\270\255\344\270\244\344\270\252\350\212\202\347\202\271\347\232\204\346\234\200\344\275\216\345\205\254\345\205\261\347\245\226\345\205\210.md" deleted file mode 100644 index e77c9665f0d01550c9b894cb088dac6663bdb67a..0000000000000000000000000000000000000000 --- "a/docs/notes/68. \346\240\221\344\270\255\344\270\244\344\270\252\350\212\202\347\202\271\347\232\204\346\234\200\344\275\216\345\205\254\345\205\261\347\245\226\345\205\210.md" +++ /dev/null @@ -1,55 +0,0 @@ -# 68. 树中两个节点的最低公共祖先 - - -## 68.1 二叉查找树 - -### 题目链接 - -[Leetcode : 235. Lowest Common Ancestor of a Binary Search Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/) - -### 解题思路 - -在二叉查找树中,两个节点 p, q 的公共祖先 root 满足 root.val >= p.val && root.val <= q.val。 - -

- -```java -public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { - if (root == null) - return root; - if (root.val > p.val && root.val > q.val) - return lowestCommonAncestor(root.left, p, q); - if (root.val < p.val && root.val < q.val) - return lowestCommonAncestor(root.right, p, q); - return root; -} -``` - -## 68.2 普通二叉树 - -### 题目链接 - -[Leetcode : 236. Lowest Common Ancestor of a Binary Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) - -### 解题思路 - -在左右子树中查找是否存在 p 或者 q,如果 p 和 q 分别在两个子树中,那么就说明根节点就是最低公共祖先。 - -

- -```java -public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { - if (root == null || root == p || root == q) - return root; - TreeNode left = lowestCommonAncestor(root.left, p, q); - TreeNode right = lowestCommonAncestor(root.right, p, q); - return left == null ? right : right == null ? left : root; -} -``` - - - - - - -
diff --git "a/docs/notes/7. \351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.md" "b/docs/notes/7. \351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index b557ea0ba0673b2aac8c6abc808e8dde5c0b0ea2..0000000000000000000000000000000000000000 --- "a/docs/notes/7. \351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,48 +0,0 @@ -# 7. 重建二叉树 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 - - - -

- -## 解题思路 - -前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为树的左子树中序遍历结果,右部分为树的右子树中序遍历的结果。然后分别对左右子树递归地求解。 - -

- -```java -// 缓存中序遍历数组每个值对应的索引 -private Map indexForInOrders = new HashMap<>(); - -public TreeNode reConstructBinaryTree(int[] pre, int[] in) { - for (int i = 0; i < in.length; i++) - indexForInOrders.put(in[i], i); - return reConstructBinaryTree(pre, 0, pre.length - 1, 0); -} - -private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL) { - if (preL > preR) - return null; - TreeNode root = new TreeNode(pre[preL]); - int inIndex = indexForInOrders.get(root.val); - int leftTreeSize = inIndex - inL; - root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeSize, inL); - root.right = reConstructBinaryTree(pre, preL + leftTreeSize + 1, preR, inL + leftTreeSize + 1); - return root; -} -``` - - - - - - -
diff --git "a/docs/notes/8. \344\272\214\345\217\211\346\240\221\347\232\204\344\270\213\344\270\200\344\270\252\347\273\223\347\202\271.md" "b/docs/notes/8. \344\272\214\345\217\211\346\240\221\347\232\204\344\270\213\344\270\200\344\270\252\347\273\223\347\202\271.md" deleted file mode 100644 index 38d26414202b95f58420bc2a1726cb4ecbd72f4a..0000000000000000000000000000000000000000 --- "a/docs/notes/8. \344\272\214\345\217\211\346\240\221\347\232\204\344\270\213\344\270\200\344\270\252\347\273\223\347\202\271.md" +++ /dev/null @@ -1,74 +0,0 @@ -# 8. 二叉树的下一个结点 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回 。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 - -```java -public class TreeLinkNode { - - int val; - TreeLinkNode left = null; - TreeLinkNode right = null; - TreeLinkNode next = null; // 指向父结点的指针 - - TreeLinkNode(int val) { - this.val = val; - } -} -``` - -## 解题思路 - -我们先来回顾一下中序遍历的过程:先遍历树的左子树,再遍历根节点,最后再遍历右子树。所以最左节点是中序遍历的第一个节点。 - -```java -void traverse(TreeNode root) { - if (root == null) return; - traverse(root.left); - visit(root); - traverse(root.right); -} -``` - -

- - - -① 如果一个节点的右子树不为空,那么该节点的下一个节点是右子树的最左节点; - -

- -② 否则,向上找第一个左链接指向的树包含该节点的祖先节点。 - -

- -```java -public TreeLinkNode GetNext(TreeLinkNode pNode) { - if (pNode.right != null) { - TreeLinkNode node = pNode.right; - while (node.left != null) - node = node.left; - return node; - } else { - while (pNode.next != null) { - TreeLinkNode parent = pNode.next; - if (parent.left == pNode) - return parent; - pNode = pNode.next; - } - } - return null; -} -``` - - - - - - -
diff --git "a/docs/notes/9. \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" "b/docs/notes/9. \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" deleted file mode 100644 index 7d1f24ec3874377d595e7658e3a94c7c9a721cc0..0000000000000000000000000000000000000000 --- "a/docs/notes/9. \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227.md" +++ /dev/null @@ -1,42 +0,0 @@ -# 9. 用两个栈实现队列 - -## 题目链接 - -[牛客网](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking&from=cyc_github) - -## 题目描述 - -用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。 - -## 解题思路 - -in 栈用来处理入栈(push)操作,out 栈用来处理出栈(pop)操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,先进入的元素先退出,这就是队列的顺序。 - -

- -```java -Stack in = new Stack(); -Stack out = new Stack(); - -public void push(int node) { - in.push(node); -} - -public int pop() throws Exception { - if (out.isEmpty()) - while (!in.isEmpty()) - out.push(in.pop()); - - if (out.isEmpty()) - throw new Exception("queue is empty"); - - return out.pop(); -} -``` - - - - - - -
diff --git a/docs/notes/Docker.md b/docs/notes/Docker.md deleted file mode 100644 index 7647d26c916acaf9c4d8e4b41d0c0268dfc4581e..0000000000000000000000000000000000000000 --- a/docs/notes/Docker.md +++ /dev/null @@ -1,96 +0,0 @@ - -* [一、解决的问题](#一解决的问题) -* [二、与虚拟机的比较](#二与虚拟机的比较) -* [三、优势](#三优势) -* [四、使用场景](#四使用场景) -* [五、镜像与容器](#五镜像与容器) -* [参考资料](#参考资料) - - - -# 一、解决的问题 - -由于不同的机器有不同的操作系统,以及不同的库和组件,在将一个应用部署到多台机器上需要进行大量的环境配置操作。 - -Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程进行隔离,被隔离的进程独立于宿主操作系统和其它隔离的进程。使用 Docker 可以不修改应用程序代码,不需要开发人员学习特定环境下的技术,就能够将现有的应用程序部署在其它机器上。 - -

- -# 二、与虚拟机的比较 - -虚拟机也是一种虚拟化技术,它与 Docker 最大的区别在于它是通过模拟硬件,并在硬件上安装操作系统来实现。 - -

- -## 启动速度 - -启动虚拟机需要先启动虚拟机的操作系统,再启动应用,这个过程非常慢; - -而启动 Docker 相当于启动宿主操作系统上的一个进程。 - -## 占用资源 - -虚拟机是一个完整的操作系统,需要占用大量的磁盘、内存和 CPU 资源,一台机器只能开启几十个的虚拟机。 - -而 Docker 只是一个进程,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。 - -# 三、优势 - -除了启动速度快以及占用资源少之外,Docker 具有以下优势: - -## 更容易迁移 - -提供一致性的运行环境。已经打包好的应用可以在不同的机器上进行迁移,而不用担心环境变化导致无法运行。 - -## 更容易维护 - -使用分层技术和镜像,使得应用可以更容易复用重复的部分。复用程度越高,维护工作也越容易。 - -## 更容易扩展 - -可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像可以非常容易得到我们想要的镜像。 - -# 四、使用场景 - -## 持续集成 - -持续集成指的是频繁地将代码集成到主干上,这样能够更快地发现错误。 - -Docker 具有轻量级以及隔离性的特点,在将代码集成到一个 Docker 中不会对其它 Docker 产生影响。 - -## 提供可伸缩的云服务 - -根据应用的负载情况,可以很容易地增加或者减少 Docker。 - -## 搭建微服务架构 - -Docker 轻量级的特点使得它很适合用于部署、维护、组合微服务。 - -# 五、镜像与容器 - -镜像是一种静态的结构,可以看成面向对象里面的类,而容器是镜像的一个实例。 - -镜像包含着容器运行时所需要的代码以及其它组件,它是一种分层结构,每一层都是只读的(read-only layers)。构建镜像时,会一层一层构建,前一层是后一层的基础。镜像的这种分层存储结构很适合镜像的复用以及定制。 - -构建容器时,通过在镜像的基础上添加一个可写层(writable layer),用来保存着容器运行过程中的修改。 - -

- -# 参考资料 - -- [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) -- [Docker container vs Virtual machine](http://www.bogotobogo.com/DevOps/Docker/Docker_Container_vs_Virtual_Machine.php) -- [How to Create Docker Container using Dockerfile](https://linoxide.com/linux-how-to/dockerfile-create-docker-container/) -- [理解 Docker(2):Docker 镜像](http://www.cnblogs.com/sammyliu/p/5877964.html) -- [为什么要使用 Docker?](https://yeasy.gitbooks.io/docker_practice/introduction/why.html) -- [What is Docker](https://www.docker.com/what-docker) -- [持续集成是什么?](http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html) - - - - - - - -
diff --git a/docs/notes/Git.md b/docs/notes/Git.md deleted file mode 100644 index 94e3dc3f6337bf3a15e757b6f41aeed3788e3fdd..0000000000000000000000000000000000000000 --- a/docs/notes/Git.md +++ /dev/null @@ -1,158 +0,0 @@ - -* [集中式与分布式](#集中式与分布式) -* [中心服务器](#中心服务器) -* [工作流](#工作流) -* [分支实现](#分支实现) -* [冲突](#冲突) -* [Fast forward](#fast-forward) -* [储藏(Stashing)](#储藏stashing) -* [SSH 传输设置](#ssh-传输设置) -* [.gitignore 文件](#gitignore-文件) -* [Git 命令一览](#git-命令一览) -* [参考资料](#参考资料) - - - -# 集中式与分布式 - -Git 属于分布式版本控制系统,而 SVN 属于集中式。 - -

- -集中式版本控制只有中心服务器拥有一份代码,而分布式版本控制每个人的电脑上就有一份完整的代码。 - -集中式版本控制有安全性问题,当中心服务器挂了所有人都没办法工作了。 - -集中式版本控制需要连网才能工作,如果网速过慢,那么提交一个文件会慢的无法让人忍受。而分布式版本控制不需要连网就能工作。 - -分布式版本控制新建分支、合并分支操作速度非常快,而集中式版本控制新建一个分支相当于复制一份完整代码。 - -# 中心服务器 - -中心服务器用来交换每个用户的修改,没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。 - -Github 就是一个中心服务器。 - -# 工作流 - -新建一个仓库之后,当前目录就成为了工作区,工作区下有一个隐藏目录 .git,它属于 Git 的版本库。 - -Git 的版本库有一个称为 Stage 的暂存区以及最后的 History 版本库,History 存储所有分支信息,使用一个 HEAD 指针指向当前分支。 - -

- -- git add files 把文件的修改添加到暂存区 -- git commit 把暂存区的修改提交到当前分支,提交之后暂存区就被清空了 -- git reset -- files 使用当前分支上的修改覆盖暂存区,用来撤销最后一次 git add files -- git checkout -- files 使用暂存区的修改覆盖工作目录,用来撤销本地修改 - -

- -可以跳过暂存区域直接从分支中取出修改,或者直接提交修改到分支中。 - -- git commit -a 直接把所有文件的修改添加到暂存区然后执行提交 -- git checkout HEAD -- files 取出最后一次修改,可以用来进行回滚操作 - -

- -# 分支实现 - -使用指针将每个提交连接成一条时间线,HEAD 指针指向当前分支指针。 - -

- -新建分支是新建一个指针指向时间线的最后一个节点,并让 HEAD 指针指向新分支,表示新分支成为当前分支。 - -

- -每次提交只会让当前分支指针向前移动,而其它分支指针不会移动。 - -

- -合并分支也只需要改变指针即可。 - -

- -# 冲突 - -当两个分支都对同一个文件的同一行进行了修改,在分支合并时就会产生冲突。 - -

- -Git 会使用 <<<<<<< ,======= ,>>>>>>> 标记出不同分支的内容,只需要把不同分支中冲突部分修改成一样就能解决冲突。 - -``` -<<<<<<< HEAD -Creating a new branch is quick & simple. -======= -Creating a new branch is quick AND simple. ->>>>>>> feature1 -``` - -# Fast forward - -"快进式合并"(fast-farward merge),会直接将 master 分支指向合并的分支,这种模式下进行分支合并会丢失分支信息,也就不能在分支历史上看出分支信息。 - -可以在合并时加上 --no-ff 参数来禁用 Fast forward 模式,并且加上 -m 参数让合并时产生一个新的 commit。 - -``` -$ git merge --no-ff -m "merge with no-ff" dev -``` - -

- -# 储藏(Stashing) - -在一个分支上操作之后,如果还没有将修改提交到分支上,此时进行切换分支,那么另一个分支上也能看到新的修改。这是因为所有分支都共用一个工作区的缘故。 - -可以使用 git stash 将当前分支的修改储藏起来,此时当前工作区的所有修改都会被存到栈中,也就是说当前工作区是干净的,没有任何未提交的修改。此时就可以安全的切换到其它分支上了。 - -``` -$ git stash -Saved working directory and index state \ "WIP on master: 049d078 added the index file" -HEAD is now at 049d078 added the index file (To restore them type "git stash apply") -``` - -该功能可以用于 bug 分支的实现。如果当前正在 dev 分支上进行开发,但是此时 master 上有个 bug 需要修复,但是 dev 分支上的开发还未完成,不想立即提交。在新建 bug 分支并切换到 bug 分支之前就需要使用 git stash 将 dev 分支的未提交修改储藏起来。 - -# SSH 传输设置 - -Git 仓库和 Github 中心仓库之间的传输是通过 SSH 加密。 - -如果工作区下没有 .ssh 目录,或者该目录下没有 id_rsa 和 id_rsa.pub 这两个文件,可以通过以下命令来创建 SSH Key: - -``` -$ ssh-keygen -t rsa -C "youremail@example.com" -``` - -然后把公钥 id_rsa.pub 的内容复制到 Github "Account settings" 的 SSH Keys 中。 - -# .gitignore 文件 - -忽略以下文件: - -- 操作系统自动生成的文件,比如缩略图; -- 编译生成的中间文件,比如 Java 编译产生的 .class 文件; -- 自己的敏感信息,比如存放口令的配置文件。 - -不需要全部自己编写,可以到 [https://github.com/github/gitignore](https://github.com/github/gitignore) 中进行查询。 - -# Git 命令一览 - -

- -比较详细的地址: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/) - - - - - - -
diff --git a/docs/notes/HTTP.md b/docs/notes/HTTP.md deleted file mode 100644 index 55ebf275f4a8a15f642c93271359f2e67b900589..0000000000000000000000000000000000000000 --- a/docs/notes/HTTP.md +++ /dev/null @@ -1,948 +0,0 @@ - -* [一 、基础概念](#一-基础概念) - * [请求和响应报文](#请求和响应报文) - * [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) -* [参考资料](#参考资料) - - - -# 一 、基础概念 - -## 请求和响应报文 - -客户端发送一个请求报文给服务器,服务器根据请求报文中的信息进行处理,并将处理结果放入响应报文中返回给客户端。 - -请求报文结构: - -- 第一行是包含了请求方法、URL、协议版本; -- 接下来的多行都是请求首部 Header,每个首部都有一个首部名称,以及对应的值。 -- 一个空行用来分隔首部和内容主体 Body -- 最后是请求的内容主体 - -``` -GET http://www.example.com/ HTTP/1.1 -Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 -Accept-Encoding: gzip, deflate -Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 -Cache-Control: max-age=0 -Host: www.example.com -If-Modified-Since: Thu, 17 Oct 2019 07:18:26 GMT -If-None-Match: "3147526947+gzip" -Proxy-Connection: keep-alive -Upgrade-Insecure-Requests: 1 -User-Agent: Mozilla/5.0 xxx - -param1=1¶m2=2 -``` - -响应报文结构: - -- 第一行包含协议版本、状态码以及描述,最常见的是 200 OK 表示请求成功了 -- 接下来多行也是首部内容 -- 一个空行分隔首部和内容主体 -- 最后是响应的内容主体 - -``` -HTTP/1.1 200 OK -Age: 529651 -Cache-Control: max-age=604800 -Connection: keep-alive -Content-Encoding: gzip -Content-Length: 648 -Content-Type: text/html; charset=UTF-8 -Date: Mon, 02 Nov 2020 17:53:39 GMT -Etag: "3147526947+ident+gzip" -Expires: Mon, 09 Nov 2020 17:53:39 GMT -Keep-Alive: timeout=4 -Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT -Proxy-Connection: keep-alive -Server: ECS (sjc/16DF) -Vary: Accept-Encoding -X-Cache: HIT - - - - - Example Domain - // 省略... - - - -``` - -## URL - -http 使用 URL( **U** niform **R**esource **L**ocator,统一资源定位符)来定位资源,它可以认为是是 URI(**U**niform **R**esource **I**dentifier,统一资源标识符)的一个子集,URL 在 URI 的基础上增加了定位能力。URI 除了包含 URL 之外,还包含 URN(Uniform Resource Name,统一资源名称),它知识用来定义一个资源的名称,并不具备定位该资源的能力。例如 urn:isbn:0451450523 用来定义一个书籍,但是却没有表示怎么找到这本书。 - -

- -- [wikipedia:统一资源标志符](https://zh.wikipedia.org/wiki/统一资源标志符) -- [wikipedia: URL](https://en.wikipedia.org/wiki/URL) -- [rfc2616:3.2.2 http URL](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.2) -- [What is the difference between a URI, a URL and a URN?](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn) - -# 二、HTTP 方法 - -客户端发送的 **请求报文** 第一行为请求行,包含了方法字段。 - -## GET - -> 获取资源 - -当前网络请求中,绝大部分使用的是 GET 方法。 - -## HEAD - -> 获取报文首部 - -和 GET 方法类似,但是不返回报文实体主体部分。 - -主要用于确认 URL 的有效性以及资源更新的日期时间等。 - -## POST - -> 传输实体主体 - -POST 主要用来传输数据,而 GET 主要用来获取资源。 - -更多 POST 与 GET 的比较请见第九章。 - -## PUT - -> 上传文件 - -由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。 - -```html -PUT /new.html HTTP/1.1 -Host: example.com -Content-type: text/html -Content-length: 16 - -

New File

-``` - -## PATCH - -> 对资源进行部分修改 - -PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。 - -```html -PATCH /file.txt HTTP/1.1 -Host: www.example.com -Content-Type: application/example -If-Match: "e0023aa4e" -Content-Length: 100 - -[description of changes] -``` - -## DELETE - -> 删除文件 - -与 PUT 功能相反,并且同样不带验证机制。 - -```html -DELETE /file.html HTTP/1.1 -``` - -## OPTIONS - -> 查询支持的方法 - -查询指定的 URL 能够支持的方法。 - -会返回 `Allow: GET, POST, HEAD, OPTIONS` 这样的内容。 - -## CONNECT - -> 要求在与代理服务器通信时建立隧道 - -使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。 - -```html -CONNECT www.example.com:443 HTTP/1.1 -``` - -

- -## TRACE - -> 追踪路径 - -服务器会将通信路径返回给客户端。 - -发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。 - -通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪)。 - -- [rfc2616:9 Method Definitions](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) - -# 三、HTTP 状态码 - -服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。 - -| 状态码 | 类别 | 含义 | -| :---: | :---: | :---: | -| 1XX | Informational(信息性状态码) | 接收的请求正在处理 | -| 2XX | Success(成功状态码) | 请求正常处理完毕 | -| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 | -| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 | -| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 | - -## 1XX 信息 - -- **100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。 - -## 2XX 成功 - -- **200 OK** - -- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 - -- **206 Partial Content** :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。 - -## 3XX 重定向 - -- **301 Moved Permanently** :永久性重定向 - -- **302 Found** :临时性重定向 - -- **303 See Other** :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。 - -- 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。 - -- **304 Not Modified** :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。 - -- **307 Temporary Redirect** :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。 - -## 4XX 客户端错误 - -- **400 Bad Request** :请求报文中存在语法错误。 - -- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。 - -- **403 Forbidden** :请求被拒绝。 - -- **404 Not Found** - -## 5XX 服务器错误 - -- **500 Internal Server Error** :服务器正在执行请求时发生错误。 - -- **503 Service Unavailable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 - -# 四、HTTP 首部 - -有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。 - -各种首部字段及其含义如下(不需要全记,仅供查阅): - -## 通用首部字段 - -| 首部字段名 | 说明 | -| :--: | :--: | -| Cache-Control | 控制缓存的行为 | -| Connection | 控制不再转发给代理的首部字段、管理持久连接| -| Date | 创建报文的日期时间 | -| Pragma | 报文指令 | -| Trailer | 报文末端的首部一览 | -| Transfer-Encoding | 指定报文主体的传输编码方式 | -| Upgrade | 升级为其他协议 | -| Via | 代理服务器的相关信息 | -| Warning | 错误通知 | - -## 请求首部字段 - -| 首部字段名 | 说明 | -| :--: | :--: | -| Accept | 用户代理可处理的媒体类型 | -| Accept-Charset | 优先的字符集 | -| Accept-Encoding | 优先的内容编码 | -| Accept-Language | 优先的语言(自然语言) | -| Authorization | Web 认证信息 | -| Expect | 期待服务器的特定行为 | -| From | 用户的电子邮箱地址 | -| Host | 请求资源所在服务器 | -| If-Match | 比较实体标记(ETag) | -| If-Modified-Since | 比较资源的更新时间 | -| If-None-Match | 比较实体标记(与 If-Match 相反) | -| If-Range | 资源未更新时发送实体 Byte 的范围请求 | -| If-Unmodified-Since | 比较资源的更新时间(与 If-Modified-Since 相反) | -| Max-Forwards | 最大传输逐跳数 | -| Proxy-Authorization | 代理服务器要求客户端的认证信息 | -| Range | 实体的字节范围请求 | -| Referer | 对请求中 URI 的原始获取方 | -| TE | 传输编码的优先级 | -| User-Agent | HTTP 客户端程序的信息 | - -## 响应首部字段 - -| 首部字段名 | 说明 | -| :--: | :--: | -| Accept-Ranges | 是否接受字节范围请求 | -| Age | 推算资源创建经过时间 | -| ETag | 资源的匹配信息 | -| Location | 令客户端重定向至指定 URI | -| Proxy-Authenticate | 代理服务器对客户端的认证信息 | -| Retry-After | 对再次发起请求的时机要求 | -| Server | HTTP 服务器的安装信息 | -| Vary | 代理服务器缓存的管理信息 | -| WWW-Authenticate | 服务器对客户端的认证信息 | - -## 实体首部字段 - -| 首部字段名 | 说明 | -| :--: | :--: | -| Allow | 资源可支持的 HTTP 方法 | -| Content-Encoding | 实体主体适用的编码方式 | -| Content-Language | 实体主体的自然语言 | -| Content-Length | 实体主体的大小 | -| Content-Location | 替代对应资源的 URI | -| Content-MD5 | 实体主体的报文摘要 | -| Content-Range | 实体主体的位置范围 | -| Content-Type | 实体主体的媒体类型 | -| Expires | 实体主体过期的日期时间 | -| Last-Modified | 资源的最后修改日期时间 | - -# 五、具体应用 - -## 连接管理 - -

- -### 1. 短连接与长连接 - -当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问的 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。 - -长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。 - -- 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 `Connection : close`; -- 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 `Connection : Keep-Alive`。 - -### 2. 流水线 - -默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。 - -流水线是在同一条长连接上连续发出请求,而不用等待响应返回,这样可以减少延迟。 - -## Cookie - -HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 - -Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。 - -Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。 - -### 1. 用途 - -- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) -- 个性化设置(如用户自定义设置、主题等) -- 浏览器行为跟踪(如跟踪分析用户行为等) - -### 2. 创建过程 - -服务器发送的响应报文包含 Set-Cookie 首部字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。 - -```html -HTTP/1.0 200 OK -Content-type: text/html -Set-Cookie: yummy_cookie=choco -Set-Cookie: tasty_cookie=strawberry - -[page content] -``` - -客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。 - -```html -GET /sample_page.html HTTP/1.1 -Host: www.example.org -Cookie: yummy_cookie=choco; tasty_cookie=strawberry -``` - -### 3. 分类 - -- 会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。 -- 持久性 Cookie:指定过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie。 - -```html -Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; -``` - -### 4. 作用域 - -Domain 标识指定了哪些主机可以接受 Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了 Domain,则一般包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。 - -Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。例如,设置 Path=/docs,则以下地址都会匹配: - -- /docs -- /docs/Web/ -- /docs/Web/HTTP - -### 5. JavaScript - -浏览器通过 `document.cookie` 属性可创建新的 Cookie,也可通过该属性访问非 HttpOnly 标记的 Cookie。 - -```html -document.cookie = "yummy_cookie=choco"; -document.cookie = "tasty_cookie=strawberry"; -console.log(document.cookie); -``` - -### 6. HttpOnly - -标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 `document.cookie` API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。 - -```html -Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly -``` - -### 7. Secure - -标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。 - -### 8. Session - -除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 - -Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 - -使用 Session 维护用户登录状态的过程如下: - -- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; -- 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID; -- 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中; -- 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。 - -应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。 - -### 9. 浏览器禁用 Cookie - -此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外,不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。 - -### 10. Cookie 与 Session 选择 - -- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session; -- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密; -- 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。 - -## 缓存 - -### 1. 优点 - -- 缓解服务器压力; -- 降低客户端获取资源的延迟:缓存通常位于内存中,读取缓存的速度更快。并且缓存服务器在地理位置上也有可能比源服务器来得近,例如浏览器缓存。 - -### 2. 实现方法 - -- 让代理服务器进行缓存; -- 让客户端浏览器进行缓存。 - -### 3. Cache-Control - -HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。 - -**3.1 禁止进行缓存** - -no-store 指令规定不能对请求或响应的任何一部分进行缓存。 - -```html -Cache-Control: no-store -``` - -**3.2 强制确认缓存** - -no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效时才能使用该缓存对客户端的请求进行响应。 - -```html -Cache-Control: no-cache -``` - -**3.3 私有缓存和公共缓存** - -private 指令规定了将资源作为私有缓存,只能被单独用户使用,一般存储在用户浏览器中。 - -```html -Cache-Control: private -``` - -public 指令规定了将资源作为公共缓存,可以被多个用户使用,一般存储在代理服务器中。 - -```html -Cache-Control: public -``` - -**3.4 缓存过期机制** - -max-age 指令出现在请求报文,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。 - -max-age 指令出现在响应报文,表示缓存资源在缓存服务器中保存的时间。 - -```html -Cache-Control: max-age=31536000 -``` - -Expires 首部字段也可以用于告知缓存服务器该资源什么时候会过期。 - -```html -Expires: Wed, 04 Jul 2012 08:26:05 GMT -``` - -- 在 HTTP/1.1 中,会优先处理 max-age 指令; -- 在 HTTP/1.0 中,max-age 指令会被忽略掉。 - -### 4. 缓存验证 - -需要先了解 ETag 首部字段的含义,它是资源的唯一标识。URL 不能唯一表示资源,例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。 - -```html -ETag: "82e22293907ce725faf67773957acd12" -``` - -可以将缓存资源的 ETag 值放入 If-None-Match 首部,服务器收到该请求后,判断缓存资源的 ETag 值和资源的最新 ETag 值是否一致,如果一致则表示缓存资源有效,返回 304 Not Modified。 - -```html -If-None-Match: "82e22293907ce725faf67773957acd12" -``` - -Last-Modified 首部字段也可以用于缓存验证,它包含在源服务器发送的响应报文中,指示源服务器对资源的最后修改时间。但是它是一种弱校验器,因为只能精确到一秒,所以它通常作为 ETag 的备用方案。如果响应首部字段里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 OK。如果请求的资源从那时起未经修改,那么返回一个不带有实体主体的 304 Not Modified 响应报文。 - -```html -Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT -``` - -```html -If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT -``` - -## 内容协商 - -通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。 - -### 1. 类型 - -**1.1 服务端驱动型** - -客户端设置特定的 HTTP 首部字段,例如 Accept、Accept-Charset、Accept-Encoding、Accept-Language,服务器根据这些字段返回特定的资源。 - -它存在以下问题: - -- 服务器很难知道客户端浏览器的全部信息; -- 客户端提供的信息相当冗长(HTTP/2 协议的首部压缩机制缓解了这个问题),并且存在隐私风险(HTTP 指纹识别技术); -- 给定的资源需要返回不同的展现形式,共享缓存的效率会降低,而服务器端的实现会越来越复杂。 - -**1.2 代理驱动型** - -服务器返回 300 Multiple Choices 或者 406 Not Acceptable,客户端从中选出最合适的那个资源。 - -### 2. Vary - -```html -Vary: Accept-Language -``` - -在使用内容协商的情况下,只有当缓存服务器中的缓存满足内容协商条件时,才能使用该缓存,否则应该向源服务器请求该资源。 - -例如,一个客户端发送了一个包含 Accept-Language 首部字段的请求之后,源服务器返回的响应包含 `Vary: Accept-Language` 内容,缓存服务器对这个响应进行缓存之后,在客户端下一次访问同一个 URL 资源,并且 Accept-Language 与缓存中的对应的值相同时才会返回该缓存。 - -## 内容编码 - -内容编码将实体主体进行压缩,从而减少传输的数据量。 - -常用的内容编码有:gzip、compress、deflate、identity。 - -浏览器发送 Accept-Encoding 首部,其中包含有它所支持的压缩算法,以及各自的优先级。服务器则从中选择一种,使用该算法对响应的消息主体进行压缩,并且发送 Content-Encoding 首部来告知浏览器它选择了哪一种算法。由于该内容协商过程是基于编码类型来选择资源的展现形式的,响应报文的 Vary 首部字段至少要包含 Content-Encoding。 - -## 范围请求 - -如果网络出现中断,服务器只发送了一部分数据,范围请求可以使得客户端只请求服务器未发送的那部分数据,从而避免服务器重新发送所有数据。 - -### 1. Range - -在请求报文中添加 Range 首部字段指定请求的范围。 - -```html -GET /z4d4kWk.jpg HTTP/1.1 -Host: i.imgur.com -Range: bytes=0-1023 -``` - -请求成功的话服务器返回的响应包含 206 Partial Content 状态码。 - -```html -HTTP/1.1 206 Partial Content -Content-Range: bytes 0-1023/146515 -Content-Length: 1024 -... -(binary content) -``` - -### 2. Accept-Ranges - -响应首部字段 Accept-Ranges 用于告知客户端是否能处理范围请求,可以处理使用 bytes,否则使用 none。 - -```html -Accept-Ranges: bytes -``` - -### 3. 响应状态码 - -- 在请求成功的情况下,服务器会返回 206 Partial Content 状态码。 -- 在请求的范围越界的情况下,服务器会返回 416 Requested Range Not Satisfiable 状态码。 -- 在不支持范围请求的情况下,服务器会返回 200 OK 状态码。 - -## 分块传输编码 - -Chunked Transfer Encoding,可以把数据分割成多块,让浏览器逐步显示页面。 - -## 多部分对象集合 - -一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔,每个部分都可以有首部字段。 - -例如,上传多个表单时可以使用如下方式: - -```html -Content-Type: multipart/form-data; boundary=AaB03x - ---AaB03x -Content-Disposition: form-data; name="submit-name" - -Larry ---AaB03x -Content-Disposition: form-data; name="files"; filename="file1.txt" -Content-Type: text/plain - -... contents of file1.txt ... ---AaB03x-- -``` - -## 虚拟主机 - -HTTP/1.1 使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。 - -## 通信数据转发 - -### 1. 代理 - -代理服务器接受客户端的请求,并且转发给其它服务器。 - -使用代理的主要目的是: - -- 缓存 -- 负载均衡 -- 网络访问控制 -- 访问日志记录 - -代理服务器分为正向代理和反向代理两种: - -- 用户察觉得到正向代理的存在。 - -

- -- 而反向代理一般位于内部网络中,用户察觉不到。 - -

- -### 2. 网关 - -与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。 - -### 3. 隧道 - -使用 SSL 等加密手段,在客户端和服务器之间建立一条安全的通信线路。 - -# 六、HTTPS - -HTTP 有以下安全性问题: - -- 使用明文进行通信,内容可能会被窃听; -- 不验证通信方的身份,通信方的身份有可能遭遇伪装; -- 无法证明报文的完整性,报文有可能遭篡改。 - -HTTPS 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信,也就是说 HTTPS 使用了隧道进行通信。 - -通过使用 SSL,HTTPS 具有了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)。 - -

- -## 加密 - -### 1. 对称密钥加密 - -对称密钥加密(Symmetric-Key Encryption),加密和解密使用同一密钥。 - -- 优点:运算速度快; -- 缺点:无法安全地将密钥传输给通信方。 - -

- -### 2.非对称密钥加密 - -非对称密钥加密,又称公开密钥加密(Public-Key Encryption),加密和解密使用不同的密钥。 - -公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。 - -非对称密钥除了用来加密,还可以用来进行签名。因为私有密钥无法被其他人获取,因此通信发送方使用其私有密钥进行签名,通信接收方使用发送方的公开密钥对签名进行解密,就能判断这个签名是否正确。 - -- 优点:可以更安全地将公开密钥传输给通信发送方; -- 缺点:运算速度慢。 - -

- -### 3. HTTPS 采用的加密方式 - -上面提到对称密钥加密方式的传输效率更高,但是无法安全地将密钥 Secret Key 传输给通信方。而非对称密钥加密方式可以保证传输的安全性,因此我们可以利用非对称密钥加密方式将 Secret Key 传输给通信方。HTTPS 采用混合的加密机制,正是利用了上面提到的方案: - -- 使用非对称密钥加密方式,传输对称密钥加密方式所需要的 Secret Key,从而保证安全性; -- 获取到 Secret Key 后,再使用对称密钥加密方式进行通信,从而保证效率。(下图中的 Session Key 就是 Secret Key) - -

- -## 认证 - -通过使用 **证书** 来对通信方进行认证。 - -数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。 - -服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。 - -进行 HTTPS 通信时,服务器会把证书发送给客户端。客户端取得其中的公开密钥之后,先使用数字签名进行验证,如果验证通过,就可以开始通信了。 - -

- -## 完整性保护 - -SSL 提供报文摘要功能来进行完整性保护。 - -HTTP 也提供了 MD5 报文摘要功能,但不是安全的。例如报文内容被篡改之后,同时重新计算 MD5 的值,通信接收方是无法意识到发生了篡改。 - -HTTPS 的报文摘要功能之所以安全,是因为它结合了加密和认证这两个操作。试想一下,加密之后的报文,遭到篡改之后,也很难重新计算报文摘要,因为无法轻易获取明文。 - -## HTTPS 的缺点 - -- 因为需要进行加密解密等过程,因此速度会更慢; -- 需要支付证书授权的高额费用。 - -# 七、HTTP/2.0 - -## HTTP/1.x 缺陷 - -HTTP/1.x 实现简单是以牺牲性能为代价的: - -- 客户端需要使用多个连接才能实现并发和缩短延迟; -- 不会压缩请求和响应首部,从而导致不必要的网络流量; -- 不支持有效的资源优先级,致使底层 TCP 连接的利用率低下。 - -## 二进制分帧层 - -HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式的。 - -

- -在通信过程中,只会有一个 TCP 连接存在,它承载了任意数量的双向数据流(Stream)。 - -- 一个数据流(Stream)都有一个唯一标识符和可选的优先级信息,用于承载双向信息。 -- 消息(Message)是与逻辑请求或响应对应的完整的一系列帧。 -- 帧(Frame)是最小的通信单位,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。 - -

- -## 服务端推送 - -HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 page.html 页面,服务端就把 script.js 和 style.css 等与之相关的资源一起发给客户端。 - -

- -## 首部压缩 - -HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。 - -HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见过的首部字段表,从而避免了重复传输。 - -不仅如此,HTTP/2.0 也使用 Huffman 编码对首部字段进行压缩。 - -

- -# 八、HTTP/1.1 新特性 - -详细内容请见上文 - -- 默认是长连接 -- 支持流水线 -- 支持同时打开多个 TCP 连接 -- 支持虚拟主机 -- 新增状态码 100 -- 支持分块传输编码 -- 新增缓存处理指令 max-age - -# 九、GET 和 POST 比较 - -## 作用 - -GET 用于获取资源,而 POST 用于传输实体主体。 - -## 参数 - -GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体中。不能因为 POST 参数存储在实体主体中就认为它的安全性更高,因为照样可以通过一些抓包工具(Fiddler)查看。 - -因为 URL 只支持 ASCII 码,因此 GET 的参数中如果存在中文等字符就需要先进行编码。例如 `中文` 会转换为 `%E4%B8%AD%E6%96%87`,而空格会转换为 `%20`。POST 参数支持标准字符集。 - -``` -GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1 -``` - -``` -POST /test/demo_form.asp HTTP/1.1 -Host: w3schools.com -name1=value1&name2=value2 -``` - -## 安全 - -安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的。 - -GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发生了改变。 - -安全的方法除了 GET 之外还有:HEAD、OPTIONS。 - -不安全的方法除了 POST 之外还有 PUT、DELETE。 - -## 幂等性 - -幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。 - -所有的安全方法也都是幂等的。 - -在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。 - -GET /pageX HTTP/1.1 是幂等的,连续调用多次,客户端接收到的结果都是一样的: - -``` -GET /pageX HTTP/1.1 -GET /pageX HTTP/1.1 -GET /pageX HTTP/1.1 -GET /pageX HTTP/1.1 -``` - -POST /add_row HTTP/1.1 不是幂等的,如果调用多次,就会增加多行记录: - -``` -POST /add_row HTTP/1.1 -> Adds a 1nd row -POST /add_row HTTP/1.1 -> Adds a 2nd row -POST /add_row HTTP/1.1 -> Adds a 3rd row -``` - -DELETE /idX/delete HTTP/1.1 是幂等的,即使不同的请求接收到的状态码不一样: - -``` -DELETE /idX/delete HTTP/1.1 -> Returns 200 if idX exists -DELETE /idX/delete HTTP/1.1 -> Returns 404 as it just got deleted -DELETE /idX/delete HTTP/1.1 -> Returns 404 -``` - -## 可缓存 - -如果要对响应进行缓存,需要满足以下条件: - -- 请求报文的 HTTP 方法本身是可缓存的,包括 GET 和 HEAD,但是 PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的。 -- 响应报文的状态码是可缓存的,包括:200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501。 -- 响应报文的 Cache-Control 首部字段没有指定不进行缓存。 - -## XMLHttpRequest - -为了阐述 POST 和 GET 的另一个区别,需要先了解 XMLHttpRequest: - -> XMLHttpRequest 是一个 API,它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个通过 URL 来获取数据的简单方式,并且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。XMLHttpRequest 在 AJAX 中被大量使用。 - -- 在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做,例如火狐就不会。 -- 而 GET 方法 Header 和 Data 会一起发送。 - -# 参考资料 - -- 上野宣. 图解 HTTP[M]. 人民邮电出版社, 2014. -- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) -- [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn) -- [htmlspecialchars](http://php.net/manual/zh/function.htmlspecialchars.php) -- [Difference between file URI and URL in java](http://java2db.com/java-io/how-to-get-and-the-difference-between-file-uri-and-url-in-java) -- [How to Fix SQL Injection Using Java PreparedStatement & CallableStatement](https://software-security.sans.org/developer-how-to/fix-sql-injection-in-java-using-prepared-callable-statement) -- [浅谈 HTTP 中 Get 与 Post 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html) -- [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/) -- [HTTP (HyperText Transfer Protocol)](https://www.ntu.edu.sg/home/ehchua/programming/webprogramming/HTTP_Basics.html) -- [Web-VPN: Secure Proxies with SPDY & Chrome](https://www.igvita.com/2011/12/01/web-vpn-secure-proxies-with-spdy-chrome/) -- [File:HTTP persistent connection.svg](http://en.wikipedia.org/wiki/File:HTTP_persistent_connection.svg) -- [Proxy server](https://en.wikipedia.org/wiki/Proxy_server) -- [What Is This HTTPS/SSL Thing And Why Should You Care?](https://www.x-cart.com/blog/what-is-https-and-ssl.html) -- [What is SSL Offloading?](https://securebox.comodo.com/ssl-sniffing/ssl-offloading/) -- [Sun Directory Server Enterprise Edition 7.0 Reference - Key Encryption](https://docs.oracle.com/cd/E19424-01/820-4811/6ng8i26bn/index.html) -- [An Introduction to Mutual SSL Authentication](https://www.codeproject.com/Articles/326574/An-Introduction-to-Mutual-SSL-Authentication) -- [The Difference Between URLs and URIs](https://danielmiessler.com/study/url-uri/) -- [Cookie 与 Session 的区别](https://juejin.im/entry/5766c29d6be3ff006a31b84e#comment) -- [COOKIE 和 SESSION 有什么区别](https://www.zhihu.com/question/19786827) -- [Cookie/Session 的机制与安全](https://harttle.land/2015/08/10/cookie-session.html) -- [HTTPS 证书原理](https://shijianan.com/2017/06/11/https/) -- [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) -- [XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest) -- [XMLHttpRequest (XHR) Uses Multiple Packets for HTTP POST?](https://blog.josephscott.org/2009/08/27/xmlhttprequest-xhr-uses-multiple-packets-for-http-post/) -- [Symmetric vs. Asymmetric Encryption – What are differences?](https://www.ssl2buy.com/wiki/symmetric-vs-asymmetric-encryption-what-are-differences) -- [Web 性能优化与 HTTP/2](https://www.kancloud.cn/digest/web-performance-http2) -- [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn) - - - - - - -
diff --git a/docs/notes/Java IO.md b/docs/notes/Java IO.md deleted file mode 100644 index 41b4ed3ec556462402f744c354e9463c2e3f548b..0000000000000000000000000000000000000000 --- a/docs/notes/Java IO.md +++ /dev/null @@ -1,627 +0,0 @@ - -* [一、概览](#一概览) -* [二、磁盘操作](#二磁盘操作) -* [三、字节操作](#三字节操作) - * [实现文件复制](#实现文件复制) - * [装饰者模式](#装饰者模式) -* [四、字符操作](#四字符操作) - * [编码与解码](#编码与解码) - * [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 的 I/O 大概可以分成以下几类: - -- 磁盘操作:File -- 字节操作:InputStream 和 OutputStream -- 字符操作:Reader 和 Writer -- 对象操作:Serializable -- 网络操作:Socket -- 新的输入/输出:NIO - -# 二、磁盘操作 - -File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。 - -递归地列出一个目录下所有文件: - -```java -public static void listAllFiles(File dir) { - if (dir == null || !dir.exists()) { - return; - } - if (dir.isFile()) { - System.out.println(dir.getName()); - return; - } - for (File file : dir.listFiles()) { - listAllFiles(file); - } -} -``` - -从 Java7 开始,可以使用 Paths 和 Files 代替 File。 - -# 三、字节操作 - -## 实现文件复制 - -```java -public static void copyFile(String src, String dist) throws IOException { - FileInputStream in = new FileInputStream(src); - FileOutputStream out = new FileOutputStream(dist); - - byte[] buffer = new byte[20 * 1024]; - int cnt; - - // read() 最多读取 buffer.length 个字节 - // 返回的是实际读取的个数 - // 返回 -1 的时候表示读到 eof,即文件尾 - while ((cnt = in.read(buffer, 0, buffer.length)) != -1) { - out.write(buffer, 0, cnt); - } - - in.close(); - out.close(); -} -``` - -## 装饰者模式 - -Java I/O 使用了装饰者模式来实现。以 InputStream 为例, - -- InputStream 是抽象组件; -- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作; -- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。 - -

- -实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。 - -```java -FileInputStream fileInputStream = new FileInputStream(filePath); -BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); -``` - -DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。 - -# 四、字符操作 - -## 编码与解码 - -编码就是把字符转换为字节,而解码是把字节重新组合成字符。 - -如果编码和解码过程使用不同的编码方式那么就出现了乱码。 - -- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节; -- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节; -- UTF-16be 编码中,中文字符和英文字符都占 2 个字节。 - -UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。 - -Java 的内存编码使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。 - -## String 的编码方式 - -String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。 - -```java -String str1 = "中文"; -byte[] bytes = str1.getBytes("UTF-8"); -String str2 = new String(bytes, "UTF-8"); -System.out.println(str2); -``` - -在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。 - -```java -byte[] bytes = str1.getBytes(); -``` - -## Reader 与 Writer - -不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。 - -- InputStreamReader 实现从字节流解码成字符流; -- OutputStreamWriter 实现字符流编码成为字节流。 - -## 实现逐行输出文本文件的内容 - -```java -public static void readFileContent(String filePath) throws IOException { - - FileReader fileReader = new FileReader(filePath); - BufferedReader bufferedReader = new BufferedReader(fileReader); - - String line; - while ((line = bufferedReader.readLine()) != null) { - System.out.println(line); - } - - // 装饰者模式使得 BufferedReader 组合了一个 Reader 对象 - // 在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法 - // 因此只要一个 close() 调用即可 - bufferedReader.close(); -} -``` - -# 五、对象操作 - -## 序列化 - -序列化就是将一个对象转换成字节序列,方便存储和传输。 - -- 序列化:ObjectOutputStream.writeObject() -- 反序列化:ObjectInputStream.readObject() - -不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。 - -## Serializable - -序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。 - -```java -public static void main(String[] args) throws IOException, ClassNotFoundException { - - A a1 = new A(123, "abc"); - String objectFile = "file/a1"; - - ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile)); - objectOutputStream.writeObject(a1); - objectOutputStream.close(); - - ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(objectFile)); - A a2 = (A) objectInputStream.readObject(); - objectInputStream.close(); - System.out.println(a2); -} - -private static class A implements Serializable { - - private int x; - private String y; - - A(int x, String y) { - this.x = x; - this.y = y; - } - - @Override - public String toString() { - return "x = " + x + " " + "y = " + y; - } -} -``` - -## transient - -transient 关键字可以使一些属性不会被序列化。 - -ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。 - -```java -private transient Object[] elementData; -``` - -# 六、网络操作 - -Java 中的网络支持: - -- InetAddress:用于表示网络上的硬件资源,即 IP 地址; -- URL:统一资源定位符; -- Sockets:使用 TCP 协议实现网络通信; -- Datagram:使用 UDP 协议实现网络通信。 - -## InetAddress - -没有公有的构造函数,只能通过静态方法来创建实例。 - -```java -InetAddress.getByName(String host); -InetAddress.getByAddress(byte[] address); -``` - -## URL - -可以直接从 URL 中读取字节流数据。 - -```java -public static void main(String[] args) throws IOException { - - URL url = new URL("http://www.baidu.com"); - - /* 字节流 */ - InputStream is = url.openStream(); - - /* 字符流 */ - InputStreamReader isr = new InputStreamReader(is, "utf-8"); - - /* 提供缓存功能 */ - BufferedReader br = new BufferedReader(isr); - - String line; - while ((line = br.readLine()) != null) { - System.out.println(line); - } - - br.close(); -} -``` - -## Sockets - -- ServerSocket:服务器端类 -- Socket:客户端类 -- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。 - -

- -## Datagram - -- DatagramSocket:通信类 -- DatagramPacket:数据包类 - -# 七、NIO - -新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。 - -## 流与块 - -I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。 - -面向流的 I/O 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。 - -面向块的 I/O 一次处理一个数据块,按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。 - -I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。 - -## 通道与缓冲区 - -### 1. 通道 - -通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。 - -通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。 - -通道包括以下类型: - -- FileChannel:从文件中读写数据; -- DatagramChannel:通过 UDP 读写网络中数据; -- SocketChannel:通过 TCP 读写网络中数据; -- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。 - -### 2. 缓冲区 - -发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。 - -缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。 - -缓冲区包括以下类型: - -- ByteBuffer -- CharBuffer -- ShortBuffer -- IntBuffer -- LongBuffer -- FloatBuffer -- DoubleBuffer - -## 缓冲区状态变量 - -- capacity:最大容量; -- position:当前已经读写的字节数; -- limit:还可以读写的字节数。 - -状态变量的改变过程举例: - -① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。 - -

- -② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5,limit 保持不变。 - -

- -③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。 - -

- -④ 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。 - -

- -⑤ 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。 - -

- -## 文件 NIO 实例 - -以下展示了使用 NIO 快速复制文件的实例: - -```java -public static void fastCopy(String src, String dist) throws IOException { - - /* 获得源文件的输入字节流 */ - FileInputStream fin = new FileInputStream(src); - - /* 获取输入字节流的文件通道 */ - FileChannel fcin = fin.getChannel(); - - /* 获取目标文件的输出字节流 */ - FileOutputStream fout = new FileOutputStream(dist); - - /* 获取输出字节流的文件通道 */ - FileChannel fcout = fout.getChannel(); - - /* 为缓冲区分配 1024 个字节 */ - ByteBuffer buffer = ByteBuffer.allocateDirect(1024); - - while (true) { - - /* 从输入通道中读取数据到缓冲区中 */ - int r = fcin.read(buffer); - - /* read() 返回 -1 表示 EOF */ - if (r == -1) { - break; - } - - /* 切换读写 */ - buffer.flip(); - - /* 把缓冲区的内容写入输出文件中 */ - fcout.write(buffer); - - /* 清空缓冲区 */ - buffer.clear(); - } -} -``` - -## 选择器 - -NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。 - -NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。 - -通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。 - -因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。 - -应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。 - -

- -### 1. 创建选择器 - -```java -Selector selector = Selector.open(); -``` - -### 2. 将通道注册到选择器上 - -```java -ServerSocketChannel ssChannel = ServerSocketChannel.open(); -ssChannel.configureBlocking(false); -ssChannel.register(selector, SelectionKey.OP_ACCEPT); -``` - -通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。 - -在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类: - -- SelectionKey.OP_CONNECT -- SelectionKey.OP_ACCEPT -- SelectionKey.OP_READ -- SelectionKey.OP_WRITE - -它们在 SelectionKey 的定义如下: - -```java -public static final int OP_READ = 1 << 0; -public static final int OP_WRITE = 1 << 2; -public static final int OP_CONNECT = 1 << 3; -public static final int OP_ACCEPT = 1 << 4; -``` - -可以看出每个事件可以被当成一个位域,从而组成事件集整数。例如: - -```java -int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; -``` - -### 3. 监听事件 - -```java -int num = selector.select(); -``` - -使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。 - -### 4. 获取到达的事件 - -```java -Set keys = selector.selectedKeys(); -Iterator keyIterator = keys.iterator(); -while (keyIterator.hasNext()) { - SelectionKey key = keyIterator.next(); - if (key.isAcceptable()) { - // ... - } else if (key.isReadable()) { - // ... - } - keyIterator.remove(); -} -``` - -### 5. 事件循环 - -因为一次 select() 调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内。 - -```java -while (true) { - int num = selector.select(); - Set keys = selector.selectedKeys(); - Iterator keyIterator = keys.iterator(); - while (keyIterator.hasNext()) { - SelectionKey key = keyIterator.next(); - if (key.isAcceptable()) { - // ... - } else if (key.isReadable()) { - // ... - } - keyIterator.remove(); - } -} -``` - -## 套接字 NIO 实例 - -```java -public class NIOServer { - - public static void main(String[] args) throws IOException { - - Selector selector = Selector.open(); - - ServerSocketChannel ssChannel = ServerSocketChannel.open(); - ssChannel.configureBlocking(false); - ssChannel.register(selector, SelectionKey.OP_ACCEPT); - - ServerSocket serverSocket = ssChannel.socket(); - InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888); - serverSocket.bind(address); - - while (true) { - - selector.select(); - Set keys = selector.selectedKeys(); - Iterator keyIterator = keys.iterator(); - - while (keyIterator.hasNext()) { - - SelectionKey key = keyIterator.next(); - - if (key.isAcceptable()) { - - ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel(); - - // 服务器会为每个新连接创建一个 SocketChannel - SocketChannel sChannel = ssChannel1.accept(); - sChannel.configureBlocking(false); - - // 这个新连接主要用于从客户端读取数据 - sChannel.register(selector, SelectionKey.OP_READ); - - } else if (key.isReadable()) { - - SocketChannel sChannel = (SocketChannel) key.channel(); - System.out.println(readDataFromSocketChannel(sChannel)); - sChannel.close(); - } - - keyIterator.remove(); - } - } - } - - private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException { - - ByteBuffer buffer = ByteBuffer.allocate(1024); - StringBuilder data = new StringBuilder(); - - while (true) { - - buffer.clear(); - int n = sChannel.read(buffer); - if (n == -1) { - break; - } - buffer.flip(); - int limit = buffer.limit(); - char[] dst = new char[limit]; - for (int i = 0; i < limit; i++) { - dst[i] = (char) buffer.get(i); - } - data.append(dst); - buffer.clear(); - } - return data.toString(); - } -} -``` - -```java -public class NIOClient { - - public static void main(String[] args) throws IOException { - Socket socket = new Socket("127.0.0.1", 8888); - OutputStream out = socket.getOutputStream(); - String s = "hello world"; - out.write(s.getBytes()); - out.close(); - } -} -``` - -## 内存映射文件 - -内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。 - -向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。 - -下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。 - -```java -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) -- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html) -- [Java NIO 浅析](https://tech.meituan.com/nio.html) -- [IBM: 深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html) -- [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.html) -- [IBM: Java 序列化的高级认识](https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html) -- [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) - - - - - - -
diff --git "a/docs/notes/Java \345\237\272\347\241\200.md" "b/docs/notes/Java \345\237\272\347\241\200.md" deleted file mode 100644 index 3f39803d3a44c5f44a7b4b8f116a10da4c06d826..0000000000000000000000000000000000000000 --- "a/docs/notes/Java \345\237\272\347\241\200.md" +++ /dev/null @@ -1,1468 +0,0 @@ - -* [一、数据类型](#一数据类型) - * [基本类型](#基本类型) - * [包装类型](#包装类型) - * [缓存池](#缓存池) -* [二、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) -* [参考资料](#参考资料) - - - -# 一、数据类型 - -## 基本类型 - -- byte/8 -- char/16 -- short/16 -- int/32 -- float/32 -- long/64 -- double/64 -- boolean/\~ - -boolean 只有两个值:true、false,可以使用 1 bit 来存储,但是具体大小没有明确规定。JVM 会在编译时期将 boolean 类型的数据转换为 int,使用 1 来表示 true,0 表示 false。JVM 支持 boolean 数组,但是是通过读写 byte 数组来实现的。 - -- [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) - -## 包装类型 - -基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。 - -```java -Integer x = 2; // 装箱 调用了 Integer.valueOf(2) -int y = x; // 拆箱 调用了 X.intValue() -``` - -- [Autoboxing and Unboxing](https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html) - -## 缓存池 - -new Integer(123) 与 Integer.valueOf(123) 的区别在于: - -- new Integer(123) 每次都会新建一个对象; -- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。 - -```java -Integer x = new Integer(123); -Integer y = new Integer(123); -System.out.println(x == y); // false -Integer z = Integer.valueOf(123); -Integer k = Integer.valueOf(123); -System.out.println(z == k); // true -``` - -valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。 - -```java -public static Integer valueOf(int i) { - if (i >= IntegerCache.low && i <= IntegerCache.high) - return IntegerCache.cache[i + (-IntegerCache.low)]; - return new Integer(i); -} -``` - -在 Java 8 中,Integer 缓存池的大小默认为 -128\~127。 - -```java -static final int low = -128; -static final int high; -static final Integer cache[]; - -static { - // high value may be configured by property - int h = 127; - String integerCacheHighPropValue = - sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); - if (integerCacheHighPropValue != null) { - try { - int i = parseInt(integerCacheHighPropValue); - i = Math.max(i, 127); - // Maximum array size is Integer.MAX_VALUE - h = Math.min(i, Integer.MAX_VALUE - (-low) -1); - } catch( NumberFormatException nfe) { - // If the property cannot be parsed into an int, ignore it. - } - } - high = h; - - cache = new Integer[(high - low) + 1]; - int j = low; - for(int k = 0; k < cache.length; k++) - cache[k] = new Integer(j++); - - // range [-128, 127] must be interned (JLS7 5.1.7) - assert IntegerCache.high >= 127; -} -``` - -编译器会在自动装箱过程调用 valueOf() 方法,因此多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来创建,那么就会引用相同的对象。 - -```java -Integer m = 123; -Integer n = 123; -System.out.println(m == n); // true -``` - -基本类型对应的缓冲池如下: - -- boolean values true and false -- all byte values -- short values between -128 and 127 -- int values between -128 and 127 -- char in the range \u0000 to \u007F - -在使用这些基本类型对应的包装类型时,如果该数值范围在缓冲池范围内,就可以直接使用缓冲池中的对象。 - -在 jdk 1.8 所有的数值类缓冲池中,Integer 的缓冲池 IntegerCache 很特殊,这个缓冲池的下界是 - 128,上界默认是 127,但是这个上界是可调的,在启动 jvm 的时候,通过 -XX:AutoBoxCacheMax=<size> 来指定这个缓冲池的大小,该选项在 JVM 初始化的时候会设定一个名为 java.lang.IntegerCache.high 系统属性,然后 IntegerCache 初始化的时候就会读取该系统属性来决定上界。 - -[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 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承) - -在 Java 8 中,String 内部使用 char 数组存储数据。 - -```java -public final class String - implements java.io.Serializable, Comparable, CharSequence { - /** The value is used for character storage. */ - private final char value[]; -} -``` - -在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 `coder` 来标识使用了哪种编码。 - -```java -public final class String - implements java.io.Serializable, Comparable, CharSequence { - /** The value is used for character storage. */ - private final byte[] value; - - /** The identifier of the encoding used to encode the bytes in {@code value}. */ - private final byte coder; -} -``` - -value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。 - -## 不可变的好处 - -**1. 可以缓存 hash 值** - -因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。 - -**2. String Pool 的需要** - -如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。 - -

- -**3. 安全性** - -String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。 - -**4. 线程安全** - -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 - -**1. 可变性** - -- String 不可变 -- StringBuffer 和 StringBuilder 可变 - -**2. 线程安全** - -- String 不可变,因此是线程安全的 -- StringBuilder 不是线程安全的 -- StringBuffer 是线程安全的,内部使用 synchronized 进行同步 - -[StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder) - -## String Pool - -字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。 - -当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。 - -下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 和 s2.intern() 方法取得同一个字符串引用。intern() 首先把 "aaa" 放到 String Pool 中,然后返回这个字符串引用,因此 s3 和 s4 引用的是同一个字符串。 - -```java -String s1 = new String("aaa"); -String s2 = new String("aaa"); -System.out.println(s1 == s2); // false -String s3 = s1.intern(); -String s4 = s2.intern(); -System.out.println(s3 == s4); // true -``` - -如果是采用 "bbb" 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。 - -```java -String s5 = "bbb"; -String s6 = "bbb"; -System.out.println(s5 == s6); // true -``` - -在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 - -- [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") - -使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)。 - -- "abc" 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 "abc" 字符串字面量; -- 而使用 new 的方式会在堆中创建一个字符串对象。 - -创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。 - -```java -public class NewStringTest { - public static void main(String[] args) { - String s = new String("abc"); - } -} -``` - -使用 javap -verbose 进行反编译,得到以下内容: - -```java -// ... -Constant pool: -// ... - #2 = Class #18 // java/lang/String - #3 = String #19 // abc -// ... - #18 = Utf8 java/lang/String - #19 = Utf8 abc -// ... - - public static void main(java.lang.String[]); - descriptor: ([Ljava/lang/String;)V - flags: ACC_PUBLIC, ACC_STATIC - Code: - stack=3, locals=2, args_size=1 - 0: new #2 // class java/lang/String - 3: dup - 4: ldc #3 // String abc - 6: invokespecial #4 // Method java/lang/String."":(Ljava/lang/String;)V - 9: astore_1 -// ... -``` - -在 Constant Pool 中,#19 存储这字符串字面量 "abc",#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。 - -以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。 - -```java -public String(String original) { - this.value = original.value; - this.hash = original.hash; -} -``` - -# 三、运算 - -## 参数传递 - -Java 的参数是以值传递的形式传入方法中,而不是引用传递。 - -以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。 - -```java -public class Dog { - - String name; - - Dog(String name) { - this.name = name; - } - - String getName() { - return this.name; - } - - void setName(String name) { - this.name = name; - } - - String getObjectAddress() { - return super.toString(); - } -} -``` - -在方法中改变对象的字段值会改变原对象该字段值,因为引用的是同一个对象。 - -```java -class PassByValueExample { - public static void main(String[] args) { - Dog dog = new Dog("A"); - func(dog); - System.out.println(dog.getName()); // B - } - - private static void func(Dog dog) { - dog.setName("B"); - } -} -``` - -但是在方法中将指针引用了其它对象,那么此时方法里和方法外的两个指针指向了不同的对象,在一个指针改变其所指向对象的内容对另一个指针所指向的对象没有影响。 - -```java -public class PassByValueExample { - public static void main(String[] args) { - Dog dog = new Dog("A"); - System.out.println(dog.getObjectAddress()); // Dog@4554617c - func(dog); - System.out.println(dog.getObjectAddress()); // Dog@4554617c - System.out.println(dog.getName()); // A - } - - private static void func(Dog dog) { - System.out.println(dog.getObjectAddress()); // Dog@4554617c - dog = new Dog("B"); - System.out.println(dog.getObjectAddress()); // Dog@74a14482 - System.out.println(dog.getName()); // B - } -} -``` - -[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 - -Java 不能隐式执行向下转型,因为这会使得精度降低。 - -1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。 - -```java -// float f = 1.1; -``` - -1.1f 字面量才是 float 类型。 - -```java -float f = 1.1f; -``` - -## 隐式类型转换 - -因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型向下转型为 short 类型。 - -```java -short s1 = 1; -// s1 = s1 + 1; -``` - -但是使用 += 或者 ++ 运算符会执行隐式类型转换。 - -```java -s1 += 1; -s1++; -``` - -上面的语句相当于将 s1 + 1 的计算结果进行了向下转型: - -```java -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 - -从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。 - -```java -String s = "a"; -switch (s) { - case "a": - System.out.println("aaa"); - break; - case "b": - System.out.println("bbb"); - break; -} -``` - -switch 不支持 long,是因为 switch 的设计初衷是对那些只有少数几个值的类型进行等值判断,如果值过于复杂,那么还是用 if 比较合适。 - -```java -// long x = 111; -// switch (x) { // Incompatible types. Found: 'long', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum' -// case 111: -// System.out.println(111); -// break; -// case 222: -// System.out.println(222); -// break; -// } -``` - -[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 - -**1. 数据** - -声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。 - -- 对于基本类型,final 使数值不变; -- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。 - -```java -final int x = 1; -// x = 2; // cannot assign value to final variable 'x' -final A y = new A(); -y.a = 1; -``` - -**2. 方法** - -声明方法不能被子类重写。 - -private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。 - -**3. 类** - -声明类不允许被继承。 - -## static - -**1. 静态变量** - -- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。 -- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。 - -```java -public class A { - - private int x; // 实例变量 - private static int y; // 静态变量 - - public static void main(String[] args) { - // int x = A.x; // Non-static field 'x' cannot be referenced from a static context - A a = new A(); - int x = a.x; - int y = A.y; - } -} -``` - -**2. 静态方法** - -静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。 - -```java -public abstract class A { - public static void func1(){ - } - // public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static' -} -``` - -只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字,因此这两个关键字与具体对象关联。 - -```java -public class A { - - private static int x; - private int y; - - public static void func1(){ - int a = x; - // int b = y; // Non-static field 'y' cannot be referenced from a static context - // int b = this.y; // 'A.this' cannot be referenced from a static context - } -} -``` - -**3. 静态语句块** - -静态语句块在类初始化时运行一次。 - -```java -public class A { - static { - System.out.println("123"); - } - - public static void main(String[] args) { - A a1 = new A(); - A a2 = new A(); - } -} -``` - -```html -123 -``` - -**4. 静态内部类** - -非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。 - -```java -public class OuterClass { - - class InnerClass { - } - - static class StaticInnerClass { - } - - public static void main(String[] args) { - // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context - OuterClass outerClass = new OuterClass(); - InnerClass innerClass = outerClass.new InnerClass(); - StaticInnerClass staticInnerClass = new StaticInnerClass(); - } -} -``` - -静态内部类不能访问外部类的非静态的变量和方法。 - -**5. 静态导包** - -在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。 - -```java -import static com.xxx.ClassName.* -``` - -**6. 初始化顺序** - -静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。 - -```java -public static String staticField = "静态变量"; -``` - -```java -static { - System.out.println("静态语句块"); -} -``` - -```java -public String field = "实例变量"; -``` - -```java -{ - System.out.println("普通语句块"); -} -``` - -最后才是构造函数的初始化。 - -```java -public InitialOrderTest() { - System.out.println("构造函数"); -} -``` - -存在继承的情况下,初始化顺序为: - -- 父类(静态变量、静态语句块) -- 子类(静态变量、静态语句块) -- 父类(实例变量、普通语句块) -- 父类(构造函数) -- 子类(实例变量、普通语句块) -- 子类(构造函数) - -# 五、Object 通用方法 - -## 概览 - -```java - -public native int hashCode() - -public boolean equals(Object obj) - -protected native Object clone() throws CloneNotSupportedException - -public String toString() - -public final native Class getClass() - -protected void finalize() throws Throwable {} - -public final native void notify() - -public final native void notifyAll() - -public final native void wait(long timeout) throws InterruptedException - -public final void wait(long timeout, int nanos) throws InterruptedException - -public final void wait() throws InterruptedException -``` - -## equals() - -**1. 等价关系** - -两个对象具有等价关系,需要满足以下五个条件: - -Ⅰ 自反性 - -```java -x.equals(x); // true -``` - -Ⅱ 对称性 - -```java -x.equals(y) == y.equals(x); // true -``` - -Ⅲ 传递性 - -```java -if (x.equals(y) && y.equals(z)) - x.equals(z); // true; -``` - -Ⅳ 一致性 - -多次调用 equals() 方法结果不变 - -```java -x.equals(y) == x.equals(y); // true -``` - -Ⅴ 与 null 的比较 - -对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false - -```java -x.equals(null); // false; -``` - -**2. 等价与相等** - -- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。 -- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。 - -```java -Integer x = new Integer(1); -Integer y = new Integer(1); -System.out.println(x.equals(y)); // true -System.out.println(x == y); // false -``` - -**3. 实现** - -- 检查是否为同一个对象的引用,如果是直接返回 true; -- 检查是否是同一个类型,如果不是,直接返回 false; -- 将 Object 对象进行转型; -- 判断每个关键域是否相等。 - -```java -public class EqualExample { - - private int x; - private int y; - private int z; - - public EqualExample(int x, int y, int z) { - this.x = x; - this.y = y; - this.z = z; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - EqualExample that = (EqualExample) o; - - if (x != that.x) return false; - if (y != that.y) return false; - return z == that.z; - } -} -``` - -## hashCode() - -hashCode() 返回哈希值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价,这是因为计算哈希值具有随机性,两个值不同的对象可能计算出相同的哈希值。 - -在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象哈希值也相等。 - -HashSet 和 HashMap 等集合类使用了 hashCode() 方法来计算对象应该存储的位置,因此要将对象添加到这些集合类中,需要让对应的类实现 hashCode() 方法。 - -下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象。但是 EqualExample 没有实现 hashCode() 方法,因此这两个对象的哈希值是不同的,最终导致集合添加了两个等价的对象。 - -```java -EqualExample e1 = new EqualExample(1, 1, 1); -EqualExample e2 = new EqualExample(1, 1, 1); -System.out.println(e1.equals(e2)); // true -HashSet set = new HashSet<>(); -set.add(e1); -set.add(e2); -System.out.println(set.size()); // 2 -``` - -理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的哈希值上。这就要求了哈希函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。 - -R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位,最左边的位丢失。并且一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。 - -```java -@Override -public int hashCode() { - int result = 17; - result = 31 * result + x; - result = 31 * result + y; - result = 31 * result + z; - return result; -} -``` - -## toString() - -默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。 - -```java -public class ToStringExample { - - private int number; - - public ToStringExample(int number) { - this.number = number; - } -} -``` - -```java -ToStringExample example = new ToStringExample(123); -System.out.println(example.toString()); -``` - -```html -ToStringExample@4554617c -``` - -## clone() - -**1. cloneable** - -clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。 - -```java -public class CloneExample { - private int a; - private int b; -} -``` - -```java -CloneExample e1 = new CloneExample(); -// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object' -``` - -重写 clone() 得到以下实现: - -```java -public class CloneExample { - private int a; - private int b; - - @Override - public CloneExample clone() throws CloneNotSupportedException { - return (CloneExample)super.clone(); - } -} -``` - -```java -CloneExample e1 = new CloneExample(); -try { - CloneExample e2 = e1.clone(); -} catch (CloneNotSupportedException e) { - e.printStackTrace(); -} -``` - -```html -java.lang.CloneNotSupportedException: CloneExample -``` - -以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。 - -应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。 - -```java -public class CloneExample implements Cloneable { - private int a; - private int b; - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } -} -``` - -**2. 浅拷贝** - -拷贝对象和原始对象的引用类型引用同一个对象。 - -```java -public class ShallowCloneExample implements Cloneable { - - private int[] arr; - - public ShallowCloneExample() { - arr = new int[10]; - for (int i = 0; i < arr.length; i++) { - arr[i] = i; - } - } - - public void set(int index, int value) { - arr[index] = value; - } - - public int get(int index) { - return arr[index]; - } - - @Override - protected ShallowCloneExample clone() throws CloneNotSupportedException { - return (ShallowCloneExample) super.clone(); - } -} -``` - -```java -ShallowCloneExample e1 = new ShallowCloneExample(); -ShallowCloneExample e2 = null; -try { - e2 = e1.clone(); -} catch (CloneNotSupportedException e) { - e.printStackTrace(); -} -e1.set(2, 222); -System.out.println(e2.get(2)); // 222 -``` - -**3. 深拷贝** - -拷贝对象和原始对象的引用类型引用不同对象。 - -```java -public class DeepCloneExample implements Cloneable { - - private int[] arr; - - public DeepCloneExample() { - arr = new int[10]; - for (int i = 0; i < arr.length; i++) { - arr[i] = i; - } - } - - public void set(int index, int value) { - arr[index] = value; - } - - public int get(int index) { - return arr[index]; - } - - @Override - protected DeepCloneExample clone() throws CloneNotSupportedException { - DeepCloneExample result = (DeepCloneExample) super.clone(); - result.arr = new int[arr.length]; - for (int i = 0; i < arr.length; i++) { - result.arr[i] = arr[i]; - } - return result; - } -} -``` - -```java -DeepCloneExample e1 = new DeepCloneExample(); -DeepCloneExample e2 = null; -try { - e2 = e1.clone(); -} catch (CloneNotSupportedException e) { - e.printStackTrace(); -} -e1.set(2, 222); -System.out.println(e2.get(2)); // 2 -``` - -**4. clone() 的替代方案** - -使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。 - -```java -public class CloneConstructorExample { - - private int[] arr; - - public CloneConstructorExample() { - arr = new int[10]; - for (int i = 0; i < arr.length; i++) { - arr[i] = i; - } - } - - public CloneConstructorExample(CloneConstructorExample original) { - arr = new int[original.arr.length]; - for (int i = 0; i < original.arr.length; i++) { - arr[i] = original.arr[i]; - } - } - - public void set(int index, int value) { - arr[index] = value; - } - - public int get(int index) { - return arr[index]; - } -} -``` - -```java -CloneConstructorExample e1 = new CloneConstructorExample(); -CloneConstructorExample e2 = new CloneConstructorExample(e1); -e1.set(2, 222); -System.out.println(e2.get(2)); // 2 -``` - -# 六、继承 - -## 访问权限 - -Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。 - -可以对类或类中的成员(字段和方法)加上访问修饰符。 - -- 类可见表示其它类可以用这个类创建实例对象。 -- 成员可见表示其它类可以用这个类的实例对象访问到该成员; - -protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。 - -设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。 - -如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例去代替,也就是确保满足里氏替换原则。 - -字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。 - -```java -public class AccessExample { - public String id; -} -``` - -可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。 - -```java -public class AccessExample { - - private int id; - - public String getId() { - return id + ""; - } - - public void setId(String id) { - this.id = Integer.valueOf(id); - } -} -``` - -但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。 - -```java -public class AccessWithInnerClassExample { - - private class InnerClass { - int x; - } - - private InnerClass innerClass; - - public AccessWithInnerClassExample() { - innerClass = new InnerClass(); - } - - public int getValue() { - return innerClass.x; // 直接访问 - } -} -``` - -## 抽象类与接口 - -**1. 抽象类** - -抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。 - -抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。 - -```java -public abstract class AbstractClassExample { - - protected int x; - private int y; - - public abstract void func1(); - - public void func2() { - System.out.println("func2"); - } -} -``` - -```java -public class AbstractExtendClassExample extends AbstractClassExample { - @Override - public void func1() { - System.out.println("func1"); - } -} -``` - -```java -// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated -AbstractClassExample ac2 = new AbstractExtendClassExample(); -ac2.func1(); -``` - -**2. 接口** - -接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。 - -从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类,让它们都实现新增的方法。 - -接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。从 Java 9 开始,允许将方法定义为 private,这样就能定义某些复用的代码又不会把方法暴露出去。 - -接口的字段默认都是 static 和 final 的。 - -```java -public interface InterfaceExample { - - void func1(); - - default void func2(){ - System.out.println("func2"); - } - - int x = 123; - // int y; // Variable 'y' might not have been initialized - public int z = 0; // Modifier 'public' is redundant for interface fields - // private int k = 0; // Modifier 'private' not allowed here - // protected int l = 0; // Modifier 'protected' not allowed here - // private void fun3(); // Modifier 'private' not allowed here -} -``` - -```java -public class InterfaceImplementExample implements InterfaceExample { - @Override - public void func1() { - System.out.println("func1"); - } -} -``` - -```java -// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated -InterfaceExample ie2 = new InterfaceImplementExample(); -ie2.func1(); -System.out.println(InterfaceExample.x); -``` - -**3. 比较** - -- 从设计层面上看,抽象类提供了一种 IS-A 关系,需要满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。 -- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。 -- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。 -- 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。 - -**4. 使用选择** - -使用接口: - -- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Comparable 接口中的 compareTo() 方法; -- 需要使用多重继承。 - -使用抽象类: - -- 需要在几个相关的类中共享代码。 -- 需要能控制继承来的成员的访问权限,而不是都为 public。 -- 需要继承非静态和非常量字段。 - -在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。 - -- [Abstract Methods and Classes](https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html) -- [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/) -- [When to Use Abstract Class and Interface](https://dzone.com/articles/when-to-use-abstract-class-and-intreface) -- [Java 9 Private Methods in Interfaces](https://www.journaldev.com/12850/java-9-private-methods-interfaces) - - -## super - -- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果子类需要调用父类其它构造函数,那么就可以使用 super() 函数。 -- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。 - -```java -public class SuperExample { - - protected int x; - protected int y; - - public SuperExample(int x, int y) { - this.x = x; - this.y = y; - } - - public void func() { - System.out.println("SuperExample.func()"); - } -} -``` - -```java -public class SuperExtendExample extends SuperExample { - - private int z; - - public SuperExtendExample(int x, int y, int z) { - super(x, y); - this.z = z; - } - - @Override - public void func() { - super.func(); - System.out.println("SuperExtendExample.func()"); - } -} -``` - -```java -SuperExample e = new SuperExtendExample(1, 2, 3); -e.func(); -``` - -```html -SuperExample.func() -SuperExtendExample.func() -``` - -[Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html) - -## 重写与重载 - -**1. 重写(Override)** - -存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。 - -为了满足里式替换原则,重写有以下三个限制: - -- 子类方法的访问权限必须大于等于父类方法; -- 子类方法的返回类型必须是父类方法返回类型或为其子类型。 -- 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。 - -使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。 - -下面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 方法。其中: - -- 子类方法访问权限为 public,大于父类的 protected。 -- 子类的返回类型为 ArrayList,是父类返回类型 List 的子类。 -- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。 -- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。 - -```java -class SuperClass { - protected List func() throws Throwable { - return new ArrayList<>(); - } -} - -class SubClass extends SuperClass { - @Override - public ArrayList func() throws Exception { - return new ArrayList<>(); - } -} -``` - -在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有再到父类中查看,看是否从父类继承来。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为: - -- this.func(this) -- super.func(this) -- this.func(super) -- super.func(super) - - -```java -/* - A - | - B - | - C - | - D - */ - - -class A { - - public void show(A obj) { - System.out.println("A.show(A)"); - } - - public void show(C obj) { - System.out.println("A.show(C)"); - } -} - -class B extends A { - - @Override - public void show(A obj) { - System.out.println("B.show(A)"); - } -} - -class C extends B { -} - -class D extends C { -} -``` - -```java -public static void main(String[] args) { - - A a = new A(); - B b = new B(); - C c = new C(); - D d = new D(); - - // 在 A 中存在 show(A obj),直接调用 - a.show(a); // A.show(A) - // 在 A 中不存在 show(B obj),将 B 转型成其父类 A - a.show(b); // A.show(A) - // 在 B 中存在从 A 继承来的 show(C obj),直接调用 - b.show(c); // A.show(C) - // 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其父类 C - b.show(d); // A.show(C) - - // 引用的还是 B 对象,所以 ba 和 b 的调用结果一样 - A ba = new B(); - ba.show(c); // A.show(C) - ba.show(d); // A.show(C) -} -``` - -**2. 重载(Overload)** - -存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。 - -应该注意的是,返回值不同,其它都相同不算是重载。 - -```java -class OverloadingExample { - public void show(int x) { - System.out.println(x); - } - - public void show(int x, String y) { - System.out.println(x + " " + y); - } -} -``` - -```java -public static void main(String[] args) { - OverloadingExample example = new OverloadingExample(); - example.show(1); - example.show(1, "2"); -} -``` - -# 七、反射 - -每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。 - -类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 `Class.forName("com.mysql.jdbc.Driver")` 这种方式来控制类的加载,该方法会返回一个 Class 对象。 - -反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。 - -Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类: - -- **Field** :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段; -- **Method** :可以使用 invoke() 方法调用与 Method 对象关联的方法; -- **Constructor** :可以用 Constructor 的 newInstance() 创建新的对象。 - -**反射的优点:** - -* **可扩展性** :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。 -* **类浏览器和可视化开发环境** :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。 -* **调试器和测试工具** : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。 - -**反射的缺点:** - -尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。 - -* **性能开销** :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。 - -* **安全限制** :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。 - -* **内部暴露** :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。 - - -- [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 分为两种: - -- **受检异常** :需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复; -- **非受检异常** :是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序崩溃并且无法恢复。 - -

- -- [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 stands for "Type" - private T t; - public void set(T t) { this.t = t; } - public T get() { return 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 各版本的新特性 - -**New highlights in Java SE 8** - -1. Lambda Expressions -2. Pipelines and Streams -3. Date and Time API -4. Default Methods -5. Type Annotations -6. Nashhorn JavaScript Engine -7. Concurrent Accumulators -8. Parallel operations -9. PermGen Error Removed - -**New highlights in Java SE 7** - -1. Strings in Switch Statement -2. Type Inference for Generic Instance Creation -3. Multiple Exception Handling -4. Support for Dynamic Languages -5. Try with Resources -6. Java nio Package -7. Binary Literals, Underscore in literals -8. Diamond Syntax - -- [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 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。 -- Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台。 -- Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。 -- Java 支持自动垃圾回收,而 C++ 需要手动回收。 -- Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。 -- Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。 -- Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。 - -[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:Java Runtime Environment,Java 运行环境的简称,为 Java 的运行提供了所需的环境。它是一个 JVM 程序,主要包括了 JVM 的标准实现和一些 Java 基本类库。 -- JDK:Java Development Kit,Java 开发工具包,提供了 Java 的开发及运行环境。JDK 是 Java 开发的核心,集成了 JRE 以及一些其它的工具,比如编译 Java 源码的编译器 javac 等。 - -# 参考资料 - -- Eckel B. Java 编程思想[M]. 机械工业出版社, 2002. -- Bloch J. Effective java[M]. Addison-Wesley Professional, 2017. - - - - - - -
diff --git "a/docs/notes/Java \345\256\271\345\231\250.md" "b/docs/notes/Java \345\256\271\345\231\250.md" deleted file mode 100644 index ef212fbab55e5c10e81f23d665f5a28959fcc2b0..0000000000000000000000000000000000000000 --- "a/docs/notes/Java \345\256\271\345\231\250.md" +++ /dev/null @@ -1,1138 +0,0 @@ - -* [一、概览](#一概览) - * [Collection](#collection) - * [Map](#map) -* [二、容器中的设计模式](#二容器中的设计模式) - * [迭代器模式](#迭代器模式) - * [适配器模式](#适配器模式) -* [三、源码分析](#三源码分析) - * [ArrayList](#arraylist) - * [Vector](#vector) - * [CopyOnWriteArrayList](#copyonwritearraylist) - * [LinkedList](#linkedlist) - * [HashMap](#hashmap) - * [ConcurrentHashMap](#concurrenthashmap) - * [LinkedHashMap](#linkedhashmap) - * [WeakHashMap](#weakhashmap) -* [参考资料](#参考资料) - - - -# 一、概览 - -容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。 - -## Collection - -

- -### 1. Set - -- TreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。 - -- HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。 - -- LinkedHashSet:具有 HashSet 的查找效率,并且内部使用双向链表维护元素的插入顺序。 - -### 2. List - -- ArrayList:基于动态数组实现,支持随机访问。 - -- Vector:和 ArrayList 类似,但它是线程安全的。 - -- LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。 - -### 3. Queue - -- LinkedList:可以用它来实现双向队列。 - -- PriorityQueue:基于堆结构实现,可以用它来实现优先队列。 - -## Map - -

- -- TreeMap:基于红黑树实现。 - -- HashMap:基于哈希表实现。 - -- HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程同时写入 HashTable 不会导致数据不一致。它是遗留类,不应该去使用它,而是使用 ConcurrentHashMap 来支持线程安全,ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。 - -- LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。 - - -# 二、容器中的设计模式 - -## 迭代器模式 - -

- -Collection 继承了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。 - -从 JDK 1.5 之后可以使用 foreach 方法来遍历实现了 Iterable 接口的聚合对象。 - -```java -List list = new ArrayList<>(); -list.add("a"); -list.add("b"); -for (String item : list) { - System.out.println(item); -} -``` - -## 适配器模式 - -java.util.Arrays#asList() 可以把数组类型转换为 List 类型。 - -```java -@SafeVarargs -public static List asList(T... a) -``` - -应该注意的是 asList() 的参数为泛型的变长参数,不能使用基本类型数组作为参数,只能使用相应的包装类型数组。 - -```java -Integer[] arr = {1, 2, 3}; -List list = Arrays.asList(arr); -``` - -也可以使用以下方式调用 asList(): - -```java -List list = Arrays.asList(1, 2, 3); -``` - -# 三、源码分析 - -如果没有特别说明,以下源码分析基于 JDK 1.8。 - -在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码。 - -## ArrayList - - -### 1. 概览 - -因为 ArrayList 是基于数组实现的,所以支持快速随机访问。RandomAccess 接口标识着该类支持快速随机访问。 - -```java -public class ArrayList extends AbstractList - implements List, RandomAccess, Cloneable, java.io.Serializable -``` - -数组的默认大小为 10。 - -```java -private static final int DEFAULT_CAPACITY = 10; -``` - -

- -### 2. 扩容 - -添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity >> 1)`,也就是旧容量的 1.5 倍。 - -扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。 - -```java -public boolean add(E e) { - ensureCapacityInternal(size + 1); // Increments modCount!! - elementData[size++] = e; - return true; -} - -private void ensureCapacityInternal(int minCapacity) { - if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { - minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); - } - ensureExplicitCapacity(minCapacity); -} - -private void ensureExplicitCapacity(int minCapacity) { - modCount++; - // overflow-conscious code - if (minCapacity - elementData.length > 0) - grow(minCapacity); -} - -private void grow(int minCapacity) { - // overflow-conscious code - int oldCapacity = elementData.length; - int newCapacity = oldCapacity + (oldCapacity >> 1); - if (newCapacity - minCapacity < 0) - newCapacity = minCapacity; - if (newCapacity - MAX_ARRAY_SIZE > 0) - newCapacity = hugeCapacity(minCapacity); - // minCapacity is usually close to size, so this is a win: - elementData = Arrays.copyOf(elementData, newCapacity); -} -``` - -### 3. 删除元素 - -需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看到 ArrayList 删除元素的代价是非常高的。 - -```java -public E remove(int index) { - rangeCheck(index); - modCount++; - E oldValue = elementData(index); - int numMoved = size - index - 1; - if (numMoved > 0) - System.arraycopy(elementData, index+1, elementData, index, numMoved); - elementData[--size] = null; // clear to let GC do its work - return oldValue; -} -``` - -### 4. 序列化 - -ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。 - -保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。 - -```java -transient Object[] elementData; // non-private to simplify nested class access -``` - -ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。 - -```java -private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - elementData = EMPTY_ELEMENTDATA; - - // Read in size, and any hidden stuff - s.defaultReadObject(); - - // Read in capacity - s.readInt(); // ignored - - if (size > 0) { - // be like clone(), allocate array based upon size not capacity - ensureCapacityInternal(size); - - Object[] a = elementData; - // Read in all elements in the proper order. - for (int i=0; i