diff --git a/notes/Leetcode 题解.md b/notes/Leetcode 题解.md index fcdafdec..e79ad01b 100644 --- a/notes/Leetcode 题解.md +++ b/notes/Leetcode 题解.md @@ -5005,13 +5005,13 @@ public int maxChunksToSorted(int[] arr) { ```html A: a1 → a2 - ↘ - c1 → c2 → c3 - ↗ + ↘ + c1 → c2 → c3 + ↗ B: b1 → b2 → b3 ``` -要求:时间复杂度为 O(N) 空间复杂度为 O(1) +要求:时间复杂度为 O(N),空间复杂度为 O(1) 设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 @@ -5028,7 +5028,7 @@ public ListNode getIntersectionNode(ListNode headA, ListNode headB) { } ``` -如果只是判断是否存在交点,那么就是另一个问题,即 [编程之美:3.6]() 的问题。有两种解法:把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环;或者直接比较第一个链表最后一个节点和第二个链表最后一个节点是否相同。 +如果只是判断是否存在交点,那么就是另一个问题,即 [编程之美:3.6]() 的问题。有两种解法:把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环;或者直接比较两个链表的最后一个节点是否相同。 **链表反转** @@ -5038,7 +5038,9 @@ public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ```java public ListNode reverseList(ListNode head) { - if (head == null || head.next == null) return head; + if (head == null || head.next == null) { + return head; + } ListNode next = head.next; ListNode newHead = reverseList(next); next.next = head; @@ -5091,9 +5093,9 @@ Given 1->1->2->3->3, return 1->2->3. ```java public ListNode deleteDuplicates(ListNode head) { - if (head == null || head.next == null) return head; + if(head == null || head.next == null) return head; head.next = deleteDuplicates(head.next); - return head.next != null && head.val == head.next.val ? head.next : head; + return head.val == head.next.val ? head.next : head; } ``` @@ -5108,19 +5110,18 @@ After removing the second node from the end, the linked list becomes 1->2->3->5. ```java public ListNode removeNthFromEnd(ListNode head, int n) { - ListNode newHead = new ListNode(-1); - newHead.next = head; - ListNode fast = newHead; + ListNode fast = head; while (n-- > 0) { fast = fast.next; } - ListNode slow = newHead; + 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 newHead.next; + return head; } ``` @@ -5132,22 +5133,23 @@ public ListNode removeNthFromEnd(ListNode head, int n) { Given 1->2->3->4, you should return the list as 2->1->4->3. ``` -题目要求:不能修改结点的 val 值;O(1) 空间复杂度。 +题目要求:不能修改结点的 val 值,O(1) 空间复杂度。 ```java public ListNode swapPairs(ListNode head) { - ListNode newHead = new ListNode(-1); - newHead.next = head; - ListNode cur = head, pre = newHead; - while (cur != null && cur.next != null) { - ListNode next = cur.next; - pre.next = next; - cur.next = next.next; - next.next = cur; - pre = cur; - cur = cur.next; + 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 newHead.next; + return node.next; } ``` @@ -5194,7 +5196,7 @@ private Stack buildStack(ListNode l) { [234. Palindrome Linked List (Easy)](https://leetcode.com/problems/palindrome-linked-list/description/) -要求以 O(1) 的空间复杂度来求解。 +题目要求:以 O(1) 的空间复杂度来求解。 切成两半,把后半段反转,然后比较两半是否相等。 @@ -5206,19 +5208,15 @@ public boolean isPalindrome(ListNode head) { slow = slow.next; fast = fast.next.next; } - - if (fast != null) { // 偶数节点,让 slow 指向下一个节点 - slow = slow.next; - } - - cut(head, slow); // 切成两个链表 - ListNode l1 = head, l2 = slow; - l2 = reverse(l2); - return isEqual(l1, l2); + if (fast != null) slow = slow.next; // 偶数节点,让 slow 指向下一个节点 + cut(head, slow); // 切成两个链表 + return isEqual(head, reverse(slow)); } private void cut(ListNode head, ListNode cutNode) { - while (head.next != cutNode) head = head.next; + while (head.next != cutNode) { + head = head.next; + } head.next = null; } @@ -5243,33 +5241,6 @@ private boolean isEqual(ListNode l1, ListNode l2) { } ``` -**链表元素按奇偶聚集** - -[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; -} -``` - **分隔链表** [725. Split Linked List in Parts(Medium)](https://leetcode.com/problems/split-linked-list-in-parts/description/) @@ -5310,6 +5281,33 @@ public ListNode[] splitListToParts(ListNode root, int k) { } ``` +**链表元素按奇偶聚集** + +[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; +} +``` + ## 树 ### 递归 diff --git a/notes/Redis.md b/notes/Redis.md index 2e10bb7a..90f8e4a5 100644 --- a/notes/Redis.md +++ b/notes/Redis.md @@ -475,7 +475,7 @@ Redis 服务器是一个事件驱动程序。 服务器通过套接字与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。 -Redis 基于 Reactor 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用响应的事件处理器。 +Redis 基于 Reactor 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用相应的事件处理器。

@@ -488,7 +488,7 @@ Redis 基于 Reactor 模式开发了自己的网络事件处理器,使用 I/O - 定时事件:是让一段程序在指定的时间之内执行一次; - 周期性事件:是让一段程序每隔指定时间就执行一次。 -Redis 将所有时间事件都放在一个无序链表中,通过遍历整个链表查找出已到达的时间事件,并调用响应的事件处理器。 +Redis 将所有时间事件都放在一个无序链表中,通过遍历整个链表查找出已到达的时间事件,并调用相应的事件处理器。 ## 事件的调度与执行 @@ -572,7 +572,7 @@ Sentinel(哨兵)可以监听主服务器,并在主服务器进入下线状 假设有 4 个 Reids 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,... 等等,有不同的方式来选择一个指定的键存储在哪个实例中。最简单的方式是范围分片,例如用户 id 从 0\~1000 的存储到实例 R0 中,用户 id 从 1001\~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。 -主要有三种分片方式: +根据执行分片的位置,可以分为三种分片方式: - 客户端分片:客户端使用一致性哈希等算法决定键应当分布到哪个节点。 - 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。 diff --git a/notes/数据库系统原理.md b/notes/数据库系统原理.md index 05976f78..cecb6d4d 100644 --- a/notes/数据库系统原理.md +++ b/notes/数据库系统原理.md @@ -422,7 +422,7 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE; 对于 A->B,如果能找到 A 的真子集 A',使得 A'-> B,那么 A->B 就是部分函数依赖,否则就是完全函数依赖; -对于 A->B,B->C,则 A->C 是一个传递依赖。 +对于 A->B,B->C,则 A->C 是一个传递函数依赖。 ## 异常 @@ -509,9 +509,9 @@ Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门 ### 3. 第三范式 (3NF) -非主属性不传递依赖于键码。 +非主属性不传递函数依赖于键码。 -上面的 关系-1 中存在以下传递依赖:Sno -> Sdept -> Mname,可以进行以下分解: +上面的 关系-1 中存在以下传递函数依赖:Sno -> Sdept -> Mname,可以进行以下分解: 关系-11 diff --git a/notes/算法.md b/notes/算法.md index 207621ce..fab64757 100644 --- a/notes/算法.md +++ b/notes/算法.md @@ -305,7 +305,7 @@ public class ArrayStack implements MyStack { ### 2. 链表实现 -需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素使就可以通过 next 指针遍历到前一个压入栈的元素从而让这个元素称为新的栈顶元素。 +需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素时就可以通过 next 指针遍历到前一个压入栈的元素从而让这个元素称为新的栈顶元素。 ```java public class ListStack implements MyStack { @@ -671,9 +671,9 @@ public class Selection> extends Sort { ## 冒泡排序 -通过从左到右不断交换相邻逆序的相邻元素,在一轮的交换之后,可以让未排序的元素上浮到最右侧,是的右侧是已排序的。 +通过从左到右不断交换相邻逆序的相邻元素,在一轮的交换之后,可以让未排序的元素上浮到右侧。 -在一轮交换中,如果没有发生交换,就说明数组已经是有序的,此时可以直接退出。 +在一轮循环中,如果没有发生交换,就说明数组已经是有序的,此时可以直接退出。 ```java public class Bubble> extends Sort { @@ -739,7 +739,7 @@ public class Shell> extends Sort { int N = nums.length; int h = 1; while (h < N / 3) - h = 3 * h + 1; // 1, 4, 13, 40, ... + h = 3 * h + 1; // 1, 4, 13, 40, ... while (h >= 1) { for (int i = h; i < N; i++) @@ -772,7 +772,7 @@ public abstract class MergeSort> extends Sort { int i = l, j = m + 1; for (int k = l; k <= h; k++) - aux[k] = nums[k]; // 将数据复制到辅助数组 + aux[k] = nums[k]; // 将数据复制到辅助数组 for (int k = l; k <= h; k++) { if (i > m) @@ -780,7 +780,7 @@ public abstract class MergeSort> extends Sort { else if (j > h) nums[k] = aux[i++]; else if (aux[i].compareTo(nums[j]) <= 0) - nums[k] = aux[i++]; // 先进行这一步,保证稳定性 + nums[k] = aux[i++]; // 先进行这一步,保证稳定性 else nums[k] = aux[j++]; } @@ -792,7 +792,6 @@ public abstract class MergeSort> extends Sort {

- ```java public class Up2DownMergeSort> extends MergeSort { @Override @@ -955,7 +954,7 @@ public T select(T[] nums, int k) { } ``` -该算法是线性级别的,因为每次正好将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。 +该算法是线性级别的。因为每次能将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。 ## 堆排序 @@ -963,7 +962,7 @@ public T select(T[] nums, int k) { 堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。 -堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。 +堆可以用数组来表示,因为堆是完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。

@@ -1012,7 +1011,7 @@ private void swim(int k) { } ``` -类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为下沉。一个节点有两个子节点,应当与两个子节点中最大那么节点进行交换。 +类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为下沉。一个节点如果有两个子节点,应当与两个子节点中最大那么节点进行交换。

@@ -1057,11 +1056,11 @@ public T delMax() { ### 5. 堆排序 -由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。 +由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序。并且堆排序是原地排序,不占用额外空间。 (一)构建堆 -无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。 +无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。

@@ -1071,7 +1070,7 @@ public T delMax() {

-

+

```java public class HeapSort> extends Sort { @@ -1116,7 +1115,7 @@ public class HeapSort> extends Sort { 堆排序时一种原地排序,没有利用额外的空间。 -现代操作系统很少使用堆排序,因为它无法利用缓存,也就是数组元素很少和相邻的元素进行比较。 +现代操作系统很少使用堆排序,因为它无法利用局部性原理进行缓存,也就是数组元素很少和相邻的元素进行比较。 ## 小结 diff --git a/pics/b20a3466-44b4-445e-87c7-dd4fb9ef44b2.png b/pics/b20a3466-44b4-445e-87c7-dd4fb9ef44b2.png new file mode 100644 index 00000000..3d5adabd Binary files /dev/null and b/pics/b20a3466-44b4-445e-87c7-dd4fb9ef44b2.png differ