From 9a4f6ea86cffff577870849dcdd5d724295937e9 Mon Sep 17 00:00:00 2001
From: CyC2018 <1029579233@qq.com>
Date: Sun, 12 Aug 2018 22:53:28 +0800
Subject: [PATCH] auto commit
---
notes/HTTP.md | 31 +-
"notes/Java \345\256\271\345\231\250.md" | 61 +-
"notes/Leetcode \351\242\230\350\247\243.md" | 5169 +++++++++--------
...code-Database \351\242\230\350\247\243.md" | 20 +-
...214\207 offer \351\242\230\350\247\243.md" | 281 +-
...73\345\207\273\346\212\200\346\234\257.md" | 8 +-
...73\347\273\237\345\216\237\347\220\206.md" | 30 +-
"notes/\347\256\227\346\263\225.md" | 26 +-
...76\350\256\241\345\237\272\347\241\200.md" | 2 +-
...76\350\256\241\346\250\241\345\274\217.md" | 43 +-
pics/VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png | Bin 0 -> 21504 bytes
11 files changed, 2834 insertions(+), 2837 deletions(-)
create mode 100644 pics/VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png
diff --git a/notes/HTTP.md b/notes/HTTP.md
index 243dc283..537b24fe 100644
--- a/notes/HTTP.md
+++ b/notes/HTTP.md
@@ -45,7 +45,7 @@
* [二进制分帧层](#二进制分帧层)
* [服务端推送](#服务端推送)
* [首部压缩](#首部压缩)
-* [八、GET 和 POST 的区别](#八get-和-post-的区别)
+* [八、GET 和 POST 比较](#八get-和-post-比较)
* [作用](#作用)
* [参数](#参数)
* [安全](#安全)
@@ -61,13 +61,13 @@
## URL
+URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
+
- URI(Uniform Resource Identifier,统一资源标识符)
- URL(Uniform Resource Locator,统一资源定位符)
-- URN(Uniform Resource Name,统一资源名称),例如 urn:isbn:0-486-27557-4。
-
-URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
+- URN(Uniform Resource Name,统一资源名称)
-
+
## 请求和响应报文
@@ -197,7 +197,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
-- **206 Partial Content** :表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。
+- **206 Partial Content** :表示客户端进行了范围请求,响应报文包含由 Content-Range 指定范围的实体内容。
## 3XX 重定向
@@ -219,7 +219,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
-- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由。
+- **403 Forbidden** :请求被拒绝。
- **404 Not Found**
@@ -331,7 +331,7 @@ Set-Cookie: tasty_cookie=strawberry
[page content]
```
-客户端之后对同一个服务器发送请求时,会从浏览器中读出 Cookie 信息通过 Cookie 请求首部字段发送给服务器。
+客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。
```html
GET /sample_page.html HTTP/1.1
@@ -382,9 +382,9 @@ Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
-Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在内存型数据库中,比如 Redis,效率会更高。
+Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。
-使用 Session 维护用户登录的过程如下:
+使用 Session 维护用户登录状态的过程如下:
- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
- 服务器验证该用户名和密码;
@@ -499,7 +499,7 @@ If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
### 1. 短连接与长连接
-当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。
+当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
@@ -688,7 +688,7 @@ HTTPs 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)
### 3. HTTPs 采用的加密方式
-HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证安全性,之后使用对称密钥加密进行通信来保证效率。(下图中的 Session Key 就是对称密钥)
+HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率。(下图中的 Session Key 就是对称密钥)
@@ -717,7 +717,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
## HTTPs 的缺点
- 因为需要进行加密解密等过程,因此速度会更慢;
-- 需要支付证书授权的高费用。
+- 需要支付证书授权的高额费用。
## 配置 HTTPs
@@ -727,7 +727,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
## HTTP/1.x 缺陷
- HTTP/1.x 实现简单是以牺牲应用性能为代价的:
+ HTTP/1.x 实现简单是以牺牲性能为代价的:
- 客户端需要使用多个连接才能实现并发和缩短延迟;
- 不会压缩请求和响应首部,从而导致不必要的网络流量;
@@ -763,7 +763,7 @@ HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见
-# 八、GET 和 POST 的区别
+# 八、GET 和 POST 比较
## 作用
@@ -870,6 +870,7 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)
- [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn)
- [htmlspecialchars](http://php.net/manual/zh/function.htmlspecialchars.php)
+- [Difference between file URI and URL in java](http://java2db.com/java-io/how-to-get-and-the-difference-between-file-uri-and-url-in-java)
- [How to Fix SQL Injection Using Java PreparedStatement & CallableStatement](https://software-security.sans.org/developer-how-to/fix-sql-injection-in-java-using-prepared-callable-statement)
- [浅谈 HTTP 中 Get 与 Post 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html)
- [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/)
diff --git "a/notes/Java \345\256\271\345\231\250.md" "b/notes/Java \345\256\271\345\231\250.md"
index bf1921fc..af118e4c 100644
--- "a/notes/Java \345\256\271\345\231\250.md"
+++ "b/notes/Java \345\256\271\345\231\250.md"
@@ -25,7 +25,7 @@
## Collection
-
+
### 1. Set
@@ -129,12 +129,67 @@ private static final int DEFAULT_CAPACITY = 10;
ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
-保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
+保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。
```java
transient Object[] elementData; // non-private to simplify nested class access
```
+ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
+
+```java
+private void readObject(java.io.ObjectInputStream s)
+ throws java.io.IOException, ClassNotFoundException {
+ elementData = EMPTY_ELEMENTDATA;
+
+ // Read in size, and any hidden stuff
+ s.defaultReadObject();
+
+ // Read in capacity
+ s.readInt(); // ignored
+
+ if (size > 0) {
+ // be like clone(), allocate array based upon size not capacity
+ ensureCapacityInternal(size);
+
+ Object[] a = elementData;
+ // Read in all elements in the proper order.
+ for (int i=0; i> 1)`,也就是旧容量的 1.5 倍。
@@ -1080,7 +1135,7 @@ Set <|.. LinkedHashSet
SortSet <|.. TreeSet
List <|.. ArrayList
List <|.. Vector
-List <|.. LinkeList
+List <|.. LinkedList
Queue <|.. LinkedList
Queue <|.. PriorityQueue
diff --git "a/notes/Leetcode \351\242\230\350\247\243.md" "b/notes/Leetcode \351\242\230\350\247\243.md"
index 2813ce5d..44f3d9dc 100644
--- "a/notes/Leetcode \351\242\230\350\247\243.md"
+++ "b/notes/Leetcode \351\242\230\350\247\243.md"
@@ -1,18 +1,18 @@
* [算法思想](#算法思想)
- * [贪心思想](#贪心思想)
* [双指针](#双指针)
* [排序](#排序)
* [快速选择](#快速选择)
* [堆排序](#堆排序)
* [桶排序](#桶排序)
* [荷兰国旗问题](#荷兰国旗问题)
+ * [贪心思想](#贪心思想)
* [二分查找](#二分查找)
+ * [分治](#分治)
* [搜索](#搜索)
* [BFS](#bfs)
* [DFS](#dfs)
* [Backtracking](#backtracking)
- * [分治](#分治)
* [动态规划](#动态规划)
* [斐波那契数列](#斐波那契数列)
* [矩阵路径](#矩阵路径)
@@ -33,10 +33,6 @@
* [多数投票问题](#多数投票问题)
* [其它](#其它)
* [数据结构相关](#数据结构相关)
- * [栈和队列](#栈和队列)
- * [哈希表](#哈希表)
- * [字符串](#字符串)
- * [数组与矩阵](#数组与矩阵)
* [链表](#链表)
* [树](#树)
* [递归](#递归)
@@ -44,6 +40,10 @@
* [前中后序遍历](#前中后序遍历)
* [BST](#bst)
* [Trie](#trie)
+ * [栈和队列](#栈和队列)
+ * [哈希表](#哈希表)
+ * [字符串](#字符串)
+ * [数组与矩阵](#数组与矩阵)
* [图](#图)
* [二分图](#二分图)
* [拓扑排序](#拓扑排序)
@@ -55,666 +55,369 @@
# 算法思想
-## 贪心思想
+## 双指针
-贪心思想保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
+双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
-**分配饼干**
+**有序数组的 Two Sum**
-[455. Assign Cookies (Easy)](https://leetcode.com/problems/assign-cookies/description/)
+[Leetcode :167. Two Sum II - Input array is sorted (Easy)](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/)
```html
-Input: [1,2], [1,2,3]
-Output: 2
-
-Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2.
-You have 3 cookies and their sizes are big enough to gratify all of the children,
-You need to output 2.
+Input: numbers={2, 7, 11, 15}, target=9
+Output: index1=1, index2=2
```
-题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。
+题目描述:在有序数组中找出两个数,使它们的和为 target。
-因为最小的孩子最容易得到满足,因此先满足最小孩子。给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。因此贪心策略
+使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
-证明:假设在某次选择中,贪心策略选择给当前满足度最小的孩子分配第 m 个饼干,第 m 个饼干为可以满足该孩子的最小饼干。假设存在一种最优策略,给该孩子分配第 n 个饼干,并且 m < n。我们可以发现,经过这一轮分配,贪心策略分配后剩下的饼干一定有一个比最优策略来得大。因此在后续的分配中,贪心策略一定能满足更多的孩子。也就是说不存在比贪心策略更优的策略,即贪心策略就是最优策略。
+- 如果两个指针指向元素的和 sum == target,那么得到要求的结果;
+- 如果 sum > target,移动较大的元素,使 sum 变小一些;
+- 如果 sum < target,移动较小的元素,使 sum 变大一些。
```java
-public int findContentChildren(int[] g, int[] s) {
- Arrays.sort(g);
- Arrays.sort(s);
- int gi = 0, si = 0;
- while (gi < g.length && si < s.length) {
- if (g[gi] <= s[si]) {
- gi++;
+public int[] twoSum(int[] numbers, int target) {
+ int i = 0, j = numbers.length - 1;
+ while (i < j) {
+ int sum = numbers[i] + numbers[j];
+ if (sum == target) {
+ return new int[]{i + 1, j + 1};
+ } else if (sum < target) {
+ i++;
+ } else {
+ j--;
}
- si++;
}
- return gi;
+ return null;
}
```
-**不重叠的区间个数**
+**两数平方和**
-[435. Non-overlapping Intervals (Medium)](https://leetcode.com/problems/non-overlapping-intervals/description/)
+[633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/)
```html
-Input: [ [1,2], [1,2], [1,2] ]
-
-Output: 2
-
-Explanation: You need to remove two [1,2] to make the rest of intervals non-overlapping.
+Input: 5
+Output: True
+Explanation: 1 * 1 + 2 * 2 = 5
```
-```html
-Input: [ [1,2], [2,3] ]
-
-Output: 0
+题目描述:判断一个数是否为两个数的平方和,例如 5 = 12 + 22。
-Explanation: You don't need to remove any of the intervals since they're already non-overlapping.
+```java
+public boolean judgeSquareSum(int c) {
+ int i = 0, j = (int) Math.sqrt(c);
+ while (i <= j) {
+ int powSum = i * i + j * j;
+ if (powSum == c) {
+ return true;
+ } else if (powSum > c) {
+ j--;
+ } else {
+ i++;
+ }
+ }
+ return false;
+}
```
-题目描述:计算让一组区间不重叠所需要移除的区间个数。
+**反转字符串中的元音字符**
-计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。
+[345. Reverse Vowels of a String (Easy)](https://leetcode.com/problems/reverse-vowels-of-a-string/description/)
-在每次选择中,区间的结尾最为重要,选择的区间结尾越小,留给后面的区间的空间越大,那么后面能够选择的区间个数也就越大。
+```html
+Given s = "leetcode", return "leotcede".
+```
-按区间的结尾进行排序,每次选择结尾最小,并且和前一个区间不重叠的区间。
+使用双指针,指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。
```java
-public int eraseOverlapIntervals(Interval[] intervals) {
- if (intervals.length == 0) {
- return 0;
- }
- Arrays.sort(intervals, Comparator.comparingInt(o -> o.end));
- int cnt = 1;
- int end = intervals[0].end;
- for (int i = 1; i < intervals.length; i++) {
- if (intervals[i].start < end) {
- continue;
+private final static HashSet vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
+
+public String reverseVowels(String s) {
+ int i = 0, j = s.length() - 1;
+ char[] result = new char[s.length()];
+ while (i <= j) {
+ char ci = s.charAt(i);
+ char cj = s.charAt(j);
+ if (!vowels.contains(ci)) {
+ result[i++] = ci;
+ } else if (!vowels.contains(cj)) {
+ result[j--] = cj;
+ } else {
+ result[i++] = cj;
+ result[j--] = ci;
}
- end = intervals[i].end;
- cnt++;
}
- return intervals.length - cnt;
+ return new String(result);
}
```
-使用 lambda 表示式创建 Comparator 会导致算法运行时间过长,如果注重运行时间,可以修改为普通创建 Comparator 语句:
-
-```java
-Arrays.sort(intervals, new Comparator() {
- @Override
- public int compare(Interval o1, Interval o2) {
- return o1.end - o2.end;
- }
-});
-```
-
-**投飞镖刺破气球**
-
-[452. Minimum Number of Arrows to Burst Balloons (Medium)](https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/description/)
+**回文字符串**
-```
-Input:
-[[10,16], [2,8], [1,6], [7,12]]
+[680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/)
-Output:
-2
+```html
+Input: "abca"
+Output: True
+Explanation: You could delete the character 'c'.
```
-题目描述:气球在一个水平数轴上摆放,可以重叠,飞镖垂直投向坐标轴,使得路径上的气球都会刺破。求解最小的投飞镖次数使所有气球都被刺破。
-
-也是计算不重叠的区间个数,不过和 Non-overlapping Intervals 的区别在于,[1, 2] 和 [2, 3] 在本题中算是重叠区间。
+题目描述:可以删除一个字符,判断是否能构成回文字符串。
```java
-public int findMinArrowShots(int[][] points) {
- if (points.length == 0) {
- return 0;
+public boolean validPalindrome(String s) {
+ int i = -1, j = s.length();
+ while (++i < --j) {
+ if (s.charAt(i) != s.charAt(j)) {
+ return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j);
+ }
}
- Arrays.sort(points, Comparator.comparingInt(o -> o[1]));
- int cnt = 1, end = points[0][1];
- for (int i = 1; i < points.length; i++) {
- if (points[i][0] <= end) {
- continue;
+ return true;
+}
+
+private boolean isPalindrome(String s, int i, int j) {
+ while (i < j) {
+ if (s.charAt(i++) != s.charAt(j--)) {
+ return false;
}
- cnt++;
- end = points[i][1];
}
- return cnt;
+ return true;
}
```
-**根据身高和序号重组队列**
+**归并两个有序数组**
-[406. Queue Reconstruction by Height(Medium)](https://leetcode.com/problems/queue-reconstruction-by-height/description/)
+[88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/)
```html
Input:
-[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
+nums1 = [1,2,3,0,0,0], m = 3
+nums2 = [2,5,6], n = 3
-Output:
-[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
+Output: [1,2,2,3,5,6]
```
-题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。
-
-为了在每次插入操作时不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入第 k 个位置可能会变成第 k+1 个位置。
+题目描述:把归并结果存到第一个数组上。
-身高降序、k 值升序,然后按排好序的顺序插入队列的第 k 个位置中。
+需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值。
```java
-public int[][] reconstructQueue(int[][] people) {
- if (people == null || people.length == 0 || people[0].length == 0) {
- return new int[0][0];
- }
- Arrays.sort(people, (a, b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]));
- List queue = new ArrayList<>();
- for (int[] p : people) {
- queue.add(p[1], p);
+public void merge(int[] nums1, int m, int[] nums2, int n) {
+ int index1 = m - 1, index2 = n - 1;
+ int indexMerge = m + n - 1;
+ while (index1 >= 0 || index2 >= 0) {
+ if (index1 < 0) {
+ nums1[indexMerge--] = nums2[index2--];
+ } else if (index2 < 0) {
+ nums1[indexMerge--] = nums1[index1--];
+ } else if (nums1[index1] > nums2[index2]) {
+ nums1[indexMerge--] = nums1[index1--];
+ } else {
+ nums1[indexMerge--] = nums2[index2--];
+ }
}
- return queue.toArray(new int[queue.size()][]);
}
```
-**分隔字符串使同种字符出现在一起**
+**判断链表是否存在环**
-[763. Partition Labels (Medium)](https://leetcode.com/problems/partition-labels/description/)
+[141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/)
-```html
-Input: S = "ababcbacadefegdehijhklij"
-Output: [9,7,8]
-Explanation:
-The partition is "ababcbaca", "defegde", "hijhklij".
-This is a partition so that each letter appears in at most one part.
-A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits S into less parts.
-```
+使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。
```java
-public List partitionLabels(String S) {
- int[] lastIndexsOfChar = new int[26];
- for (int i = 0; i < S.length(); i++) {
- lastIndexsOfChar[char2Index(S.charAt(i))] = i;
+public boolean hasCycle(ListNode head) {
+ if (head == null) {
+ return false;
}
- List partitions = new ArrayList<>();
- int firstIndex = 0;
- while (firstIndex < S.length()) {
- int lastIndex = firstIndex;
- for (int i = firstIndex; i < S.length() && i <= lastIndex; i++) {
- int index = lastIndexsOfChar[char2Index(S.charAt(i))];
- if (index > lastIndex) {
- lastIndex = index;
- }
+ ListNode l1 = head, l2 = head.next;
+ while (l1 != null && l2 != null && l2.next != null) {
+ if (l1 == l2) {
+ return true;
}
- partitions.add(lastIndex - firstIndex + 1);
- firstIndex = lastIndex + 1;
+ l1 = l1.next;
+ l2 = l2.next.next;
}
- return partitions;
+ return false;
}
+```
-private int char2Index(char c) {
- return c - 'a';
-}
-```
-
+**最长子序列**
-**种植花朵**
+[524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/)
-[605. Can Place Flowers (Easy)](https://leetcode.com/problems/can-place-flowers/description/)
+```
+Input:
+s = "abpcplea", d = ["ale","apple","monkey","plea"]
-```html
-Input: flowerbed = [1,0,0,0,1], n = 1
-Output: True
+Output:
+"apple"
```
-题目描述:花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。
+题目描述:删除 s 中的一些字符,使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个相同长度的结果,返回字典序的最大字符串。
```java
-public boolean canPlaceFlowers(int[] flowerbed, int n) {
- int len = flowerbed.length;
- int cnt = 0;
- for (int i = 0; i < len && cnt < n; i++) {
- if (flowerbed[i] == 1) {
+public String findLongestWord(String s, List d) {
+ String longestWord = "";
+ for (String target : d) {
+ int l1 = longestWord.length(), l2 = target.length();
+ if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)) {
continue;
}
- int pre = i == 0 ? 0 : flowerbed[i - 1];
- int next = i == len - 1 ? 0 : flowerbed[i + 1];
- if (pre == 0 && next == 0) {
- cnt++;
- flowerbed[i] = 1;
+ if (isValid(s, target)) {
+ longestWord = target;
}
}
- return cnt >= n;
+ return longestWord;
}
-```
-
-**判断是否为子序列**
-
-[392. Is Subsequence (Medium)](https://leetcode.com/problems/is-subsequence/description/)
-```html
-s = "abc", t = "ahbgdc"
-Return true.
-```
-
-```java
-public boolean isSubsequence(String s, String t) {
- int index = -1;
- for (char c : s.toCharArray()) {
- index = t.indexOf(c, index + 1);
- if (index == -1) {
- return false;
+private boolean isValid(String s, String target) {
+ int i = 0, j = 0;
+ while (i < s.length() && j < target.length()) {
+ if (s.charAt(i) == target.charAt(j)) {
+ j++;
}
+ i++;
}
- return true;
+ return j == target.length();
}
```
-**修改一个数成为非递减数组**
+## 排序
-[665. Non-decreasing Array (Easy)](https://leetcode.com/problems/non-decreasing-array/description/)
+### 快速选择
-```html
-Input: [4,2,3]
-Output: True
-Explanation: You could modify the first 4 to 1 to get a non-decreasing array.
-```
+用于求解 **Kth Element** 问题,使用快速排序的 partition() 进行实现。
-题目描述:判断一个数组能不能只修改一个数就成为非递减数组。
+需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。
-在出现 nums[i] < nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且 **不影响后续的操作** 。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,就有可能比 nums[i + 1] 大,从而影响了后续操作。还有一个比较特别的情况就是 nums[i] < nums[i - 2],只修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组,只能修改 nums[i] = nums[i - 1]。
+### 堆排序
-```java
-public boolean checkPossibility(int[] nums) {
- int cnt = 0;
- for (int i = 1; i < nums.length && cnt < 2; i++) {
- if (nums[i] >= nums[i - 1]) {
- continue;
- }
- cnt++;
- if (i - 2 >= 0 && nums[i - 2] > nums[i]) {
- nums[i] = nums[i - 1];
- } else {
- nums[i - 1] = nums[i];
- }
- }
- return cnt <= 1;
-}
-```
+用于求解 **TopK Elements** 问题,通过维护一个大小为 K 的堆,堆中的元素就是 TopK Elements。
-**股票的最大收益**
+堆排序也可以用于求解 Kth Element 问题,堆顶元素就是 Kth Element。
-[122. Best Time to Buy and Sell Stock II (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/)
+快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。
-题目描述:一次股票交易包含买入和卖出,多个交易之间不能交叉进行。
+可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。
-对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中,从而在局部最优的情况下也保证全局最优。
+**Kth Element**
+
+[215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/)
+
+**排序** :时间复杂度 O(NlogN),空间复杂度 O(1)
```java
-public int maxProfit(int[] prices) {
- int profit = 0;
- for (int i = 1; i < prices.length; i++) {
- if (prices[i] > prices[i - 1]) {
- profit += (prices[i] - prices[i - 1]);
- }
- }
- return profit;
+public int findKthLargest(int[] nums, int k) {
+ Arrays.sort(nums);
+ return nums[nums.length - k];
}
```
-## 双指针
-
-双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
-
-**有序数组的 Two Sum**
-
-[Leetcode :167. Two Sum II - Input array is sorted (Easy)](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/)
+**堆排序** :时间复杂度 O(NlogK),空间复杂度 O(K)。
-```html
-Input: numbers={2, 7, 11, 15}, target=9
-Output: index1=1, index2=2
+```java
+public int findKthLargest(int[] nums, int k) {
+ PriorityQueue pq = new PriorityQueue<>(); // 小顶堆
+ for (int val : nums) {
+ pq.add(val);
+ if (pq.size() > k) // 维护堆的大小为 K
+ pq.poll();
+ }
+ return pq.peek();
+}
```
-题目描述:在有序数组中找出两个数,使它们的和为 target。
-
-使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
-
-如果两个指针指向元素的和 sum == target,那么得到要求的结果;如果 sum > target,移动较大的元素,使 sum 变小一些;如果 sum < target,移动较小的元素,使 sum 变大一些。
+**快速选择** :时间复杂度 O(N),空间复杂度 O(1)
```java
-public int[] twoSum(int[] numbers, int target) {
- int i = 0, j = numbers.length - 1;
- while (i < j) {
- int sum = numbers[i] + numbers[j];
- if (sum == target) {
- return new int[]{i + 1, j + 1};
- } else if (sum < target) {
- i++;
+public int findKthLargest(int[] nums, int k) {
+ k = nums.length - k;
+ int l = 0, h = nums.length - 1;
+ while (l < h) {
+ int j = partition(nums, l, h);
+ if (j == k) {
+ break;
+ } else if (j < k) {
+ l = j + 1;
} else {
- j--;
+ h = j - 1;
}
}
- return null;
+ return nums[k];
}
-```
-
-**两数平方和**
-
-[633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/)
-
-```html
-Input: 5
-Output: True
-Explanation: 1 * 1 + 2 * 2 = 5
-```
-
-题目描述:判断一个数是否为两个数的平方和,例如 5 = 12 + 22。
-```java
-public boolean judgeSquareSum(int c) {
- int i = 0, j = (int) Math.sqrt(c);
- while (i <= j) {
- int powSum = i * i + j * j;
- if (powSum == c) {
- return true;
- } else if (powSum > c) {
- j--;
- } else {
- i++;
+private int partition(int[] a, int l, int h) {
+ int i = l, j = h + 1;
+ while (true) {
+ while (a[++i] < a[l] && i < h) ;
+ while (a[--j] > a[l] && j > l) ;
+ if (i >= j) {
+ break;
}
+ swap(a, i, j);
}
- return false;
+ swap(a, l, j);
+ return j;
+}
+
+private void swap(int[] a, int i, int j) {
+ int t = a[i];
+ a[i] = a[j];
+ a[j] = t;
}
```
-**反转字符串中的元音字符**
+### 桶排序
-[345. Reverse Vowels of a String (Easy)](https://leetcode.com/problems/reverse-vowels-of-a-string/description/)
+**出现频率最多的 k 个数**
+
+[347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/)
```html
-Given s = "leetcode", return "leotcede".
+Given [1,1,1,2,2,3] and k = 2, return [1,2].
```
-使用双指针,指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。
+设置若干个桶,每个桶存储出现频率相同的数,并且桶的下标代表桶中数出现的频率,即第 i 个桶中存储的数出现的频率为 i。
-```java
-private final static HashSet vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
+把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。
-public String reverseVowels(String s) {
- int i = 0, j = s.length() - 1;
- char[] result = new char[s.length()];
- while (i <= j) {
- char ci = s.charAt(i);
- char cj = s.charAt(j);
- if (!vowels.contains(ci)) {
- result[i++] = ci;
- } else if (!vowels.contains(cj)) {
- result[j--] = cj;
- } else {
- result[i++] = cj;
- result[j--] = ci;
+```java
+public List topKFrequent(int[] nums, int k) {
+ Map frequencyForNum = new HashMap<>();
+ for (int num : nums) {
+ frequencyForNum.put(num, frequencyForNum.getOrDefault(num, 0) + 1);
+ }
+ List[] buckets = new ArrayList[nums.length + 1];
+ for (int key : frequencyForNum.keySet()) {
+ int frequency = frequencyForNum.get(key);
+ if (buckets[frequency] == null) {
+ buckets[frequency] = new ArrayList<>();
}
+ buckets[frequency].add(key);
}
- return new String(result);
+ List topK = new ArrayList<>();
+ for (int i = buckets.length - 1; i >= 0 && topK.size() < k; i--) {
+ if (buckets[i] != null) {
+ topK.addAll(buckets[i]);
+ }
+ }
+ return topK;
}
```
-**回文字符串**
+**按照字符出现次数对字符串排序**
-[680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/)
+[451. Sort Characters By Frequency (Medium)](https://leetcode.com/problems/sort-characters-by-frequency/description/)
```html
-Input: "abca"
-Output: True
-Explanation: You could delete the character 'c'.
-```
+Input:
+"tree"
-题目描述:可以删除一个字符,判断是否能构成回文字符串。
-
-```java
-public boolean validPalindrome(String s) {
- int i = -1, j = s.length();
- while (++i < --j) {
- if (s.charAt(i) != s.charAt(j)) {
- return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j);
- }
- }
- return true;
-}
-
-private boolean isPalindrome(String s, int i, int j) {
- while (i < j) {
- if (s.charAt(i++) != s.charAt(j--)) {
- return false;
- }
- }
- return true;
-}
-```
-
-**归并两个有序数组**
-
-[88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/)
-
-```html
-Input:
-nums1 = [1,2,3,0,0,0], m = 3
-nums2 = [2,5,6], n = 3
-
-Output: [1,2,2,3,5,6]
-```
-
-题目描述:把归并结果存到第一个数组上。
-
-需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值
-
-```java
-public void merge(int[] nums1, int m, int[] nums2, int n) {
- int index1 = m - 1, index2 = n - 1;
- int indexMerge = m + n - 1;
- while (index1 >= 0 || index2 >= 0) {
- if (index1 < 0) {
- nums1[indexMerge--] = nums2[index2--];
- } else if (index2 < 0) {
- nums1[indexMerge--] = nums1[index1--];
- } else if (nums1[index1] > nums2[index2]) {
- nums1[indexMerge--] = nums1[index1--];
- } else {
- nums1[indexMerge--] = nums2[index2--];
- }
- }
-}
-```
-
-**判断链表是否存在环**
-
-[141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/)
-
-使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。
-
-```java
-public boolean hasCycle(ListNode head) {
- if (head == null) {
- return false;
- }
- ListNode l1 = head, l2 = head.next;
- while (l1 != null && l2 != null && l2.next != null) {
- if (l1 == l2) {
- return true;
- }
- l1 = l1.next;
- l2 = l2.next.next;
- }
- return false;
-}
-```
-
-**最长子序列**
-
-[524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/)
-
-```
-Input:
-s = "abpcplea", d = ["ale","apple","monkey","plea"]
-
-Output:
-"apple"
-```
-
-题目描述:删除 s 中的一些字符,使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个相同长度的结果,返回按字典序排序的最大字符串。
-
-```java
-public String findLongestWord(String s, List d) {
- String longestWord = "";
- for (String target : d) {
- int l1 = longestWord.length(), l2 = target.length();
- if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)) {
- continue;
- }
- if (isValid(s, target)) {
- longestWord = target;
- }
- }
- return longestWord;
-}
-
-private boolean isValid(String s, String target) {
- int i = 0, j = 0;
- while (i < s.length() && j < target.length()) {
- if (s.charAt(i) == target.charAt(j)) {
- j++;
- }
- i++;
- }
- return j == target.length();
-}
-```
-
-## 排序
-
-### 快速选择
-
-一般用于求解 **Kth Element** 问题,可以在 O(N) 时间复杂度,O(1) 空间复杂度完成求解工作。
-
-与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。
-
-### 堆排序
-
-堆排序用于求解 **TopK Elements** 问题,通过维护一个大小为 K 的堆,堆中的元素就是 TopK Elements。当然它也可以用于求解 Kth Element 问题,堆顶元素就是 Kth Element。快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。
-
-**Kth Element**
-
-[215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/)
-
-**排序** :时间复杂度 O(NlogN),空间复杂度 O(1)
-
-```java
-public int findKthLargest(int[] nums, int k) {
- Arrays.sort(nums);
- return nums[nums.length - k];
-}
-```
-
-**堆排序** :时间复杂度 O(NlogK),空间复杂度 O(K)。
-
-```java
-public int findKthLargest(int[] nums, int k) {
- PriorityQueue pq = new PriorityQueue<>(); // 小顶堆
- for (int val : nums) {
- pq.add(val);
- if (pq.size() > k) // 维护堆的大小为 K
- pq.poll();
- }
- return pq.peek();
-}
-```
-
-**快速选择** :时间复杂度 O(N),空间复杂度 O(1)
-
-```java
-public int findKthLargest(int[] nums, int k) {
- k = nums.length - k;
- int l = 0, h = nums.length - 1;
- while (l < h) {
- int j = partition(nums, l, h);
- if (j == k) {
- break;
- } else if (j < k) {
- l = j + 1;
- } else {
- h = j - 1;
- }
- }
- return nums[k];
-}
-
-private int partition(int[] a, int l, int h) {
- int i = l, j = h + 1;
- while (true) {
- while (a[++i] < a[l] && i < h) ;
- while (a[--j] > a[l] && j > l) ;
- if (i >= j) {
- break;
- }
- swap(a, i, j);
- }
- swap(a, l, j);
- return j;
-}
-
-private void swap(int[] a, int i, int j) {
- int t = a[i];
- a[i] = a[j];
- a[j] = t;
-}
-```
-
-### 桶排序
-
-**出现频率最多的 k 个数**
-
-[347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/)
-
-```html
-Given [1,1,1,2,2,3] and k = 2, return [1,2].
-```
-
-设置若干个桶,每个桶存储出现频率相同的数,并且桶的下标代表桶中数出现的频率,即第 i 个桶中存储的数出现的频率为 i。把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。
-
-```java
-public List topKFrequent(int[] nums, int k) {
- Map frequencyForNum = new HashMap<>();
- for (int num : nums) {
- frequencyForNum.put(num, frequencyForNum.getOrDefault(num, 0) + 1);
- }
- List[] buckets = new ArrayList[nums.length + 1];
- for (int key : frequencyForNum.keySet()) {
- int frequency = frequencyForNum.get(key);
- if (buckets[frequency] == null) {
- buckets[frequency] = new ArrayList<>();
- }
- buckets[frequency].add(key);
- }
- List topK = new ArrayList<>();
- for (int i = buckets.length - 1; i >= 0 && topK.size() < k; i--) {
- if (buckets[i] != null) {
- topK.addAll(buckets[i]);
- }
- }
- return topK;
-}
-```
-
-**按照字符出现次数对字符串排序**
-
-[451. Sort Characters By Frequency (Medium)](https://leetcode.com/problems/sort-characters-by-frequency/description/)
-
-```html
-Input:
-"tree"
-
-Output:
-"eert"
+Output:
+"eert"
Explanation:
'e' appears twice while 'r' and 't' both appear once.
@@ -752,7 +455,9 @@ public String frequencySort(String s) {
### 荷兰国旗问题
-荷兰国旗包含三种颜色:红、白、蓝。有这三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。
+荷兰国旗包含三种颜色:红、白、蓝。
+
+有三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。
它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。
@@ -790,409 +495,763 @@ private void swap(int[] nums, int i, int j) {
}
```
-## 二分查找
+## 贪心思想
-**正常实现**
+保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
-```java
-public int binarySearch(int[] nums, int key) {
- int l = 0, h = nums.length - 1;
- while (l <= h) {
- int m = l + (h - l) / 2;
- if (nums[m] == key) {
- return m;
- } else if (nums[m] > key) {
- h = m - 1;
- } else {
- l = m + 1;
- }
- }
- return -1;
-}
-```
-
-**时间复杂度**
-
-二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度都为 O(logN)。
-
-**m 计算**
-
-有两种计算中值 m 的方式:
-
-- m = (l + h) / 2
-- m = l + (h - l) / 2
+**分配饼干**
-l + h 可能出现加法溢出,最好使用第二种方式。
+[455. Assign Cookies (Easy)](https://leetcode.com/problems/assign-cookies/description/)
-**返回值**
+```html
+Input: [1,2], [1,2,3]
+Output: 2
-循环退出时如果仍然没有查找到 key,那么表示查找失败。可以有两种返回值:
+Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2.
+You have 3 cookies and their sizes are big enough to gratify all of the children,
+You need to output 2.
+```
-- -1:以一个错误码表示没有查找到 key
-- l:将 key 插入到 nums 中的正确位置
+题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。
-**变种**
+给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。因为最小的孩子最容易得到满足,所以先满足最小的孩子。
-二分查找可以有很多变种,变种实现要注意边界值的判断。例如在一个有重复元素的数组中查找 key 的最左位置的实现如下:
+证明:假设在某次选择中,贪心策略选择给当前满足度最小的孩子分配第 m 个饼干,第 m 个饼干为可以满足该孩子的最小饼干。假设存在一种最优策略,给该孩子分配第 n 个饼干,并且 m < n。我们可以发现,经过这一轮分配,贪心策略分配后剩下的饼干一定有一个比最优策略来得大。因此在后续的分配中,贪心策略一定能满足更多的孩子。也就是说不存在比贪心策略更优的策略,即贪心策略就是最优策略。
```java
-public int binarySearch(int[] nums, int key) {
- int l = 0, h = nums.length - 1;
- while (l < h) {
- int m = l + (h - l) / 2;
- if (nums[m] >= key) {
- h = m;
- } else {
- l = m + 1;
+public int findContentChildren(int[] g, int[] s) {
+ Arrays.sort(g);
+ Arrays.sort(s);
+ int gi = 0, si = 0;
+ while (gi < g.length && si < s.length) {
+ if (g[gi] <= s[si]) {
+ gi++;
}
+ si++;
}
- return l;
+ return gi;
}
```
-该实现和正常实现有以下不同:
+**不重叠的区间个数**
-- 循环条件为 l < h
-- h 的赋值表达式为 h = m
-- 最后返回 l 而不是 -1
+[435. Non-overlapping Intervals (Medium)](https://leetcode.com/problems/non-overlapping-intervals/description/)
-在 nums[m] >= key 的情况下,可以推导出最左 key 位于 [l, m] 区间中,这是一个闭区间。h 的赋值表达式为 h = m,因为 m 位置也可能是解。
+```html
+Input: [ [1,2], [1,2], [1,2] ]
-在 h 的赋值表达式为 h = mid 的情况下,如果循环条件为 l <= h,那么会出现循环无法退出的情况,因此循环条件只能是 l < h。以下演示了循环条件为 l <= h 时循环无法退出的情况:
+Output: 2
-```text
-nums = {0, 1, 2}, key = 1
-l m h
-0 1 2 nums[m] >= key
-0 0 1 nums[m] < key
-1 1 1 nums[m] >= key
-1 1 1 nums[m] >= key
-...
+Explanation: You need to remove two [1,2] to make the rest of intervals non-overlapping.
```
-当循环体退出时,不表示没有查找到 key,因此最后返回的结果不应该为 -1。为了验证有没有查找到,需要在调用端判断一下返回位置上的值和 key 是否相等。
+```html
+Input: [ [1,2], [2,3] ]
-**求开方**
+Output: 0
-[69. Sqrt(x) (Easy)](https://leetcode.com/problems/sqrtx/description/)
+Explanation: You don't need to remove any of the intervals since they're already non-overlapping.
+```
-```html
-Input: 4
-Output: 2
+题目描述:计算让一组区间不重叠所需要移除的区间个数。
-Input: 8
-Output: 2
-Explanation: The square root of 8 is 2.82842..., and since we want to return an integer, the decimal part will be truncated.
-```
+计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。
-一个数 x 的开方 sqrt 一定在 0 \~ x 之间,并且满足 sqrt == x / sqrt。可以利用二分查找在 0 \~ x 之间查找 sqrt。
+在每次选择中,区间的结尾最为重要,选择的区间结尾越小,留给后面的区间的空间越大,那么后面能够选择的区间个数也就越大。
-对于 x = 8,它的开方是 2.82842...,最后应该返回 2 而不是 3。在循环条件为 l <= h 并且循环退出时,h 总是比 l 小 1,也就是说 h = 2,l = 3,因此最后的返回值应该为 h 而不是 l。
+按区间的结尾进行排序,每次选择结尾最小,并且和前一个区间不重叠的区间。
```java
-public int mySqrt(int x) {
- if (x <= 1) {
- return x;
+public int eraseOverlapIntervals(Interval[] intervals) {
+ if (intervals.length == 0) {
+ return 0;
}
- int l = 1, h = x;
- while (l <= h) {
- int mid = l + (h - l) / 2;
- int sqrt = x / mid;
- if (sqrt == mid) {
- return mid;
- } else if (mid > sqrt) {
- h = mid - 1;
- } else {
- l = mid + 1;
+ Arrays.sort(intervals, Comparator.comparingInt(o -> o.end));
+ int cnt = 1;
+ int end = intervals[0].end;
+ for (int i = 1; i < intervals.length; i++) {
+ if (intervals[i].start < end) {
+ continue;
}
+ end = intervals[i].end;
+ cnt++;
}
- return h;
+ return intervals.length - cnt;
}
```
-**大于给定元素的最小元素**
-
-[744. Find Smallest Letter Greater Than Target (Easy)](https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/)
-
-```html
-Input:
-letters = ["c", "f", "j"]
-target = "d"
-Output: "f"
-
-Input:
-letters = ["c", "f", "j"]
-target = "k"
-Output: "c"
-```
-
-题目描述:给定一个有序的字符数组 letters 和一个字符 target,要求找出 letters 中大于 target 的最小字符,如果找不到就返回第 1 个字符。
+使用 lambda 表示式创建 Comparator 会导致算法运行时间过长,如果注重运行时间,可以修改为普通创建 Comparator 语句:
```java
-public char nextGreatestLetter(char[] letters, char target) {
- int n = letters.length;
- int l = 0, h = n - 1;
- while (l <= h) {
- int m = l + (h - l) / 2;
- if (letters[m] <= target) {
- l = m + 1;
- } else {
- h = m - 1;
- }
+Arrays.sort(intervals, new Comparator() {
+ @Override
+ public int compare(Interval o1, Interval o2) {
+ return o1.end - o2.end;
}
- return l < n ? letters[l] : letters[0];
-}
+});
```
-**有序数组的 Single Element**
+**投飞镖刺破气球**
-[540. Single Element in a Sorted Array (Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/description/)
+[452. Minimum Number of Arrows to Burst Balloons (Medium)](https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/description/)
-```html
-Input: [1,1,2,3,3,4,4,8,8]
-Output: 2
```
+Input:
+[[10,16], [2,8], [1,6], [7,12]]
-题目描述:一个有序数组只有一个数不出现两次,找出这个数。要求以 O(logN) 时间复杂度进行求解。
-
-令 index 为 Single Element 在数组中的位置。如果 m 为偶数,并且 m + 1 < index,那么 nums[m] == nums[m + 1];m + 1 >= index,那么 nums[m] != nums[m + 1]。
+Output:
+2
+```
-从上面的规律可以知道,如果 nums[m] == nums[m + 1],那么 index 所在的数组位置为 [m + 2, h],此时令 l = m + 2;如果 nums[m] != nums[m + 1],那么 index 所在的数组位置为 [l, m],此时令 h = m。
+题目描述:气球在一个水平数轴上摆放,可以重叠,飞镖垂直投向坐标轴,使得路径上的气球都会刺破。求解最小的投飞镖次数使所有气球都被刺破。
-因为 h 的赋值表达式为 h = m,那么循环条件也就只能使用 l < h 这种形式。
+也是计算不重叠的区间个数,不过和 Non-overlapping Intervals 的区别在于,[1, 2] 和 [2, 3] 在本题中算是重叠区间。
```java
-public int singleNonDuplicate(int[] nums) {
- int l = 0, h = nums.length - 1;
- while (l < h) {
- int m = l + (h - l) / 2;
- if (m % 2 == 1) {
- m--; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数
- }
- if (nums[m] == nums[m + 1]) {
- l = m + 2;
- } else {
- h = m;
+public int findMinArrowShots(int[][] points) {
+ if (points.length == 0) {
+ return 0;
+ }
+ Arrays.sort(points, Comparator.comparingInt(o -> o[1]));
+ int cnt = 1, end = points[0][1];
+ for (int i = 1; i < points.length; i++) {
+ if (points[i][0] <= end) {
+ continue;
}
+ cnt++;
+ end = points[i][1];
}
- return nums[l];
+ return cnt;
}
```
-**第一个错误的版本**
+**根据身高和序号重组队列**
-[278. First Bad Version (Easy)](https://leetcode.com/problems/first-bad-version/description/)
+[406. Queue Reconstruction by Height(Medium)](https://leetcode.com/problems/queue-reconstruction-by-height/description/)
-题目描述:给定一个元素 n 代表有 [1, 2, ..., n] 版本,可以调用 isBadVersion(int x) 知道某个版本是否错误,要求找到第一个错误的版本。
+```html
+Input:
+[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
-如果第 m 个版本出错,则表示第一个错误的版本在 [l, m] 之间,令 h = m;否则第一个错误的版本在 [m + 1, h] 之间,令 l = m + 1。
+Output:
+[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
+```
-因为 h 的赋值表达式为 h = m,因此循环条件为 l < h。
+题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。
+
+为了在每次插入操作时不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入第 k 个位置可能会变成第 k+1 个位置。
+
+身高降序、k 值升序,然后按排好序的顺序插入队列的第 k 个位置中。
```java
-public int firstBadVersion(int n) {
- int l = 1, h = n;
- while (l < h) {
- int mid = l + (h - l) / 2;
- if (isBadVersion(mid)) {
- h = mid;
- } else {
- l = mid + 1;
- }
+public int[][] reconstructQueue(int[][] people) {
+ if (people == null || people.length == 0 || people[0].length == 0) {
+ return new int[0][0];
}
- return l;
+ Arrays.sort(people, (a, b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]));
+ List queue = new ArrayList<>();
+ for (int[] p : people) {
+ queue.add(p[1], p);
+ }
+ return queue.toArray(new int[queue.size()][]);
}
```
-**旋转数组的最小数字**
+**分隔字符串使同种字符出现在一起**
-[153. Find Minimum in Rotated Sorted Array (Medium)](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/)
+[763. Partition Labels (Medium)](https://leetcode.com/problems/partition-labels/description/)
```html
-Input: [3,4,5,1,2],
-Output: 1
+Input: S = "ababcbacadefegdehijhklij"
+Output: [9,7,8]
+Explanation:
+The partition is "ababcbaca", "defegde", "hijhklij".
+This is a partition so that each letter appears in at most one part.
+A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits S into less parts.
```
```java
-public int findMin(int[] nums) {
- 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;
- }
+public List partitionLabels(String S) {
+ int[] lastIndexsOfChar = new int[26];
+ for (int i = 0; i < S.length(); i++) {
+ lastIndexsOfChar[char2Index(S.charAt(i))] = i;
}
- return nums[l];
+ List partitions = new ArrayList<>();
+ int firstIndex = 0;
+ while (firstIndex < S.length()) {
+ int lastIndex = firstIndex;
+ for (int i = firstIndex; i < S.length() && i <= lastIndex; i++) {
+ int index = lastIndexsOfChar[char2Index(S.charAt(i))];
+ if (index > lastIndex) {
+ lastIndex = index;
+ }
+ }
+ partitions.add(lastIndex - firstIndex + 1);
+ firstIndex = lastIndex + 1;
+ }
+ return partitions;
+}
+
+private int char2Index(char c) {
+ return c - 'a';
}
```
-**查找区间**
-[34. Search for a Range (Medium)](https://leetcode.com/problems/search-for-a-range/description/)
+**种植花朵**
-```html
-Input: nums = [5,7,7,8,8,10], target = 8
-Output: [3,4]
+[605. Can Place Flowers (Easy)](https://leetcode.com/problems/can-place-flowers/description/)
-Input: nums = [5,7,7,8,8,10], target = 6
-Output: [-1,-1]
+```html
+Input: flowerbed = [1,0,0,0,1], n = 1
+Output: True
```
-```java
-public int[] searchRange(int[] nums, int target) {
- int first = binarySearch(nums, target);
- int last = binarySearch(nums, target + 1) - 1;
- if (first == nums.length || nums[first] != target) {
- return new int[]{-1, -1};
- } else {
- return new int[]{first, Math.max(first, last)};
- }
-}
+题目描述:花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。
-private int binarySearch(int[] nums, int target) {
- int l = 0, h = nums.length; // 注意 h 的初始值
- while (l < h) {
- int m = l + (h - l) / 2;
- if (nums[m] >= target) {
- h = m;
- } else {
- l = m + 1;
+```java
+public boolean canPlaceFlowers(int[] flowerbed, int n) {
+ int len = flowerbed.length;
+ int cnt = 0;
+ for (int i = 0; i < len && cnt < n; i++) {
+ if (flowerbed[i] == 1) {
+ continue;
+ }
+ int pre = i == 0 ? 0 : flowerbed[i - 1];
+ int next = i == len - 1 ? 0 : flowerbed[i + 1];
+ if (pre == 0 && next == 0) {
+ cnt++;
+ flowerbed[i] = 1;
}
}
- return l;
+ return cnt >= n;
}
```
-## 搜索
-
-深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。
+**判断是否为子序列**
-### BFS
+[392. Is Subsequence (Medium)](https://leetcode.com/problems/is-subsequence/description/)
-
+```html
+s = "abc", t = "ahbgdc"
+Return true.
+```
-广度优先搜索的搜索过程有点像一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。
+```java
+public boolean isSubsequence(String s, String t) {
+ int index = -1;
+ for (char c : s.toCharArray()) {
+ index = t.indexOf(c, index + 1);
+ if (index == -1) {
+ return false;
+ }
+ }
+ return true;
+}
+```
-第一层:
+**修改一个数成为非递减数组**
-- 0 -> {6,2,1,5};
+[665. Non-decreasing Array (Easy)](https://leetcode.com/problems/non-decreasing-array/description/)
-第二层:
+```html
+Input: [4,2,3]
+Output: True
+Explanation: You could modify the first 4 to 1 to get a non-decreasing array.
+```
-- 6 -> {4}
-- 2 -> {}
-- 1 -> {}
-- 5 -> {3}
+题目描述:判断一个数组能不能只修改一个数就成为非递减数组。
-第三层:
+在出现 nums[i] < nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且 **不影响后续的操作** 。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,就有可能比 nums[i + 1] 大,从而影响了后续操作。还有一个比较特别的情况就是 nums[i] < nums[i - 2],只修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组,只能修改 nums[i] = nums[i - 1]。
-- 4 -> {}
-- 3 -> {}
+```java
+public boolean checkPossibility(int[] nums) {
+ int cnt = 0;
+ for (int i = 1; i < nums.length && cnt < 2; i++) {
+ if (nums[i] >= nums[i - 1]) {
+ continue;
+ }
+ cnt++;
+ if (i - 2 >= 0 && nums[i - 2] > nums[i]) {
+ nums[i] = nums[i - 1];
+ } else {
+ nums[i - 1] = nums[i];
+ }
+ }
+ return cnt <= 1;
+}
+```
-可以看到,每一层遍历的节点都与根节点距离相同。设 di 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di<=dj。利用这个结论,可以求解最短路径等 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径。
+**股票的最大收益**
-在程序实现 BFS 时需要考虑以下问题:
+[122. Best Time to Buy and Sell Stock II (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/)
-- 队列:用来存储每一轮遍历得到的节点;
-- 标记:对于遍历过的节点,应该将它标记,防止重复遍历。
+题目描述:一次股票交易包含买入和卖出,多个交易之间不能交叉进行。
-**计算在网格中从原点到特定点的最短路径长度**
+对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中,从而在局部最优的情况下也保证全局最优。
-```html
-[[1,1,0,1],
- [1,0,1,0],
- [1,1,1,1],
- [1,0,1,1]]
+```java
+public int maxProfit(int[] prices) {
+ int profit = 0;
+ for (int i = 1; i < prices.length; i++) {
+ if (prices[i] > prices[i - 1]) {
+ profit += (prices[i] - prices[i - 1]);
+ }
+ }
+ return profit;
+}
```
-1 表示可以经过某个位置,求解从 (0, 0) 位置到 (tr, tc) 位置的最短路径长度。
+## 二分查找
-```java
-public int minPathLength(int[][] grids, int tr, int tc) {
- final int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
- final int m = grids.length, n = grids[0].length;
- Queue> queue = new LinkedList<>();
- queue.add(new Pair<>(0, 0));
- int pathLength = 0;
- while (!queue.isEmpty()) {
- int size = queue.size();
- pathLength++;
- while (size-- > 0) {
- Pair cur = queue.poll();
- for (int[] d : direction) {
- int nr = cur.getKey() + d[0], nc = cur.getValue() + d[1];
- Pair next = new Pair<>(nr, nc);
- if (next.getKey() < 0 || next.getValue() >= m
- || next.getKey() < 0 || next.getValue() >= n) {
+**正常实现**
- continue;
- }
- grids[next.getKey()][next.getValue()] = 0; // 标记
- if (next.getKey() == tr && next.getValue() == tc) {
- return pathLength;
- }
- queue.add(next);
- }
+```java
+public int binarySearch(int[] nums, int key) {
+ int l = 0, h = nums.length - 1;
+ while (l <= h) {
+ int m = l + (h - l) / 2;
+ if (nums[m] == key) {
+ return m;
+ } else if (nums[m] > key) {
+ h = m - 1;
+ } else {
+ l = m + 1;
}
}
return -1;
}
```
-**组成整数的最小平方数数量**
+**时间复杂度**
-[279. Perfect Squares (Medium)](https://leetcode.com/problems/perfect-squares/description/)
+二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度都为 O(logN)。
-```html
-For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.
-```
+**m 计算**
-可以将每个整数看成图中的一个节点,如果两个整数之差为一个平方数,那么这两个整数所在的节点就有一条边。
+有两种计算中值 m 的方式:
-要求解最小的平方数数量,就是求解从节点 n 到节点 0 的最短路径。
+- m = (l + h) / 2
+- m = l + (h - l) / 2
-本题也可以用动态规划求解,在之后动态规划部分中会再次出现。
+l + h 可能出现加法溢出,最好使用第二种方式。
+
+**返回值**
+
+循环退出时如果仍然没有查找到 key,那么表示查找失败。可以有两种返回值:
+
+- -1:以一个错误码表示没有查找到 key
+- l:将 key 插入到 nums 中的正确位置
+
+**变种**
+
+二分查找可以有很多变种,变种实现要注意边界值的判断。例如在一个有重复元素的数组中查找 key 的最左位置的实现如下:
```java
-public int numSquares(int n) {
- List squares = generateSquares(n);
- Queue queue = new LinkedList<>();
- boolean[] marked = new boolean[n + 1];
- queue.add(n);
- marked[n] = true;
- int level = 0;
- while (!queue.isEmpty()) {
- int size = queue.size();
- level++;
- while (size-- > 0) {
- int cur = queue.poll();
- for (int s : squares) {
- int next = cur - s;
- if (next < 0) {
- break;
- }
- if (next == 0) {
- return level;
- }
- if (marked[next]) {
- continue;
- }
- marked[next] = true;
- queue.add(cur - s);
- }
+public int binarySearch(int[] nums, int key) {
+ int l = 0, h = nums.length - 1;
+ while (l < h) {
+ int m = l + (h - l) / 2;
+ if (nums[m] >= key) {
+ h = m;
+ } else {
+ l = m + 1;
}
}
- return n;
+ return l;
}
+```
-/**
- * 生成小于 n 的平方数序列
- * @return 1,4,9,...
- */
-private List generateSquares(int n) {
- List squares = new ArrayList<>();
+该实现和正常实现有以下不同:
+
+- 循环条件为 l < h
+- h 的赋值表达式为 h = m
+- 最后返回 l 而不是 -1
+
+在 nums[m] >= key 的情况下,可以推导出最左 key 位于 [l, m] 区间中,这是一个闭区间。h 的赋值表达式为 h = m,因为 m 位置也可能是解。
+
+在 h 的赋值表达式为 h = mid 的情况下,如果循环条件为 l <= h,那么会出现循环无法退出的情况,因此循环条件只能是 l < h。以下演示了循环条件为 l <= h 时循环无法退出的情况:
+
+```text
+nums = {0, 1, 2}, key = 1
+l m h
+0 1 2 nums[m] >= key
+0 0 1 nums[m] < key
+1 1 1 nums[m] >= key
+1 1 1 nums[m] >= key
+...
+```
+
+当循环体退出时,不表示没有查找到 key,因此最后返回的结果不应该为 -1。为了验证有没有查找到,需要在调用端判断一下返回位置上的值和 key 是否相等。
+
+**求开方**
+
+[69. Sqrt(x) (Easy)](https://leetcode.com/problems/sqrtx/description/)
+
+```html
+Input: 4
+Output: 2
+
+Input: 8
+Output: 2
+Explanation: The square root of 8 is 2.82842..., and since we want to return an integer, the decimal part will be truncated.
+```
+
+一个数 x 的开方 sqrt 一定在 0 \~ x 之间,并且满足 sqrt == x / sqrt。可以利用二分查找在 0 \~ x 之间查找 sqrt。
+
+对于 x = 8,它的开方是 2.82842...,最后应该返回 2 而不是 3。在循环条件为 l <= h 并且循环退出时,h 总是比 l 小 1,也就是说 h = 2,l = 3,因此最后的返回值应该为 h 而不是 l。
+
+```java
+public int mySqrt(int x) {
+ if (x <= 1) {
+ return x;
+ }
+ int l = 1, h = x;
+ while (l <= h) {
+ int mid = l + (h - l) / 2;
+ int sqrt = x / mid;
+ if (sqrt == mid) {
+ return mid;
+ } else if (mid > sqrt) {
+ h = mid - 1;
+ } else {
+ l = mid + 1;
+ }
+ }
+ return h;
+}
+```
+
+**大于给定元素的最小元素**
+
+[744. Find Smallest Letter Greater Than Target (Easy)](https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/)
+
+```html
+Input:
+letters = ["c", "f", "j"]
+target = "d"
+Output: "f"
+
+Input:
+letters = ["c", "f", "j"]
+target = "k"
+Output: "c"
+```
+
+题目描述:给定一个有序的字符数组 letters 和一个字符 target,要求找出 letters 中大于 target 的最小字符,如果找不到就返回第 1 个字符。
+
+```java
+public char nextGreatestLetter(char[] letters, char target) {
+ int n = letters.length;
+ int l = 0, h = n - 1;
+ while (l <= h) {
+ int m = l + (h - l) / 2;
+ if (letters[m] <= target) {
+ l = m + 1;
+ } else {
+ h = m - 1;
+ }
+ }
+ return l < n ? letters[l] : letters[0];
+}
+```
+
+**有序数组的 Single Element**
+
+[540. Single Element in a Sorted Array (Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/description/)
+
+```html
+Input: [1,1,2,3,3,4,4,8,8]
+Output: 2
+```
+
+题目描述:一个有序数组只有一个数不出现两次,找出这个数。要求以 O(logN) 时间复杂度进行求解。
+
+令 index 为 Single Element 在数组中的位置。如果 m 为偶数,并且 m + 1 < index,那么 nums[m] == nums[m + 1];m + 1 >= index,那么 nums[m] != nums[m + 1]。
+
+从上面的规律可以知道,如果 nums[m] == nums[m + 1],那么 index 所在的数组位置为 [m + 2, h],此时令 l = m + 2;如果 nums[m] != nums[m + 1],那么 index 所在的数组位置为 [l, m],此时令 h = m。
+
+因为 h 的赋值表达式为 h = m,那么循环条件也就只能使用 l < h 这种形式。
+
+```java
+public int singleNonDuplicate(int[] nums) {
+ int l = 0, h = nums.length - 1;
+ while (l < h) {
+ int m = l + (h - l) / 2;
+ if (m % 2 == 1) {
+ m--; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数
+ }
+ if (nums[m] == nums[m + 1]) {
+ l = m + 2;
+ } else {
+ h = m;
+ }
+ }
+ return nums[l];
+}
+```
+
+**第一个错误的版本**
+
+[278. First Bad Version (Easy)](https://leetcode.com/problems/first-bad-version/description/)
+
+题目描述:给定一个元素 n 代表有 [1, 2, ..., n] 版本,可以调用 isBadVersion(int x) 知道某个版本是否错误,要求找到第一个错误的版本。
+
+如果第 m 个版本出错,则表示第一个错误的版本在 [l, m] 之间,令 h = m;否则第一个错误的版本在 [m + 1, h] 之间,令 l = m + 1。
+
+因为 h 的赋值表达式为 h = m,因此循环条件为 l < h。
+
+```java
+public int firstBadVersion(int n) {
+ int l = 1, h = n;
+ while (l < h) {
+ int mid = l + (h - l) / 2;
+ if (isBadVersion(mid)) {
+ h = mid;
+ } else {
+ l = mid + 1;
+ }
+ }
+ return l;
+}
+```
+
+**旋转数组的最小数字**
+
+[153. Find Minimum in Rotated Sorted Array (Medium)](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/)
+
+```html
+Input: [3,4,5,1,2],
+Output: 1
+```
+
+```java
+public int findMin(int[] nums) {
+ 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];
+}
+```
+
+**查找区间**
+
+[34. Search for a Range (Medium)](https://leetcode.com/problems/search-for-a-range/description/)
+
+```html
+Input: nums = [5,7,7,8,8,10], target = 8
+Output: [3,4]
+
+Input: nums = [5,7,7,8,8,10], target = 6
+Output: [-1,-1]
+```
+
+```java
+public int[] searchRange(int[] nums, int target) {
+ int first = binarySearch(nums, target);
+ int last = binarySearch(nums, target + 1) - 1;
+ if (first == nums.length || nums[first] != target) {
+ return new int[]{-1, -1};
+ } else {
+ return new int[]{first, Math.max(first, last)};
+ }
+}
+
+private int binarySearch(int[] nums, int target) {
+ int l = 0, h = nums.length; // 注意 h 的初始值
+ while (l < h) {
+ int m = l + (h - l) / 2;
+ if (nums[m] >= target) {
+ h = m;
+ } else {
+ l = m + 1;
+ }
+ }
+ return l;
+}
+```
+
+## 分治
+
+**给表达式加括号**
+
+[241. Different Ways to Add Parentheses (Medium)](https://leetcode.com/problems/different-ways-to-add-parentheses/description/)
+
+```html
+Input: "2-1-1".
+
+((2-1)-1) = 0
+(2-(1-1)) = 2
+
+Output : [0, 2]
+```
+
+```java
+public List diffWaysToCompute(String input) {
+ List ways = new ArrayList<>();
+ for (int i = 0; i < input.length(); i++) {
+ char c = input.charAt(i);
+ if (c == '+' || c == '-' || c == '*') {
+ List left = diffWaysToCompute(input.substring(0, i));
+ List right = diffWaysToCompute(input.substring(i + 1));
+ for (int l : left) {
+ for (int r : right) {
+ switch (c) {
+ case '+':
+ ways.add(l + r);
+ break;
+ case '-':
+ ways.add(l - r);
+ break;
+ case '*':
+ ways.add(l * r);
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (ways.size() == 0) {
+ ways.add(Integer.valueOf(input));
+ }
+ return ways;
+}
+```
+
+## 搜索
+
+深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。
+
+### BFS
+
+
+
+广度优先搜索的搜索过程有点像一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。
+
+第一层:
+
+- 0 -> {6,2,1,5};
+
+第二层:
+
+- 6 -> {4}
+- 2 -> {}
+- 1 -> {}
+- 5 -> {3}
+
+第三层:
+
+- 4 -> {}
+- 3 -> {}
+
+可以看到,每一层遍历的节点都与根节点距离相同。设 di 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di<=dj。利用这个结论,可以求解最短路径等 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径。
+
+在程序实现 BFS 时需要考虑以下问题:
+
+- 队列:用来存储每一轮遍历得到的节点;
+- 标记:对于遍历过的节点,应该将它标记,防止重复遍历。
+
+**计算在网格中从原点到特定点的最短路径长度**
+
+```html
+[[1,1,0,1],
+ [1,0,1,0],
+ [1,1,1,1],
+ [1,0,1,1]]
+```
+
+1 表示可以经过某个位置,求解从 (0, 0) 位置到 (tr, tc) 位置的最短路径长度。
+
+```java
+public int minPathLength(int[][] grids, int tr, int tc) {
+ final int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
+ final int m = grids.length, n = grids[0].length;
+ Queue> queue = new LinkedList<>();
+ queue.add(new Pair<>(0, 0));
+ int pathLength = 0;
+ while (!queue.isEmpty()) {
+ int size = queue.size();
+ pathLength++;
+ while (size-- > 0) {
+ Pair cur = queue.poll();
+ for (int[] d : direction) {
+ int nr = cur.getKey() + d[0], nc = cur.getValue() + d[1];
+ Pair next = new Pair<>(nr, nc);
+ if (next.getKey() < 0 || next.getValue() >= m
+ || next.getKey() < 0 || next.getValue() >= n) {
+
+ continue;
+ }
+ grids[next.getKey()][next.getValue()] = 0; // 标记
+ if (next.getKey() == tr && next.getValue() == tc) {
+ return pathLength;
+ }
+ queue.add(next);
+ }
+ }
+ }
+ return -1;
+}
+```
+
+**组成整数的最小平方数数量**
+
+[279. Perfect Squares (Medium)](https://leetcode.com/problems/perfect-squares/description/)
+
+```html
+For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.
+```
+
+可以将每个整数看成图中的一个节点,如果两个整数之差为一个平方数,那么这两个整数所在的节点就有一条边。
+
+要求解最小的平方数数量,就是求解从节点 n 到节点 0 的最短路径。
+
+本题也可以用动态规划求解,在之后动态规划部分中会再次出现。
+
+```java
+public int numSquares(int n) {
+ List squares = generateSquares(n);
+ Queue queue = new LinkedList<>();
+ boolean[] marked = new boolean[n + 1];
+ queue.add(n);
+ marked[n] = true;
+ int level = 0;
+ while (!queue.isEmpty()) {
+ int size = queue.size();
+ level++;
+ while (size-- > 0) {
+ int cur = queue.poll();
+ for (int s : squares) {
+ int next = cur - s;
+ if (next < 0) {
+ break;
+ }
+ if (next == 0) {
+ return level;
+ }
+ if (marked[next]) {
+ continue;
+ }
+ marked[next] = true;
+ queue.add(cur - s);
+ }
+ }
+ }
+ return n;
+}
+
+/**
+ * 生成小于 n 的平方数序列
+ * @return 1,4,9,...
+ */
+private List generateSquares(int n) {
+ List squares = new ArrayList<>();
int square = 1;
int diff = 3;
while (square <= n) {
@@ -2266,96 +2325,49 @@ private int cubeNum(int i, int j) {
```java
-private List> solutions;
-private char[][] nQueens;
-private boolean[] colUsed;
-private boolean[] diagonals45Used;
-private boolean[] diagonals135Used;
-private int n;
-
-public List> solveNQueens(int n) {
- solutions = new ArrayList<>();
- nQueens = new char[n][n];
- for (int i = 0; i < n; i++) {
- Arrays.fill(nQueens[i], '.');
- }
- colUsed = new boolean[n];
- diagonals45Used = new boolean[2 * n - 1];
- diagonals135Used = new boolean[2 * n - 1];
- this.n = n;
- backtracking(0);
- return solutions;
-}
-
-private void backtracking(int row) {
- if (row == n) {
- List list = new ArrayList<>();
- for (char[] chars : nQueens) {
- list.add(new String(chars));
- }
- solutions.add(list);
- return;
- }
-
- for (int col = 0; col < n; col++) {
- int diagonals45Idx = row + col;
- int diagonals135Idx = n - 1 - (row - col);
- if (colUsed[col] || diagonals45Used[diagonals45Idx] || diagonals135Used[diagonals135Idx]) {
- continue;
- }
- nQueens[row][col] = 'Q';
- colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = true;
- backtracking(row + 1);
- colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = false;
- nQueens[row][col] = '.';
- }
-}
-```
-
-## 分治
-
-**给表达式加括号**
-
-[241. Different Ways to Add Parentheses (Medium)](https://leetcode.com/problems/different-ways-to-add-parentheses/description/)
-
-```html
-Input: "2-1-1".
-
-((2-1)-1) = 0
-(2-(1-1)) = 2
-
-Output : [0, 2]
-```
-
-```java
-public List diffWaysToCompute(String input) {
- List ways = new ArrayList<>();
- for (int i = 0; i < input.length(); i++) {
- char c = input.charAt(i);
- if (c == '+' || c == '-' || c == '*') {
- List left = diffWaysToCompute(input.substring(0, i));
- List right = diffWaysToCompute(input.substring(i + 1));
- for (int l : left) {
- for (int r : right) {
- switch (c) {
- case '+':
- ways.add(l + r);
- break;
- case '-':
- ways.add(l - r);
- break;
- case '*':
- ways.add(l * r);
- break;
- }
- }
- }
+private List> solutions;
+private char[][] nQueens;
+private boolean[] colUsed;
+private boolean[] diagonals45Used;
+private boolean[] diagonals135Used;
+private int n;
+
+public List> solveNQueens(int n) {
+ solutions = new ArrayList<>();
+ nQueens = new char[n][n];
+ for (int i = 0; i < n; i++) {
+ Arrays.fill(nQueens[i], '.');
+ }
+ colUsed = new boolean[n];
+ diagonals45Used = new boolean[2 * n - 1];
+ diagonals135Used = new boolean[2 * n - 1];
+ this.n = n;
+ backtracking(0);
+ return solutions;
+}
+
+private void backtracking(int row) {
+ if (row == n) {
+ List list = new ArrayList<>();
+ for (char[] chars : nQueens) {
+ list.add(new String(chars));
}
+ solutions.add(list);
+ return;
}
- if (ways.size() == 0) {
- ways.add(Integer.valueOf(input));
+
+ for (int col = 0; col < n; col++) {
+ int diagonals45Idx = row + col;
+ int diagonals135Idx = n - 1 - (row - col);
+ if (colUsed[col] || diagonals45Used[diagonals45Idx] || diagonals135Used[diagonals135Idx]) {
+ continue;
+ }
+ nQueens[row][col] = 'Q';
+ colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = true;
+ backtracking(row + 1);
+ colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = false;
+ nQueens[row][col] = '.';
}
- return ways;
}
```
@@ -4036,2366 +4048,2372 @@ public int maximumProduct(int[] nums) {
# 数据结构相关
-## 栈和队列
-
-**用栈实现队列**
-
-[232. Implement Queue using Stacks (Easy)](https://leetcode.com/problems/implement-queue-using-stacks/description/)
-
-```java
-class MyQueue {
-
- private Stack in = new Stack<>();
- private Stack out = new Stack<>();
-
- public void push(int x) {
- in.push(x);
- }
+## 链表
- public int pop() {
- in2out();
- return out.pop();
- }
+链表是空节点,或者有一个值和一个指向下一个链表的指针,因此很多链表问题可以用递归来处理。
- public int peek() {
- in2out();
- return out.peek();
- }
+**找出两个链表的交点**
- private void in2out() {
- if (out.isEmpty()) {
- while (!in.isEmpty()) {
- out.push(in.pop());
- }
- }
- }
+[160. Intersection of Two Linked Lists (Easy)](https://leetcode.com/problems/intersection-of-two-linked-lists/description/)
- public boolean empty() {
- return in.isEmpty() && out.isEmpty();
- }
-}
+```html
+A: a1 → a2
+ ↘
+ c1 → c2 → c3
+ ↗
+B: b1 → b2 → b3
```
-**用队列实现栈**
-
-[225. Implement Stack using Queues (Easy)](https://leetcode.com/problems/implement-stack-using-queues/description/)
-
-在将一个元素 x 插入队列时,需要让除了 x 之外的所有元素出队列,再入队列。此时 x 在队首,第一个出队列,实现了后进先出顺序。
-
-```java
-class MyStack {
-
- private Queue queue;
-
- public MyStack() {
- queue = new LinkedList<>();
- }
-
- public void push(int x) {
- queue.add(x);
- int cnt = queue.size();
- while (cnt-- > 1) {
- queue.add(queue.poll());
- }
- }
-
- public int pop() {
- return queue.remove();
- }
-
- public int top() {
- return queue.peek();
- }
-
- public boolean empty() {
- return queue.isEmpty();
- }
-}
-```
+要求:时间复杂度为 O(N),空间复杂度为 O(1)
-**最小值栈**
+设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。
-[155. Min Stack (Easy)](https://leetcode.com/problems/min-stack/description/)
+当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
```java
-class MinStack {
-
- private Stack dataStack;
- private Stack minStack;
- private int min;
-
- public MinStack() {
- dataStack = new Stack<>();
- minStack = new Stack<>();
- min = Integer.MAX_VALUE;
- }
-
- public void push(int x) {
- dataStack.add(x);
- min = Math.min(min, x);
- minStack.add(min);
- }
-
- public void pop() {
- dataStack.pop();
- minStack.pop();
- min = minStack.isEmpty() ? Integer.MAX_VALUE : minStack.peek();
- }
-
- public int top() {
- return dataStack.peek();
- }
-
- public int getMin() {
- return minStack.peek();
+public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
+ ListNode l1 = headA, l2 = headB;
+ while (l1 != l2) {
+ l1 = (l1 == null) ? headB : l1.next;
+ l2 = (l2 == null) ? headA : l2.next;
}
+ return l1;
}
```
-对于实现最小值队列问题,可以先将队列使用栈来实现,然后就将问题转换为最小值栈,这个问题出现在 编程之美:3.7。
+如果只是判断是否存在交点,那么就是另一个问题,即 [编程之美 3.6]() 的问题。有两种解法:
-**用栈实现括号匹配**
+- 把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环;
+- 或者直接比较两个链表的最后一个节点是否相同。
-[20. Valid Parentheses (Easy)](https://leetcode.com/problems/valid-parentheses/description/)
+**链表反转**
-```html
-"()[]{}"
+[206. Reverse Linked List (Easy)](https://leetcode.com/problems/reverse-linked-list/description/)
-Output : true
-```
+递归
```java
-public boolean isValid(String s) {
- Stack stack = new Stack<>();
- for (char c : s.toCharArray()) {
- if (c == '(' || c == '{' || c == '[') {
- stack.push(c);
- } else {
- if (stack.isEmpty()) {
- return false;
- }
- char cStack = stack.pop();
- boolean b1 = c == ')' && cStack != '(';
- boolean b2 = c == ']' && cStack != '[';
- boolean b3 = c == '}' && cStack != '{';
- if (b1 || b2 || b3) {
- return false;
- }
- }
+public ListNode reverseList(ListNode head) {
+ if (head == null || head.next == null) {
+ return head;
}
- return stack.isEmpty();
+ ListNode next = head.next;
+ ListNode newHead = reverseList(next);
+ next.next = head;
+ head.next = null;
+ return newHead;
}
```
-**数组中元素与下一个比它大的元素之间的距离**
-
-[739. Daily Temperatures (Medium)](https://leetcode.com/problems/daily-temperatures/description/)
-
-```html
-Input: [73, 74, 75, 71, 69, 72, 76, 73]
-Output: [1, 1, 4, 2, 1, 1, 0, 0]
-```
-
-在遍历数组时用 Stack 把数组中的数存起来,如果当前遍历的数比栈顶元素来的大,说明栈顶元素的下一个比它大的数就是当前元素。
+头插法
```java
-public int[] dailyTemperatures(int[] temperatures) {
- int n = temperatures.length;
- int[] dist = new int[n];
- Stack indexs = new Stack<>();
- for (int curIndex = 0; curIndex < n; curIndex++) {
- while (!indexs.isEmpty() && temperatures[curIndex] > temperatures[indexs.peek()]) {
- int preIndex = indexs.pop();
- dist[preIndex] = curIndex - preIndex;
- }
- indexs.add(curIndex);
+public ListNode reverseList(ListNode head) {
+ ListNode newHead = new ListNode(-1);
+ while (head != null) {
+ ListNode next = head.next;
+ head.next = newHead.next;
+ newHead.next = head;
+ head = next;
}
- return dist;
+ return newHead.next;
}
```
-**循环数组中比当前元素大的下一个元素**
-
-[503. Next Greater Element II (Medium)](https://leetcode.com/problems/next-greater-element-ii/description/)
-
-```text
-Input: [1,2,1]
-Output: [2,-1,2]
-Explanation: The first 1's next greater number is 2;
-The number 2 can't find next greater number;
-The second 1's next greater number needs to search circularly, which is also 2.
-```
+**归并两个有序的链表**
-与 739. Daily Temperatures (Medium) 不同的是,数组是循环数组,并且最后要求的不是距离而是下一个元素。
+[21. Merge Two Sorted Lists (Easy)](https://leetcode.com/problems/merge-two-sorted-lists/description/)
```java
-public int[] nextGreaterElements(int[] nums) {
- int n = nums.length;
- int[] next = new int[n];
- Arrays.fill(next, -1);
- Stack pre = new Stack<>();
- for (int i = 0; i < n * 2; i++) {
- int num = nums[i % n];
- while (!pre.isEmpty() && nums[pre.peek()] < num) {
- next[pre.pop()] = num;
- }
- if (i < n){
- pre.push(i);
- }
+public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
+ if (l1 == null) return l2;
+ if (l2 == null) return l1;
+ if (l1.val < l2.val) {
+ l1.next = mergeTwoLists(l1.next, l2);
+ return l1;
+ } else {
+ l2.next = mergeTwoLists(l1, l2.next);
+ return l2;
}
- return next;
}
```
-## 哈希表
-
-哈希表使用 O(N) 空间复杂度存储数据,从而能够以 O(1) 时间复杂度求解问题。
-
-Java 中的 **HashSet** 用于存储一个集合,可以查找元素是否在集合中。
-
-如果元素有穷,并且范围不大,那么可以用一个布尔数组来存储一个元素是否存在。例如对于只有小写字符的元素,就可以用一个长度为 26 的布尔数组来存储一个字符集合,使得空间复杂度降低为 O(1)。
-
-Java 中的 **HashMap** 主要用于映射关系,从而把两个元素联系起来。
+**从有序链表中删除重复节点**
-在对一个内容进行压缩或者其它转换时,利用 HashMap 可以把原始内容和转换后的内容联系起来。例如在一个简化 url 的系统中[Leetcdoe : 535. Encode and Decode TinyURL (Medium)](https://leetcode.com/problems/encode-and-decode-tinyurl/description/),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源。
+[83. Remove Duplicates from Sorted List (Easy)](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/)
-HashMap 也可以用来对元素进行计数统计,此时键为元素,值为计数。和 HashSet 类似,如果元素有穷并且范围不大,可以用整型数组来进行统计。
+```html
+Given 1->1->2, return 1->2.
+Given 1->1->2->3->3, return 1->2->3.
+```
-**数组中的两个数和为给定值**
+```java
+public ListNode deleteDuplicates(ListNode head) {
+ if (head == null || head.next == null) return head;
+ head.next = deleteDuplicates(head.next);
+ return head.val == head.next.val ? head.next : head;
+}
+```
-[1. Two Sum (Easy)](https://leetcode.com/problems/two-sum/description/)
+**删除链表的倒数第 n 个节点**
-可以先对数组进行排序,然后使用双指针方法或者二分查找方法。这样做的时间复杂度为 O(NlogN),空间复杂度为 O(1)。
+[19. Remove Nth Node From End of List (Medium)](https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/)
-用 HashMap 存储数组元素和索引的映射,在访问到 nums[i] 时,判断 HashMap 中是否存在 target - nums[i],如果存在说明 target - nums[i] 所在的索引和 i 就是要找的两个数。该方法的时间复杂度为 O(N),空间复杂度为 O(N),使用空间来换取时间。
+```html
+Given linked list: 1->2->3->4->5, and n = 2.
+After removing the second node from the end, the linked list becomes 1->2->3->5.
+```
```java
-public int[] twoSum(int[] nums, int target) {
- HashMap indexForNum = new HashMap<>();
- for (int i = 0; i < nums.length; i++) {
- if (indexForNum.containsKey(target - nums[i])) {
- return new int[]{indexForNum.get(target - nums[i]), i};
- } else {
- indexForNum.put(nums[i], i);
- }
+public ListNode removeNthFromEnd(ListNode head, int n) {
+ ListNode fast = head;
+ while (n-- > 0) {
+ fast = fast.next;
}
- return null;
+ if (fast == null) return head.next;
+ ListNode slow = head;
+ while (fast.next != null) {
+ fast = fast.next;
+ slow = slow.next;
+ }
+ slow.next = slow.next.next;
+ return head;
}
```
-**判断数组是否含有重复元素**
+**交换链表中的相邻结点**
-[217. Contains Duplicate (Easy)](https://leetcode.com/problems/contains-duplicate/description/)
+[24. Swap Nodes in Pairs (Medium)](https://leetcode.com/problems/swap-nodes-in-pairs/description/)
+
+```html
+Given 1->2->3->4, you should return the list as 2->1->4->3.
+```
+
+题目要求:不能修改结点的 val 值,O(1) 空间复杂度。
```java
-public boolean containsDuplicate(int[] nums) {
- Set set = new HashSet<>();
- for (int num : nums) {
- set.add(num);
+public ListNode swapPairs(ListNode head) {
+ ListNode node = new ListNode(-1);
+ node.next = head;
+ ListNode pre = node;
+ while (pre.next != null && pre.next.next != null) {
+ ListNode l1 = pre.next, l2 = pre.next.next;
+ ListNode next = l2.next;
+ l1.next = next;
+ l2.next = l1;
+ pre.next = l2;
+
+ pre = l1;
}
- return set.size() < nums.length;
+ return node.next;
}
```
-**最长和谐序列**
+**链表求和**
-[594. Longest Harmonious Subsequence (Easy)](https://leetcode.com/problems/longest-harmonious-subsequence/description/)
+[445. Add Two Numbers II (Medium)](https://leetcode.com/problems/add-two-numbers-ii/description/)
```html
-Input: [1,3,2,2,5,2,3,7]
-Output: 5
-Explanation: The longest harmonious subsequence is [3,2,2,2,3].
+Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
+Output: 7 -> 8 -> 0 -> 7
```
-和谐序列中最大数和最小数只差正好为 1,应该注意的是序列的元素不一定是数组的连续元素。
+题目要求:不能修改原始链表。
```java
-public int findLHS(int[] nums) {
- Map countForNum = new HashMap<>();
- for (int num : nums) {
- countForNum.put(num, countForNum.getOrDefault(num, 0) + 1);
+public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
+ Stack l1Stack = buildStack(l1);
+ Stack l2Stack = buildStack(l2);
+ ListNode head = new ListNode(-1);
+ int carry = 0;
+ while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) {
+ int x = l1Stack.isEmpty() ? 0 : l1Stack.pop();
+ int y = l2Stack.isEmpty() ? 0 : l2Stack.pop();
+ int sum = x + y + carry;
+ ListNode node = new ListNode(sum % 10);
+ node.next = head.next;
+ head.next = node;
+ carry = sum / 10;
}
- int longest = 0;
- for (int num : countForNum.keySet()) {
- if (countForNum.containsKey(num + 1)) {
- longest = Math.max(longest, countForNum.get(num + 1) + countForNum.get(num));
- }
+ return head.next;
+}
+
+private Stack buildStack(ListNode l) {
+ Stack stack = new Stack<>();
+ while (l != null) {
+ stack.push(l.val);
+ l = l.next;
}
- return longest;
+ return stack;
}
```
-**最长连续序列**
+**回文链表**
-[128. Longest Consecutive Sequence (Hard)](https://leetcode.com/problems/longest-consecutive-sequence/description/)
+[234. Palindrome Linked List (Easy)](https://leetcode.com/problems/palindrome-linked-list/description/)
-```html
-Given [100, 4, 200, 1, 3, 2],
-The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4.
-```
+题目要求:以 O(1) 的空间复杂度来求解。
-要求以 O(N) 的时间复杂度求解。
+切成两半,把后半段反转,然后比较两半是否相等。
```java
-public int longestConsecutive(int[] nums) {
- Map countForNum = new HashMap<>();
- for (int num : nums) {
- countForNum.put(num, 1);
- }
- for (int num : nums) {
- forward(countForNum, num);
+public boolean isPalindrome(ListNode head) {
+ if (head == null || head.next == null) return true;
+ ListNode slow = head, fast = head.next;
+ while (fast != null && fast.next != null) {
+ slow = slow.next;
+ fast = fast.next.next;
}
- return maxCount(countForNum);
+ if (fast != null) slow = slow.next; // 偶数节点,让 slow 指向下一个节点
+ cut(head, slow); // 切成两个链表
+ return isEqual(head, reverse(slow));
}
-private int forward(Map countForNum, int num) {
- if (!countForNum.containsKey(num)) {
- return 0;
+private void cut(ListNode head, ListNode cutNode) {
+ while (head.next != cutNode) {
+ head = head.next;
}
- int cnt = countForNum.get(num);
- if (cnt > 1) {
- return cnt;
+ head.next = null;
+}
+
+private ListNode reverse(ListNode head) {
+ ListNode newHead = null;
+ while (head != null) {
+ ListNode nextNode = head.next;
+ head.next = newHead;
+ newHead = head;
+ head = nextNode;
}
- cnt = forward(countForNum, num + 1) + 1;
- countForNum.put(num, cnt);
- return cnt;
+ return newHead;
}
-private int maxCount(Map countForNum) {
- int max = 0;
- for (int num : countForNum.keySet()) {
- max = Math.max(max, countForNum.get(num));
+private boolean isEqual(ListNode l1, ListNode l2) {
+ while (l1 != null && l2 != null) {
+ if (l1.val != l2.val) return false;
+ l1 = l1.next;
+ l2 = l2.next;
}
- return max;
+ return true;
}
```
-## 字符串
-
-**两个字符串包含的字符是否完全相同**
+**分隔链表**
-[242. Valid Anagram (Easy)](https://leetcode.com/problems/valid-anagram/description/)
+[725. Split Linked List in Parts(Medium)](https://leetcode.com/problems/split-linked-list-in-parts/description/)
```html
-s = "anagram", t = "nagaram", return true.
-s = "rat", t = "car", return false.
+Input:
+root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
+Output: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
+Explanation:
+The input has been split into consecutive parts with size difference at most 1, and earlier parts are a larger size than the later parts.
```
-字符串只包含小写字符,总共有 26 个小写字符。可以用 HashMap 来映射字符与出现次数。因为键的范围很小,因此可以使用长度为 26 的整型数组对字符串出现的字符进行统计,然后比较两个字符串出现的字符数量是否相同。
+题目描述:把链表分隔成 k 部分,每部分的长度都应该尽可能相同,排在前面的长度应该大于等于后面的。
```java
-public boolean isAnagram(String s, String t) {
- int[] cnts = new int[26];
- for (char c : s.toCharArray()) {
- cnts[c - 'a']++;
- }
- for (char c : t.toCharArray()) {
- cnts[c - 'a']--;
+public ListNode[] splitListToParts(ListNode root, int k) {
+ int N = 0;
+ ListNode cur = root;
+ while (cur != null) {
+ N++;
+ cur = cur.next;
}
- for (int cnt : cnts) {
- if (cnt != 0) {
- return false;
+ int mod = N % k;
+ int size = N / k;
+ ListNode[] ret = new ListNode[k];
+ cur = root;
+ for (int i = 0; cur != null && i < k; i++) {
+ ret[i] = cur;
+ int curSize = size + (mod-- > 0 ? 1 : 0);
+ for (int j = 0; j < curSize - 1; j++) {
+ cur = cur.next;
}
+ ListNode next = cur.next;
+ cur.next = null;
+ cur = next;
}
- return true;
+ return ret;
+}
+```
+
+**链表元素按奇偶聚集**
+
+[328. Odd Even Linked List (Medium)](https://leetcode.com/problems/odd-even-linked-list/description/)
+
+```html
+Example:
+Given 1->2->3->4->5->NULL,
+return 1->3->5->2->4->NULL.
+```
+
+```java
+public ListNode oddEvenList(ListNode head) {
+ if (head == null) {
+ return head;
+ }
+ ListNode odd = head, even = head.next, evenHead = even;
+ while (even != null && even.next != null) {
+ odd.next = odd.next.next;
+ odd = odd.next;
+ even.next = even.next.next;
+ even = even.next;
+ }
+ odd.next = evenHead;
+ return head;
}
```
-**计算一组字符集合可以组成的回文字符串的最大长度**
+## 树
-[409. Longest Palindrome (Easy)](https://leetcode.com/problems/longest-palindrome/description/)
+### 递归
-```html
-Input : "abccccdd"
-Output : 7
-Explanation : One longest palindrome that can be built is "dccaccd", whose length is 7.
-```
+一棵树要么是空树,要么有两个指针,每个指针指向一棵树。树是一种递归结构,很多树的问题可以使用递归来处理。
-使用长度为 256 的整型数组来统计每个字符出现的个数,每个字符有偶数个可以用来构成回文字符串。
+**树的高度**
-因为回文字符串最中间的那个字符可以单独出现,所以如果有单独的字符就把它放到最中间。
+[104. Maximum Depth of Binary Tree (Easy)](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/)
```java
-public int longestPalindrome(String s) {
- int[] cnts = new int[256];
- for (char c : s.toCharArray()) {
- cnts[c]++;
- }
- int palindrome = 0;
- for (int cnt : cnts) {
- palindrome += (cnt / 2) * 2;
- }
- if (palindrome < s.length()) {
- palindrome++; // 这个条件下 s 中一定有单个未使用的字符存在,可以把这个字符放到回文的最中间
- }
- return palindrome;
+public int maxDepth(TreeNode root) {
+ if (root == null) return 0;
+ return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
```
-**字符串同构**
+**平衡树**
-[205. Isomorphic Strings (Easy)](https://leetcode.com/problems/isomorphic-strings/description/)
+[110. Balanced Binary Tree (Easy)](https://leetcode.com/problems/balanced-binary-tree/description/)
```html
-Given "egg", "add", return true.
-Given "foo", "bar", return false.
-Given "paper", "title", return true.
+ 3
+ / \
+ 9 20
+ / \
+ 15 7
```
-记录一个字符上次出现的位置,如果两个字符串中的字符上次出现的位置一样,那么就属于同构。
+平衡树左右子树高度差都小于等于 1
```java
-public boolean isIsomorphic(String s, String t) {
- int[] preIndexOfS = new int[256];
- int[] preIndexOfT = new int[256];
- for (int i = 0; i < s.length(); i++) {
- char sc = s.charAt(i), tc = t.charAt(i);
- if (preIndexOfS[sc] != preIndexOfT[tc]) {
- return false;
- }
- preIndexOfS[sc] = i + 1;
- preIndexOfT[tc] = i + 1;
- }
- return true;
+private boolean result = true;
+
+public boolean isBalanced(TreeNode root) {
+ maxDepth(root);
+ return result;
+}
+
+public int maxDepth(TreeNode root) {
+ if (root == null) return 0;
+ int l = maxDepth(root.left);
+ int r = maxDepth(root.right);
+ if (Math.abs(l - r) > 1) result = false;
+ return 1 + Math.max(l, r);
}
```
-**回文子字符串**
+**两节点的最长路径**
-[647. Palindromic Substrings (Medium)](https://leetcode.com/problems/palindromic-substrings/description/)
+[543. Diameter of Binary Tree (Easy)](https://leetcode.com/problems/diameter-of-binary-tree/description/)
```html
-Input: "aaa"
-Output: 6
-Explanation: Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa".
-```
+Input:
-从字符串的某一位开始,尝试着去扩展子字符串。
+ 1
+ / \
+ 2 3
+ / \
+ 4 5
+
+Return 3, which is the length of the path [4,2,1,3] or [5,2,1,3].
+```
```java
-private int cnt = 0;
+private int max = 0;
-public int countSubstrings(String s) {
- for (int i = 0; i < s.length(); i++) {
- extendSubstrings(s, i, i); // 奇数长度
- extendSubstrings(s, i, i + 1); // 偶数长度
- }
- return cnt;
+public int diameterOfBinaryTree(TreeNode root) {
+ depth(root);
+ return max;
}
-private void extendSubstrings(String s, int start, int end) {
- while (start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)) {
- start--;
- end++;
- cnt++;
- }
+private int depth(TreeNode root) {
+ if (root == null) return 0;
+ int leftDepth = depth(root.left);
+ int rightDepth = depth(root.right);
+ max = Math.max(max, leftDepth + rightDepth);
+ return Math.max(leftDepth, rightDepth) + 1;
}
```
-**判断一个整数是否是回文数**
-
-[9. Palindrome Number (Easy)](https://leetcode.com/problems/palindrome-number/description/)
-
-要求不能使用额外空间,也就不能将整数转换为字符串进行判断。
+**翻转树**
-将整数分成左右两部分,右边那部分需要转置,然后判断这两部分是否相等。
+[226. Invert Binary Tree (Easy)](https://leetcode.com/problems/invert-binary-tree/description/)
```java
-public boolean isPalindrome(int x) {
- if (x == 0) {
- return true;
- }
- if (x < 0 || x % 10 == 0) {
- return false;
- }
- int right = 0;
- while (x > right) {
- right = right * 10 + x % 10;
- x /= 10;
- }
- return x == right || x == right / 10;
+public TreeNode invertTree(TreeNode root) {
+ if (root == null) return null;
+ TreeNode left = root.left; // 后面的操作会改变 left 指针,因此先保存下来
+ root.left = invertTree(root.right);
+ root.right = invertTree(left);
+ return root;
}
```
-**统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数**
+**归并两棵树**
-[696. Count Binary Substrings (Easy)](https://leetcode.com/problems/count-binary-substrings/description/)
+[617. Merge Two Binary Trees (Easy)](https://leetcode.com/problems/merge-two-binary-trees/description/)
```html
-Input: "00110011"
-Output: 6
-Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01".
+Input:
+ Tree 1 Tree 2
+ 1 2
+ / \ / \
+ 3 2 1 3
+ / \ \
+ 5 4 7
+
+Output:
+ 3
+ / \
+ 4 5
+ / \ \
+ 5 4 7
```
```java
-public int countBinarySubstrings(String s) {
- int preLen = 0, curLen = 1, count = 0;
- for (int i = 1; i < s.length(); i++) {
- if (s.charAt(i) == s.charAt(i - 1)) {
- curLen++;
- } else {
- preLen = curLen;
- curLen = 1;
- }
-
- if (preLen >= curLen) {
- count++;
- }
- }
- return count;
+public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
+ if (t1 == null && t2 == null) return null;
+ if (t1 == null) return t2;
+ if (t2 == null) return t1;
+ TreeNode root = new TreeNode(t1.val + t2.val);
+ root.left = mergeTrees(t1.left, t2.left);
+ root.right = mergeTrees(t1.right, t2.right);
+ return root;
}
```
-**字符串循环移位包含**
+**判断路径和是否等于一个数**
-[编程之美:3.1](#)
+[Leetcdoe : 112. Path Sum (Easy)](https://leetcode.com/problems/path-sum/description/)
```html
-s1 = AABCD, s2 = CDAA
-Return : true
+Given the below binary tree and sum = 22,
+
+ 5
+ / \
+ 4 8
+ / / \
+ 11 13 4
+ / \ \
+ 7 2 1
+
+return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.
```
-给定两个字符串 s1 和 s2,要求判定 s2 是否能够被 s1 做循环移位得到的字符串包含。
+路径和定义为从 root 到 leaf 的所有节点的和。
-s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 是否是 s1s1 的子字符串即可。
+```java
+public boolean hasPathSum(TreeNode root, int sum) {
+ if (root == null) return false;
+ if (root.left == null && root.right == null && root.val == sum) return true;
+ return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
+}
+```
-**字符串循环移位**
+**统计路径和等于一个数的路径数量**
-[编程之美:2.17](#)
+[437. Path Sum III (Easy)](https://leetcode.com/problems/path-sum-iii/description/)
```html
-s = "abcd123" k = 3
-Return "123abcd"
+root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
+
+ 10
+ / \
+ 5 -3
+ / \ \
+ 3 2 11
+ / \ \
+3 -2 1
+
+Return 3. The paths that sum to 8 are:
+
+1. 5 -> 3
+2. 5 -> 2 -> 1
+3. -3 -> 11
```
-将字符串向右循环移动 k 位。
+路径不一定以 root 开头,也不一定以 leaf 结尾,但是必须连续。
-将 abcd123 中的 abcd 和 123 单独逆序,得到 dcba321,然后对整个字符串进行逆序,得到 123abcd。
+```java
+public int pathSum(TreeNode root, int sum) {
+ if (root == null) return 0;
+ int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
+ return ret;
+}
-**字符串中单词的翻转**
+private int pathSumStartWithRoot(TreeNode root, int sum) {
+ if (root == null) return 0;
+ int ret = 0;
+ if (root.val == sum) ret++;
+ ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val);
+ return ret;
+}
+```
-[程序员代码面试指南](#)
+**子树**
+
+[572. Subtree of Another Tree (Easy)](https://leetcode.com/problems/subtree-of-another-tree/description/)
+
+```html
+Given tree s:
+ 3
+ / \
+ 4 5
+ / \
+ 1 2
-```html
-s = "I am a student"
-return "student a am I"
-```
+Given tree t:
+ 4
+ / \
+ 1 2
-将每个单词逆序,然后将整个字符串逆序。
+Return true, because t has the same structure and node values with a subtree of s.
-## 数组与矩阵
+Given tree s:
-**把数组中的 0 移到末尾**
+ 3
+ / \
+ 4 5
+ / \
+ 1 2
+ /
+ 0
-[283. Move Zeroes (Easy)](https://leetcode.com/problems/move-zeroes/description/)
+Given tree t:
+ 4
+ / \
+ 1 2
-```html
-For example, given nums = [0, 1, 0, 3, 12], after calling your function, nums should be [1, 3, 12, 0, 0].
+Return false.
```
```java
-public void moveZeroes(int[] nums) {
- int idx = 0;
- for (int num : nums) {
- if (num != 0) {
- nums[idx++] = num;
- }
- }
- while (idx < nums.length) {
- nums[idx++] = 0;
- }
+public boolean isSubtree(TreeNode s, TreeNode t) {
+ if (s == null) return false;
+ return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);
+}
+
+private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
+ if (t == null && s == null) return true;
+ if (t == null || s == null) return false;
+ if (t.val != s.val) return false;
+ return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
}
```
-**改变矩阵维度**
+**树的对称**
-[566. Reshape the Matrix (Easy)](https://leetcode.com/problems/reshape-the-matrix/description/)
+[101. Symmetric Tree (Easy)](https://leetcode.com/problems/symmetric-tree/description/)
```html
-Input:
-nums =
-[[1,2],
- [3,4]]
-r = 1, c = 4
-
-Output:
-[[1,2,3,4]]
-
-Explanation:
-The row-traversing of nums is [1,2,3,4]. The new reshaped matrix is a 1 * 4 matrix, fill it row by row by using the previous list.
+ 1
+ / \
+ 2 2
+ / \ / \
+3 4 4 3
```
```java
-public int[][] matrixReshape(int[][] nums, int r, int c) {
- int m = nums.length, n = nums[0].length;
- if (m * n != r * c) {
- return nums;
- }
- int[][] reshapedNums = new int[r][c];
- int index = 0;
- for (int i = 0; i < r; i++) {
- for (int j = 0; j < c; j++) {
- reshapedNums[i][j] = nums[index / n][index % n];
- index++;
- }
- }
- return reshapedNums;
+public boolean isSymmetric(TreeNode root) {
+ if (root == null) return true;
+ return isSymmetric(root.left, root.right);
+}
+
+private boolean isSymmetric(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 isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left);
}
```
-**找出数组中最长的连续 1**
+**最小路径**
-[485. Max Consecutive Ones (Easy)](https://leetcode.com/problems/max-consecutive-ones/description/)
+[111. Minimum Depth of Binary Tree (Easy)](https://leetcode.com/problems/minimum-depth-of-binary-tree/description/)
+
+树的根节点到叶子节点的最小路径长度
```java
-public int findMaxConsecutiveOnes(int[] nums) {
- int max = 0, cur = 0;
- for (int x : nums) {
- cur = x == 0 ? 0 : cur + 1;
- max = Math.max(max, cur);
- }
- return max;
+public int minDepth(TreeNode root) {
+ if (root == null) return 0;
+ int left = minDepth(root.left);
+ int right = minDepth(root.right);
+ if (left == 0 || right == 0) return left + right + 1;
+ return Math.min(left, right) + 1;
}
```
-**一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出重复的数和丢失的数**
+**统计左叶子节点的和**
-[645. Set Mismatch (Easy)](https://leetcode.com/problems/set-mismatch/description/)
+[404. Sum of Left Leaves (Easy)](https://leetcode.com/problems/sum-of-left-leaves/description/)
```html
-Input: nums = [1,2,2,4]
-Output: [2,3]
-```
+ 3
+ / \
+ 9 20
+ / \
+ 15 7
-```html
-Input: nums = [1,2,2,4]
-Output: [2,3]
+There are two left leaves in the binary tree, with values 9 and 15 respectively. Return 24.
```
-最直接的方法是先对数组进行排序,这种方法时间复杂度为 O(NlogN)。本题可以以 O(N) 的时间复杂度、O(1) 空间复杂度来求解。
-
-主要思想是通过交换数组元素,使得数组上的元素在正确的位置上。遍历数组,如果第 i 位上的元素不是 i + 1,那么一直交换第 i 位和 nums[i] - 1 位置上的元素。
-
```java
-public int[] findErrorNums(int[] nums) {
- for (int i = 0; i < nums.length; i++) {
- while (nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]) {
- swap(nums, i, nums[i] - 1);
- }
- }
- for (int i = 0; i < nums.length; i++) {
- if (nums[i] != i + 1) {
- return new int[]{nums[i], i + 1};
- }
- }
- return null;
+public int sumOfLeftLeaves(TreeNode root) {
+ if (root == null) return 0;
+ if (isLeaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right);
+ return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}
-private void swap(int[] nums, int i, int j) {
- int tmp = nums[i];
- nums[i] = nums[j];
- nums[j] = tmp;
+private boolean isLeaf(TreeNode node){
+ if (node == null) return false;
+ return node.left == null && node.right == null;
}
```
-类似题目:
-
-- [448. Find All Numbers Disappeared in an Array (Easy)](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/),寻找所有丢失的元素
-- [442. Find All Duplicates in an Array (Medium)](https://leetcode.com/problems/find-all-duplicates-in-an-array/description/),寻找所有重复的元素。
-
-**找出数组中重复的数,数组值在 [1, n] 之间**
+**相同节点值的最大路径长度**
-[287. Find the Duplicate Number (Medium)](https://leetcode.com/problems/find-the-duplicate-number/description/)
+[687. Longest Univalue Path (Easy)](https://leetcode.com/problems/longest-univalue-path/)
-要求不能修改数组,也不能使用额外的空间。
+```html
+ 1
+ / \
+ 4 5
+ / \ \
+ 4 4 5
-二分查找解法:
+Output : 2
+```
```java
-public int findDuplicate(int[] nums) {
- int l = 1, h = nums.length - 1;
- while (l <= h) {
- int mid = l + (h - l) / 2;
- int cnt = 0;
- for (int i = 0; i < nums.length; i++) {
- if (nums[i] <= mid) cnt++;
- }
- if (cnt > mid) h = mid - 1;
- else l = mid + 1;
- }
- return l;
-}
-```
+private int path = 0;
-双指针解法,类似于有环链表中找出环的入口:
+public int longestUnivaluePath(TreeNode root) {
+ dfs(root);
+ return path;
+}
-```java
-public int findDuplicate(int[] nums) {
- int slow = nums[0], fast = nums[nums[0]];
- while (slow != fast) {
- slow = nums[slow];
- fast = nums[nums[fast]];
- }
- fast = 0;
- while (slow != fast) {
- slow = nums[slow];
- fast = nums[fast];
- }
- return slow;
+private int dfs(TreeNode root){
+ if (root == null) return 0;
+ int left = dfs(root.left);
+ int right = dfs(root.right);
+ int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0;
+ int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0;
+ path = Math.max(path, leftPath + rightPath);
+ return Math.max(leftPath, rightPath);
}
```
-**有序矩阵查找**
+**间隔遍历**
-[240. Search a 2D Matrix II (Medium)](https://leetcode.com/problems/search-a-2d-matrix-ii/description/)
+[337. House Robber III (Medium)](https://leetcode.com/problems/house-robber-iii/description/)
```html
-[
- [ 1, 5, 9],
- [10, 11, 13],
- [12, 13, 15]
-]
+ 3
+ / \
+ 2 3
+ \ \
+ 3 1
+Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.
```
```java
-public boolean searchMatrix(int[][] matrix, int target) {
- if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;
- int m = matrix.length, n = matrix[0].length;
- int row = 0, col = n - 1;
- while (row < m && col >= 0) {
- if (target == matrix[row][col]) return true;
- else if (target < matrix[row][col]) col--;
- else row++;
- }
- return false;
+public int rob(TreeNode root) {
+ if (root == null) return 0;
+ int val1 = root.val;
+ if (root.left != null) val1 += rob(root.left.left) + rob(root.left.right);
+ if (root.right != null) val1 += rob(root.right.left) + rob(root.right.right);
+ int val2 = rob(root.left) + rob(root.right);
+ return Math.max(val1, val2);
}
```
-**有序矩阵的 Kth Element**
+**找出二叉树中第二小的节点**
+
+[671. Second Minimum Node In a Binary Tree (Easy)](https://leetcode.com/problems/second-minimum-node-in-a-binary-tree/description/)
+
+```html
+Input:
+ 2
+ / \
+ 2 5
+ / \
+ 5 7
+
+Output: 5
+```
+
+一个节点要么具有 0 个或 2 个子节点,如果有子节点,那么根节点是最小的节点。
-[378. Kth Smallest Element in a Sorted Matrix ((Medium))](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/)
+```java
+public int findSecondMinimumValue(TreeNode root) {
+ if (root == null) return -1;
+ if (root.left == null && root.right == null) return -1;
+ int leftVal = root.left.val;
+ int rightVal = root.right.val;
+ if (leftVal == root.val) leftVal = findSecondMinimumValue(root.left);
+ if (rightVal == root.val) rightVal = findSecondMinimumValue(root.right);
+ if (leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal);
+ if (leftVal != -1) return leftVal;
+ return rightVal;
+}
+```
-```html
-matrix = [
- [ 1, 5, 9],
- [10, 11, 13],
- [12, 13, 15]
-],
-k = 8,
+### 层次遍历
-return 13.
-```
+使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
-解题参考:[Share my thoughts and Clean Java Code](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/discuss/85173)
+**一棵树每层节点的平均数**
-二分查找解法:
+[637. Average of Levels in Binary Tree (Easy)](https://leetcode.com/problems/average-of-levels-in-binary-tree/description/)
```java
-public int kthSmallest(int[][] matrix, int k) {
- int m = matrix.length, n = matrix[0].length;
- int lo = matrix[0][0], hi = matrix[m - 1][n - 1];
- while(lo <= hi) {
- int mid = lo + (hi - lo) / 2;
- int cnt = 0;
- for(int i = 0; i < m; i++) {
- for(int j = 0; j < n && matrix[i][j] <= mid; j++) {
- cnt++;
- }
+public List averageOfLevels(TreeNode root) {
+ List ret = new ArrayList<>();
+ if (root == null) return ret;
+ Queue queue = new LinkedList<>();
+ queue.add(root);
+ while (!queue.isEmpty()) {
+ int cnt = queue.size();
+ double sum = 0;
+ for (int i = 0; i < cnt; i++) {
+ TreeNode node = queue.poll();
+ sum += node.val;
+ if (node.left != null) queue.add(node.left);
+ if (node.right != null) queue.add(node.right);
}
- if(cnt < k) lo = mid + 1;
- else hi = mid - 1;
+ ret.add(sum / cnt);
}
- return lo;
+ return ret;
}
```
-堆解法:
+**得到左下角的节点**
-```java
-public int kthSmallest(int[][] matrix, int k) {
- int m = matrix.length, n = matrix[0].length;
- PriorityQueue pq = new PriorityQueue();
- for(int j = 0; j < n; j++) pq.offer(new Tuple(0, j, matrix[0][j]));
- for(int i = 0; i < k - 1; i++) { // 小根堆,去掉 k - 1 个堆顶元素,此时堆顶元素就是第 k 的数
- Tuple t = pq.poll();
- if(t.x == m - 1) continue;
- pq.offer(new Tuple(t.x + 1, t.y, matrix[t.x + 1][t.y]));
- }
- return pq.poll().val;
-}
+[513. Find Bottom Left Tree Value (Easy)](https://leetcode.com/problems/find-bottom-left-tree-value/description/)
-class Tuple implements Comparable {
- int x, y, val;
- public Tuple(int x, int y, int val) {
- this.x = x; this.y = y; this.val = val;
- }
+```html
+Input:
- @Override
- public int compareTo(Tuple that) {
- return this.val - that.val;
+ 1
+ / \
+ 2 3
+ / / \
+ 4 5 6
+ /
+ 7
+
+Output:
+7
+```
+
+```java
+public int findBottomLeftValue(TreeNode root) {
+ Queue queue = new LinkedList<>();
+ queue.add(root);
+ while (!queue.isEmpty()) {
+ root = queue.poll();
+ if (root.right != null) queue.add(root.right);
+ if (root.left != null) queue.add(root.left);
}
+ return root.val;
}
```
-**数组相邻差值的个数**
-
-[667. Beautiful Arrangement II (Medium)](https://leetcode.com/problems/beautiful-arrangement-ii/description/)
+### 前中后序遍历
```html
-Input: n = 3, k = 2
-Output: [1, 3, 2]
-Explanation: The [1, 3, 2] has three different positive integers ranging from 1 to 3, and the [2, 1] has exactly 2 distinct integers: 1 and 2.
+ 1
+ / \
+ 2 3
+ / \ \
+4 5 6
```
-题目描述:数组元素为 1\~n 的整数,要求构建数组,使得相邻元素的差值不相同的个数为 k。
+- 层次遍历顺序:[1 2 3 4 5 6]
+- 前序遍历顺序:[1 2 4 5 3 6]
+- 中序遍历顺序:[4 2 5 1 3 6]
+- 后序遍历顺序:[4 5 2 6 3 1]
-让前 k+1 个元素构建出 k 个不相同的差值,序列为:1 k+1 2 k 3 k-1 ... k/2 k/2+1.
+层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。
+
+前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。
+
+① 前序
```java
-public int[] constructArray(int n, int k) {
- int[] ret = new int[n];
- ret[0] = 1;
- for (int i = 1, interval = k; i <= k; i++, interval--) {
- ret[i] = i % 2 == 1 ? ret[i - 1] + interval : ret[i - 1] - interval;
- }
- for (int i = k + 1; i < n; i++) {
- ret[i] = i + 1;
- }
- return ret;
+void dfs(TreeNode root) {
+ visit(root);
+ dfs(root.left);
+ dfs(root.right);
}
```
-**数组的度**
-
-[697. Degree of an Array (Easy)](https://leetcode.com/problems/degree-of-an-array/description/)
+② 中序
-```html
-Input: [1,2,2,3,1,4,2]
-Output: 6
+```java
+void dfs(TreeNode root) {
+ dfs(root.left);
+ visit(root);
+ dfs(root.right);
+}
```
-题目描述:数组的度定义为元素出现的最高频率,例如上面的数组度为 3。要求找到一个最小的子数组,这个子数组的度和原数组一样。
+③ 后序
```java
-public int findShortestSubArray(int[] nums) {
- Map numsCnt = new HashMap<>();
- Map numsLastIndex = new HashMap<>();
- Map numsFirstIndex = new HashMap<>();
- for (int i = 0; i < nums.length; i++) {
- int num = nums[i];
- numsCnt.put(num, numsCnt.getOrDefault(num, 0) + 1);
- numsLastIndex.put(num, i);
- if (!numsFirstIndex.containsKey(num)) {
- numsFirstIndex.put(num, i);
- }
- }
- int maxCnt = 0;
- for (int num : nums) {
- maxCnt = Math.max(maxCnt, numsCnt.get(num));
- }
- int ret = nums.length;
- for (int i = 0; i < nums.length; i++) {
- int num = nums[i];
- int cnt = numsCnt.get(num);
- if (cnt != maxCnt) continue;
- ret = Math.min(ret, numsLastIndex.get(num) - numsFirstIndex.get(num) + 1);
- }
- return ret;
+void dfs(TreeNode root) {
+ dfs(root.left);
+ dfs(root.right);
+ visit(root);
}
```
-**对角元素相等的矩阵**
-
-[766. Toeplitz Matrix (Easy)](https://leetcode.com/problems/toeplitz-matrix/description/)
-
-```html
-1234
-5123
-9512
+**非递归实现二叉树的前序遍历**
-In the above grid, the diagonals are "[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]", and in each diagonal all elements are the same, so the answer is True.
-```
+[144. Binary Tree Preorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-preorder-traversal/description/)
```java
-public boolean isToeplitzMatrix(int[][] matrix) {
- for (int i = 0; i < matrix[0].length; i++) {
- if (!check(matrix, matrix[0][i], 0, i)) {
- return false;
- }
- }
- for (int i = 0; i < matrix.length; i++) {
- if (!check(matrix, matrix[i][0], i, 0)) {
- return false;
- }
- }
- return true;
-}
-
-private boolean check(int[][] matrix, int expectValue, int row, int col) {
- if (row >= matrix.length || col >= matrix[0].length) {
- return true;
- }
- if (matrix[row][col] != expectValue) {
- return false;
+public List preorderTraversal(TreeNode root) {
+ List ret = new ArrayList<>();
+ Stack stack = new Stack<>();
+ stack.push(root);
+ while (!stack.isEmpty()) {
+ TreeNode node = stack.pop();
+ if (node == null) continue;
+ ret.add(node.val);
+ stack.push(node.right); // 先右后左,保证左子树先遍历
+ stack.push(node.left);
}
- return check(matrix, expectValue, row + 1, col + 1);
+ return ret;
}
```
-**嵌套数组**
-
-[565. Array Nesting (Medium)](https://leetcode.com/problems/array-nesting/description/)
-
-```html
-Input: A = [5,4,0,3,1,6,2]
-Output: 4
-Explanation:
-A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2.
+**非递归实现二叉树的后序遍历**
-One of the longest S[K]:
-S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0}
-```
+[145. Binary Tree Postorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-postorder-traversal/description/)
-题目描述:S[i] 表示一个集合,集合的第一个元素是 A[i],第二个元素是 A[A[i]],如此嵌套下去。求最大的 S[i]。
+前序遍历为 root -> left -> right,后序遍历为 left -> right -> root。可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。
```java
-public int arrayNesting(int[] nums) {
- int max = 0;
- for (int i = 0; i < nums.length; i++) {
- int cnt = 0;
- for (int j = i; nums[j] != -1; ) {
- cnt++;
- int t = nums[j];
- nums[j] = -1; // 标记该位置已经被访问
- j = t;
-
- }
- max = Math.max(max, cnt);
+public List postorderTraversal(TreeNode root) {
+ List ret = new ArrayList<>();
+ Stack stack = new Stack<>();
+ stack.push(root);
+ while (!stack.isEmpty()) {
+ TreeNode node = stack.pop();
+ if (node == null) continue;
+ ret.add(node.val);
+ stack.push(node.left);
+ stack.push(node.right);
}
- return max;
+ Collections.reverse(ret);
+ return ret;
}
```
-**分隔数组**
-
-[769. Max Chunks To Make Sorted (Medium)](https://leetcode.com/problems/max-chunks-to-make-sorted/description/)
-
-```html
-Input: arr = [1,0,2,3,4]
-Output: 4
-Explanation:
-We can split into two chunks, such as [1, 0], [2, 3, 4].
-However, splitting into [1, 0], [2], [3], [4] is the highest number of chunks possible.
-```
+**非递归实现二叉树的中序遍历**
-题目描述:分隔数组,使得对每部分排序后数组就为有序。
+[94. Binary Tree Inorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-inorder-traversal/description/)
```java
-public int maxChunksToSorted(int[] arr) {
- if (arr == null) return 0;
- int ret = 0;
- int right = arr[0];
- for (int i = 0; i < arr.length; i++) {
- right = Math.max(right, arr[i]);
- if (right == i) ret++;
+public List inorderTraversal(TreeNode root) {
+ List ret = new ArrayList<>();
+ if (root == null) return ret;
+ Stack stack = new Stack<>();
+ TreeNode cur = root;
+ while (cur != null || !stack.isEmpty()) {
+ while (cur != null) {
+ stack.push(cur);
+ cur = cur.left;
+ }
+ TreeNode node = stack.pop();
+ ret.add(node.val);
+ cur = node.right;
}
return ret;
}
```
-## 链表
+### BST
-链表是空节点,或者有一个值和一个指向下一个链表的指针,因此很多链表问题可以用递归来处理。
+二叉查找树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点。
-**找出两个链表的交点**
+二叉查找树中序遍历有序。
-[160. Intersection of Two Linked Lists (Easy)](https://leetcode.com/problems/intersection-of-two-linked-lists/description/)
+**修剪二叉查找树**
+
+[669. Trim a Binary Search Tree (Easy)](https://leetcode.com/problems/trim-a-binary-search-tree/description/)
```html
-A: a1 → a2
- ↘
- c1 → c2 → c3
- ↗
-B: b1 → b2 → b3
-```
+Input:
-要求:时间复杂度为 O(N),空间复杂度为 O(1)
+ 3
+ / \
+ 0 4
+ \
+ 2
+ /
+ 1
-设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。
+ L = 1
+ R = 3
-当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
+Output:
+
+ 3
+ /
+ 2
+ /
+ 1
+```
+
+题目描述:只保留值在 L \~ R 之间的节点
```java
-public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
- ListNode l1 = headA, l2 = headB;
- while (l1 != l2) {
- l1 = (l1 == null) ? headB : l1.next;
- l2 = (l2 == null) ? headA : l2.next;
- }
- return l1;
+public TreeNode trimBST(TreeNode root, int L, int R) {
+ if (root == null) return null;
+ if (root.val > R) return trimBST(root.left, L, R);
+ if (root.val < L) return trimBST(root.right, L, R);
+ root.left = trimBST(root.left, L, R);
+ root.right = trimBST(root.right, L, R);
+ return root;
}
```
-如果只是判断是否存在交点,那么就是另一个问题,即 [编程之美:3.6]() 的问题。有两种解法:把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环;或者直接比较两个链表的最后一个节点是否相同。
+**寻找二叉查找树的第 k 个元素**
-**链表反转**
+[230. Kth Smallest Element in a BST (Medium)](https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/)
-[206. Reverse Linked List (Easy)](https://leetcode.com/problems/reverse-linked-list/description/)
-递归
+中序遍历解法:
```java
-public ListNode reverseList(ListNode head) {
- if (head == null || head.next == null) {
- return head;
- }
- ListNode next = head.next;
- ListNode newHead = reverseList(next);
- next.next = head;
- head.next = null;
- return newHead;
-}
-```
+private int cnt = 0;
+private int val;
-头插法
+public int kthSmallest(TreeNode root, int k) {
+ inOrder(root, k);
+ return val;
+}
-```java
-public ListNode reverseList(ListNode head) {
- ListNode newHead = new ListNode(-1);
- while (head != null) {
- ListNode next = head.next;
- head.next = newHead.next;
- newHead.next = head;
- head = next;
+private void inOrder(TreeNode node, int k) {
+ if (node == null) return;
+ inOrder(node.left, k);
+ cnt++;
+ if (cnt == k) {
+ val = node.val;
+ return;
}
- return newHead.next;
+ inOrder(node.right, k);
}
```
-**归并两个有序的链表**
-
-[21. Merge Two Sorted Lists (Easy)](https://leetcode.com/problems/merge-two-sorted-lists/description/)
+递归解法:
```java
-public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
- if (l1 == null) return l2;
- if (l2 == null) return l1;
- if (l1.val < l2.val) {
- l1.next = mergeTwoLists(l1.next, l2);
- return l1;
- } else {
- l2.next = mergeTwoLists(l1, l2.next);
- return l2;
- }
+public int kthSmallest(TreeNode root, int k) {
+ int leftCnt = count(root.left);
+ if (leftCnt == k - 1) return root.val;
+ if (leftCnt > k - 1) return kthSmallest(root.left, k);
+ return kthSmallest(root.right, k - leftCnt - 1);
+}
+
+private int count(TreeNode node) {
+ if (node == null) return 0;
+ return 1 + count(node.left) + count(node.right);
}
```
-**从有序链表中删除重复节点**
+**把二叉查找树每个节点的值都加上比它大的节点的值**
-[83. Remove Duplicates from Sorted List (Easy)](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/)
+[Convert BST to Greater Tree (Easy)](https://leetcode.com/problems/convert-bst-to-greater-tree/description/)
```html
-Given 1->1->2, return 1->2.
-Given 1->1->2->3->3, return 1->2->3.
+Input: The root of a Binary Search Tree like this:
+
+ 5
+ / \
+ 2 13
+
+Output: The root of a Greater Tree like this:
+
+ 18
+ / \
+ 20 13
```
+先遍历右子树。
+
```java
-public ListNode deleteDuplicates(ListNode head) {
- if(head == null || head.next == null) return head;
- head.next = deleteDuplicates(head.next);
- return head.val == head.next.val ? head.next : head;
+private int sum = 0;
+
+public TreeNode convertBST(TreeNode root) {
+ traver(root);
+ return root;
+}
+
+private void traver(TreeNode node) {
+ if (node == null) return;
+ traver(node.right);
+ sum += node.val;
+ node.val = sum;
+ traver(node.left);
}
```
-**删除链表的倒数第 n 个节点**
+**二叉查找树的最近公共祖先**
-[19. Remove Nth Node From End of List (Medium)](https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/)
+[235. Lowest Common Ancestor of a Binary Search Tree (Easy)](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/)
```html
-Given linked list: 1->2->3->4->5, and n = 2.
-After removing the second node from the end, the linked list becomes 1->2->3->5.
+ _______6______
+ / \
+ ___2__ ___8__
+ / \ / \
+0 4 7 9
+ / \
+ 3 5
+
+For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another example is LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.
```
```java
-public ListNode removeNthFromEnd(ListNode head, int n) {
- ListNode fast = head;
- while (n-- > 0) {
- fast = fast.next;
- }
- if (fast == null) return head.next;
- ListNode slow = head;
- while (fast.next != null) {
- fast = fast.next;
- slow = slow.next;
- }
- slow.next = slow.next.next;
- return head;
+public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
+ 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;
}
```
-**交换链表中的相邻结点**
+**二叉树的最近公共祖先**
-[24. Swap Nodes in Pairs (Medium)](https://leetcode.com/problems/swap-nodes-in-pairs/description/)
+[236. Lowest Common Ancestor of a Binary Tree (Medium) ](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/)
```html
-Given 1->2->3->4, you should return the list as 2->1->4->3.
-```
-
-题目要求:不能修改结点的 val 值,O(1) 空间复杂度。
+ _______3______
+ / \
+ ___5__ ___1__
+ / \ / \
+6 2 0 8
+ / \
+ 7 4
-```java
-public ListNode swapPairs(ListNode head) {
- ListNode node = new ListNode(-1);
- node.next = head;
- ListNode pre = node;
- while (pre.next != null && pre.next.next != null) {
- ListNode l1 = pre.next, l2 = pre.next.next;
- ListNode next = l2.next;
- l1.next = next;
- l2.next = l1;
- pre.next = l2;
+For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.
+```
- pre = l1;
- }
- return node.next;
+```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;
}
```
-**链表求和**
-
-[445. Add Two Numbers II (Medium)](https://leetcode.com/problems/add-two-numbers-ii/description/)
-
-```html
-Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
-Output: 7 -> 8 -> 0 -> 7
-```
+**从有序数组中构造二叉查找树**
-题目要求:不能修改原始链表。
+[108. Convert Sorted Array to Binary Search Tree (Easy)](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/)
```java
-public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
- Stack l1Stack = buildStack(l1);
- Stack l2Stack = buildStack(l2);
- ListNode head = new ListNode(-1);
- int carry = 0;
- while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) {
- int x = l1Stack.isEmpty() ? 0 : l1Stack.pop();
- int y = l2Stack.isEmpty() ? 0 : l2Stack.pop();
- int sum = x + y + carry;
- ListNode node = new ListNode(sum % 10);
- node.next = head.next;
- head.next = node;
- carry = sum / 10;
- }
- return head.next;
+public TreeNode sortedArrayToBST(int[] nums) {
+ return toBST(nums, 0, nums.length - 1);
}
-private Stack buildStack(ListNode l) {
- Stack stack = new Stack<>();
- while (l != null) {
- stack.push(l.val);
- l = l.next;
- }
- return stack;
+private TreeNode toBST(int[] nums, int sIdx, int eIdx){
+ if (sIdx > eIdx) return null;
+ int mIdx = (sIdx + eIdx) / 2;
+ TreeNode root = new TreeNode(nums[mIdx]);
+ root.left = toBST(nums, sIdx, mIdx - 1);
+ root.right = toBST(nums, mIdx + 1, eIdx);
+ return root;
}
```
-**回文链表**
+**根据有序链表构造平衡的二叉查找树**
-[234. Palindrome Linked List (Easy)](https://leetcode.com/problems/palindrome-linked-list/description/)
+[109. Convert Sorted List to Binary Search Tree (Medium)](https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/description/)
-题目要求:以 O(1) 的空间复杂度来求解。
+```html
+Given the sorted linked list: [-10,-3,0,5,9],
-切成两半,把后半段反转,然后比较两半是否相等。
+One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST:
+
+ 0
+ / \
+ -3 9
+ / /
+ -10 5
+```
```java
-public boolean isPalindrome(ListNode head) {
- if (head == null || head.next == null) return true;
+public TreeNode sortedListToBST(ListNode head) {
+ if (head == null) return null;
+ if (head.next == null) return new TreeNode(head.val);
+ ListNode preMid = preMid(head);
+ ListNode mid = preMid.next;
+ preMid.next = null; // 断开链表
+ TreeNode t = new TreeNode(mid.val);
+ t.left = sortedListToBST(head);
+ t.right = sortedListToBST(mid.next);
+ return t;
+}
+
+private ListNode preMid(ListNode head) {
ListNode slow = head, fast = head.next;
+ ListNode pre = head;
while (fast != null && fast.next != null) {
+ pre = slow;
slow = slow.next;
fast = fast.next.next;
}
- if (fast != null) slow = slow.next; // 偶数节点,让 slow 指向下一个节点
- cut(head, slow); // 切成两个链表
- return isEqual(head, reverse(slow));
+ return pre;
}
+```
-private void cut(ListNode head, ListNode cutNode) {
- while (head.next != cutNode) {
- head = head.next;
- }
- head.next = null;
-}
+**在二叉查找树中寻找两个节点,使它们的和为一个给定值**
-private ListNode reverse(ListNode head) {
- ListNode newHead = null;
- while (head != null) {
- ListNode nextNode = head.next;
- head.next = newHead;
- newHead = head;
- head = nextNode;
+[653. Two Sum IV - Input is a BST (Easy)](https://leetcode.com/problems/two-sum-iv-input-is-a-bst/description/)
+
+```html
+Input:
+
+ 5
+ / \
+ 3 6
+ / \ \
+2 4 7
+
+Target = 9
+
+Output: True
+```
+
+使用中序遍历得到有序数组之后,再利用双指针对数组进行查找。
+
+应该注意到,这一题不能用分别在左右子树两部分来处理这种思想,因为两个待求的节点可能分别在左右子树中。
+
+```java
+public boolean findTarget(TreeNode root, int k) {
+ List nums = new ArrayList<>();
+ inOrder(root, nums);
+ int i = 0, j = nums.size() - 1;
+ while (i < j) {
+ int sum = nums.get(i) + nums.get(j);
+ if (sum == k) return true;
+ if (sum < k) i++;
+ else j--;
}
- return newHead;
+ return false;
}
-private boolean isEqual(ListNode l1, ListNode l2) {
- while (l1 != null && l2 != null) {
- if (l1.val != l2.val) return false;
- l1 = l1.next;
- l2 = l2.next;
- }
- return true;
+private void inOrder(TreeNode root, List nums) {
+ if (root == null) return;
+ inOrder(root.left, nums);
+ nums.add(root.val);
+ inOrder(root.right, nums);
}
```
-**分隔链表**
+**在二叉查找树中查找两个节点之差的最小绝对值**
-[725. Split Linked List in Parts(Medium)](https://leetcode.com/problems/split-linked-list-in-parts/description/)
+[530. Minimum Absolute Difference in BST (Easy)](https://leetcode.com/problems/minimum-absolute-difference-in-bst/description/)
```html
Input:
-root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
-Output: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
-Explanation:
-The input has been split into consecutive parts with size difference at most 1, and earlier parts are a larger size than the later parts.
+
+ 1
+ \
+ 3
+ /
+ 2
+
+Output:
+
+1
```
-题目描述:把链表分隔成 k 部分,每部分的长度都应该尽可能相同,排在前面的长度应该大于等于后面的。
+利用二叉查找树的中序遍历为有序的性质,计算中序遍历中临近的两个节点之差的绝对值,取最小值。
```java
-public ListNode[] splitListToParts(ListNode root, int k) {
- int N = 0;
- ListNode cur = root;
- while (cur != null) {
- N++;
- cur = cur.next;
- }
- int mod = N % k;
- int size = N / k;
- ListNode[] ret = new ListNode[k];
- cur = root;
- for (int i = 0; cur != null && i < k; i++) {
- ret[i] = cur;
- int curSize = size + (mod-- > 0 ? 1 : 0);
- for (int j = 0; j < curSize - 1; j++) {
- cur = cur.next;
- }
- ListNode next = cur.next;
- cur.next = null;
- cur = next;
- }
- return ret;
+private int minDiff = Integer.MAX_VALUE;
+private TreeNode preNode = null;
+
+public int getMinimumDifference(TreeNode root) {
+ inOrder(root);
+ return minDiff;
+}
+
+private void inOrder(TreeNode node) {
+ if (node == null) return;
+ inOrder(node.left);
+ if (preNode != null) minDiff = Math.min(minDiff, node.val - preNode.val);
+ preNode = node;
+ inOrder(node.right);
}
```
-**链表元素按奇偶聚集**
+**寻找二叉查找树中出现次数最多的值**
-[328. Odd Even Linked List (Medium)](https://leetcode.com/problems/odd-even-linked-list/description/)
+[501. Find Mode in Binary Search Tree (Easy)](https://leetcode.com/problems/find-mode-in-binary-search-tree/description/)
```html
-Example:
-Given 1->2->3->4->5->NULL,
-return 1->3->5->2->4->NULL.
+ 1
+ \
+ 2
+ /
+ 2
+
+return [2].
```
+答案可能不止一个,也就是有多个值出现的次数一样多。
+
```java
-public ListNode oddEvenList(ListNode head) {
- if (head == null) {
- return head;
+private int curCnt = 1;
+private int maxCnt = 1;
+private TreeNode preNode = null;
+
+public int[] findMode(TreeNode root) {
+ List maxCntNums = new ArrayList<>();
+ inOrder(root, maxCntNums);
+ int[] ret = new int[maxCntNums.size()];
+ int idx = 0;
+ for (int num : maxCntNums) {
+ ret[idx++] = num;
}
- ListNode odd = head, even = head.next, evenHead = even;
- while (even != null && even.next != null) {
- odd.next = odd.next.next;
- odd = odd.next;
- even.next = even.next.next;
- even = even.next;
+ return ret;
+}
+
+private void inOrder(TreeNode node, List nums) {
+ if (node == null) return;
+ inOrder(node.left, nums);
+ if (preNode != null) {
+ if (preNode.val == node.val) curCnt++;
+ else curCnt = 1;
}
- odd.next = evenHead;
- return head;
+ if (curCnt > maxCnt) {
+ maxCnt = curCnt;
+ nums.clear();
+ nums.add(node.val);
+ } else if (curCnt == maxCnt) {
+ nums.add(node.val);
+ }
+ preNode = node;
+ inOrder(node.right, nums);
}
```
-## 树
+### Trie
-### 递归
+
-一棵树要么是空树,要么有两个指针,每个指针指向一棵树。树是一种递归结构,很多树的问题可以使用递归来处理。
+Trie,又称前缀树或字典树,用于判断字符串是否存在或者是否具有某种字符串前缀。
-**树的高度**
+**实现一个 Trie**
-[104. Maximum Depth of Binary Tree (Easy)](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/)
+[208. Implement Trie (Prefix Tree) (Medium)](https://leetcode.com/problems/implement-trie-prefix-tree/description/)
```java
-public int maxDepth(TreeNode root) {
- if (root == null) return 0;
- return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
-}
-```
+class Trie {
-**平衡树**
+ private class Node {
+ Node[] childs = new Node[26];
+ boolean isLeaf;
+ }
-[110. Balanced Binary Tree (Easy)](https://leetcode.com/problems/balanced-binary-tree/description/)
+ private Node root = new Node();
-```html
- 3
- / \
- 9 20
- / \
- 15 7
-```
+ public Trie() {
+ }
-平衡树左右子树高度差都小于等于 1
+ public void insert(String word) {
+ insert(word, root);
+ }
-```java
-private boolean result = true;
+ private void insert(String word, Node node) {
+ if (node == null) return;
+ if (word.length() == 0) {
+ node.isLeaf = true;
+ return;
+ }
+ int index = indexForChar(word.charAt(0));
+ if (node.childs[index] == null) {
+ node.childs[index] = new Node();
+ }
+ insert(word.substring(1), node.childs[index]);
+ }
-public boolean isBalanced(TreeNode root) {
- maxDepth(root);
- return result;
-}
+ public boolean search(String word) {
+ return search(word, root);
+ }
-public int maxDepth(TreeNode root) {
- if (root == null) return 0;
- int l = maxDepth(root.left);
- int r = maxDepth(root.right);
- if (Math.abs(l - r) > 1) result = false;
- return 1 + Math.max(l, r);
+ private boolean search(String word, Node node) {
+ if (node == null) return false;
+ if (word.length() == 0) return node.isLeaf;
+ int index = indexForChar(word.charAt(0));
+ return search(word.substring(1), node.childs[index]);
+ }
+
+ public boolean startsWith(String prefix) {
+ return startWith(prefix, root);
+ }
+
+ private boolean startWith(String prefix, Node node) {
+ if (node == null) return false;
+ if (prefix.length() == 0) return true;
+ int index = indexForChar(prefix.charAt(0));
+ return startWith(prefix.substring(1), node.childs[index]);
+ }
+
+ private int indexForChar(char c) {
+ return c - 'a';
+ }
}
```
-**两节点的最长路径**
+**实现一个 Trie,用来求前缀和**
-[543. Diameter of Binary Tree (Easy)](https://leetcode.com/problems/diameter-of-binary-tree/description/)
+[677. Map Sum Pairs (Medium)](https://leetcode.com/problems/map-sum-pairs/description/)
```html
-Input:
-
- 1
- / \
- 2 3
- / \
- 4 5
-
-Return 3, which is the length of the path [4,2,1,3] or [5,2,1,3].
+Input: insert("apple", 3), Output: Null
+Input: sum("ap"), Output: 3
+Input: insert("app", 2), Output: Null
+Input: sum("ap"), Output: 5
```
```java
-private int max = 0;
+class MapSum {
-public int diameterOfBinaryTree(TreeNode root) {
- depth(root);
- return max;
-}
+ private class Node {
+ Node[] child = new Node[26];
+ int value;
+ }
-private int depth(TreeNode root) {
- if (root == null) return 0;
- int leftDepth = depth(root.left);
- int rightDepth = depth(root.right);
- max = Math.max(max, leftDepth + rightDepth);
- return Math.max(leftDepth, rightDepth) + 1;
-}
-```
+ private Node root = new Node();
-**翻转树**
+ public MapSum() {
-[226. Invert Binary Tree (Easy)](https://leetcode.com/problems/invert-binary-tree/description/)
+ }
-```java
-public TreeNode invertTree(TreeNode root) {
- if (root == null) return null;
- TreeNode left = root.left; // 后面的操作会改变 left 指针,因此先保存下来
- root.left = invertTree(root.right);
- root.right = invertTree(left);
- return root;
-}
-```
+ public void insert(String key, int val) {
+ insert(key, root, val);
+ }
-**归并两棵树**
+ private void insert(String key, Node node, int val) {
+ if (node == null) return;
+ if (key.length() == 0) {
+ node.value = val;
+ return;
+ }
+ int index = indexForChar(key.charAt(0));
+ if (node.child[index] == null) {
+ node.child[index] = new Node();
+ }
+ insert(key.substring(1), node.child[index], val);
+ }
-[617. Merge Two Binary Trees (Easy)](https://leetcode.com/problems/merge-two-binary-trees/description/)
+ public int sum(String prefix) {
+ return sum(prefix, root);
+ }
-```html
-Input:
- Tree 1 Tree 2
- 1 2
- / \ / \
- 3 2 1 3
- / \ \
- 5 4 7
-Output:
-Merged tree:
- 3
- / \
- 4 5
- / \ \
- 5 4 7
-```
+ private int sum(String prefix, Node node) {
+ if (node == null) return 0;
+ if (prefix.length() != 0) {
+ int index = indexForChar(prefix.charAt(0));
+ return sum(prefix.substring(1), node.child[index]);
+ }
+ int sum = node.value;
+ for (Node child : node.child) {
+ sum += sum(prefix, child);
+ }
+ return sum;
+ }
-```java
-public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
- if (t1 == null && t2 == null) return null;
- if (t1 == null) return t2;
- if (t2 == null) return t1;
- TreeNode root = new TreeNode(t1.val + t2.val);
- root.left = mergeTrees(t1.left, t2.left);
- root.right = mergeTrees(t1.right, t2.right);
- return root;
+ private int indexForChar(char c) {
+ return c - 'a';
+ }
}
```
-**判断路径和是否等于一个数**
-[Leetcdoe : 112. Path Sum (Easy)](https://leetcode.com/problems/path-sum/description/)
+## 栈和队列
-```html
-Given the below binary tree and sum = 22,
- 5
- / \
- 4 8
- / / \
- 11 13 4
- / \ \
- 7 2 1
-return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.
-```
+**用栈实现队列**
+
+[232. Implement Queue using Stacks (Easy)](https://leetcode.com/problems/implement-queue-using-stacks/description/)
-路径和定义为从 root 到 leaf 的所有节点的和
+栈的顺序为后进先出,而队列的顺序为先进先出。使用两个栈实现队列,一个元素需要经过两个栈才能出队列,在经过第一个栈时元素顺序被反转,经过第二个栈时再次被反转,此时就是先进先出顺序。
```java
-public boolean hasPathSum(TreeNode root, int sum) {
- if (root == null) return false;
- if (root.left == null && root.right == null && root.val == sum) return true;
- return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
-}
-```
+class MyQueue {
-**统计路径和等于一个数的路径数量**
+ private Stack in = new Stack<>();
+ private Stack out = new Stack<>();
-[437. Path Sum III (Easy)](https://leetcode.com/problems/path-sum-iii/description/)
+ public void push(int x) {
+ in.push(x);
+ }
-```html
-root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
+ public int pop() {
+ in2out();
+ return out.pop();
+ }
- 10
- / \
- 5 -3
- / \ \
- 3 2 11
- / \ \
-3 -2 1
+ public int peek() {
+ in2out();
+ return out.peek();
+ }
+
+ private void in2out() {
+ if (out.isEmpty()) {
+ while (!in.isEmpty()) {
+ out.push(in.pop());
+ }
+ }
+ }
+
+ public boolean empty() {
+ return in.isEmpty() && out.isEmpty();
+ }
+}
+```
-Return 3. The paths that sum to 8 are:
+**用队列实现栈**
-1. 5 -> 3
-2. 5 -> 2 -> 1
-3. -3 -> 11
-```
+[225. Implement Stack using Queues (Easy)](https://leetcode.com/problems/implement-stack-using-queues/description/)
-路径不一定以 root 开头,也不一定以 leaf 结尾,但是必须连续。
+在将一个元素 x 插入队列时,为了维护原来的后进先出顺序,需要让 x 插入队列首部。而队列的默认插入顺序是队列尾部,因此在将 x 插入队列尾部之后,需要让除了 x 之外的所有元素出队列,再入队列。
```java
-public int pathSum(TreeNode root, int sum) {
- if (root == null) return 0;
- int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
- return ret;
-}
+class MyStack {
-private int pathSumStartWithRoot(TreeNode root, int sum) {
- if (root == null) return 0;
- int ret = 0;
- if (root.val == sum) ret++;
- ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val);
- return ret;
-}
-```
+ private Queue queue;
-**子树**
+ public MyStack() {
+ queue = new LinkedList<>();
+ }
-[572. Subtree of Another Tree (Easy)](https://leetcode.com/problems/subtree-of-another-tree/description/)
+ public void push(int x) {
+ queue.add(x);
+ int cnt = queue.size();
+ while (cnt-- > 1) {
+ queue.add(queue.poll());
+ }
+ }
-```html
-Given tree s:
- 3
- / \
- 4 5
- / \
- 1 2
+ public int pop() {
+ return queue.remove();
+ }
-Given tree t:
- 4
- / \
- 1 2
+ public int top() {
+ return queue.peek();
+ }
-Return true, because t has the same structure and node values with a subtree of s.
+ public boolean empty() {
+ return queue.isEmpty();
+ }
+}
+```
-Given tree s:
+**最小值栈**
- 3
- / \
- 4 5
- / \
- 1 2
- /
- 0
+[155. Min Stack (Easy)](https://leetcode.com/problems/min-stack/description/)
-Given tree t:
- 4
- / \
- 1 2
+```java
+class MinStack {
-Return false.
-```
+ private Stack dataStack;
+ private Stack minStack;
+ private int min;
-```java
-public boolean isSubtree(TreeNode s, TreeNode t) {
- if (s == null) return false;
- return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);
-}
+ public MinStack() {
+ dataStack = new Stack<>();
+ minStack = new Stack<>();
+ min = Integer.MAX_VALUE;
+ }
-private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
- if (t == null && s == null) return true;
- if (t == null || s == null) return false;
- if (t.val != s.val) return false;
- return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
+ public void push(int x) {
+ dataStack.add(x);
+ min = Math.min(min, x);
+ minStack.add(min);
+ }
+
+ public void pop() {
+ dataStack.pop();
+ minStack.pop();
+ min = minStack.isEmpty() ? Integer.MAX_VALUE : minStack.peek();
+ }
+
+ public int top() {
+ return dataStack.peek();
+ }
+
+ public int getMin() {
+ return minStack.peek();
+ }
}
```
-**树的对称**
+对于实现最小值队列问题,可以先将队列使用栈来实现,然后就将问题转换为最小值栈,这个问题出现在 编程之美:3.7。
-[101. Symmetric Tree (Easy)](https://leetcode.com/problems/symmetric-tree/description/)
+**用栈实现括号匹配**
+
+[20. Valid Parentheses (Easy)](https://leetcode.com/problems/valid-parentheses/description/)
```html
- 1
- / \
- 2 2
- / \ / \
-3 4 4 3
+"()[]{}"
+
+Output : true
```
```java
-public boolean isSymmetric(TreeNode root) {
- if (root == null) return true;
- return isSymmetric(root.left, root.right);
-}
-
-private boolean isSymmetric(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 isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left);
+public boolean isValid(String s) {
+ Stack stack = new Stack<>();
+ for (char c : s.toCharArray()) {
+ if (c == '(' || c == '{' || c == '[') {
+ stack.push(c);
+ } else {
+ if (stack.isEmpty()) {
+ return false;
+ }
+ char cStack = stack.pop();
+ boolean b1 = c == ')' && cStack != '(';
+ boolean b2 = c == ']' && cStack != '[';
+ boolean b3 = c == '}' && cStack != '{';
+ if (b1 || b2 || b3) {
+ return false;
+ }
+ }
+ }
+ return stack.isEmpty();
}
```
-**最小路径**
+**数组中元素与下一个比它大的元素之间的距离**
-[111. Minimum Depth of Binary Tree (Easy)](https://leetcode.com/problems/minimum-depth-of-binary-tree/description/)
+[739. Daily Temperatures (Medium)](https://leetcode.com/problems/daily-temperatures/description/)
-树的根节点到叶子节点的最小路径长度
+```html
+Input: [73, 74, 75, 71, 69, 72, 76, 73]
+Output: [1, 1, 4, 2, 1, 1, 0, 0]
+```
+
+在遍历数组时用栈把数组中的数存起来,如果当前遍历的数比栈顶元素来的大,说明栈顶元素的下一个比它大的数就是当前元素。
```java
-public int minDepth(TreeNode root) {
- if (root == null) return 0;
- int left = minDepth(root.left);
- int right = minDepth(root.right);
- if (left == 0 || right == 0) return left + right + 1;
- return Math.min(left, right) + 1;
+public int[] dailyTemperatures(int[] temperatures) {
+ int n = temperatures.length;
+ int[] dist = new int[n];
+ Stack indexs = new Stack<>();
+ for (int curIndex = 0; curIndex < n; curIndex++) {
+ while (!indexs.isEmpty() && temperatures[curIndex] > temperatures[indexs.peek()]) {
+ int preIndex = indexs.pop();
+ dist[preIndex] = curIndex - preIndex;
+ }
+ indexs.add(curIndex);
+ }
+ return dist;
}
```
-**统计左叶子节点的和**
-
-[404. Sum of Left Leaves (Easy)](https://leetcode.com/problems/sum-of-left-leaves/description/)
+**循环数组中比当前元素大的下一个元素**
-```html
- 3
- / \
- 9 20
- / \
- 15 7
+[503. Next Greater Element II (Medium)](https://leetcode.com/problems/next-greater-element-ii/description/)
-There are two left leaves in the binary tree, with values 9 and 15 respectively. Return 24.
+```text
+Input: [1,2,1]
+Output: [2,-1,2]
+Explanation: The first 1's next greater number is 2;
+The number 2 can't find next greater number;
+The second 1's next greater number needs to search circularly, which is also 2.
```
-```java
-public int sumOfLeftLeaves(TreeNode root) {
- if (root == null) return 0;
- if (isLeaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right);
- return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
-}
+与 739. Daily Temperatures (Medium) 不同的是,数组是循环数组,并且最后要求的不是距离而是下一个元素。
-private boolean isLeaf(TreeNode node){
- if (node == null) return false;
- return node.left == null && node.right == null;
+```java
+public int[] nextGreaterElements(int[] nums) {
+ int n = nums.length;
+ int[] next = new int[n];
+ Arrays.fill(next, -1);
+ Stack pre = new Stack<>();
+ for (int i = 0; i < n * 2; i++) {
+ int num = nums[i % n];
+ while (!pre.isEmpty() && nums[pre.peek()] < num) {
+ next[pre.pop()] = num;
+ }
+ if (i < n){
+ pre.push(i);
+ }
+ }
+ return next;
}
```
-**相同节点值的最大路径长度**
+## 哈希表
-[687. Longest Univalue Path (Easy)](https://leetcode.com/problems/longest-univalue-path/)
+哈希表使用 O(N) 空间复杂度存储数据,并且以 O(1) 时间复杂度求解问题。
-```html
- 1
- / \
- 4 5
- / \ \
- 4 4 5
+- Java 中的 **HashSet** 用于存储一个集合,可以查找元素是否在集合中。如果元素有穷,并且范围不大,那么可以用一个布尔数组来存储一个元素是否存在。例如对于只有小写字符的元素,就可以用一个长度为 26 的布尔数组来存储一个字符集合,使得空间复杂度降低为 O(1)。
-Output : 2
-```
+- Java 中的 **HashMap** 主要用于映射关系,从而把两个元素联系起来。HashMap 也可以用来对元素进行计数统计,此时键为元素,值为计数。和 HashSet 类似,如果元素有穷并且范围不大,可以用整型数组来进行统计。在对一个内容进行压缩或者其它转换时,利用 HashMap 可以把原始内容和转换后的内容联系起来。例如在一个简化 url 的系统中 [Leetcdoe : 535. Encode and Decode TinyURL (Medium)](https://leetcode.com/problems/encode-and-decode-tinyurl/description/),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源。
-```java
-private int path = 0;
-public int longestUnivaluePath(TreeNode root) {
- dfs(root);
- return path;
-}
+**数组中两个数的和为给定值**
-private int dfs(TreeNode root){
- if (root == null) return 0;
- int left = dfs(root.left);
- int right = dfs(root.right);
- int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0;
- int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0;
- path = Math.max(path, leftPath + rightPath);
- return Math.max(leftPath, rightPath);
+[1. Two Sum (Easy)](https://leetcode.com/problems/two-sum/description/)
+
+可以先对数组进行排序,然后使用双指针方法或者二分查找方法。这样做的时间复杂度为 O(NlogN),空间复杂度为 O(1)。
+
+用 HashMap 存储数组元素和索引的映射,在访问到 nums[i] 时,判断 HashMap 中是否存在 target - nums[i],如果存在说明 target - nums[i] 所在的索引和 i 就是要找的两个数。该方法的时间复杂度为 O(N),空间复杂度为 O(N),使用空间来换取时间。
+
+```java
+public int[] twoSum(int[] nums, int target) {
+ HashMap indexForNum = new HashMap<>();
+ for (int i = 0; i < nums.length; i++) {
+ if (indexForNum.containsKey(target - nums[i])) {
+ return new int[]{indexForNum.get(target - nums[i]), i};
+ } else {
+ indexForNum.put(nums[i], i);
+ }
+ }
+ return null;
}
```
-**间隔遍历**
-
-[337. House Robber III (Medium)](https://leetcode.com/problems/house-robber-iii/description/)
+**判断数组是否含有重复元素**
-```html
- 3
- / \
- 2 3
- \ \
- 3 1
-Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.
-```
+[217. Contains Duplicate (Easy)](https://leetcode.com/problems/contains-duplicate/description/)
```java
-public int rob(TreeNode root) {
- if (root == null) return 0;
- int val1 = root.val;
- if (root.left != null) val1 += rob(root.left.left) + rob(root.left.right);
- if (root.right != null) val1 += rob(root.right.left) + rob(root.right.right);
- int val2 = rob(root.left) + rob(root.right);
- return Math.max(val1, val2);
+public boolean containsDuplicate(int[] nums) {
+ Set set = new HashSet<>();
+ for (int num : nums) {
+ set.add(num);
+ }
+ return set.size() < nums.length;
}
```
-**找出二叉树中第二小的节点**
+**最长和谐序列**
-[671. Second Minimum Node In a Binary Tree (Easy)](https://leetcode.com/problems/second-minimum-node-in-a-binary-tree/description/)
+[594. Longest Harmonious Subsequence (Easy)](https://leetcode.com/problems/longest-harmonious-subsequence/description/)
```html
-Input:
- 2
- / \
- 2 5
- / \
- 5 7
-
+Input: [1,3,2,2,5,2,3,7]
Output: 5
+Explanation: The longest harmonious subsequence is [3,2,2,2,3].
```
-一个节点要么具有 0 个或 2 个子节点,如果有子节点,那么根节点是最小的节点。
+和谐序列中最大数和最小数只差正好为 1,应该注意的是序列的元素不一定是数组的连续元素。
```java
-public int findSecondMinimumValue(TreeNode root) {
- if (root == null) return -1;
- if (root.left == null && root.right == null) return -1;
- int leftVal = root.left.val;
- int rightVal = root.right.val;
- if (leftVal == root.val) leftVal = findSecondMinimumValue(root.left);
- if (rightVal == root.val) rightVal = findSecondMinimumValue(root.right);
- if (leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal);
- if (leftVal != -1) return leftVal;
- return rightVal;
+public int findLHS(int[] nums) {
+ Map countForNum = new HashMap<>();
+ for (int num : nums) {
+ countForNum.put(num, countForNum.getOrDefault(num, 0) + 1);
+ }
+ int longest = 0;
+ for (int num : countForNum.keySet()) {
+ if (countForNum.containsKey(num + 1)) {
+ longest = Math.max(longest, countForNum.get(num + 1) + countForNum.get(num));
+ }
+ }
+ return longest;
}
```
-### 层次遍历
+**最长连续序列**
-使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
+[128. Longest Consecutive Sequence (Hard)](https://leetcode.com/problems/longest-consecutive-sequence/description/)
-**一棵树每层节点的平均数**
+```html
+Given [100, 4, 200, 1, 3, 2],
+The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4.
+```
-[637. Average of Levels in Binary Tree (Easy)](https://leetcode.com/problems/average-of-levels-in-binary-tree/description/)
+要求以 O(N) 的时间复杂度求解。
```java
-public List averageOfLevels(TreeNode root) {
- List ret = new ArrayList<>();
- if (root == null) return ret;
- Queue queue = new LinkedList<>();
- queue.add(root);
- while (!queue.isEmpty()) {
- int cnt = queue.size();
- double sum = 0;
- for (int i = 0; i < cnt; i++) {
- TreeNode node = queue.poll();
- sum += node.val;
- if (node.left != null) queue.add(node.left);
- if (node.right != null) queue.add(node.right);
- }
- ret.add(sum / cnt);
+public int longestConsecutive(int[] nums) {
+ Map countForNum = new HashMap<>();
+ for (int num : nums) {
+ countForNum.put(num, 1);
}
- return ret;
+ for (int num : nums) {
+ forward(countForNum, num);
+ }
+ return maxCount(countForNum);
+}
+
+private int forward(Map countForNum, int num) {
+ if (!countForNum.containsKey(num)) {
+ return 0;
+ }
+ int cnt = countForNum.get(num);
+ if (cnt > 1) {
+ return cnt;
+ }
+ cnt = forward(countForNum, num + 1) + 1;
+ countForNum.put(num, cnt);
+ return cnt;
+}
+
+private int maxCount(Map countForNum) {
+ int max = 0;
+ for (int num : countForNum.keySet()) {
+ max = Math.max(max, countForNum.get(num));
+ }
+ return max;
}
```
-**得到左下角的节点**
+## 字符串
-[513. Find Bottom Left Tree Value (Easy)](https://leetcode.com/problems/find-bottom-left-tree-value/description/)
+**字符串循环移位包含**
+
+[编程之美 3.1](#)
```html
-Input:
+s1 = AABCD, s2 = CDAA
+Return : true
+```
- 1
- / \
- 2 3
- / / \
- 4 5 6
- /
- 7
+给定两个字符串 s1 和 s2,要求判定 s2 是否能够被 s1 做循环移位得到的字符串包含。
-Output:
-7
-```
+s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 是否是 s1s1 的子字符串即可。
-```java
-public int findBottomLeftValue(TreeNode root) {
- Queue queue = new LinkedList<>();
- queue.add(root);
- while (!queue.isEmpty()) {
- root = queue.poll();
- if (root.right != null) queue.add(root.right);
- if (root.left != null) queue.add(root.left);
- }
- return root.val;
-}
-```
+**字符串循环移位**
-### 前中后序遍历
+[编程之美 2.17](#)
```html
- 1
- / \
- 2 3
- / \ \
-4 5 6
+s = "abcd123" k = 3
+Return "123abcd"
```
-- 层次遍历顺序:[1 2 3 4 5 6]
-- 前序遍历顺序:[1 2 4 5 3 6]
-- 中序遍历顺序:[4 2 5 1 3 6]
-- 后序遍历顺序:[4 5 2 6 3 1]
+将字符串向右循环移动 k 位。
-层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。
+将 abcd123 中的 abcd 和 123 单独翻转,得到 dcba321,然后对整个字符串进行翻转,得到 123abcd。
-前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。
+**字符串中单词的翻转**
-① 前序
+[程序员代码面试指南](#)
-```java
-void dfs(TreeNode root) {
- visit(root);
- dfs(root.left);
- dfs(root.right);
-}
+```html
+s = "I am a student"
+Return "student a am I"
```
-② 中序
+将每个单词翻转,然后将整个字符串翻转。
-```java
-void dfs(TreeNode root) {
- dfs(root.left);
- visit(root);
- dfs(root.right);
-}
+**两个字符串包含的字符是否完全相同**
+
+[242. Valid Anagram (Easy)](https://leetcode.com/problems/valid-anagram/description/)
+
+```html
+s = "anagram", t = "nagaram", return true.
+s = "rat", t = "car", return false.
```
-③ 后序
+可以用 HashMap 来映射字符与出现次数,然后比较两个字符串出现的字符数量是否相同。
+
+由于本题的字符串只包含 26 个小写字符,因此可以使用长度为 26 的整型数组对字符串出现的字符进行统计,不再使用 HashMap。
```java
-void dfs(TreeNode root) {
- dfs(root.left);
- dfs(root.right);
- visit(root);
+public boolean isAnagram(String s, String t) {
+ int[] cnts = new int[26];
+ for (char c : s.toCharArray()) {
+ cnts[c - 'a']++;
+ }
+ for (char c : t.toCharArray()) {
+ cnts[c - 'a']--;
+ }
+ for (int cnt : cnts) {
+ if (cnt != 0) {
+ return false;
+ }
+ }
+ return true;
}
```
-**非递归实现二叉树的前序遍历**
+**计算一组字符集合可以组成的回文字符串的最大长度**
-[144. Binary Tree Preorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-preorder-traversal/description/)
+[409. Longest Palindrome (Easy)](https://leetcode.com/problems/longest-palindrome/description/)
+
+```html
+Input : "abccccdd"
+Output : 7
+Explanation : One longest palindrome that can be built is "dccaccd", whose length is 7.
+```
+
+使用长度为 256 的整型数组来统计每个字符出现的个数,每个字符有偶数个可以用来构成回文字符串。
+
+因为回文字符串最中间的那个字符可以单独出现,所以如果有单独的字符就把它放到最中间。
```java
-public List preorderTraversal(TreeNode root) {
- List ret = new ArrayList<>();
- Stack stack = new Stack<>();
- stack.push(root);
- while (!stack.isEmpty()) {
- TreeNode node = stack.pop();
- if (node == null) continue;
- ret.add(node.val);
- stack.push(node.right); // 先右后左,保证左子树先遍历
- stack.push(node.left);
+public int longestPalindrome(String s) {
+ int[] cnts = new int[256];
+ for (char c : s.toCharArray()) {
+ cnts[c]++;
+ }
+ int palindrome = 0;
+ for (int cnt : cnts) {
+ palindrome += (cnt / 2) * 2;
}
- return ret;
+ if (palindrome < s.length()) {
+ palindrome++; // 这个条件下 s 中一定有单个未使用的字符存在,可以把这个字符放到回文的最中间
+ }
+ return palindrome;
}
```
-**非递归实现二叉树的后序遍历**
-
-[145. Binary Tree Postorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-postorder-traversal/description/)
+**字符串同构**
-前序遍历为 root -> left -> right,后序遍历为 left -> right -> root,可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。
+[205. Isomorphic Strings (Easy)](https://leetcode.com/problems/isomorphic-strings/description/)
-```java
-public List postorderTraversal(TreeNode root) {
- List ret = new ArrayList<>();
- Stack stack = new Stack<>();
- stack.push(root);
- while (!stack.isEmpty()) {
- TreeNode node = stack.pop();
- if (node == null) continue;
- ret.add(node.val);
- stack.push(node.left);
- stack.push(node.right);
- }
- Collections.reverse(ret);
- return ret;
-}
+```html
+Given "egg", "add", return true.
+Given "foo", "bar", return false.
+Given "paper", "title", return true.
```
-**非递归实现二叉树的中序遍历**
-
-[94. Binary Tree Inorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-inorder-traversal/description/)
+记录一个字符上次出现的位置,如果两个字符串中的字符上次出现的位置一样,那么就属于同构。
```java
-public List inorderTraversal(TreeNode root) {
- List ret = new ArrayList<>();
- if (root == null) return ret;
- Stack stack = new Stack<>();
- TreeNode cur = root;
- while (cur != null || !stack.isEmpty()) {
- while (cur != null) {
- stack.push(cur);
- cur = cur.left;
+public boolean isIsomorphic(String s, String t) {
+ int[] preIndexOfS = new int[256];
+ int[] preIndexOfT = new int[256];
+ for (int i = 0; i < s.length(); i++) {
+ char sc = s.charAt(i), tc = t.charAt(i);
+ if (preIndexOfS[sc] != preIndexOfT[tc]) {
+ return false;
}
- TreeNode node = stack.pop();
- ret.add(node.val);
- cur = node.right;
+ preIndexOfS[sc] = i + 1;
+ preIndexOfT[tc] = i + 1;
}
- return ret;
+ return true;
}
```
-### BST
+**回文子字符串个数**
-二叉查找树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点。
+[647. Palindromic Substrings (Medium)](https://leetcode.com/problems/palindromic-substrings/description/)
-二叉查找树中序遍历有序。
+```html
+Input: "aaa"
+Output: 6
+Explanation: Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa".
+```
-**修剪二叉查找树**
+从字符串的某一位开始,尝试着去扩展子字符串。
-[669. Trim a Binary Search Tree (Easy)](https://leetcode.com/problems/trim-a-binary-search-tree/description/)
+```java
+private int cnt = 0;
-```html
-Input:
+public int countSubstrings(String s) {
+ for (int i = 0; i < s.length(); i++) {
+ extendSubstrings(s, i, i); // 奇数长度
+ extendSubstrings(s, i, i + 1); // 偶数长度
+ }
+ return cnt;
+}
- 3
- / \
- 0 4
- \
- 2
- /
- 1
+private void extendSubstrings(String s, int start, int end) {
+ while (start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)) {
+ start--;
+ end++;
+ cnt++;
+ }
+}
+```
- L = 1
- R = 3
+**判断一个整数是否是回文数**
-Output:
+[9. Palindrome Number (Easy)](https://leetcode.com/problems/palindrome-number/description/)
- 3
- /
- 2
- /
- 1
-```
+要求不能使用额外空间,也就不能将整数转换为字符串进行判断。
-题目描述:只保留值在 L \~ R 之间的节点
+将整数分成左右两部分,右边那部分需要转置,然后判断这两部分是否相等。
```java
-public TreeNode trimBST(TreeNode root, int L, int R) {
- if (root == null) return null;
- if (root.val > R) return trimBST(root.left, L, R);
- if (root.val < L) return trimBST(root.right, L, R);
- root.left = trimBST(root.left, L, R);
- root.right = trimBST(root.right, L, R);
- return root;
+public boolean isPalindrome(int x) {
+ if (x == 0) {
+ return true;
+ }
+ if (x < 0 || x % 10 == 0) {
+ return false;
+ }
+ int right = 0;
+ while (x > right) {
+ right = right * 10 + x % 10;
+ x /= 10;
+ }
+ return x == right || x == right / 10;
}
```
-**寻找二叉查找树的第 k 个元素**
-
-[230. Kth Smallest Element in a BST (Medium)](https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/)
+**统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数**
+[696. Count Binary Substrings (Easy)](https://leetcode.com/problems/count-binary-substrings/description/)
-中序遍历解法:
+```html
+Input: "00110011"
+Output: 6
+Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01".
+```
```java
-private int cnt = 0;
-private int val;
-
-public int kthSmallest(TreeNode root, int k) {
- inOrder(root, k);
- return val;
-}
+public int countBinarySubstrings(String s) {
+ int preLen = 0, curLen = 1, count = 0;
+ for (int i = 1; i < s.length(); i++) {
+ if (s.charAt(i) == s.charAt(i - 1)) {
+ curLen++;
+ } else {
+ preLen = curLen;
+ curLen = 1;
+ }
-private void inOrder(TreeNode node, int k) {
- if (node == null) return;
- inOrder(node.left, k);
- cnt++;
- if (cnt == k) {
- val = node.val;
- return;
+ if (preLen >= curLen) {
+ count++;
+ }
}
- inOrder(node.right, k);
+ return count;
}
```
-递归解法:
+## 数组与矩阵
-```java
-public int kthSmallest(TreeNode root, int k) {
- int leftCnt = count(root.left);
- if (leftCnt == k - 1) return root.val;
- if (leftCnt > k - 1) return kthSmallest(root.left, k);
- return kthSmallest(root.right, k - leftCnt - 1);
-}
+**把数组中的 0 移到末尾**
-private int count(TreeNode node) {
- if (node == null) return 0;
- return 1 + count(node.left) + count(node.right);
+[283. Move Zeroes (Easy)](https://leetcode.com/problems/move-zeroes/description/)
+
+```html
+For example, given nums = [0, 1, 0, 3, 12], after calling your function, nums should be [1, 3, 12, 0, 0].
+```
+
+```java
+public void moveZeroes(int[] nums) {
+ int idx = 0;
+ for (int num : nums) {
+ if (num != 0) {
+ nums[idx++] = num;
+ }
+ }
+ while (idx < nums.length) {
+ nums[idx++] = 0;
+ }
}
```
-**把二叉查找树每个节点的值都加上比它大的节点的值**
+**改变矩阵维度**
-[Convert BST to Greater Tree (Easy)](https://leetcode.com/problems/convert-bst-to-greater-tree/description/)
+[566. Reshape the Matrix (Easy)](https://leetcode.com/problems/reshape-the-matrix/description/)
```html
-Input: The root of a Binary Search Tree like this:
-
- 5
- / \
- 2 13
+Input:
+nums =
+[[1,2],
+ [3,4]]
+r = 1, c = 4
-Output: The root of a Greater Tree like this:
+Output:
+[[1,2,3,4]]
- 18
- / \
- 20 13
+Explanation:
+The row-traversing of nums is [1,2,3,4]. The new reshaped matrix is a 1 * 4 matrix, fill it row by row by using the previous list.
```
-先遍历右子树。
-
```java
-private int sum = 0;
-
-public TreeNode convertBST(TreeNode root) {
- traver(root);
- return root;
+public int[][] matrixReshape(int[][] nums, int r, int c) {
+ int m = nums.length, n = nums[0].length;
+ if (m * n != r * c) {
+ return nums;
+ }
+ int[][] reshapedNums = new int[r][c];
+ int index = 0;
+ for (int i = 0; i < r; i++) {
+ for (int j = 0; j < c; j++) {
+ reshapedNums[i][j] = nums[index / n][index % n];
+ index++;
+ }
+ }
+ return reshapedNums;
}
+```
-private void traver(TreeNode node) {
- if (node == null) return;
- traver(node.right);
- sum += node.val;
- node.val = sum;
- traver(node.left);
+**找出数组中最长的连续 1**
+
+[485. Max Consecutive Ones (Easy)](https://leetcode.com/problems/max-consecutive-ones/description/)
+
+```java
+public int findMaxConsecutiveOnes(int[] nums) {
+ int max = 0, cur = 0;
+ for (int x : nums) {
+ cur = x == 0 ? 0 : cur + 1;
+ max = Math.max(max, cur);
+ }
+ return max;
}
```
-**二叉查找树的最近公共祖先**
+**有序矩阵查找**
-[235. Lowest Common Ancestor of a Binary Search Tree (Easy)](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/)
+[240. Search a 2D Matrix II (Medium)](https://leetcode.com/problems/search-a-2d-matrix-ii/description/)
```html
- _______6______
- / \
- ___2__ ___8__
- / \ / \
-0 4 7 9
- / \
- 3 5
-
-For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another example is LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.
+[
+ [ 1, 5, 9],
+ [10, 11, 13],
+ [12, 13, 15]
+]
```
```java
-public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
- 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;
+public boolean searchMatrix(int[][] matrix, int target) {
+ if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;
+ int m = matrix.length, n = matrix[0].length;
+ int row = 0, col = n - 1;
+ while (row < m && col >= 0) {
+ if (target == matrix[row][col]) return true;
+ else if (target < matrix[row][col]) col--;
+ else row++;
+ }
+ return false;
}
```
-**二叉树的最近公共祖先**
+**有序矩阵的 Kth Element**
-[236. Lowest Common Ancestor of a Binary Tree (Medium) ](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/)
+[378. Kth Smallest Element in a Sorted Matrix ((Medium))](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/)
```html
- _______3______
- / \
- ___5__ ___1__
- / \ / \
-6 2 0 8
- / \
- 7 4
-
-For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.
-```
+matrix = [
+ [ 1, 5, 9],
+ [10, 11, 13],
+ [12, 13, 15]
+],
+k = 8,
-```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;
-}
+return 13.
```
-**从有序数组中构造二叉查找树**
+解题参考:[Share my thoughts and Clean Java Code](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/discuss/85173)
-[108. Convert Sorted Array to Binary Search Tree (Easy)](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/)
+二分查找解法:
```java
-public TreeNode sortedArrayToBST(int[] nums) {
- return toBST(nums, 0, nums.length - 1);
-}
-
-private TreeNode toBST(int[] nums, int sIdx, int eIdx){
- if (sIdx > eIdx) return null;
- int mIdx = (sIdx + eIdx) / 2;
- TreeNode root = new TreeNode(nums[mIdx]);
- root.left = toBST(nums, sIdx, mIdx - 1);
- root.right = toBST(nums, mIdx + 1, eIdx);
- return root;
+public int kthSmallest(int[][] matrix, int k) {
+ int m = matrix.length, n = matrix[0].length;
+ int lo = matrix[0][0], hi = matrix[m - 1][n - 1];
+ while (lo <= hi) {
+ int mid = lo + (hi - lo) / 2;
+ int cnt = 0;
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < n && matrix[i][j] <= mid; j++) {
+ cnt++;
+ }
+ }
+ if (cnt < k) lo = mid + 1;
+ else hi = mid - 1;
+ }
+ return lo;
}
```
-**根据有序链表构造平衡的二叉查找树**
-
-[109. Convert Sorted List to Binary Search Tree (Medium)](https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/description/)
-
-```html
-Given the sorted linked list: [-10,-3,0,5,9],
-
-One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST:
-
- 0
- / \
- -3 9
- / /
- -10 5
-```
+堆解法:
```java
-public TreeNode sortedListToBST(ListNode head) {
- if (head == null) return null;
- if (head.next == null) return new TreeNode(head.val);
- ListNode preMid = preMid(head);
- ListNode mid = preMid.next;
- preMid.next = null; // 断开链表
- TreeNode t = new TreeNode(mid.val);
- t.left = sortedListToBST(head);
- t.right = sortedListToBST(mid.next);
- return t;
+public int kthSmallest(int[][] matrix, int k) {
+ int m = matrix.length, n = matrix[0].length;
+ PriorityQueue pq = new PriorityQueue();
+ for(int j = 0; j < n; j++) pq.offer(new Tuple(0, j, matrix[0][j]));
+ for(int i = 0; i < k - 1; i++) { // 小根堆,去掉 k - 1 个堆顶元素,此时堆顶元素就是第 k 的数
+ Tuple t = pq.poll();
+ if(t.x == m - 1) continue;
+ pq.offer(new Tuple(t.x + 1, t.y, matrix[t.x + 1][t.y]));
+ }
+ return pq.poll().val;
}
-private ListNode preMid(ListNode head) {
- ListNode slow = head, fast = head.next;
- ListNode pre = head;
- while (fast != null && fast.next != null) {
- pre = slow;
- slow = slow.next;
- fast = fast.next.next;
+class Tuple implements Comparable {
+ int x, y, val;
+ public Tuple(int x, int y, int val) {
+ this.x = x; this.y = y; this.val = val;
+ }
+
+ @Override
+ public int compareTo(Tuple that) {
+ return this.val - that.val;
}
- return pre;
}
```
-**在二叉查找树中寻找两个节点,使它们的和为一个给定值**
+**一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出重复的数和丢失的数**
-[653. Two Sum IV - Input is a BST (Easy)](https://leetcode.com/problems/two-sum-iv-input-is-a-bst/description/)
+[645. Set Mismatch (Easy)](https://leetcode.com/problems/set-mismatch/description/)
```html
-Input:
-
- 5
- / \
- 3 6
- / \ \
-2 4 7
-
-Target = 9
+Input: nums = [1,2,2,4]
+Output: [2,3]
+```
-Output: True
+```html
+Input: nums = [1,2,2,4]
+Output: [2,3]
```
-使用中序遍历得到有序数组之后,再利用双指针对数组进行查找。
+最直接的方法是先对数组进行排序,这种方法时间复杂度为 O(NlogN)。本题可以以 O(N) 的时间复杂度、O(1) 空间复杂度来求解。
-应该注意到,这一题不能用分别在左右子树两部分来处理这种思想,因为两个待求的节点可能分别在左右子树中。
+主要思想是通过交换数组元素,使得数组上的元素在正确的位置上。
```java
-public boolean findTarget(TreeNode root, int k) {
- List nums = new ArrayList<>();
- inOrder(root, nums);
- int i = 0, j = nums.size() - 1;
- while (i < j) {
- int sum = nums.get(i) + nums.get(j);
- if (sum == k) return true;
- if (sum < k) i++;
- else j--;
+public int[] findErrorNums(int[] nums) {
+ for (int i = 0; i < nums.length; i++) {
+ while (nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]) {
+ swap(nums, i, nums[i] - 1);
+ }
}
- return false;
+ for (int i = 0; i < nums.length; i++) {
+ if (nums[i] != i + 1) {
+ return new int[]{nums[i], i + 1};
+ }
+ }
+ return null;
}
-private void inOrder(TreeNode root, List nums) {
- if (root == null) return;
- inOrder(root.left, nums);
- nums.add(root.val);
- inOrder(root.right, nums);
+private void swap(int[] nums, int i, int j) {
+ int tmp = nums[i];
+ nums[i] = nums[j];
+ nums[j] = tmp;
}
```
-**在二叉查找树中查找两个节点之差的最小绝对值**
-
-[530. Minimum Absolute Difference in BST (Easy)](https://leetcode.com/problems/minimum-absolute-difference-in-bst/description/)
+类似题目:
-```html
-Input:
+- [448. Find All Numbers Disappeared in an Array (Easy)](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/),寻找所有丢失的元素
+- [442. Find All Duplicates in an Array (Medium)](https://leetcode.com/problems/find-all-duplicates-in-an-array/description/),寻找所有重复的元素。
- 1
- \
- 3
- /
- 2
+**找出数组中重复的数,数组值在 [1, n] 之间**
-Output:
+[287. Find the Duplicate Number (Medium)](https://leetcode.com/problems/find-the-duplicate-number/description/)
-1
-```
+要求不能修改数组,也不能使用额外的空间。
-利用二叉查找树的中序遍历为有序的性质,计算中序遍历中临近的两个节点之差的绝对值,取最小值。
+二分查找解法:
```java
-private int minDiff = Integer.MAX_VALUE;
-private TreeNode preNode = null;
-
-public int getMinimumDifference(TreeNode root) {
- inOrder(root);
- return minDiff;
+public int findDuplicate(int[] nums) {
+ int l = 1, h = nums.length - 1;
+ while (l <= h) {
+ int mid = l + (h - l) / 2;
+ int cnt = 0;
+ for (int i = 0; i < nums.length; i++) {
+ if (nums[i] <= mid) cnt++;
+ }
+ if (cnt > mid) h = mid - 1;
+ else l = mid + 1;
+ }
+ return l;
}
+```
-private void inOrder(TreeNode node) {
- if (node == null) return;
- inOrder(node.left);
- if (preNode != null) minDiff = Math.min(minDiff, node.val - preNode.val);
- preNode = node;
- inOrder(node.right);
+双指针解法,类似于有环链表中找出环的入口:
+
+```java
+public int findDuplicate(int[] nums) {
+ int slow = nums[0], fast = nums[nums[0]];
+ while (slow != fast) {
+ slow = nums[slow];
+ fast = nums[nums[fast]];
+ }
+ fast = 0;
+ while (slow != fast) {
+ slow = nums[slow];
+ fast = nums[fast];
+ }
+ return slow;
}
```
-**寻找二叉查找树中出现次数最多的值**
+**数组相邻差值的个数**
-[501. Find Mode in Binary Search Tree (Easy)](https://leetcode.com/problems/find-mode-in-binary-search-tree/description/)
+[667. Beautiful Arrangement II (Medium)](https://leetcode.com/problems/beautiful-arrangement-ii/description/)
```html
- 1
- \
- 2
- /
- 2
-
-return [2].
+Input: n = 3, k = 2
+Output: [1, 3, 2]
+Explanation: The [1, 3, 2] has three different positive integers ranging from 1 to 3, and the [2, 1] has exactly 2 distinct integers: 1 and 2.
```
-答案可能不止一个,也就是有多个值出现的次数一样多,并且是最大的。
-
-```java
-private int curCnt = 1;
-private int maxCnt = 1;
-private TreeNode preNode = null;
+题目描述:数组元素为 1\~n 的整数,要求构建数组,使得相邻元素的差值不相同的个数为 k。
-public int[] findMode(TreeNode root) {
- List maxCntNums = new ArrayList<>();
- inOrder(root, maxCntNums);
- int[] ret = new int[maxCntNums.size()];
- int idx = 0;
- for (int num : maxCntNums) {
- ret[idx++] = num;
- }
- return ret;
-}
+让前 k+1 个元素构建出 k 个不相同的差值,序列为:1 k+1 2 k 3 k-1 ... k/2 k/2+1.
-private void inOrder(TreeNode node, List nums) {
- if (node == null) return;
- inOrder(node.left, nums);
- if (preNode != null) {
- if (preNode.val == node.val) curCnt++;
- else curCnt = 1;
+```java
+public int[] constructArray(int n, int k) {
+ int[] ret = new int[n];
+ ret[0] = 1;
+ for (int i = 1, interval = k; i <= k; i++, interval--) {
+ ret[i] = i % 2 == 1 ? ret[i - 1] + interval : ret[i - 1] - interval;
}
- if (curCnt > maxCnt) {
- maxCnt = curCnt;
- nums.clear();
- nums.add(node.val);
- } else if (curCnt == maxCnt) {
- nums.add(node.val);
+ for (int i = k + 1; i < n; i++) {
+ ret[i] = i + 1;
}
- preNode = node;
- inOrder(node.right, nums);
+ return ret;
}
```
-### Trie
-
-
+**数组的度**
-Trie,又称前缀树或字典树,用于判断字符串是否存在或者是否具有某种字符串前缀。
+[697. Degree of an Array (Easy)](https://leetcode.com/problems/degree-of-an-array/description/)
-**实现一个 Trie**
+```html
+Input: [1,2,2,3,1,4,2]
+Output: 6
+```
-[208. Implement Trie (Prefix Tree) (Medium)](https://leetcode.com/problems/implement-trie-prefix-tree/description/)
+题目描述:数组的度定义为元素出现的最高频率,例如上面的数组度为 3。要求找到一个最小的子数组,这个子数组的度和原数组一样。
```java
-class Trie {
-
- private class Node {
- Node[] childs = new Node[26];
- boolean isLeaf;
+public int findShortestSubArray(int[] nums) {
+ Map numsCnt = new HashMap<>();
+ Map numsLastIndex = new HashMap<>();
+ Map numsFirstIndex = new HashMap<>();
+ for (int i = 0; i < nums.length; i++) {
+ int num = nums[i];
+ numsCnt.put(num, numsCnt.getOrDefault(num, 0) + 1);
+ numsLastIndex.put(num, i);
+ if (!numsFirstIndex.containsKey(num)) {
+ numsFirstIndex.put(num, i);
+ }
}
-
- private Node root = new Node();
-
- public Trie() {
+ int maxCnt = 0;
+ for (int num : nums) {
+ maxCnt = Math.max(maxCnt, numsCnt.get(num));
}
-
- public void insert(String word) {
- insert(word, root);
+ int ret = nums.length;
+ for (int i = 0; i < nums.length; i++) {
+ int num = nums[i];
+ int cnt = numsCnt.get(num);
+ if (cnt != maxCnt) continue;
+ ret = Math.min(ret, numsLastIndex.get(num) - numsFirstIndex.get(num) + 1);
}
+ return ret;
+}
+```
- private void insert(String word, Node node) {
- if (node == null) return;
- if (word.length() == 0) {
- node.isLeaf = true;
- return;
- }
- int index = indexForChar(word.charAt(0));
- if (node.childs[index] == null) {
- node.childs[index] = new Node();
- }
- insert(word.substring(1), node.childs[index]);
- }
+**对角元素相等的矩阵**
- public boolean search(String word) {
- return search(word, root);
- }
+[766. Toeplitz Matrix (Easy)](https://leetcode.com/problems/toeplitz-matrix/description/)
- private boolean search(String word, Node node) {
- if (node == null) return false;
- if (word.length() == 0) return node.isLeaf;
- int index = indexForChar(word.charAt(0));
- return search(word.substring(1), node.childs[index]);
- }
+```html
+1234
+5123
+9512
- public boolean startsWith(String prefix) {
- return startWith(prefix, root);
- }
+In the above grid, the diagonals are "[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]", and in each diagonal all elements are the same, so the answer is True.
+```
- private boolean startWith(String prefix, Node node) {
- if (node == null) return false;
- if (prefix.length() == 0) return true;
- int index = indexForChar(prefix.charAt(0));
- return startWith(prefix.substring(1), node.childs[index]);
+```java
+public boolean isToeplitzMatrix(int[][] matrix) {
+ for (int i = 0; i < matrix[0].length; i++) {
+ if (!check(matrix, matrix[0][i], 0, i)) {
+ return false;
+ }
+ }
+ for (int i = 0; i < matrix.length; i++) {
+ if (!check(matrix, matrix[i][0], i, 0)) {
+ return false;
+ }
}
+ return true;
+}
- private int indexForChar(char c) {
- return c - 'a';
+private boolean check(int[][] matrix, int expectValue, int row, int col) {
+ if (row >= matrix.length || col >= matrix[0].length) {
+ return true;
+ }
+ if (matrix[row][col] != expectValue) {
+ return false;
}
+ return check(matrix, expectValue, row + 1, col + 1);
}
```
-**实现一个 Trie,用来求前缀和**
+**嵌套数组**
-[677. Map Sum Pairs (Medium)](https://leetcode.com/problems/map-sum-pairs/description/)
+[565. Array Nesting (Medium)](https://leetcode.com/problems/array-nesting/description/)
```html
-Input: insert("apple", 3), Output: Null
-Input: sum("ap"), Output: 3
-Input: insert("app", 2), Output: Null
-Input: sum("ap"), Output: 5
-```
-
-```java
-class MapSum {
+Input: A = [5,4,0,3,1,6,2]
+Output: 4
+Explanation:
+A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2.
- private class Node {
- Node[] child = new Node[26];
- int value;
- }
+One of the longest S[K]:
+S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0}
+```
- private Node root = new Node();
+题目描述:S[i] 表示一个集合,集合的第一个元素是 A[i],第二个元素是 A[A[i]],如此嵌套下去。求最大的 S[i]。
- public MapSum() {
+```java
+public int arrayNesting(int[] nums) {
+ int max = 0;
+ for (int i = 0; i < nums.length; i++) {
+ int cnt = 0;
+ for (int j = i; nums[j] != -1; ) {
+ cnt++;
+ int t = nums[j];
+ nums[j] = -1; // 标记该位置已经被访问
+ j = t;
+ }
+ max = Math.max(max, cnt);
}
+ return max;
+}
+```
- public void insert(String key, int val) {
- insert(key, root, val);
- }
+**分隔数组**
- private void insert(String key, Node node, int val) {
- if (node == null) return;
- if (key.length() == 0) {
- node.value = val;
- return;
- }
- int index = indexForChar(key.charAt(0));
- if (node.child[index] == null) {
- node.child[index] = new Node();
- }
- insert(key.substring(1), node.child[index], val);
- }
+[769. Max Chunks To Make Sorted (Medium)](https://leetcode.com/problems/max-chunks-to-make-sorted/description/)
- public int sum(String prefix) {
- return sum(prefix, root);
- }
+```html
+Input: arr = [1,0,2,3,4]
+Output: 4
+Explanation:
+We can split into two chunks, such as [1, 0], [2, 3, 4].
+However, splitting into [1, 0], [2], [3], [4] is the highest number of chunks possible.
+```
- private int sum(String prefix, Node node) {
- if (node == null) return 0;
- if (prefix.length() != 0) {
- int index = indexForChar(prefix.charAt(0));
- return sum(prefix.substring(1), node.child[index]);
- }
- int sum = node.value;
- for (Node child : node.child) {
- sum += sum(prefix, child);
- }
- return sum;
- }
+题目描述:分隔数组,使得对每部分排序后数组就为有序。
- private int indexForChar(char c) {
- return c - 'a';
+```java
+public int maxChunksToSorted(int[] arr) {
+ if (arr == null) return 0;
+ int ret = 0;
+ int right = arr[0];
+ for (int i = 0; i < arr.length; i++) {
+ right = Math.max(right, arr[i]);
+ if (right == i) ret++;
}
+ return ret;
}
```
+
## 图
### 二分图
@@ -7056,3 +7074,4 @@ public int[] countBits(int num) {
- 何海涛, 软件工程师. 剑指 Offer: 名企面试官精讲典型编程题[M]. 电子工业出版社, 2014.
- 《编程之美》小组. 编程之美[M]. 电子工业出版社, 2008.
- 左程云. 程序员代码面试指南[M]. 电子工业出版社, 2015.
+
diff --git "a/notes/Leetcode-Database \351\242\230\350\247\243.md" "b/notes/Leetcode-Database \351\242\230\350\247\243.md"
index 5ab726c5..5b4a3d00 100644
--- "a/notes/Leetcode-Database \351\242\230\350\247\243.md"
+++ "b/notes/Leetcode-Database \351\242\230\350\247\243.md"
@@ -461,7 +461,7 @@ Employee 表:
+----+-------+--------+-----------+
```
-查找所有员工,他们的薪资大于其经理薪资。
+查找薪资大于其经理薪资的员工信息。
## SQL Schema
@@ -924,27 +924,27 @@ VALUES
```sql
SELECT
s1.id - 1 AS id,
- s1.student
+ s1.student
FROM
- seat s1
+ seat s1
WHERE
s1.id MOD 2 = 0 UNION
SELECT
s2.id + 1 AS id,
- s2.student
+ s2.student
FROM
- seat s2
+ seat s2
WHERE
- s2.id MOD 2 = 1
+ s2.id MOD 2 = 1
AND s2.id != ( SELECT max( s3.id ) FROM seat s3 ) UNION
SELECT
s4.id AS id,
- s4.student
+ s4.student
FROM
- seat s4
+ seat s4
WHERE
- s4.id MOD 2 = 1
- AND s4.id = ( SELECT max( s5.id ) FROM seat s5 )
+ s4.id MOD 2 = 1
+ AND s4.id = ( SELECT max( s5.id ) FROM seat s5 )
ORDER BY
id;
```
diff --git "a/notes/\345\211\221\346\214\207 offer \351\242\230\350\247\243.md" "b/notes/\345\211\221\346\214\207 offer \351\242\230\350\247\243.md"
index 47d5d94e..0c9bd1f9 100644
--- "a/notes/\345\211\221\346\214\207 offer \351\242\230\350\247\243.md"
+++ "b/notes/\345\211\221\346\214\207 offer \351\242\230\350\247\243.md"
@@ -633,11 +633,12 @@ public int RectCover(int n) {
## 解题思路
-当 nums[m] <= nums[h] 的情况下,说明解在 [l, m] 之间,此时令 h = m;否则解在 [m + 1, h] 之间,令 l = m + 1。
+- 当 nums[m] <= nums[h] 的情况下,说明解在 [l, m] 之间,此时令 h = m;
+- 否则解在 [m + 1, h] 之间,令 l = m + 1。
-因为 h 的赋值表达式为 h = m,因此循环体的循环条件应该为 l < h,详细解释请见 [Leetcode 题解](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3.md#%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE) 二分查找部分。
+因为 h 的赋值表达式为 h = m,因此循环体的循环条件应该为 l < h,详细解释请见 [Leetcode 题解](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3.md) 二分查找部分。
-但是如果出现 nums[l] == nums[m] == nums[h],那么此时无法确定解在哪个区间,因此需要切换到顺序查找。
+但是如果出现 nums[l] == nums[m] == nums[h],那么此时无法确定解在哪个区间,需要切换到顺序查找。
复杂度:O(logN) + O(1)
@@ -1217,8 +1218,7 @@ public ListNode ReverseList(ListNode head) {
### 递归
```java
-public ListNode Merge(ListNode list1, ListNode list2)
-{
+public ListNode Merge(ListNode list1, ListNode list2) {
if (list1 == null)
return list2;
if (list2 == null)
@@ -1236,8 +1236,7 @@ public ListNode Merge(ListNode list1, ListNode list2)
### 迭代
```java
-public ListNode Merge(ListNode list1, ListNode list2)
-{
+public ListNode Merge(ListNode list1, ListNode list2) {
ListNode head = new ListNode(-1);
ListNode cur = head;
while (list1 != null && list2 != null) {
@@ -1269,15 +1268,13 @@ public ListNode Merge(ListNode list1, ListNode list2)
## 解题思路
```java
-public boolean HasSubtree(TreeNode root1, TreeNode root2)
-{
+public boolean HasSubtree(TreeNode root1, TreeNode root2) {
if (root1 == null || root2 == null)
return false;
return isSubtreeWithRoot(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
}
-private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2)
-{
+private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) {
if (root2 == null)
return true;
if (root1 == null)
@@ -1298,9 +1295,10 @@ private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2)
## 解题思路
+### 递归
+
```java
-public void Mirror(TreeNode root)
-{
+public void Mirror(TreeNode root) {
if (root == null)
return;
swap(root);
@@ -1308,14 +1306,36 @@ public void Mirror(TreeNode root)
Mirror(root.right);
}
-private void swap(TreeNode root)
-{
+private void swap(TreeNode root) {
TreeNode t = root.left;
root.left = root.right;
root.right = t;
}
```
+### 迭代
+
+```java
+public void Mirror(TreeNode root) {
+ Stack stack = new Stack<>();
+ stack.push(root);
+ while (!stack.isEmpty()) {
+ TreeNode node = stack.pop();
+ if (node == null)
+ continue;
+ swap(node);
+ stack.push(node.left);
+ stack.push(node.right);
+ }
+}
+
+private void swap(TreeNode node) {
+ TreeNode t = node.left;
+ node.left = node.right;
+ node.right = t;
+}
+```
+
# 28 对称的二叉树
[NowCder](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -1327,15 +1347,13 @@ private void swap(TreeNode root)
## 解题思路
```java
-boolean isSymmetrical(TreeNode pRoot)
-{
+boolean isSymmetrical(TreeNode pRoot) {
if (pRoot == null)
return true;
return isSymmetrical(pRoot.left, pRoot.right);
}
-boolean isSymmetrical(TreeNode t1, TreeNode t2)
-{
+boolean isSymmetrical(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null)
return true;
if (t1 == null || t2 == null)
@@ -1359,8 +1377,7 @@ boolean isSymmetrical(TreeNode t1, TreeNode t2)
## 解题思路
```java
-public ArrayList printMatrix(int[][] matrix)
-{
+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) {
@@ -1394,25 +1411,21 @@ public ArrayList printMatrix(int[][] matrix)
private Stack dataStack = new Stack<>();
private Stack minStack = new Stack<>();
-public void push(int node)
-{
+public void push(int node) {
dataStack.push(node);
minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(), node));
}
-public void pop()
-{
+public void pop() {
dataStack.pop();
minStack.pop();
}
-public int top()
-{
+public int top() {
return dataStack.peek();
}
-public int min()
-{
+public int min() {
return minStack.peek();
}
```
@@ -1430,8 +1443,7 @@ public int min()
使用一个栈来模拟压入弹出操作。
```java
-public boolean IsPopOrder(int[] pushSequence, int[] popSequence)
-{
+public boolean IsPopOrder(int[] pushSequence, int[] popSequence) {
int n = pushSequence.length;
Stack stack = new Stack<>();
for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) {
@@ -1464,8 +1476,7 @@ public boolean IsPopOrder(int[] pushSequence, int[] popSequence)
不需要使用两个队列分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
```java
-public ArrayList PrintFromTopToBottom(TreeNode root)
-{
+public ArrayList PrintFromTopToBottom(TreeNode root) {
Queue queue = new LinkedList<>();
ArrayList ret = new ArrayList<>();
queue.add(root);
@@ -1495,8 +1506,7 @@ public ArrayList PrintFromTopToBottom(TreeNode root)
## 解题思路
```java
-ArrayList> Print(TreeNode pRoot)
-{
+ArrayList> Print(TreeNode pRoot) {
ArrayList> ret = new ArrayList<>();
Queue queue = new LinkedList<>();
queue.add(pRoot);
@@ -1529,8 +1539,7 @@ ArrayList> Print(TreeNode pRoot)
## 解题思路
```java
-public ArrayList> Print(TreeNode pRoot)
-{
+public ArrayList> Print(TreeNode pRoot) {
ArrayList> ret = new ArrayList<>();
Queue queue = new LinkedList<>();
queue.add(pRoot);
@@ -1571,15 +1580,13 @@ public ArrayList> Print(TreeNode pRoot)
## 解题思路
```java
-public boolean VerifySquenceOfBST(int[] sequence)
-{
+public boolean VerifySquenceOfBST(int[] sequence) {
if (sequence == null || sequence.length == 0)
return false;
return verify(sequence, 0, sequence.length - 1);
}
-private boolean verify(int[] sequence, int first, int last)
-{
+private boolean verify(int[] sequence, int first, int last) {
if (last - first <= 1)
return true;
int rootVal = sequence[last];
@@ -1610,14 +1617,12 @@ private boolean verify(int[] sequence, int first, int last)
```java
private ArrayList> ret = new ArrayList<>();
-public ArrayList> FindPath(TreeNode root, int target)
-{
+public ArrayList> FindPath(TreeNode root, int target) {
backtracking(root, target, new ArrayList<>());
return ret;
}
-private void backtracking(TreeNode node, int target, ArrayList path)
-{
+private void backtracking(TreeNode node, int target, ArrayList path) {
if (node == null)
return;
path.add(node.val);
@@ -1641,8 +1646,7 @@ private void backtracking(TreeNode node, int target, ArrayList path)
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。
```java
-public class RandomListNode
-{
+public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
@@ -1670,8 +1674,7 @@ public class RandomListNode
```java
-public RandomListNode Clone(RandomListNode pHead)
-{
+public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null)
return null;
// 插入新节点
@@ -1718,14 +1721,12 @@ public RandomListNode Clone(RandomListNode pHead)
private TreeNode pre = null;
private TreeNode head = null;
-public TreeNode Convert(TreeNode root)
-{
+public TreeNode Convert(TreeNode root) {
inOrder(root);
return head;
}
-private void inOrder(TreeNode node)
-{
+private void inOrder(TreeNode node) {
if (node == null)
return;
inOrder(node.left);
@@ -1752,21 +1753,18 @@ private void inOrder(TreeNode node)
```java
private String deserializeStr;
-public String Serialize(TreeNode root)
-{
+public String Serialize(TreeNode root) {
if (root == null)
return "#";
return root.val + " " + Serialize(root.left) + " " + Serialize(root.right);
}
-public TreeNode Deserialize(String str)
-{
+public TreeNode Deserialize(String str) {
deserializeStr = str;
return Deserialize();
}
-private TreeNode Deserialize()
-{
+private TreeNode Deserialize() {
if (deserializeStr.length() == 0)
return null;
int index = deserializeStr.indexOf(" ");
@@ -1795,8 +1793,7 @@ private TreeNode Deserialize()
```java
private ArrayList ret = new ArrayList<>();
-public ArrayList Permutation(String str)
-{
+public ArrayList Permutation(String str) {
if (str.length() == 0)
return ret;
char[] chars = str.toCharArray();
@@ -1805,8 +1802,7 @@ public ArrayList Permutation(String str)
return ret;
}
-private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s)
-{
+private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) {
if (s.length() == chars.length) {
ret.add(s.toString());
return;
@@ -1836,8 +1832,7 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s)
使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不相等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,因此继续查找就能找出 majority。
```java
-public int MoreThanHalfNum_Solution(int[] nums)
-{
+public int MoreThanHalfNum_Solution(int[] nums) {
int majority = nums[0];
for (int i = 1, cnt = 1; i < nums.length; i++) {
cnt = nums[i] == majority ? cnt + 1 : cnt - 1;
@@ -1868,8 +1863,7 @@ public int MoreThanHalfNum_Solution(int[] nums)
快速排序的 partition() 方法,会返回一个整数 j 使得 a[l..j-1] 小于等于 a[j],且 a[j+1..h] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素。可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。
```java
-public ArrayList GetLeastNumbers_Solution(int[] nums, int k)
-{
+public ArrayList GetLeastNumbers_Solution(int[] nums, int k) {
ArrayList ret = new ArrayList<>();
if (k > nums.length || k <= 0)
return ret;
@@ -1880,8 +1874,7 @@ public ArrayList GetLeastNumbers_Solution(int[] nums, int k)
return ret;
}
-public void findKthSmallest(int[] nums, int k)
-{
+public void findKthSmallest(int[] nums, int k) {
int l = 0, h = nums.length - 1;
while (l < h) {
int j = partition(nums, l, h);
@@ -1894,8 +1887,7 @@ public void findKthSmallest(int[] nums, int k)
}
}
-private int partition(int[] nums, int l, int h)
-{
+private int partition(int[] nums, int l, int h) {
int p = nums[l]; /* 切分元素 */
int i = l, j = h + 1;
while (true) {
@@ -1909,8 +1901,7 @@ private int partition(int[] nums, int l, int h)
return j;
}
-private void swap(int[] nums, int i, int j)
-{
+private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
@@ -1927,8 +1918,7 @@ private void swap(int[] nums, int i, int j)
维护一个大小为 K 的最小堆过程如下:在添加一个元素之后,如果大顶堆的大小大于 K,那么需要将大顶堆的堆顶元素去除。
```java
-public ArrayList GetLeastNumbers_Solution(int[] nums, int k)
-{
+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);
@@ -1959,8 +1949,7 @@ private PriorityQueue right = new PriorityQueue<>();
/* 当前数据流读入的元素个数 */
private int N = 0;
-public void Insert(Integer val)
-{
+public void Insert(Integer val) {
/* 插入要保证两个堆存于平衡状态 */
if (N % 2 == 0) {
/* N 为偶数的情况下插入到右半边。
@@ -1975,8 +1964,7 @@ public void Insert(Integer val)
N++;
}
-public Double GetMedian()
-{
+public Double GetMedian() {
if (N % 2 == 0)
return (left.peek() + right.peek()) / 2.0;
else
@@ -1998,16 +1986,14 @@ public Double GetMedian()
private int[] cnts = new int[256];
private Queue queue = new LinkedList<>();
-public void Insert(char ch)
-{
+public void Insert(char ch) {
cnts[ch]++;
queue.add(ch);
while (!queue.isEmpty() && cnts[queue.peek()] > 1)
queue.poll();
}
-public char FirstAppearingOnce()
-{
+public char FirstAppearingOnce() {
return queue.isEmpty() ? '#' : queue.peek();
}
```
@@ -2023,8 +2009,7 @@ public char FirstAppearingOnce()
## 解题思路
```java
-public int FindGreatestSumOfSubArray(int[] nums)
-{
+public int FindGreatestSumOfSubArray(int[] nums) {
if (nums == null || nums.length == 0)
return 0;
int greatestSum = Integer.MIN_VALUE;
@@ -2044,8 +2029,7 @@ public int FindGreatestSumOfSubArray(int[] nums)
## 解题思路
```java
-public int NumberOf1Between1AndN_Solution(int n)
-{
+public int NumberOf1Between1AndN_Solution(int n) {
int cnt = 0;
for (int m = 1; m <= n; m *= 10) {
int a = n / m, b = n % m;
@@ -2066,8 +2050,7 @@ public int NumberOf1Between1AndN_Solution(int n)
## 解题思路
```java
-public int getDigitAtIndex(int index)
-{
+public int getDigitAtIndex(int index) {
if (index < 0)
return -1;
int place = 1; // 1 表示个位,2 表示 十位...
@@ -2085,8 +2068,7 @@ public int getDigitAtIndex(int index)
* place 位数的数字组成的字符串长度
* 10, 90, 900, ...
*/
-private int getAmountOfPlace(int place)
-{
+private int getAmountOfPlace(int place) {
if (place == 1)
return 10;
return (int) Math.pow(10, place - 1) * 9;
@@ -2096,8 +2078,7 @@ private int getAmountOfPlace(int place)
* place 位数的起始数字
* 0, 10, 100, ...
*/
-private int getBeginNumberOfPlace(int place)
-{
+private int getBeginNumberOfPlace(int place) {
if (place == 1)
return 0;
return (int) Math.pow(10, place - 1);
@@ -2106,8 +2087,7 @@ private int getBeginNumberOfPlace(int place)
/**
* 在 place 位数组成的字符串中,第 index 个数
*/
-private int getDigitAtIndex(int index, int place)
-{
+private int getDigitAtIndex(int index, int place) {
int beginNumber = getBeginNumberOfPlace(place);
int shiftNumber = index / place;
String number = (beginNumber + shiftNumber) + "";
@@ -2129,8 +2109,7 @@ private int getDigitAtIndex(int index, int place)
可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 < S2+S1,那么应该把 S1 排在前面,否则应该把 S2 排在前面。
```java
-public String PrintMinNumber(int[] numbers)
-{
+public String PrintMinNumber(int[] numbers) {
if (numbers == null || numbers.length == 0)
return "";
int n = numbers.length;
@@ -2156,8 +2135,7 @@ public String PrintMinNumber(int[] numbers)
## 解题思路
```java
-public int numDecodings(String s)
-{
+public int numDecodings(String s) {
if (s == null || s.length() == 0)
return 0;
int n = s.length();
@@ -2200,8 +2178,7 @@ public int numDecodings(String s)
应该用动态规划求解,而不是深度优先搜索,深度优先搜索过于复杂,不是最优解。
```java
-public int getMost(int[][] values)
-{
+public int getMost(int[][] values) {
if (values == null || values.length == 0 || values[0].length == 0)
return 0;
int n = values[0].length;
@@ -2224,8 +2201,7 @@ public int getMost(int[][] values)
## 解题思路
```java
-public int longestSubStringWithoutDuplication(String str)
-{
+public int longestSubStringWithoutDuplication(String str) {
int curLen = 0;
int maxLen = 0;
int[] preIndexs = new int[26];
@@ -2257,8 +2233,7 @@ public int longestSubStringWithoutDuplication(String str)
## 解题思路
```java
-public int GetUglyNumber_Solution(int N)
-{
+public int GetUglyNumber_Solution(int N) {
if (N <= 6)
return N;
int i2 = 0, i3 = 0, i5 = 0;
@@ -2291,8 +2266,7 @@ public int GetUglyNumber_Solution(int N)
最直观的解法是使用 HashMap 对出现次数进行统计,但是考虑到要统计的字符范围有限,因此可以使用整型数组代替 HashMap。
```java
-public int FirstNotRepeatingChar(String str)
-{
+public int FirstNotRepeatingChar(String str) {
int[] cnts = new int[256];
for (int i = 0; i < str.length(); i++)
cnts[str.charAt(i)]++;
@@ -2306,8 +2280,7 @@ public int FirstNotRepeatingChar(String str)
以上实现的空间复杂度还不是最优的。考虑到只需要找到只出现一次的字符,那么我们只需要统计的次数信息只有 0,1,更大,使用两个比特位就能存储这些信息。
```java
-public int FirstNotRepeatingChar2(String str)
-{
+public int FirstNotRepeatingChar2(String str) {
BitSet bs1 = new BitSet(256);
BitSet bs2 = new BitSet(256);
for (char c : str.toCharArray()) {
@@ -2339,15 +2312,13 @@ public int FirstNotRepeatingChar2(String str)
private long cnt = 0;
private int[] tmp; // 在这里创建辅助数组,而不是在 merge() 递归函数中创建
-public int InversePairs(int[] nums)
-{
+public int InversePairs(int[] nums) {
tmp = new int[nums.length];
mergeSort(nums, 0, nums.length - 1);
return (int) (cnt % 1000000007);
}
-private void mergeSort(int[] nums, int l, int h)
-{
+private void mergeSort(int[] nums, int l, int h) {
if (h - l < 1)
return;
int m = l + (h - l) / 2;
@@ -2356,8 +2327,7 @@ private void mergeSort(int[] nums, int l, int h)
merge(nums, l, m, h);
}
-private void merge(int[] nums, int l, int m, int h)
-{
+private void merge(int[] nums, int l, int m, int h) {
int i = l, j = m + 1, k = l;
while (i <= m || j <= h) {
if (i > m)
@@ -2392,8 +2362,7 @@ private void merge(int[] nums, int l, int m, int h)
当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部重新开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部重新开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
```java
-public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2)
-{
+public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode l1 = pHead1, l2 = pHead2;
while (l1 != l2) {
l1 = (l1 == null) ? pHead2 : l1.next;
@@ -2420,15 +2389,13 @@ Output:
## 解题思路
```java
-public int GetNumberOfK(int[] nums, int K)
-{
+public int GetNumberOfK(int[] nums, int K) {
int first = binarySearch(nums, K);
int last = binarySearch(nums, K + 1);
return (first == nums.length || nums[first] != K) ? 0 : last - first;
}
-private int binarySearch(int[] nums, int K)
-{
+private int binarySearch(int[] nums, int K) {
int l = 0, h = nums.length;
while (l < h) {
int m = l + (h - l) / 2;
@@ -2453,14 +2420,12 @@ private int binarySearch(int[] nums, int K)
private TreeNode ret;
private int cnt = 0;
-public TreeNode KthNode(TreeNode pRoot, int k)
-{
+public TreeNode KthNode(TreeNode pRoot, int k) {
inOrder(pRoot, k);
return ret;
}
-private void inOrder(TreeNode root, int k)
-{
+private void inOrder(TreeNode root, int k) {
if (root == null || cnt >= k)
return;
inOrder(root.left, k);
@@ -2484,8 +2449,7 @@ private void inOrder(TreeNode root, int k)
## 解题思路
```java
-public int TreeDepth(TreeNode root)
-{
+public int TreeDepth(TreeNode root) {
return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right));
}
```
@@ -2505,14 +2469,12 @@ public int TreeDepth(TreeNode root)
```java
private boolean isBalanced = true;
-public boolean IsBalanced_Solution(TreeNode root)
-{
+public boolean IsBalanced_Solution(TreeNode root) {
height(root);
return isBalanced;
}
-private int height(TreeNode root)
-{
+private int height(TreeNode root) {
if (root == null || !isBalanced)
return 0;
int left = height(root.left);
@@ -2538,8 +2500,7 @@ private int height(TreeNode root)
diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。
```java
-public void FindNumsAppearOnce(int[] nums, int num1[], int num2[])
-{
+public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) {
int diff = 0;
for (int num : nums)
diff ^= num;
@@ -2570,8 +2531,7 @@ public void FindNumsAppearOnce(int[] nums, int num1[], int num2[])
- 如果 sum < target,移动较小的元素,使 sum 变大一些。
```java
-public ArrayList FindNumbersWithSum(int[] array, int sum)
-{
+public ArrayList FindNumbersWithSum(int[] array, int sum) {
int i = 0, j = array.length - 1;
while (i < j) {
int cur = array[i] + array[j];
@@ -2604,8 +2564,7 @@ public ArrayList FindNumbersWithSum(int[] array, int sum)
## 解题思路
```java
-public ArrayList> FindContinuousSequence(int sum)
-{
+public ArrayList> FindContinuousSequence(int sum) {
ArrayList> ret = new ArrayList<>();
int start = 1, end = 2;
int curSum = 3;
@@ -2648,8 +2607,7 @@ public ArrayList> FindContinuousSequence(int sum)
正确的解法应该是和书上一样,先旋转每个单词,再旋转整个字符串。
```java
-public String ReverseSentence(String str)
-{
+public String ReverseSentence(String str) {
int n = str.length();
char[] chars = str.toCharArray();
int i = 0, j = 0;
@@ -2664,14 +2622,12 @@ public String ReverseSentence(String str)
return new String(chars);
}
-private void reverse(char[] c, int i, int j)
-{
+private void reverse(char[] c, int i, int j) {
while (i < j)
swap(c, i++, j--);
}
-private void swap(char[] c, int i, int j)
-{
+private void swap(char[] c, int i, int j) {
char t = c[i];
c[i] = c[j];
c[j] = t;
@@ -2691,8 +2647,7 @@ private void swap(char[] c, int i, int j)
先将 "abc" 和 "XYZdef" 分别翻转,得到 "cbafedZYX",然后再把整个字符串翻转得到 "XYZdefabc"。
```java
-public String LeftRotateString(String str, int n)
-{
+public String LeftRotateString(String str, int n) {
if (n >= str.length())
return str;
char[] chars = str.toCharArray();
@@ -2702,14 +2657,12 @@ public String LeftRotateString(String str, int n)
return new String(chars);
}
-private void reverse(char[] chars, int i, int j)
-{
+private void reverse(char[] chars, int i, int j) {
while (i < j)
swap(chars, i++, j--);
}
-private void swap(char[] chars, int i, int j)
-{
+private void swap(char[] chars, int i, int j) {
char t = chars[i];
chars[i] = chars[j];
chars[j] = t;
@@ -2727,8 +2680,7 @@ private void swap(char[] chars, int i, int j)
## 解题思路
```java
-public ArrayList maxInWindows(int[] num, int size)
-{
+public ArrayList maxInWindows(int[] num, int size) {
ArrayList ret = new ArrayList<>();
if (size > num.length || size < 1)
return ret;
@@ -2762,8 +2714,7 @@ public ArrayList maxInWindows(int[] num, int size)
空间复杂度:O(N2)
```java
-public List> dicesSum(int n)
-{
+public List> dicesSum(int n) {
final int face = 6;
final int pointNum = face * n;
long[][] dp = new long[n + 1][pointNum + 1];
@@ -2790,8 +2741,7 @@ public List> dicesSum(int n)
空间复杂度:O(N)
```java
-public List> dicesSum(int n)
-{
+public List> dicesSum(int n) {
final int face = 6;
final int pointNum = face * n;
long[][] dp = new long[2][pointNum + 1];
@@ -2829,8 +2779,7 @@ public List> dicesSum(int n)
## 解题思路
```java
-public boolean isContinuous(int[] nums)
-{
+public boolean isContinuous(int[] nums) {
if (nums.length < 5)
return false;
Arrays.sort(nums);
@@ -2861,8 +2810,7 @@ public boolean isContinuous(int[] nums)
约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈,所以最后需要对 n 取余。
```java
-public int LastRemaining_Solution(int n, int m)
-{
+public int LastRemaining_Solution(int n, int m) {
if (n == 0) /* 特殊输入的处理 */
return -1;
if (n == 1) /* 返回条件 */
@@ -2884,8 +2832,7 @@ public int LastRemaining_Solution(int n, int m)
使用贪心策略,假设第 i 轮进行卖出操作,买入操作价格应该在 i 之前并且价格最低。
```java
-public int maxProfit(int[] prices)
-{
+public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0)
return 0;
int soFarMin = prices[0];
@@ -2915,8 +2862,7 @@ public int maxProfit(int[] prices)
以下实现中,递归的返回条件为 n <= 0,取非后就是 n > 0,递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。
```java
-public int Sum_Solution(int n)
-{
+public int Sum_Solution(int n) {
int sum = n;
boolean b = (n > 0) && ((sum += Sum_Solution(n - 1)) > 0);
return sum;
@@ -2938,8 +2884,7 @@ a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进
递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。
```java
-public int Add(int a, int b)
-{
+public int Add(int a, int b) {
return b == 0 ? a : Add(a ^ b, (a & b) << 1);
}
```
@@ -2955,8 +2900,7 @@ public int Add(int a, int b)
## 解题思路
```java
-public int[] multiply(int[] A)
-{
+public int[] multiply(int[] A) {
int n = A.length;
int[] B = new int[n];
for (int i = 0, product = 1; i < n; product *= A[i], i++) /* 从左往右累乘 */
@@ -2988,8 +2932,7 @@ Output:
## 解题思路
```java
-public int StrToInt(String str)
-{
+public int StrToInt(String str) {
if (str == null || str.length() == 0)
return 0;
boolean isNegative = str.charAt(0) == '-';
diff --git "a/notes/\346\224\273\345\207\273\346\212\200\346\234\257.md" "b/notes/\346\224\273\345\207\273\346\212\200\346\234\257.md"
index c99071a0..d5a735ba 100644
--- "a/notes/\346\224\273\345\207\273\346\212\200\346\234\257.md"
+++ "b/notes/\346\224\273\345\207\273\346\212\200\346\234\257.md"
@@ -29,7 +29,7 @@
## 危害
-- 窃取用户的 Cookie 值
+- 窃取用户的 Cookie
- 伪造虚假的输入表单骗取个人信息
- 显示伪造的文章或者图片
@@ -47,7 +47,7 @@
富文本编辑器允许用户输入 HTML 代码,就不能简单地将 `<` 等字符进行过滤了,极大地提高了 XSS 攻击的可能性。
-富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,可以定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。
+富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,通过定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。
以下例子中,form 和 script 等标签都被转义,而 h 和 p 等标签将会保留。
@@ -131,7 +131,7 @@ http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
。
```
-如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
+如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 美元。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
@@ -153,8 +153,6 @@ Referer 首部字段位于 HTTP 报文中,用于标识请求来源的地址。
因为 CSRF 攻击是在用户无意识的情况下发生的,所以要求用户输入验证码可以让用户知道自己正在做的操作。
-也可以要求用户输入验证码来进行校验。
-
# 三、SQL 注入攻击
## 概念
diff --git "a/notes/\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md" "b/notes/\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md"
index 151ffc0f..e7ef804b 100644
--- "a/notes/\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md"
+++ "b/notes/\346\225\260\346\215\256\345\272\223\347\263\273\347\273\237\345\216\237\347\220\206.md"
@@ -383,13 +383,13 @@ MVCC 不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题
## Record Locks
-锁定的对象是记录的索引,而不是记录本身。
+锁定一个记录上的索引,而不是记录本身。
如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚集索引,因此 Record Locks 依然可以使用。
## Gap Locks
-锁定一个范围内的索引,例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。
+锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。
```sql
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
@@ -397,28 +397,14 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
## Next-Key Locks
-它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录,也锁定范围内的索引。在 user 中有以下记录:
+它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录上的索引,也锁定范围内的索引。例如一个索引包含以下值:10, 11, 13, and 20,那么就需要锁定以下区间:
```sql
-| id | last_name | first_name | age |
-|------|-------------|--------------|-------|
-| 4 | stark | tony | 21 |
-| 1 | tom | hiddleston | 30 |
-| 3 | morgan | freeman | 40 |
-| 5 | jeff | dean | 50 |
-| 2 | donald | trump | 80 |
-+------|-------------|--------------|-------+
-```
-
-那么就需要锁定以下范围:
-
-```sql
-(-∞, 21]
-(21, 30]
-(30, 40]
-(40, 50]
-(50, 80]
-(80, ∞)
+(negative infinity, 10]
+(10, 11]
+(11, 13]
+(13, 20]
+(20, positive infinity)
```
# 七、关系数据库设计理论
diff --git "a/notes/\347\256\227\346\263\225.md" "b/notes/\347\256\227\346\263\225.md"
index fb878742..cb5d4dbe 100644
--- "a/notes/\347\256\227\346\263\225.md"
+++ "b/notes/\347\256\227\346\263\225.md"
@@ -1623,10 +1623,10 @@ private List keys(Node x, Key l, Key h) {
## 2-3 查找树
-
-
2-3 查找树引入了 2- 节点和 3- 节点,目的是为了让树平衡。一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。
+
+
### 1. 插入操作
插入操作和 BST 的插入操作有很大区别,BST 的插入操作是先进行一次未命中的查找,然后再将节点插入到对应的空链接上。但是 2-3 查找树如果也这么做的话,那么就会破坏了平衡性。它是将新节点插入到叶子节点上。
@@ -2042,24 +2042,26 @@ public class SparseVector {
## 汉诺塔
-这是一个经典的递归问题,分为三步求解:
-
-1. 将 n-1 个圆盘从 from -> buffer
-2. 将 1 个圆盘从 from -> to
-3. 将 n-1 个圆盘从 buffer -> to
-
-如果只有一个圆盘,那么只需要进行一次移动操作。
+
-从上面的讨论可以知道,an = 2 * an-1 + 1,显然 an = 2n - 1,n 个圆盘需要移动 2n - 1 次。
+这是一个经典的递归问题,分为三步求解:
-
+- 将 n-1 个圆盘从 from -> buffer
+- 将 1 个圆盘从 from -> to
+
+- 将 n-1 个圆盘从 buffer -> to
+
+如果只有一个圆盘,那么只需要进行一次移动操作。
+
+从上面的讨论可以知道,an = 2 * an-1 + 1,显然 an = 2n - 1,n 个圆盘需要移动 2n - 1 次。
+
```java
public class Hanoi {
public static void move(int n, String from, String buffer, String to) {
@@ -2105,7 +2107,7 @@ from H1 to H3
生成编码时,从根节点出发,向左遍历则添加二进制位 0,向右则添加二进制位 1,直到遍历到根节点,根节点代表的字符的编码就是这个路径编码。
-
+
```java
public class Huffman {
diff --git "a/notes/\347\263\273\347\273\237\350\256\276\350\256\241\345\237\272\347\241\200.md" "b/notes/\347\263\273\347\273\237\350\256\276\350\256\241\345\237\272\347\241\200.md"
index 40f0ca9d..805764a9 100644
--- "a/notes/\347\263\273\347\273\237\350\256\276\350\256\241\345\237\272\347\241\200.md"
+++ "b/notes/\347\263\273\347\273\237\350\256\276\350\256\241\345\237\272\347\241\200.md"
@@ -79,7 +79,7 @@
实现可扩展主要有两种方式:
- 使用消息队列进行解耦,应用之间通过消息传递的方式进行通信;
-- 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以用过调用可复用的服务来实现业务逻辑,对其它产品没有影响。
+- 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以通过调用可复用的服务来实现业务逻辑,对其它产品没有影响。
# 四、可用性
diff --git "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md"
index ae44213e..b8f4dc9b 100644
--- "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md"
+++ "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md"
@@ -98,7 +98,9 @@ public static synchronized Singleton getUniqueInstance() {
(三)饿汉式-线程安全
-线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的优势。
+线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。
+
+但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
```java
private static Singleton uniqueInstance = new Singleton();
@@ -106,7 +108,7 @@ private static Singleton uniqueInstance = new Singleton();
(四)双重校验锁-线程安全
-uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行。也就是说,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
+uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
@@ -131,7 +133,7 @@ public class Singleton {
}
```
-考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,也就是说会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。
+考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,那么就会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。
```java
if (uniqueInstance == null) {
@@ -157,7 +159,7 @@ uniqueInstance 采用 volatile 关键字修饰也是很有必要的。`uniqueIns
这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。
-```source-java
+```java
public class Singleton {
private Singleton() {
@@ -299,7 +301,7 @@ public class Client {
### 意图
-定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化推迟到子类。
+定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
### 类图
@@ -1851,11 +1853,7 @@ No gumball dispensed
### 与状态模式的比较
-状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。
-
-但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。
-
-所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
+状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。
@@ -1969,7 +1967,7 @@ public abstract class CaffeineBeverage {
```
```java
-public class Coffee extends CaffeineBeverage{
+public class Coffee extends CaffeineBeverage {
@Override
void brew() {
System.out.println("Coffee.brew");
@@ -1983,7 +1981,7 @@ public class Coffee extends CaffeineBeverage{
```
```java
-public class Tea extends CaffeineBeverage{
+public class Tea extends CaffeineBeverage {
@Override
void brew() {
System.out.println("Tea.brew");
@@ -2238,7 +2236,7 @@ Number of items: 6
### 意图
-使用什么都不做的空对象来替代 NULL。
+使用什么都不做的空对象来代替 NULL。
一个方法返回 NULL,意味着方法的调用端需要去检查返回值是否是 NULL,这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值,而直接使用返回的对象,那么就有可能抛出空指针异常。
@@ -2393,7 +2391,7 @@ public abstract class TV {
```
```java
-public class Sony extends TV{
+public class Sony extends TV {
@Override
public void on() {
System.out.println("Sony.on()");
@@ -2412,7 +2410,7 @@ public class Sony extends TV{
```
```java
-public class RCA extends TV{
+public class RCA extends TV {
@Override
public void on() {
System.out.println("RCA.on()");
@@ -2551,9 +2549,6 @@ public abstract class Component {
```
```java
-import java.util.ArrayList;
-import java.util.List;
-
public class Composite extends Component {
private List child;
@@ -2659,7 +2654,7 @@ Composite:root
### 类图
-装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。
+装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component),具体组件的方法实现不需要依赖于其它对象,而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。
@@ -2770,7 +2765,7 @@ public class Client {
### 实现
-观看电影需要操作很多电器,使用外观模式可以实现一键看电影功能。
+观看电影需要操作很多电器,使用外观模式实现一键看电影功能。
```java
public class SubSystem {
@@ -2811,7 +2806,7 @@ public class Client {
### 设计原则
-最少知识原则:只和你的密友谈话。也就是客户对象所需要交互的对象应当尽可能少。
+最少知识原则:只和你的密友谈话。也就是说客户对象所需要交互的对象应当尽可能少。
## 6. 享元(Flyweight)
@@ -2822,8 +2817,8 @@ public class Client {
### 类图
- Flyweight:享元对象
-- IntrinsicState:内部状态,相同的项元对象共享
-- ExtrinsicState:外部状态
+- IntrinsicState:内部状态,享元对象共享内部状态
+- ExtrinsicState:外部状态,每个享元对象的外部状态不同
@@ -2854,8 +2849,6 @@ public class ConcreteFlyweight implements Flyweight {
```
```java
-import java.util.HashMap;
-
public class FlyweightFactory {
private HashMap flyweights = new HashMap<>();
diff --git a/pics/VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png b/pics/VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png
new file mode 100644
index 0000000000000000000000000000000000000000..c77f4ff840062457f3f722a72d6b7e05eb2e32f3
GIT binary patch
literal 21504
zcmZ_0Wk3{M*EWiYsDPwOD&5^6C=vqF<{z|7Yi$D+#E#TK_0)h9j*qjy8dyx>fPUYB9u|@@QeB
z<%1s+L-BIwhE5OH>uiM_sV{aPzPd2oL{dfHrdU+sGQT(a&^txDb%%&cYe?p^vHTY?
zj1CR~(=l}nt5rw)_tkgQ?|;P+rfYSmYyTM9M?G{DVB(WktwM4jExTvIdw4Zs|1F%S
zoO8T9!9XB?cs$CuNVBds0#=&ezRYqJ^qG)bXn1!MP9s~KRpe{GL7x3=e*d>oJlltj
z?>V#z5Phdvtn*KS6HOC0%M@Liu|$0y5Varv#{2qPc{Or-qdzBp5HZQkQ-?pZKA-4e
z^g}Mh$4qh1y*!|{;HzZw*wg-aK6`5M`#0MS@J1;FVxN#v!&-SMS0Xd`Lpah`X-Z!6
zs(m%ZpRT>^nHwF$`n9-qFX;JWPom|gqU!`AUwwabZpO*=S_Km*_9#gAH6
z@{YfZJ13H5;H`K3W7}cxOohFWvA)cy9?Z(0l
z4aG5D?WscNLT*-b9bEA}|6epB&HbX*vca7#hcdPLaZ^l?$M}mJTFK9!;Vbs(-@pmp
z7iCBY`(?mdpLKiiN@R0DZea3>-S&Y`Q+M!4=WgegWA~ZR&Pu0y_rTl?FdQ_rQKesdshw$*K!WtW;mUwlJCd}iNXH-~cipIPsXd$uDr>(=e+VSB{u|BSE)Ozf>
z152kZV2PCJ^9u{&EJGF*Y6XRPd1*hu0nyNUP2lxTMM9pV21C}b#&8k{6U!AUS=wc<
zw-%?OQ>|;2mc32liE1P|9R{*x8KC(kB_*}B33qRyqz5FWRL*e_?l^$#9=75$5@&2;
zwQn<8*lHGSkYZt%K|`r!QxDc93mt1F3|0%`s4Rj?OI?UAI`j;h0&SrL%ho~OS!Qgr
zWc`XKRPD-bkz>NQKQI$>WZrS5=dtU~bl2`LZWtD+05~j4sMWuih*{KgFMcI<7m{#W
zNucBTV1}Ho
z>py_T3V5h2thjFEDW7FeyBu#;mXrkYP7P)mwh>E&oJPy3e-#oo@89k0q=luB6NyPE5A&yiRieC9=_h61;5y6IDwUiRPw|SY3#HHdoCUo)+bxBhT{|Z
zu$d|Ny6@>;?Zadf)eQa$Y_yAM;4-Hm*4DNPvMjOFx%EQFJQ+lP*>Jjubl8nBRAk;qWGw&IOi{-A3R5Ix;5$LsNPH*{J007k_%a?Y15hbxZYyO
zYd~G6a+AKHgNC*k3yaKnY0K!-ja$ekDMFd#3oZZl_yTcxqIXHMtJcW@$>HtTC~oTB
z#Fr3h(riLn2b4t;uH`cI$d@ku4Gv`jFkbzyQMAd?2MiJBCb!NzZ<{YN6U0?a{1rzy!
zzUw%ar9j%7QPJv}?t+(hrQMH)Y7mM4Q9>IN+!I~5L#SgObK>ZLDI@(Sx%*P4TnzT#&|qsr!DUwDAwxFOlKQ4>WT-WOu_jfBcJ(
zN&2ux2sjfkIS}0c8D1yuznebj-2Xk!J+JitN=8!bn*Y8sZUvK^4k==mPFwAWtmyl<
zKS({x5RRUIY3ko%Xz+is`OXCP{*x&G^BXqFgCX0PBYUofn^{)mT%K*c{B;tnbeP)D
zm%gVvt40!Mk_2}NljWRI6eltvxH|Z`<-|-*D~rD#dU5kzY1GkQ?>)WICyCQmDZEja
zZNlDNFFYVmwG2<8-hkZvlcIit-PXx&7z9;{WR+gcZ@>RMpCKDt#MTq#{&(Yx_i@JF
zOE|>5gwVmm`$A2
z^TSDi#w76XNrhzxvKVzU3ATwEo=w<>@SHB;Vbpwny`1!xg{`hsC}n}%sVEb%=}Q0?
zB3U5ZfT)QVm5<1fPMmhP4>o}d|MA>q^G0M>-_cWw7|4|5HaLus9A!A&HAmfX#6=Yq
zRhgOC=~4nGOh=hr=Kenk3BP)+?R_-CVC<*lxBoUpjWx#O?FdTD!LB^OUP^@TNN&J*4whjxx&2w;xFaIjOEb+p+>M;oyT>OzQ(hk8KUS7W`Q!sIH
z-#+oLiGC-p)^c1{a7w?IMVA3?HJ*N7IgP8uZtHn+HeZ{_83-^A5%t##Yei<}+6o~I?M67eNhf4eSnbUwj2^9cTS?ey#Kg6idL-U!?ENR-xI4M5hpcR!BIp}v^K}BR=B|Fp=J~omUwTZb
zWJ{VNuvP3Zf*hd6$9;qmd^@M8D9ryps&cAkJ%UP#(K&j`cdZNW0|zr%Q5X-1Gn4*u
zqium}>xxYvS?(eaRD1X~`>)2C%mQuW%@yL|=abm~j=68jeRC7$}m
zMLJx5|E8~B7MhhKLr3)tmk^^HRfd-Sx#5JOrpjJ>|B2(pr@^X+z`5^6%(wY%dow_2&d&AZ)UYFs&x>+HYuJG&pakY9%
zr}+BwVb7cxF>5}pqxinN{{%YF+xv3h4l1HdenuiF&r^f@4)4cMh`IxN|HU6O-StCt
zkNSBevJN*s|2HOBwS(J~YF5qU@$aUvR+A0MunBf?f8pQ?QF9u8$+3~+ELjx>8BYZVliP?^QshXFWW
zq~!8(ip5&`t1VFXrw@u2!0)h-BE0x`02QjK9gYRKE@$1TnR3SKQfmDlgA
zer)b$j(zB~6w{HN?oevr8rF>o!pq2#_;Vxm`^M(0_xrwHTs=ouZ%r*0K-|mPdKa%`
z&UMz$lGHt3BdC`)<}u0cWFz&4${8JHAAH2dSXuXsC939X8gj^jV=aK=0y7-mRrw6(0)q6^1H`h5jk!yYUd5?&Clh;&iSLlMluap-Hd2Ld`txXhiK7
znD%73k{X=6YH_4iy@TD!>DUla$CvBB9W
ztSa+v^UYb(5QU2x&!cO`l#kehwm+{6CFin6
z+b^EF@c`0AhN>ydXhBx+ic~YBiwRK>zz;kQOLNqivKm>v(`EoCwZDky*Z&?bR7kx6
zJt1f^8oz}yf?l396(0xL9f8|vPBd9*jA7qoZJgoJ?RO~L!%oqLi`x#gl@C6MZ-BfA
ziN*i3%O{1PY7*_#xqwuyAGny#NA#q3M|F%mW4*dx_XUEOop5D1>xMgo{E5cn3uc5Y;2X&VKu?!r2e;{rwSt
z6j`r82f_qm<(qJHjyN5=%*r!nS)%FVSMaC!=)eQMd;0A!1i1D+u@Of4)ci?a$_)g7glr4QEP)
z*H-3!Y`2cf(zFL2<~)d6VE0KH6Q;I3jr@ey5%JHDkXqV)VR4r5HuGFzl+-2zK-%>o
z*<%q6gm{pE5SrOX21$I2i7FFj4Z77y3DQ7+!`&ug2He@X6`ikx7=FkX<2=atGb6s_
z1~BsX5BSuTvhHQ!i_?u!e_BacYPyEoz4Ez^h>Ccl!GV&!-2Bh91Q4VADPuPMe+YIe
znSE=jHe5~8&Unlgc?%_OT8ym8++rcox8Kh@l^*3xl9ojLJ$;D?LVh|y=fgef(*VfT
zxyVLtY!9SMU>#))qYygd6(sOU@IPZ948PN2oIZdUn5%{gESt|_HkE(Gb}8lhXl8BW
ze2*kR)jgH_+RBdBisHbH?osQ(vXkEY2HB#l=oAh50A*eqqIugmCE#d4;7urxLxXdP
z*SzUzU%a{g7zh*1Fil8Z>9fL3lQEig#&8e_d@3C%RUiH_P265krvxdOIFr+hf(VC`
zb{iPsn^>G0p%OFF3e;y#=|Ybl%bRX7rIhexOxXQ#>_^hynI^i1;4Ay*Ut;`=X4lbB
zGR5w5S_`_D1qy@m)Q7lfLS#9w=x)HvNBYKrjsc9D?Wm?|Gu$$?6mW5Q13|&(>SUMd}kt+
z1;PL!u}thSO6An6D+(s(p{TM`NUr(beLYxrCeoS!m>bwlC=u)yAO`ajV-Rugux7D-
z7?)KLft3s7E3l3__Ln{dbzhE}mns|u=fCGMf|qzi+;5qzVF6ckh;tRNq^-aPVyG7U
z8&v2R;-{ShE?z`N1Oth#*$fhp_kvoJFj4l_AJdEkA(k|JU^AHnb+-%X1WISyouE`h
zg>WqbdE!4G`wU{A0AXT-ckkw5(d!@yzk_IAJs6qa;V`IfU_Fp2VE7t;?3sgTeH~kz
z)`pmm5u6Xi2{F1c&Ko9cfn3_PWrBKhMSTxef1>UIKSQcmA5G+MeKHAFfPEJCj#T?i
zy?_Qkyt#O42kQ85H}8Ub@O=X27L!^pP6wO;1PFg#@Cllj;KRjJ^wL3aRTo!(&5sA|olPeR|iuy&R*5801
z;s9kDJCnEnk!`uC;iWh`6)$V(3TurTDwFhVaK4zZedtK{!xdQ;mlb=oWErAA#7J&5
zc@ZF|ym`exfcFWA+YJM8)!fz54~wi~!}!TB5O-HvW09kGH+Nzsos7{k5F4lh9(bob5k!4?wjXV1XP**77bqU5C(-x$$cv*fOp(82IUN}1w<%F!JTQhk!7@~?^u2W-#JB0=gv8_eyBdY
zkSfkRmzUom^=p$`ue7NRXDf3k%+j{H%$ohIZ
zw4puV%sWubXIJ&P-(KZ
z`~$rcx8BRvX-&)hxTH0I;$s;l7xUPF=vXZk?`2iAtGrL;nsodEu7SlOkyJn;kF`>s
zibJ`!wsxq(%~LsD7x#zMG@xB_~`DFd85Bqub6Mz-JV3XEb&Hb3~wFVfGlaMNIec<0skIbMe^d-42X3
zpgD%$RyXNi&PHtN2UCYi2U)!cWuV@BF!p>a9vSig&p>SC%wzp5x?$FG^>aS=AbKpBDsJ@Y}S&4aSF)r&UC^|7mHIVx*A2T^=f
zl1p17K&2JK(y^qNTZhJ`$4da@bgP1pO^_rWZydL<6|NY$v!K>FMWT%3Gs3ohO)Fio
z*~E!WfAp)A|Ag0*v$W;zQVCPZ#sfJqfYiBex-|pP5c5Hz+pb>r(L+8vy_x?0+Zz~r
zyB`>mv#Xhyq^BtLKsiJK>Z6-{<*Xw^1kO9tg|a+`GP*8~@R)l>MI_6yZ@k0G&GYr5k&!vJs=c~k9LC)jO^gk=lM2dN$mtILUX
zh(^=hS&<~6_z{kleg%uTY$&Zy>Z7(7@FjW9DA4~(&(M<#HzDB-#wsN5InT@Z6yA2G
zlNW}KDR>OXYR610Eop8yUF)oMW4{>O8IT_+JTeaF=!hD_8NqGtY@3-lh$a81rck1j
zp4n6|Kya}iTlG7%xN-dKAoaS_O0O#$QE3wnxoLt?)S0<((4IwGbE$x<4;a@wpJjLI
zc>v(
zwY54;7Xa{eWK;KrFS#xO;JJvVftB~^lce{4pfs6Qy<7I&?3ew5cn8n1vK$@XOMai0
z8+HYaOv&YrD{}rK)-pI!QF4Wouxo^NGp0~EYhsJtSJH#I9^Wf?M{EVM&OvjYh8M=<
zuEf;KRuaWimS*cWY@XYa@OX2Lw#v3{>oKX|
z^fP+-pvegJf$JDf`+2qs4^k71SvBvPOrwLZuP#FcDt>qMXXzuhl9H8xZewr`+_@SOz+s5bE|~+#NPMW)}dpYtb100yznIqas{kl
z)#(nh?qtTd824qy*(x9=Y?th1^2!P)qTl=tJDbx#MmUOd-I`r4C%@&S+#j#DKO2Kj
z#Nai*79~sMW>l~dIaFxMl4*`v;>jl*rSazHb5jAHLkW4xz5dF0
zd8s9^8PQQi=g7FzaN-ia-66r=d44%)nPi$*!`QpaL?`u$;`99E{L+UHuaC0h5|F&c
z>6uUJNj~h1SM*8eTWuUuDFMfV#+TC>1&%pBw}v#yB2ro{k5-~#Fq$_+M+bGPm7CKI
ziAFDt!y-v4P!7M+ooqBinb&I(&5&|3?OK3-97AMeYcV3Jx1qGFvpskQ*7<}u&Udu>ad->yZG_S158pt=Y~>~
zuTw@x?D|I0QosL%ew|jbdu!|^#UI=hi54}Yy{#yHZ
zW8mS)cf+SVXvi|jV$+(6lbGXHg`j0u&dy(U+mrMXIlQGQpzxWk@T8O(Nh-J@4D2Vr8Jamr^(*@%ZghBK
z(MDZ$Wtz9|Yy=Y>Y_?!PXFuxE=I5ft>$Rc%$|d}HAU>PS?(c@km=`||IFX;F9Lm{4
z2~CDJpO9u_`uO|Z18I{6ZUu3oH=6!(%XEya8Khr_FG5=ZPohx9AwNaT{lB1WN|G{0
z1|oLl6mikpUs*llA<|qf=t5_6pWwoiZY{7r#N-coK|23GI#fQ9kmeZrKOpx2^1mQA
zqOaytKi!|(VzDz}#OKs7+O@4)F={~Y3%hGZKCn|of^*Uik_%%@1@a5&=8jn$5ph(Au;KmEJw0qVkUAO10$l@Q1Jr`mst$lv2;sO97TiF}-xo%aFw
zoURGVM$k4x(cU+PK>nzMgZ}Rd7@(^BJ{^Ek-!A~DgHCRg_(pH+T1OmpAU^!Ioe_;O
zrVu!xcRawk;kV6*)q7F~%!9&PO;8{P$iqy{cV~@Fq;!vwe>WXLfss+v;uN4nKamUj
z4T|3*e{{}T%o%nkIrG&Z(3VP$n7%2>i@UySIeWvcCyv^C%+v4PEFg!v8!2r_&@g0^9PVr%j2dujOC5-a>_{nb^|#7z*`T#U*kyY
zoU-|_wkFdP>R}7qX_3hC55}Wtn`YvvjY!`%o<_r1G7W;7ew}1(;eOfOM$Z=)
zCYO*B{mA+)P4;mIaY7rW>ii=QCq)(9GQ1jCrWew$B
z=45G|(9$!zk}4?KxMxz&T-qwE2Ny#oIBYmEaK1Bv-V*RCuv;D9G|8dUDu=5=oZdC0
z#!4Mfd?iVM%3leanFP-`kw#qrNl8gXdGj>w
zS1pnm;(q#B72+-}U2kaSeu*Pb^VU<&g(vZGad~5@7u%WJ{&PzUi(LEnsJ^wtOdCLw
zXP2+Tih8O62IW!f;E1Y~saTW|2K6Pu-6eSqag#
zvpXlN!rZB7>m9?nV%H=?22+i~j2aDO=VQ*KT*G9p)|#bGWTg}KypBDupG5-NbXbBB
z;69+O0KOoC^!^P1WdCb!t=h7o*!*wbVs4Fn`}S>7>OhRgO!lNSJ{S$!h68x(%Z*K={iqZ_iFW%Uwz;M)#NKCjCCW2
z56l>db}hq)+PHJ%dp~A0!$vafP|X7bkH;h@?%)9E=M6*edAt+e8dJY<=0bUfZniL-X(^m@Q38^MfAyjB-KmE6HSlR(oeRfb^F^xVJ9OxJcmfdC!HeQN0H9CM
z