From 91f5d10c7561d00da76736ebbf6cb9102cfafcee Mon Sep 17 00:00:00 2001 From: krahets Date: Tue, 9 May 2023 00:26:55 +0800 Subject: [PATCH] build --- .../replace_linear_by_hashing.md | 29 ++- chapter_stack_and_queue/deque.md | 246 +++++++++++++++++- 2 files changed, 261 insertions(+), 14 deletions(-) diff --git a/chapter_searching/replace_linear_by_hashing.md b/chapter_searching/replace_linear_by_hashing.md index 8f6600d3f..e7dbe420a 100755 --- a/chapter_searching/replace_linear_by_hashing.md +++ b/chapter_searching/replace_linear_by_hashing.md @@ -4,21 +4,19 @@ comments: true # 12.2.   哈希优化策略 -在算法题中,**我们时常通过将线性查找替换为哈希查找来降低算法的时间复杂度**。以 LeetCode 全站第一题 [两数之和](https://leetcode.cn/problems/two-sum/) 为例。 +在算法题中,**我们常通过将线性查找替换为哈希查找来降低算法的时间复杂度**。我们借助一个算法题来加深理解。 !!! question "两数之和" - 给定一个整数数组 `nums` 和一个整数目标值 `target` ,请你在该数组中找出“和”为目标值 `target` 的那两个整数,并返回它们的数组下标。 - - 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 - - 你可以按任意顺序返回答案。 + 给定一个整数数组 `nums` 和一个整数目标值 `target` ,请在数组中搜索“和”为目标值 `target` 的两个整数,并返回他们在数组中的索引。注意,数组中同一个元素在答案里不能重复出现。返回任意一个解即可。 ## 12.2.1.   线性查找:以时间换空间 考虑直接遍历所有可能的组合。开启一个两层循环,在每轮中判断两个整数的和是否为 `target` ,若是,则返回它们的索引。 -(图) +![线性查找求解两数之和](replace_linear_by_hashing.assets/two_sum_brute_force.png) + +

Fig. 线性查找求解两数之和

=== "Java" @@ -199,12 +197,21 @@ comments: true ## 12.2.2.   哈希查找:以空间换时间 -考虑借助一个哈希表,将数组元素和元素索引构建为键值对。循环遍历数组中的每个元素 `num` 并执行: +考虑借助一个哈希表,键值对分别为数组元素和元素索引。循环遍历数组,每轮执行: -1. 判断数字 `target - num` 是否在哈希表中,若是则直接返回该两个元素的索引; -2. 将元素 `num` 和其索引添加进哈希表; +1. 判断数字 `target - nums[i]` 是否在哈希表中,若是则直接返回这两个元素的索引; +2. 将键值对 `num[i]` 和索引 `i` 添加进哈希表; -(图) +=== "<1>" + ![two_sum_hashtable_step1](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png) + +=== "<2>" + ![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png) + +=== "<3>" + ![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png) + +实现代码如下所示,仅需单层循环即可。 === "Java" diff --git a/chapter_stack_and_queue/deque.md b/chapter_stack_and_queue/deque.md index cfd149f82..6ab7ac620 100644 --- a/chapter_stack_and_queue/deque.md +++ b/chapter_stack_and_queue/deque.md @@ -1202,9 +1202,144 @@ comments: true === "C#" ```csharp title="linkedlist_deque.cs" - [class]{ListNode}-[func]{} + /* 双向链表节点 */ + class ListNode { + public int val; // 节点值 + public ListNode? next; // 后继节点引用(指针) + public ListNode? prev; // 前驱节点引用(指针) - [class]{LinkedListDeque}-[func]{} + public ListNode(int val) { + this.val = val; + prev = null; + next = null; + } + } + + /* 基于双向链表实现的双向队列 */ + class LinkedListDeque { + private ListNode? front, rear; // 头节点 front, 尾节点 rear + private int queSize = 0; // 双向队列的长度 + + public LinkedListDeque() { + front = null; + rear = null; + } + + /* 获取双向队列的长度 */ + public int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + public bool isEmpty() { + return size() == 0; + } + + /* 入队操作 */ + private void push(int num, bool isFront) { + ListNode node = new ListNode(num); + // 若链表为空,则令 front, rear 都指向 node + if (isEmpty()) { + front = node; + rear = node; + } + // 队首入队操作 + else if (isFront) { + // 将 node 添加至链表头部 + front.prev = node; + node.next = front; + front = node; // 更新头节点 + } + // 队尾入队操作 + else { + // 将 node 添加至链表尾部 + rear.next = node; + node.prev = rear; + rear = node; // 更新尾节点 + } + + queSize++; // 更新队列长度 + } + + /* 队首入队 */ + public void pushFirst(int num) { + push(num, true); + } + + /* 队尾入队 */ + public void pushLast(int num) { + push(num, false); + } + + /* 出队操作 */ + private int? pop(bool isFront) { + // 若队列为空,直接返回 null + if (isEmpty()) { + return null; + } + + int val; + // 队首出队操作 + if (isFront) { + val = front.val; // 暂存头节点值 + // 删除头节点 + ListNode fNext = front.next; + if (fNext != null) { + fNext.prev = null; + front.next = null; + } + + front = fNext; // 更新头节点 + } + // 队尾出队操作 + else { + val = rear.val; // 暂存尾节点值 + // 删除尾节点 + ListNode rPrev = rear.prev; + if (rPrev != null) { + rPrev.next = null; + rear.prev = null; + } + + rear = rPrev; // 更新尾节点 + } + + queSize--; // 更新队列长度 + return val; + } + + /* 队首出队 */ + public int? popFirst() { + return pop(true); + } + + /* 队尾出队 */ + public int? popLast() { + return pop(false); + } + + /* 访问队首元素 */ + public int? peekFirst() { + return isEmpty() ? null : front.val; + } + + /* 访问队尾元素 */ + public int? peekLast() { + return isEmpty() ? null : rear.val; + } + + /* 返回数组用于打印 */ + public int[] toArray() { + ListNode node = front; + int[] res = new int[size()]; + for (int i = 0; i < res.Length; i++) { + res[i] = node.val; + node = node.next; + } + + return res; + } + } ``` === "Swift" @@ -2289,7 +2424,112 @@ comments: true === "C#" ```csharp title="array_deque.cs" - [class]{ArrayDeque}-[func]{} + /* 基于环形数组实现的双向队列 */ + class ArrayDeque { + private readonly int[] nums; // 用于存储双向队列元素的数组 + private int front; // 队首指针,指向队首元素 + private int queSize; // 双向队列长度 + + /* 构造方法 */ + public ArrayDeque(int capacity) { + this.nums = new int[capacity]; + front = queSize = 0; + } + + /* 获取双向队列的容量 */ + public int capacity() { + return nums.Length; + } + + /* 获取双向队列的长度 */ + public int size() { + return queSize; + } + + /* 判断双向队列是否为空 */ + public bool isEmpty() { + return queSize == 0; + } + + /* 计算环形数组索引 */ + private int index(int i) { + // 通过取余操作实现数组首尾相连 + // 当 i 越过数组尾部后,回到头部 + // 当 i 越过数组头部后,回到尾部 + return (i + capacity()) % capacity(); + } + + /* 队首入队 */ + public void pushFirst(int num) { + if (queSize == capacity()) { + Console.WriteLine("双向队列已满"); + return; + } + // 队首指针向左移动一位 + // 通过取余操作,实现 front 越过数组头部后回到尾部 + front = index(front - 1); + // 将 num 添加至队首 + nums[front] = num; + queSize++; + } + + /* 队尾入队 */ + public void pushLast(int num) { + if (queSize == capacity()) { + Console.WriteLine("双向队列已满"); + return; + } + // 计算尾指针,指向队尾索引 + 1 + int rear = index(front + queSize); + // 将 num 添加至队尾 + nums[rear] = num; + queSize++; + } + + /* 队首出队 */ + public int popFirst() { + int num = peekFirst(); + // 队首指针向后移动一位 + front = index(front + 1); + queSize--; + return num; + } + + /* 队尾出队 */ + public int popLast() { + int num = peekLast(); + queSize--; + return num; + } + + /* 访问队首元素 */ + public int peekFirst() { + if (isEmpty()) { + throw new InvalidOperationException(); + } + return nums[front]; + } + + /* 访问队尾元素 */ + public int peekLast() { + if (isEmpty()) { + throw new InvalidOperationException(); + } + // 计算尾元素索引 + int last = index(front + queSize - 1); + return nums[last]; + } + + /* 返回数组用于打印 */ + public int[] toArray() { + // 仅转换有效长度范围内的列表元素 + int[] res = new int[queSize]; + for (int i = 0, j = front; i < queSize; i++, j++) { + res[i] = nums[index(j)]; + } + return res; + } + } ``` === "Swift"