diff --git a/Week_00/README.md b/Week_00/README.md new file mode 100644 index 00000000..1086d8d8 --- /dev/null +++ b/Week_00/README.md @@ -0,0 +1,102 @@ +# 200~关于git的使用:分布式 版本控制 +## 1.1、首先就是创建一个新的目录,mkdir +## 1.2、将这个目录进行git初始化,在这个目录下执行git init,会产生.git文件 +// todo 学习.git文件的原理 +## 1.3.1、第一次使用git时需要设置全局变量,user.name|user.mail +- 设置方式如下: +``` +git config —global user.name “自行设置” +git config —global user.mail “设置可以真正使用的邮箱” +``` +- 查看全局设置: +``` +git config —list +``` +//todo 除了查看global,再仔细研究一下local和system这两个选项 +## 1.3.2、关于本地仓库中的说明: +工作区:working directory,对应文件刚刚被创建,未被git track到 +缓存区:staging area,文件被提交到缓存区,被git所跟踪 +仓库:repository,执行完commit指令后,文件保存到下一个版本中,处于已经 提交的状态 +``` +git add +``` +>Untracked files: +>(use "git add ..." to include in what will be committed) +``` +git commit -m “create learn_git.html” [git log] +``` +>Changes to be committed: +>(use "git rm --cached ..." to unstage) +``` +提交完成之后:git status +``` +>On branch master +>nothing to commit, working tree clean +## 1.4、创建远程仓库 +打开github,创建仓库 +使用https协议:每次都需要输入密码,较为繁琐,若想不输入密码的话,会本 地保存明文密码,很不安全,不推荐 +使用ssh协议:第一次使用的时候,创建好git密钥【公钥,私钥】即可 +## 1.5、将本地已经创建好的仓库与远程仓库关联 +``` +git remote add origin git@github.com:******/learn_git.git +``` +注释: origin 远程仓库别名,可以自行更改 +执行完成之后可以检验一下是否添加成功:git remote +## 1.6、配置公钥和私钥 +### 1、首先本地生成github账户对应的邮箱的公钥和私钥,保存在~/.ssh文件夹中 +``` +ssh-keygen -t rsa -C “******@qq.com” +``` +### 2、将生成的文件夹下的公钥文件复制到github的账户设置中,添加即可完成操 作 +### 3、验证本地和github的远程仓库是否连接成功 +``` +执行:ssh -T git@github.com +``` +### 4、本地代码提交到远程仓库 +``` +git push -u origin master +``` +or +``` +git push -u origin main +``` +### 5、GitHub设置ssh key后push代码依旧需要输入用户名、密码的解决方法 +#### 5.1、问题描述 + Git生成密钥后将密钥配置到Github上,但是每次提交代码的时候还是要输入用户名和密码。操作步骤很是麻烦。 +#### 5.2、问题引入 +因为用的是https而不是ssh,更新origin为ssh格式即可。 +https的格式为:https://github.com/用户名/仓库名.git +ssh的格式为:git@github.com:用户名/仓库名.git + +#### 5.3、解决方法 +``` +git remote remove origin +git remote add origin git@github.com:用户名/仓库名.git +``` + +#### 5.4、新问题 +此时提交代码可以不用重复输入用户名与密码了,但是问题来了:当你拉代码的时候,会报如下错误: +>From github.com:用户名/项目名 +>* [new branch] main -> origin/main +>There is no tracking information for the current branch. +>Please specify which branch you want to merge with. +>See git-pull(1) for details. + git pull +>If you wish to set tracking information for this branch you can do so with: + git branch --set-upstream-to=origin/ main + +#### 5.5、重新设置trach branch +``` +git branch --set-upstream-to=origin/main main +``` +or +``` +git branch --set-upstream-to=origin/ main +``` +#### 5.6、warning: LF will be replaced by CRLF in 解决办法 +warning: LF will be replaced by CRLF in +原因是存在符号转义问题 +windows中的换行符为 CRLF, 而在linux下的换行符为LF,所以在执行add . 时出现提示,解决办法: +``` +git config --global core.autocrlf false +``` diff --git a/Week_01/JavaQueue.md b/Week_01/JavaQueue.md new file mode 100644 index 00000000..4014f2c3 --- /dev/null +++ b/Week_01/JavaQueue.md @@ -0,0 +1,165 @@ +# 1.用add first或add last这套新的API改写Deque的代码: +``` +import java.util.Deque; +import java.util.LinkedList; +public class NewDeque { + public void modifyDeque + Deque deque = new LinkedList(); + deque.addFirst("1"); + deque.addFirst("2"); + deque.addFirst("3"); + deque.addFirst("4") + deque.addFirst("5"); + System.out.println(deque); + String str = deque.peekFirst(); + System.out.println(str); + System.out.println(deque); + while (deque.size() > 0){ + System.out.println(deque.removeFirst()); + } + System.out.println(deque); + } +} +``` +# 2.2.分析Queue和Priority Queue的源码: +## Queue +- Queue是一个Interface,Queue队列继承了Collection接口,并扩展了队列相关方法。是一种为了可以优先处理先进入的数据而设计的集合。 +``` +boolean add(E e); +``` +在不超出规定容量的情况下可以将指定的元素立刻加入到队列,成功的时候返回success,超出容量限制时返回异常。 + +``` +boolean offer(E e); +``` +前面跟add()一样,就是与add相比,在容量受限时应该使用这个。 + +``` +E remove(); +``` +检索并删除此队列的首元素,队列为空则抛出异常。 + +``` +E poll(); +``` +检索并删除此队列的首元素,队列为空则抛出null。 + +``` +E element(); +``` +检索但并不删除此队列的首元素,队列为空则抛出异常。 + +``` +E peek(); +``` +检索但并不删除此队列的首元素,队列为空则抛出null。 + +## PriorityQueue +PriorityQueue继承于Queue,相比于一般的队列,它的出队的时候可以按照优先级进行出队,PriorityQueue可以根据给定的优先级顺序进行出队。 + +- 主要属性: +``` +private static final int DEFAULT_CAPACITY = 11; +``` +默认的容量。 + +``` +E[] storage; +``` +元素存储的地方。 + +``` +int used; +``` +数组中的实际元素数量。 + +``` +Comparator comparator; +``` +比较器。 + +``` +void clear() +``` +清空队列 + +``` +Comparator comparator() +``` +比较器 + +``` +Iterator iterator() +``` +迭代器 + +``` +boolean offer(E o) +``` +添加节点 + +``` +E peek() +``` +获取优先级队列头结点 + +``` +E poll() +``` +移除并获取优先级队列头节点 + +``` + boolean remove(Object o) +``` +移除指定元素 + +队列中元素个数 +``` +int size() +``` + +``` + boolean addAll(Collection c) +``` +添加所有元素 + +``` + 330: void resize() + 331: { + 332: E[] new_data = (E[]) new Object[2 * storage.length]; + 333: System.arraycopy(storage, 0, new_data, 0, storage.length); + 334: storage = new_data; + 335: } + 336: } +``` +扩容 + +``` + 304: void bubbleUp(int index) + 305: { + 306: // The element at INDEX was inserted into a blank spot. Now move + 307: // it up the tree to its natural resting place. + 308: while (index > 0) + 309: { + 310: // This works regardless of whether we're at 2N+1 or 2N+2. + 311: int parent = (index - 1) / 2; + 312: if (Collections.compare(storage[parent], storage[index], comparator) + 313: <= 0) + 314: { + 315: // Parent is the same or smaller than this element, so the + 316: // invariant is preserved. Note that if the new element + 317: // is smaller than the parent, then it is necessarily + 318: // smaller than the parent's other child. + 319: break; + 320: } + 321: + 322: E temp = storage[index]; + 323: storage[index] = storage[parent]; + 324: storage[parent] = temp; + 325: + 326: index = parent; + 327: } + 328: } +``` +冒泡排序的函数 + diff --git a/Week_01/README.md b/Week_01/README.md index 50de3041..82bd1bbb 100644 --- a/Week_01/README.md +++ b/Week_01/README.md @@ -1 +1,178 @@ -学习笔记 \ No newline at end of file +学习笔记 +# 1.数组、链表、跳表的基本实现和特性 +## 1.1.Array时间复杂度: + - prepend O(1),注意:正常情况下,数组prepend操作的时间复杂O(n),但是可以进行特殊优化到O(1),采用的方式是申请稍大一些一些的内存空间,然后在数组最开始预留一部分空间,然后prepend的操作则是把头下标前移一个位置即可。 + - append O(1) + - lookup O(1) + - insert O(n) + - delete O(n) +## 1.2.普通链表时间复杂度: + - prepend O(1) + - append O(1) + - lookup O(n) + - insert O(1) + - delete O(1) +## 1.3.跳表: +### 1.3.1.跳表的特点: + - 注意:只能用于元素有序的情况。 + - 所以,跳表(skip list)对标的是平衡树(AVL Tree)和 二分查找,是一种插入/删除/搜索/ 都是O(log n)的数据结构。1989年出现。 + - 它最大的优势是原理简单、容易实现、方便扩展、效率更高。因此在一些热门的项目里用来代替平衡树,如redis,LevelDB等。 +### 1.3.2.跳表如何进行加速(升维): + - 添加第一级索引 + - 添加第二级索引 + - 添加第三级索引 + - ... +### 1.3.3.跳表查询的时间复杂度分析 + - n/2,n/4,n/8,...,第k级索引节点的个数就是n/(2^k) + - 假设索引有h级,最高级的索引有2个节点,n/(2^h)=2,从而求得h=log2(n)-1 + - 例子:索引的高度:logn, 每层索引遍历的节点个数3,在跳表中查询任意数据的时间复杂度就是O(logn) +### 1.3.4.跳表的空间复杂度分析 + - 原始链表大小为n,每2个节点抽1个,每层索引的节点数:n/2,n/4,n/8,...,8,4,2 + - 原始链表的大小为n,每3个节点抽1个,每层索引的节点数:n/3,n/9,n/27,...,9,3,1 + - 空间复杂度是O(n) +## 1.4.小结 + - 数组、链表、跳表的原理和实现 + - 三者的时间复杂度、空间复杂度 + - 工程运用 + - 跳表:升维思想+空间换时间 +## 1.4.参考链接: + - [LRU缓存算法](https://www.jianshu.com/p/b1ab4a170c3c) + - [146. LRU 缓存机制](https://leetcode-cn.com/problems/lru-cache/) + - [为啥 Redis 使用跳表(Skip List)而不是使用 Red-Black?](https://www.zhihu.com/question/20202931) + - [Java 源码分析(ArrayList)](http://developer.classpath.org/doc/java/util/ArrayList-source.html) + - [Linked List 的标准实现代码](https://www.geeksforgeeks.org/implementing-a-linked-list-in-java-using-class/) + - [Java 源码分析(LinkedList)](http://developer.classpath.org/doc/java/util/LinkedList-source.html) + +# 2.实战题目解析:移动零 +## 2.1.练习步骤: + - 5-10分钟:读题和思考 + - 有思路:自己开始做和写代码; 不然,马上看题解。 + - 默写背诵、熟练 + - 然后开始自己写(闭卷) + - 复习 + - 最大误区:刷一遍;核心思想:升维+空间换时间 +## 2.2.Array实战题目 + - [盛最多水的容器](https://leetcode-cn.com/problems/container-with-most-water/) + - [移动零](https://leetcode-cn.com/problems/move-zeroes/) + - [爬楼梯](https://leetcode.com/problems/climbing-stairs/) + - [三数之和](https://leetcode-cn.com/problems/3sum/) +# 3.实战题目解析: +## 3.1.盛水最多容器 + - 一维数组的坐标变换:i,j + - 1.枚举:left bar x, right bar y,(x-y)*height_diff,O(n^2) + - 2.左右收敛(左右夹逼): 左右边界i,j, 向中间收敛 +## 3.2.爬楼梯 + - 暴力破解?基本情况? + - 找、最近、重复子问题(找重复性) +## 3.3.3数之和 + - 2数之和,简单版,a,b a+b = target + - 暴力 + - 哈希 + - 3数之和,a,b a+b = -c + - 审题: + - 1.返回不重复的三元组 + - 2.会有复数、无序 + - 3.可能不存在(实际要求返回空数组) + - 4.a+b=-c + - 5.数组内有重复数字,结果有可能有重复 + - 思路: + - 1.暴力:三重循环 + - 2.hash:两重暴力+hash + - 3.夹逼:因为不需要下标,可以排序后夹逼 + - 反馈: + - 1.通过一些边界条件,加速代码 + - 问题: + - 1.如何在hash很好的避免结果集重复? + - 实现: + - 1.暴力:超出时间限制 + - 2.hash slow + - 3.夹逼法(双指针,左右指针) + - 4.夹逼快速版 +## 3.3.4.环形链表 + - 思路: + - 1.暴力:遍历链表,hash + - 2.双指针(快慢指针) + - 题目: + - [反转链表](https://leetcode.com/problems/reverse-linked-list/) + - [两两交换链表中的节点](https://leetcode.com/problems/swap-nodes-in-pairs) + - [环形链表](https://leetcode.com/problems/linked-list-cycle) + - [环形链表 II](https://leetcode.com/problems/linked-list-cycle-ii) + - [K 个一组翻转链表](https://leetcode.com/problems/reverse-nodes-in-k-group/) +# 4.栈和队列的实现与特性 +## 4.1.Stach & Queue关键点 + - Stack: 先入后出;添加、删除皆为O(1) + - Queue: 先入先出;添加、删除皆为O(1) + - Deque(Double-End Queue): + - 1.简单理解:两端可以进出的Queue,Deque-Double-ended queue + - 2.插入和删除都是O(1)操作 +## 4.2.Stach、Queue、Deque工程实现 + - Java、Python、C++等已有实现基础 + - 如何查询接口信息?如何使用? + - stack:java 12 google 搜索 + - 示例代码 +## 4.3.Priority Queue + - 1.插入操作:O(1) + - 2.取出操作:O(logN)——按照元素优先级取出 + - 3.底层具体实现的数据结构较为多样和复杂:heap、bst、treap +## 4.4.小结 + - 1.Stack、Queue、Deque 的原理和操作复杂度 + - 2.PriorityQueue 的特点和操作复杂度 + - 3.查询Stack、Queue、Deque、PriorityQueue 的系统接口的方法 +## 4.5.参考链接 + - [Java 的 PriorityQueue 文档](http://docs.oracle.com/javase/10/docs/api/java/util/PriorityQueue.html) + - [Java 的 Stack 源码](http://developer.classpath.org/doc/java/util/Stack-source.html) + - [Java 的 Queue 源码](http://fuseyism.com/classpath/doc/java/util/Queue-source.html) + - [Python 的 heapq](http://docs.python.org/2/library/heapq.html) + - [高性能的 container 库](http://docs.python.org/2/library/collections.html) +# 5.实战题目解析:有效的括号、最小栈等问题 +## 5.1.预习题目 + - [有效的括号](https://leetcode-cn.com/problems/valid-parentheses/) + - [最小栈](https://leetcode-cn.com/problems/min-stack/) +## 5.2.实战题目 + - [柱状图中最大的矩形](https://leetcode-cn.com/problems/largest-rectangle-in-histogram) + - [滑动窗口最大值](https://leetcode-cn.com/problems/sliding-window-maximum) +## 5.3.有效的括号 + - 可以用栈来解决的问题:最近相关性(剥洋葱),先来后到 + - 计算机算法最后都归结于最近的重复性 + - 解决方法: + 1.暴力求解 + 2.栈 +## 5.3.最小栈 + - 辅助栈法 +## 5.4.柱状图中最大的矩形 + - 解决方法: + - 1.暴力求解1,枚举边界,min_height + ``` + for i->0,n-2 + for j->i+1,n-2 + (i,j) -> 最小高度,area + update max-area + ``` + - 2. 暴力求解2,枚举高度,bounds_range + ``` + for i->0,n-1: + 找到left bound,right bound, + area = height[i] * (right-left) + update max-area + ``` + - 2.stack + - 栈维护左边界,栈是从小到大排列的 +## 5.5.滑动窗口 + - 用队列解决:这里的队列是双端队列 + - 解决方法: + - 1.暴力求解O(n*k) + - 2.deque (sliding window) O(n) +## 5.6.练习题目 + - 用 add first 或 add last 这套新的 API 改写 Deque 的代码 + - 分析 Queue 和 Priority Queue 的源码 + - [删除排序数组中的重复项](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/) + - [旋转数组](https://leetcode-cn.com/problems/rotate-array/) + - [合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/) + - [合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/) + - [两数之和](https://leetcode-cn.com/problems/two-sum/) + - [移动零](https://leetcode-cn.com/problems/move-zeroes/) + - [加一](https://leetcode-cn.com/problems/plus-one/) + + - [设计循环双端队列](https://leetcode.com/problems/design-circular-deque) + + - [接雨水](https://leetcode.com/problems/trapping-rain-water/) \ No newline at end of file diff --git a/Week_01/RemoveDuplicates/Solution.cpp b/Week_01/RemoveDuplicates/Solution.cpp new file mode 100644 index 00000000..d1482a6b --- /dev/null +++ b/Week_01/RemoveDuplicates/Solution.cpp @@ -0,0 +1,19 @@ +// +// Created by HaigCode. +// +class Solution { +public: + //快慢指针 + int removeDuplicates(vector& nums) { + if(nums.size() == 0) return 0; + int left = 0, right = 1; + while(right < nums.size()){ + if(nums[right] != nums[left]){ + left ++; + nums[left] = nums[right]; + } + right ++; + } + return left + 1; + } +}; diff --git a/Week_01/design-cirsular-deque/MyCircularDeque.cpp b/Week_01/design-cirsular-deque/MyCircularDeque.cpp new file mode 100644 index 00000000..1a27a559 --- /dev/null +++ b/Week_01/design-cirsular-deque/MyCircularDeque.cpp @@ -0,0 +1,97 @@ +// +// Created by HaigCode. +// +class MyCircularDeque { +public: + int *data; + int front, rear; + int size; + /** Initialize your data structure here. Set the size of the deque to be k. */ + MyCircularDeque(int k) { + size = k + 1; + data = new int[k + 1]; + for (int i = 0; i < k + 1; ++i) { + data[i] = -1; + } + front = 0; + rear = 1; + } + + /** Adds an item at the front of Deque. Return true if the operation is successful. */ + bool insertFront(int value) { + if (isFull()) { + return false; + } else { + data[front] = value; + front = (front - 1 + size) % size; + return true; + } + } + + /** Adds an item at the rear of Deque. Return true if the operation is successful. */ + bool insertLast(int value) { + if (isFull()) { + return false; + } else { + data[rear] = value; + rear = (rear + 1) % size; + return true; + } + } + + /** Deletes an item from the front of Deque. Return true if the operation is successful. */ + bool deleteFront() { + if (isEmpty()) { + return false; + } else { + data[(front + 1) % size] = -1; + front = (front + 1) % size; + return true; + } + } + + /** Deletes an item from the rear of Deque. Return true if the operation is successful. */ + bool deleteLast() { + if (isEmpty()) { + return false; + } else { + data[(rear - 1 + size) % size] = -1; + rear = (rear - 1 + size) % size; + return true; + } + } + + /** Get the front item from the deque. */ + int getFront() { + return data[(front + 1) % size]; + } + + /** Get the last item from the deque. */ + int getRear() { + return data[(rear - 1 + size) % size]; + } + + /** Checks whether the circular deque is empty or not. */ + bool isEmpty() { + return (front + 1) % size == rear; + } + + /** Checks whether the circular deque is full or not. */ + bool isFull() { + return front == rear % size; + } +}; + + +/** + * Your MyCircularDeque object will be instantiated and called as such: + * MyCircularDeque* obj = new MyCircularDeque(k); + * bool param_1 = obj->insertFront(value); + * bool param_2 = obj->insertLast(value); + * bool param_3 = obj->deleteFront(); + * bool param_4 = obj->deleteLast(); + * int param_5 = obj->getFront(); + * int param_6 = obj->getRear(); + * bool param_7 = obj->isEmpty(); + * bool param_8 = obj->isFull(); + */ diff --git a/Week_01/merge-sorted-array/Solution.cpp b/Week_01/merge-sorted-array/Solution.cpp new file mode 100644 index 00000000..e5f77607 --- /dev/null +++ b/Week_01/merge-sorted-array/Solution.cpp @@ -0,0 +1,24 @@ +// +// Created by HaigCode. +// +class Solution { +public: + void merge(vector& nums1, int m, vector& nums2, int n) { + int p1 = m - 1; + int p2 = n - 1; + int p = m + n - 1; + + while(p >= 0){ + if(p1 < 0){ + nums1[p] = nums2[p2--]; + } else if(p2 < 0){ + nums1[p] = nums1[p1--]; + } else if(nums1[p1] <= nums2[p2]){ + nums1[p] = nums2[p2--]; + } else{ + nums1[p] = nums1[p1--]; + } + p--; + } + } +}; diff --git a/Week_01/merge-two-sorted-lists/Solution.cpp b/Week_01/merge-two-sorted-lists/Solution.cpp new file mode 100644 index 00000000..ade9d2a5 --- /dev/null +++ b/Week_01/merge-two-sorted-lists/Solution.cpp @@ -0,0 +1,24 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { + ListNode* preHead = new ListNode(-1); + + ListNode* prev = preHead; + while (l1 != nullptr && l2 != nullptr) { + if (l1->val < l2->val) { + prev->next = l1; + l1 = l1->next; + } else { + prev->next = l2; + l2 = l2->next; + } + prev = prev->next; + } + prev->next = l1 == nullptr ? l2 : l1; + return preHead->next; + } +}; \ No newline at end of file diff --git a/Week_01/move-zeroes/Solution.cpp b/Week_01/move-zeroes/Solution.cpp new file mode 100644 index 00000000..560a3626 --- /dev/null +++ b/Week_01/move-zeroes/Solution.cpp @@ -0,0 +1,21 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + void moveZeroes(vector& nums) { + if (nums.empty() || nums.size() == 0) return; + int index = 0; + //遍历,非零元素前移 + for(int i=0; i plusOne(vector& digits) { + int length = static_cast(digits.size()); + for(int i=length-1; i>=0; --i){ + digits[i] += 1; + int left = digits[i] % 10; + if(left != 0){ + return digits; + } + digits[i] = 0; + } + digits.insert(digits.begin(),1); + return digits; + } +}; diff --git a/Week_01/rotate-array/Solution.cpp b/Week_01/rotate-array/Solution.cpp new file mode 100644 index 00000000..b51feb36 --- /dev/null +++ b/Week_01/rotate-array/Solution.cpp @@ -0,0 +1,31 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + // 三次旋转 + void rotate(vector& nums, int k) { + //1.整体旋转 + reverse(nums.begin(),nums.end()); + //2.0-k旋转 + reverse(nums.begin(),nums.begin()+k%nums.size()); + //3.k-nums.size()旋转 + reverse(nums.begin()+k%nums.size(),nums.end()); + + } +}; + +class Solution2 { +public: + void rotate(vector& nums, int k) { + int N = nums.size(); + k = k % N; + for (int i = 0; i < k; i++) { + auto back = nums.back(); //返回List最后一个元素 + nums.pop_back();//删除最后一个元素 + nums.insert(nums.begin(), back);//在list头部插入最后一个元素 + } + } +}; + diff --git a/Week_01/trapping-rain-water/Solution.cpp b/Week_01/trapping-rain-water/Solution.cpp new file mode 100644 index 00000000..52509b57 --- /dev/null +++ b/Week_01/trapping-rain-water/Solution.cpp @@ -0,0 +1,24 @@ +// +// Created by HaigCode. +// +class Solution { +public: + //栈法 + int trap(vector& height){ + int ans = 0, current = 0; + stack st; + while (current < height.size()) { + while (!st.empty() && height[current] > height[st.top()]) { + int top = st.top(); + st.pop(); + if (st.empty()) + break; + int distance = current - st.top() - 1; + int bounded_height = min(height[current], height[st.top()]) - height[top]; + ans += distance * bounded_height; + } + st.push(current++); + } + return ans; + } +}; diff --git a/Week_01/two-sum/Solution.cpp b/Week_01/two-sum/Solution.cpp new file mode 100644 index 00000000..f682be43 --- /dev/null +++ b/Week_01/two-sum/Solution.cpp @@ -0,0 +1,18 @@ +// +// Created by HaigCode. +// +class Solution { +public: + //hash法 + vector twoSum(vector& nums, int target) { + unordered_map hashtable; + for(int i=0; isecond,i}; + } + hashtable[nums[i]] = i; + } + return {}; + } +}; diff --git a/Week_02/README.md b/Week_02/README.md index 50de3041..a0538c3d 100644 --- a/Week_02/README.md +++ b/Week_02/README.md @@ -1 +1,191 @@ -学习笔记 \ No newline at end of file +学习笔记 +# 5.哈希表、映射、集合的实现与特性 +## 5.1. Hash table +哈希表(Hash table),也叫散列表,是根据关键码值(key value)而直接进行访问的数据结构。 +它通过把关键码值映射到表中一个位置来访问记录,以加快查找速度。 +这个映射函数叫做散列函数(Hash Function),存放记录的数组叫做哈希表(或散列表)。 +## 5.2.工程实践 +- 电话号码簿 +- 用户信息表 +- 缓存(LRU Cache) +- 键值对存储(Redis) + +## 5.3.时间复杂度 +- O(1) +- 最坏:O(n):哈希碰撞 + + +### 5.4..Java Code: +- map :key-value对,key 不重复 + -new HashMap()/new TreeMap() + - map.set(key,value) + - map.get(key) + - map.has(key) + - map.size() + - map.clear() +- set:不重复元素的集合 + - new HashSet()/ new TreeSet() + - set.add(value) + - set.delete(value) + - set.hash(value) + + +### 5.5.做题流程 +- 1.clarification +- 2.possible solutions --> optimal(time & space) +- 3.code +- 4.test cases + +### 5.6.收藏优秀代码 +[优秀代码示例](https://shimo.im/docs/R6g9WJV89QkHrDhr/read) + + +# 6.树、二叉树、二叉搜索树的实现和特性 +## 6.1.简介 +- 单链表Linked List --> 升维 --> 树Tree +- 树Tree:没有环的图 +- 二叉树(binary tree) +- 图(graph) +- Linked List就是特殊化的Tree, Tree 就是特殊化的Graph +## 6.2.示例代码 +- python: +```python +class TreeNode: + def __init__(self,val): + self.val = val + self.left,self.right = None,None +``` +- java: +```java +public class TreeNode{ + public int val; + public TreeNode left,righ; + public TreeNode(int val){ + this.val = val; + this.left = null; + this.right = null; + } +} +``` +- C++: +```c++ +struct TreeNode{ + int val; + TreeNode *left; + TreeNode *right; + TreeNode(int x):val(x),left(NULL),right(NULL){} +} +``` + +# 6.3.二叉树遍历Pre-order/In-order/Post-order +- 前序(pre-order):根-左-右 +- 中序(In-order):左-根-右 +- 后续(Post-order):左-右-根 + +```python +def preorder(self,root): + if root: + self.raverse_path append(root.val) + self.preorder(root.left) + self.preorder(root,left) +def inorder(self, root): + if root: + self.inorder(root.left) + self.traverse_path.append(root.val) + self.inorder(root.right) +def postorder(self,root): + if root: + self.postorder(root.left) + self.postorder(root.right) + self.traverse_path.append(root.val) +``` + +## 6.4.二叉搜索树 Binary Search Tree +二叉搜索树,也称为二叉排序树,有序二叉树(ordered Binary Tree), 排序二叉树(Sorted Binary Tree),是指一棵空树或者具有下列性质的二叉树: +- 左子树上所有节点的值均小于它的根节点的值; +- 右子树上所有根节点的值均大于它的根节点的值; +- 以此类推:左、右子树也分别为二叉搜索树(重复性)。 +中序遍历:升序排列 + +二叉搜索树常见的操作: +- 1.查询 logN +- 2.插入新节点LogN +- 3.删除 + +## 6.5.堆 Heap +### 6.5.1.定义 +- Heap: 可以迅速找到一堆数中的最大最大或者最小值的数据结构 +- 将根节点最大的堆叫做大顶堆或者大根堆,根节点最小的堆叫做小顶堆或者小跟堆。 +- 常见的堆有二叉堆、斐波那契堆等。 + +- 假设是大顶堆,常见的操作API: + - find-max: O(1) + - delete-max: O(logN) + - insert(create): O(logN) or O(1) + + +### 6.5.2.二叉堆性质 +- 通过完全二叉树来实现(注意:不是二叉搜索火树) +- 二叉堆(大顶)它满足下列性质: + - 1.是一棵树; + - 2.树中任意节点的值总是 `>=` 其子节点的值。 + + +### 6.5.3.二叉堆的实现细节: +- 1.二叉堆一般都是通过 “数组”来实现; +- 2.假设“第一个元素”在数组中的索引为0 的话,则父节点和子节点的位置关系如下: + - 1.索引为i的左孩子的索引是(2*i+1); + - 2.索引为i的右孩子的索引是(2*i+2); + - 3.索引为i的父节点的索引是floor((i-1)/2); + +### 6.5.4.二叉堆 +- 0.根节点(顶堆元素)是:a[0] +- 1.索引为i的左孩子的索引是(2*i+1) +- 2.索引为i的右孩子的索引为(2*i+2) +- 3.索引为i的节点的索引是floor((i-1)/2); + +### 6.5.5.Insert 插入操作--O(logN) +- 1.新元素一律插入到堆的尾部 +- 2.依次向上调整整个堆的结构(一直到根即可) +- HeapifyUP + +### 6.5.6.Delete Max 删除堆顶操作--O(logN) +- 1.将堆尾元素替换到堆顶(即对顶被替代删除掉) +- 2.依次从根部向下调整整个堆的结构(一直到堆尾即可) +- 3.HeapifyDown + +### 6.6.7.注意: +- 二叉堆是堆(优先队列priority_queue)的一种常见且简单的实现;但是并不是最优的实现。 + +## 6.6.图的实现和特性 +### 6.6.1.图的属性和分类 +#### 6.6.1.1.图是属性 +- Graph(V,E) +- V-vetex:点 + - 1.度——入度和出度 + - 2.点和点之间:连通与否 +- E-edge:边 + - 1.有向和无向(单行线) + - 2.权重(边长) +### 6.6.2图的相关的算法 +- DFS 代码-递归写法: +```python +visited = self() # 和树中的DFS最大区别 +def dfs(node,visited): + if node in visited: #terminator + # already visited + return + visited.add(node) + + #process current node here + + for next_node in node.children(): + if not next_node in visited: + dfs(next_node,visited) +``` + +参考链接: +- [连通图个数]( https://leetcode-cn.com/problems/number-of-islands/) +- [拓扑排序(Topological Sorting)]( https://zhuanlan.zhihu.com/p/34871092) +- [最短路径(Shortest Path):Dijkstra]( https://www.bilibili.com/video/av25829980?from=search&seid=13391343514095937158) +- [最小生成树(Minimum Spanning Tree)]( https://www.bilibili.com/video/av84820276?from=search&seid=17476598104352152051) diff --git a/Week_02/binary-tree-inorder-traversal/Solution.cpp b/Week_02/binary-tree-inorder-traversal/Solution.cpp new file mode 100644 index 00000000..9f3c9bc7 --- /dev/null +++ b/Week_02/binary-tree-inorder-traversal/Solution.cpp @@ -0,0 +1,20 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + void inorder(TreeNode* root, vector& res) { + if (!root) { + return; + } + inorder(root->left, res); + res.push_back(root->val); + inorder(root->right, res); + } + vector inorderTraversal(TreeNode* root) { + vector res; + inorder(root, res); + return res; + } +}; \ No newline at end of file diff --git a/Week_02/binary-tree-preorder-traversal/Solution.cpp b/Week_02/binary-tree-preorder-traversal/Solution.cpp new file mode 100644 index 00000000..e2f115d2 --- /dev/null +++ b/Week_02/binary-tree-preorder-traversal/Solution.cpp @@ -0,0 +1,21 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + void preorder(TreeNode *root, vector &res) { + if (root == nullptr) { + return; + } + res.push_back(root->val); + preorder(root->left, res); + preorder(root->right, res); + } + + vector preorderTraversal(TreeNode *root) { + vector res; + preorder(root, res); + return res; + } +}; diff --git a/Week_02/chou-shu-lcof/Solution.cpp b/Week_02/chou-shu-lcof/Solution.cpp new file mode 100644 index 00000000..1b2a06e1 --- /dev/null +++ b/Week_02/chou-shu-lcof/Solution.cpp @@ -0,0 +1,27 @@ +// +// Created by HaigCode +// + +class Solution { +public: + static vector vec; + static priority_queue, greater> q; + static long cur; + int nthUglyNumber(int n) { //小顶堆法 + while(vec.size() < n){ + cur = vec.back(); + q.push(2 * cur); + q.push(3 * cur); + q.push(5 * cur); + while(q.top() == cur){ //去重复 + q.pop(); + } + vec.push_back(q.top()); + q.pop(); + } + return vec[n - 1]; + } +}; +vector Solution::vec = {1}; +priority_queue, greater> Solution::q; +long Solution::cur; \ No newline at end of file diff --git a/Week_02/group-anagrams/Solutions.cpp b/Week_02/group-anagrams/Solutions.cpp new file mode 100644 index 00000000..cd715028 --- /dev/null +++ b/Week_02/group-anagrams/Solutions.cpp @@ -0,0 +1,20 @@ +// +// Created by HaigCode +// + +class Solution { +public: + vector> groupAnagrams(vector& strs) { + vector>res; + unordered_map>mp; + for(const auto& str:strs){ + auto key=str; + sort(key.begin(),key.end());//key是排好序的一个单词 + mp[key].push_back(str);//value是排好序的单词对应的strs多个未排序的str集合 + } + for(const auto&m:mp){ + res.push_back(m.second);//把value集合放进res + } + return res; + } +}; \ No newline at end of file diff --git a/Week_02/hashmap_understand.md b/Week_02/hashmap_understand.md new file mode 100644 index 00000000..c35e74d8 --- /dev/null +++ b/Week_02/hashmap_understand.md @@ -0,0 +1,50 @@ +#1.capacity +capacity <<= 1: +```java +int i = 1; + +//类似于 i++就是 i = i+1;的这结构 +//i = i<<1 i等于i乘以2的1次方 +//i <<= 1; +//i = i<<2 i等于i乘以2的2次方,>>就是相除了 +i <<= 2; +System.out.println("结果是:" + i); +``` +# 2.java中有三种移位运算符: +``` +// <<:左移运算符,num << 1,相当于num乘以2 +// >>:右移运算符,num >> 1,相当于num除以2 +// >>>:无符号右移,忽略符号位,空位都以0补齐 +无符号右移的规则只记住一点:忽略了符号位扩展,0补最高位 无符号右移运算符>>> 只是对32位和64位的值有意义 +``` + +# 3.什么是哈希冲突 +- 哈希计算就是努力的把比较大的数据存放到相对较小的空间中。最常见的哈希算法是取模法。 +- 取模法:数组的长度是5。这时有一个数据是6。那么如何把这个6存放到长度只有5的数组中呢。按照取模法,计算6%5,结果是1,那么就把6放到数组下标是1的位置。那么,7就应该放到2这个位置。到此位置,冲突还没有出现。这时,有个数据是11,按照取模法,11%5=1,也等于1。那么原来数组下标是1的地方已经有数了,是6。这时又计算出1这个位置,那么数组1这个位置,就必须储存两个数了。这时,就叫哈希冲突。冲突之后就要按照顺序来存放了。 +- 如果数据的分布比较广泛,而且储存数据的数组长度比较大。那么哈希冲突就比较少。否则冲突是很高的。 + +# 4.HashMap就是一个散列表,它是通过“拉链法”解决哈希冲突的 +## 4.1.什么是拉链法: +- 拉链法又叫链地址法,拉链法就是把具有相同散列地址的关键字(同义词)值放在同一个单链表中,称为同义词链表。有m个散列地址就有m个链表,同时用指针数组T[0..m-1]存放各个链表的头指针,凡是散列地址为i的记录都以结点方式插入到以T[i]为指针的单链表中。T中各分量的初值应为空指针. +简单来说拉链法就是数组加链表。 +- 大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。 +- 嵌套类 Entry 的实例包含四个属性:key, value, hash 值和用于单向链表的 next。 + + +### 4.2.从HashMap的实现来看,我们总结拉链法的实现步骤如下: +1. 计算 key 的 hashValue +2. 根据 hashValue 值定位到 table[hashIndex] ( table[hashIndex] 是一条链表Node) +3. 若 table[hashValue] 为空则直接插入,不然则添加到链表末尾。 + +### 4.3.要点 +- HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 +- HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。 +- HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。 +- HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。 +默认加载因子是 0.75,如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。 +- HashMap是通过"拉链法"实现的哈希表 +- 默认的初始容量是16,必须是2的幂。 +- 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换) +//链表长度大于8时,将链表转化为红黑树 +//如果发现链表长度小于 6,则会将红黑树重新退化为链表 +- 因为红黑树的平均查找长度是log(n),长度为8的时候,平均查找长度为3。。如果继续使用链表,平均查找长度为8/2=4。这才有转换为树的必要。。链表长度如果是6以内,6/2=3,速度也很快的。转化为树还有生成树的时间,并不明智。中间有个差值,还可以防止链表和树频繁转换。 diff --git a/Week_02/n-ary-tree-level-order-traversal/Solution.cpp b/Week_02/n-ary-tree-level-order-traversal/Solution.cpp new file mode 100644 index 00000000..c1bb14cb --- /dev/null +++ b/Week_02/n-ary-tree-level-order-traversal/Solution.cpp @@ -0,0 +1,24 @@ +// +// Created by HaigCode.. +// + +class Solution { +public: + vector> res; + vector> levelOrder(Node* root) { + if(root != NULL){ + helper(root,0); + } + return res; + } + void helper(Node* root,int level){ + if(res.size() <= level){ + res.push_back({}); + } + res[level].push_back(root->val); + for(auto node : root->children){ + helper(node,level+1); + } + } +}; + diff --git a/Week_02/n-ary-tree-preorder-traversal/Solution.cpp b/Week_02/n-ary-tree-preorder-traversal/Solution.cpp new file mode 100644 index 00000000..4fad4cb4 --- /dev/null +++ b/Week_02/n-ary-tree-preorder-traversal/Solution.cpp @@ -0,0 +1,34 @@ +// +// Created by HaigCode +// +class Solution { +public://递归 + vector res; + vector preorder(Node* root) { + if(!root) return res; + res.push_back(root -> val); + for(auto i : root -> children){ + preorder(i); + } + return res; + } +}; + +class Solution { +public: //迭代 + vector preorder(Node* root) { + vector res; + if(!root) return res; + stack stk; + stk.push(root); + while(!stk.empty()){ + Node* tmp = stk.top(); + stk.pop(); + res.push_back(tmp -> val); + for(int i = tmp -> children.size()-1; i >= 0; --i){ + stk.push(tmp -> children[i]); + } + } + return res; + } +}; \ No newline at end of file diff --git a/Week_02/top-k-frequent-elements/Solution.cpp b/Week_02/top-k-frequent-elements/Solution.cpp new file mode 100644 index 00000000..862d1c31 --- /dev/null +++ b/Week_02/top-k-frequent-elements/Solution.cpp @@ -0,0 +1,35 @@ +// +// Created by HaigCode.. +// +class Solution { +public: + static bool cmp(pair& m, pair& n) { + return m.second > n.second; + } + + vector topKFrequent(vector& nums, int k) { + unordered_map occurrences; + for (auto& v : nums) { + occurrences[v]++; + } + + // pair 的第一个元素代表数组的值,第二个元素代表了该值出现的次数 + priority_queue, vector>, decltype(&cmp)> q(cmp); + for (auto& [num, count] : occurrences) { + if (q.size() == k) { + if (q.top().second < count) { + q.pop(); + q.emplace(num, count); + } + } else { + q.emplace(num, count); + } + } + vector ret; + while (!q.empty()) { + ret.emplace_back(q.top().first); + q.pop(); + } + return ret; + } +}; diff --git a/Week_02/two-sum/Solution.cpp b/Week_02/two-sum/Solution.cpp new file mode 100644 index 00000000..e16777ac --- /dev/null +++ b/Week_02/two-sum/Solution.cpp @@ -0,0 +1,17 @@ +// +// Created by HaigCode +// +class Solution { +public: + vector twoSum(vector& nums, int target) { + unordered_map hashtable; + for(int i=0; isecond,i}; + } + hashtable[nums[i]] = i; + } + return {}; + } +}; diff --git a/Week_02/valid-anagram/Solution.cpp b/Week_02/valid-anagram/Solution.cpp new file mode 100644 index 00000000..a46b0305 --- /dev/null +++ b/Week_02/valid-anagram/Solution.cpp @@ -0,0 +1,15 @@ +// +// Created by HaigCode +// +class Solution { +public: + bool isAnagram(string s, string t) { + if (s.length() != t.length()) { + return false; + } + sort(s.begin(), s.end()); + sort(t.begin(), t.end()); + return s == t; + } +}; + diff --git a/Week_03/README.md b/Week_03/README.md index 50de3041..3f47ced1 100644 --- a/Week_03/README.md +++ b/Week_03/README.md @@ -1 +1,90 @@ -学习笔记 \ No newline at end of file +学习笔记 +# 7.泛型递归、树的递归 +## 7.1.递归recursion +### 7.1.1.递归-循环:通过循环体来进行的循环 +### 7.1.2.盗梦空间: +- 向下进入到不同梦境中,向上又回到原来的一层 +- 通过声音同步回到上一层 +- 每一层的环境和周围的人都是一份拷贝、主角等几个人穿越不同层级的梦境(发生和携带变化) + +### 7.1.3.递归代码模板: +python代码模板: +```python +def recursion(level, param1, param2,...): + #recursion terminator,递归终止条件 + if level > MAX_LEVEL; + process_result: + return + + # process logic in current level,进行当前层业务逻辑代码 + process(level,data,...) + + # drill down,下钻到下一层 + self.recursion(level+1,p1,...) + + # reverse the current level status if needed,清理当前层状态 + +``` +Java代码模板: +```Java +public void recur(int level, int param){ + //terminator + if(level > MAX_LEVEL){ + //process result + return; + } + + //process current logic + process(level, param); + + //drill down + recur(level:level+1, newParam); + + //restore current status +} + +``` + +### 7.1.4.思维要点: +- 不要人肉进行递归(最大误区)——抵制人肉递归 +- 找到最近最简方法,将其拆解成为可重复解决的问题(重复子问题)——找最近重复性 +- 数学归纳法思维 + +# 8.分支、回溯的实现和特性 +## 8.1.分治 Divide & Conquer +- Problem --> Divide(Sub-Problem) --> Conquer(Sub-Solution) --> Merge(Solution) + +## 8.2.分治代码模板 +```python +def divide_conquer(problem,param1,param2,...): + # recursion terminator + if problem is None: + print_result + return + # prepare data , + data = prepare_data(problem) + subproblems = split_problem(problem, data) + + # conquer subproblems + subresult1 = self.divide_conquer(subproblems[0], p1, ...) + subresult2 = self.divide_conquer(subprobelms[1], p1, ...) + subresult3 = self.divide_conquer(subproblems[2], p1, ...) + + # process and generate the final result + result = process_result(subresult1, subresult2, subresult3,...) +``` + +### 8.3.回溯 +- 回溯法采用的是试错的思想,它尝试分步的去解决一个问题。 +- 在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效正确的解答的时候,它将取消上一步甚至上几步的计算,再通过其他的可能的分步解答再次尝试寻找问题的答案。 +- 回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况: + - 寻找到一个可能的正确的答案 + - 在尝试了所有可能的分步方法后宣告该问题没有答案 +- 在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。 + +- 参考链接: +- [牛顿迭代法原理](http://www.matrix67.com/blog/archives/361) +- [牛顿迭代法代码](http://www.voidcn.com/article/p-eudisdmk-zm.html) + + + diff --git a/Week_03/binary_tree_public_ancestors/Solution.cpp b/Week_03/binary_tree_public_ancestors/Solution.cpp new file mode 100644 index 00000000..bb698a6c --- /dev/null +++ b/Week_03/binary_tree_public_ancestors/Solution.cpp @@ -0,0 +1,24 @@ +// +// Created by HaigCode. +// + +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +class Solution { +public: + TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { //递归法 + if(!root) return NULL; + if(root==p||root==q) return root; + TreeNode* left = lowestCommonAncestor(root->left,p,q); + TreeNode* right = lowestCommonAncestor(root->right,p,q); + if(left && right) return root; + return left ? left : right; + } +}; diff --git a/Week_03/combinations/Solutions.cpp b/Week_03/combinations/Solutions.cpp new file mode 100644 index 00000000..aabe629c --- /dev/null +++ b/Week_03/combinations/Solutions.cpp @@ -0,0 +1,32 @@ +// +// Created by HaigCode. +// +class Solution { +public: + vector temp; + vector> ans; + + void dfs(int cur, int n, int k) { + // 剪枝:temp 长度加上区间 [cur, n] 的长度小于 k,不可能构造出长度为 k 的 temp + if (temp.size() + (n - cur + 1) < k) { + return; + } + // 记录合法的答案 + if (temp.size() == k) { + ans.push_back(temp); + return; + } + // 考虑选择当前位置 + temp.push_back(cur); + dfs(cur + 1, n, k); + temp.pop_back(); + // 考虑不选择当前位置 + dfs(cur + 1, n, k); + } + + vector> combine(int n, int k) { + dfs(1, n, k); + return ans; + } +}; + diff --git a/Week_03/construct-binary-tree-from-preorder-and-inorder-traversal/Solution.cpp b/Week_03/construct-binary-tree-from-preorder-and-inorder-traversal/Solution.cpp new file mode 100644 index 00000000..7a611258 --- /dev/null +++ b/Week_03/construct-binary-tree-from-preorder-and-inorder-traversal/Solution.cpp @@ -0,0 +1,51 @@ +// +// Created by HaigCode. +// + +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ +class Solution { +private: + unordered_map index; + +public: + TreeNode* myBuildTree(const vector& preorder, const vector& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) { + if (preorder_left > preorder_right) { + return nullptr; + } + + // 前序遍历中的第一个节点就是根节点 + int preorder_root = preorder_left; + // 在中序遍历中定位根节点 + int inorder_root = index[preorder[preorder_root]]; + + // 先把根节点建立出来 + TreeNode* root = new TreeNode(preorder[preorder_root]); + // 得到左子树中的节点数目 + int size_left_subtree = inorder_root - inorder_left; + // 递归地构造左子树,并连接到根节点 + // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素 + root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1); + // 递归地构造右子树,并连接到根节点 + // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素 + root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right); + return root; + } + + TreeNode* buildTree(vector& preorder, vector& inorder) { + int n = preorder.size(); + // 构造哈希映射,帮助我们快速定位根节点 + for (int i = 0; i < n; ++i) { + index[inorder[i]] = i; + } + return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1); + } +}; + diff --git a/Week_03/permutations-ii/Solutions.cpp b/Week_03/permutations-ii/Solutions.cpp new file mode 100644 index 00000000..21c327f7 --- /dev/null +++ b/Week_03/permutations-ii/Solutions.cpp @@ -0,0 +1,35 @@ +// +// Created by HaigCode. +// + +class Solution { + vector vis; + +public: + void backtrack(vector& nums, vector>& ans, int idx, vector& perm) { + if (idx == nums.size()) { + ans.emplace_back(perm); + return; + } + for (int i = 0; i < (int)nums.size(); ++i) { + if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) { + continue; + } + perm.emplace_back(nums[i]); + vis[i] = 1; + backtrack(nums, ans, idx + 1, perm); + vis[i] = 0; + perm.pop_back(); + } + } + + vector> permuteUnique(vector& nums) { + vector> ans; + vector perm; + vis.resize(nums.size()); + sort(nums.begin(), nums.end()); + backtrack(nums, ans, 0, perm); + return ans; + } +}; + diff --git a/Week_03/permutations/Solutions.cpp b/Week_03/permutations/Solutions.cpp new file mode 100644 index 00000000..7ab13903 --- /dev/null +++ b/Week_03/permutations/Solutions.cpp @@ -0,0 +1,27 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + void backtrack(vector>& res, vector& output, int first, int len){ + // 所有数都填完了 + if (first == len) { + res.emplace_back(output); + return; + } + for (int i = first; i < len; ++i) { + // 动态维护数组 + swap(output[i], output[first]); + // 继续递归填下一个数 + backtrack(res, output, first + 1, len); + // 撤销操作 + swap(output[i], output[first]); + } + } + vector> permute(vector& nums) { + vector > res; + backtrack(res, nums, 0, (int)nums.size()); + return res; + } +}; \ No newline at end of file diff --git a/Week_04/README.md b/Week_04/README.md index 50de3041..382a10c0 100644 --- a/Week_04/README.md +++ b/Week_04/README.md @@ -1 +1,148 @@ -学习笔记 \ No newline at end of file +# 9.深度优先搜索、广度优先搜索的实现和特性 +## 9.1.遍历搜索 +在树(图/状态集)中寻找特定节点 +## 9.2.树结构定义 +python: +``` +class TreeNode: + def __init__(self,val): + self.val = val + self.left,self.right = None,None +``` +Java: +``` +public class TreeNode{ + public int val; + public TreeNode left,right; + public TreeNode(int val){ + this.val = val; + this.left = null; + this.right = null; + } +} +``` +C++: +``` +struct TreeNode{ + int val; + TreeNode *left; + TreeNode *right; + TreeNode(int x): val(x),left(NULL),right(NULL){} +} +``` +### 9.3.搜索-遍历 +- 每个节点都要访问一次 +- 每个节点仅仅要访问一次 +- 对于节点的访问顺序不限 + - 深度优先:depth first search + - 广度优先:breadth first search + - 优先级优先:极大值搜索 + +### 9.4.DFS递归写法1 +``` +def dfs(node): + if node in visited: + # already visited + return + visited.add(node) + + # process current node + # ... # logic here + dfs(node.left) + dfs(node.right) +``` +### 9.5.DFS递归写法2 +``` +visited = set() +def dfs(node,visited): + if node in visited: # terminator + # already visited + return + + visited.add(node) + # process current node here. + ... + for next_node in node.children(): + if not next_node in visited: + dfs(next node,visited) +``` + +### 9.6.DFS非递归写法: +``` +def DFS(self, tree): + if tree.root is None: + return [] + + visited, stack = [], [tree,root] + + while stack: + node = stack.pop() + visited.add(node) + + process(node) + nodes = generate_related_nodes(node) + stack.push(nodes) + # other processing work +``` + +### 9.7.BFS代码 +``` +def BFS(graph, start, end): + queue = [] + queue.append(start) + visitd.add(start) + + while queue: + node = queue.popleft() + visited.add(node) + + process(node) + nodes = generate_related_nodes(node) + queue.push(nodes) + + # other processing work +``` + + +# 10.贪心算法Greedy +## 10.1.介绍 +- 贪心算法是一种在每一步选择中都采取在当前状态下最好或者最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。 +- 贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。 +- 局部最优 +- 贪心、回溯、动态规划区别: + - 贪心:当下做局部最优判断 + - 回溯:能够回退 + - 动态规划:最优判断 + 回退 +- 贪心法可以解决一些最优问题,如:求图中最小生成树、求哈夫曼编码等。然而对于工程和生活中的一些问题,贪心法一般不能得到我们所要求的答案。 +- 一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法。由于贪心法的高效性以及其所要求的答案比较接近最优结果,贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题。 +## 10.2.何种情况下可以应用贪心算法,即适用贪心算法的场景 +- 简单地说,问题能够分解成子问题来解决,子问题的最优解能够递推到最终问题的最优解。这种子问题最优解称为最优子结构。 +- 贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。 + +# 11.二分查找的实现、特性 +## 11.1.二分查找的前提 +- 1.目标函数单调性(单调递增或者递减) +- 2.存在上下界(bounded) +- 3.能够通过索引访问(index accessible) +## 11.2.代码模板 +``` +left,right = 0,len(array)-1 +while left <= right: + mid = (left+right)/2 + if array[mid] == target; + # find the target!! + break or return result + elif array[mid] < target: + left = mid + 1 + else: + right = mid - 1 +``` + +### 11.3.五毒神掌(五遍刷题) +### 11.4.四步做题 +- 1.审题,输入输出范围,边界条件 +- 2.所有解法都思考一遍,时间复杂度 + 空间复杂度,得到最优解法 +- 3.写 +- 4.测试样例 + + diff --git a/Week_04/assign-cookies/Solution.cpp b/Week_04/assign-cookies/Solution.cpp new file mode 100644 index 00000000..9044288b --- /dev/null +++ b/Week_04/assign-cookies/Solution.cpp @@ -0,0 +1,22 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + int findContentChildren(vector& g, vector& s) { + sort(g.begin(), g.end()); + sort(s.begin(), s.end()); + int numOfChildren = g.size(), numOfCookies = s.size(); + int count = 0; + for (int i = 0, j = 0; i < numOfChildren && j < numOfCookies; i++, j++) { + while (j < numOfCookies && g[i] > s[j]) { + j++; + } + if (j < numOfCookies) { + count++; + } + } + return count; + } +}; \ No newline at end of file diff --git a/Week_04/best-time-to-buy-and-sell-stock-ii/Solution.cpp b/Week_04/best-time-to-buy-and-sell-stock-ii/Solution.cpp new file mode 100644 index 00000000..14f75436 --- /dev/null +++ b/Week_04/best-time-to-buy-and-sell-stock-ii/Solution.cpp @@ -0,0 +1,17 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + int maxProfit(vector& prices) { + int n = prices.size(); + int dp[n][2]; + dp[0][0] = 0, dp[0][1] = -prices[0]; + for (int i = 1; i < n; ++i) { + dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]); + dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + } + return dp[n - 1][0]; + } +}; \ No newline at end of file diff --git a/Week_04/binary-search-semi-order-array/Solution.cpp b/Week_04/binary-search-semi-order-array/Solution.cpp new file mode 100644 index 00000000..588febca --- /dev/null +++ b/Week_04/binary-search-semi-order-array/Solution.cpp @@ -0,0 +1,44 @@ +// +// Created by HaigCode.. +// +//假定为升序排列且无重复元素,这个问题其实和LeetCode 153题(寻找旋转排序数组中的最小值)类似 +//找出最小元素所在下标,若下标不为0(即非有序数组),则该下标往后直至数组末尾都是无序 +class Solution { +public: + int findMin(vector& nums) {//找最小元素下标 + int low = 0; + int high = nums.size() - 1; + int min_num = nums[low]; + int min_index = low; + while (low <= high) { + int mid = low + ((high - low) >> 1); + if (nums[mid] > nums[low]) {//左半部分有序 + if (min_num < nums[low]) { + min_index = low; + min_num = nums[low]; + } + low = mid + 1; + } + else if (nums[mid] < nums[low]){//右半部分有序 + if (min_num < nums[mid]) { + min_index = mid; + min_num = nums[mid]; + } + high = mid - 1; + } + else {//此时,只剩nums[low]和nums[high]未比较 + if (min_num < nums[low]) { + min_index = low; + min_num = nums[low]; + } + if (min_num < nums[high]) { + min_index = high; + min_num = nums[high]; + } + break; + } + } + return min_index; + } +}; + diff --git a/Week_04/find-minimum-in-rotated-sorted-array/Solution.cpp b/Week_04/find-minimum-in-rotated-sorted-array/Solution.cpp new file mode 100644 index 00000000..78b69406 --- /dev/null +++ b/Week_04/find-minimum-in-rotated-sorted-array/Solution.cpp @@ -0,0 +1,26 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int findMin(vector& nums) { + if(nums.empty()) return -1; + if(nums.size() == 1) return nums[0]; + int p1 = 0, p2 = nums.size() - 1; + int mid = p1; // 假如旋转了数组的前面0个元素(也就是没有旋转),我们直接返回numbers[p1] + while(nums[p1] > nums[p2]) + { + if(p2 - p1 == 1) + { + // 循环终止条件:当p2-p1=1时,p2所指元素为最小值 + mid = p2; + break; + } + mid = (p1 + p2) / 2; + if(nums[mid] > nums[p1]) p1 = mid; + else p2 = mid; + } + return nums[mid]; + } +}; + diff --git a/Week_04/jump-game-ii/Solution.cpp b/Week_04/jump-game-ii/Solution.cpp new file mode 100644 index 00000000..c4c2ae3e --- /dev/null +++ b/Week_04/jump-game-ii/Solution.cpp @@ -0,0 +1,22 @@ +// +// Created by HaigCode. +// +int jump(vector &nums) +{ + int ans = 0; + int start = 0; + int end = 1; + while (end < nums.size()) + { + int maxPos = 0; + for (int i = start; i < end; i++) + { + // 能跳到最远的距离 + maxPos = max(maxPos, i + nums[i]); + } + start = end; // 下一次起跳点范围开始的格子 + end = maxPos + 1; // 下一次起跳点范围结束的格子 + ans++; // 跳跃次数 + } + return ans; +} diff --git a/Week_04/jump-game/Solution.cpp b/Week_04/jump-game/Solution.cpp new file mode 100644 index 00000000..7530ae37 --- /dev/null +++ b/Week_04/jump-game/Solution.cpp @@ -0,0 +1,20 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + bool canJump(vector& nums) { + int n = nums.size(); + int rightmost = 0; + for (int i = 0; i < n; ++i) { + if (i <= rightmost) { + rightmost = max(rightmost, i + nums[i]); + if (rightmost >= n - 1) { + return true; + } + } + } + return false; + } +}; \ No newline at end of file diff --git a/Week_04/lemonade-change/Solution.cpp b/Week_04/lemonade-change/Solution.cpp new file mode 100644 index 00000000..7e99df66 --- /dev/null +++ b/Week_04/lemonade-change/Solution.cpp @@ -0,0 +1,31 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + bool lemonadeChange(vector& bills) { + int five = 0, ten = 0; + for (auto& bill: bills) { + if (bill == 5) { + five++; + } else if (bill == 10) { + if (five == 0) { + return false; + } + five--; + ten++; + } else { + if (five > 0 && ten > 0) { + five--; + ten--; + } else if (five >= 3) { + five -= 3; + } else { + return false; + } + } + } + return true; + } +}; \ No newline at end of file diff --git a/Week_04/minesweeper/Solution.cpp b/Week_04/minesweeper/Solution.cpp new file mode 100644 index 00000000..2e1e3ea0 --- /dev/null +++ b/Week_04/minesweeper/Solution.cpp @@ -0,0 +1,49 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int dir_x[8] = {0, 1, 0, -1, 1, 1, -1, -1}; + int dir_y[8] = {1, 0, -1, 0, 1, -1, 1, -1}; + + void dfs(vector>& board, int x, int y) { + int cnt = 0; + for (int i = 0; i < 8; ++i) { + int tx = x + dir_x[i]; + int ty = y + dir_y[i]; + if (tx < 0 || tx >= board.size() || ty < 0 || ty >= board[0].size()) { + continue; + } + // 不用判断 M,因为如果有 M 的话游戏已经结束了 + cnt += board[tx][ty] == 'M'; + } + if (cnt > 0) { + // 规则 3 + board[x][y] = cnt + '0'; + } else { + // 规则 2 + board[x][y] = 'B'; + for (int i = 0; i < 8; ++i) { + int tx = x + dir_x[i]; + int ty = y + dir_y[i]; + // 这里不需要在存在 B 的时候继续扩展,因为 B 之前被点击的时候已经被扩展过了 + if (tx < 0 || tx >= board.size() || ty < 0 || ty >= board[0].size() || board[tx][ty] != 'E') { + continue; + } + dfs(board, tx, ty); + } + } + } + + vector> updateBoard1(vector>& board, vector& click) { + int x = click[0], y = click[1]; + if (board[x][y] == 'M') { + // 规则 1 + board[x][y] = 'X'; + } else { + dfs(board, x, y); + } + return board; + } +}; + diff --git a/Week_04/number-of-islands/Solution.cpp b/Week_04/number-of-islands/Solution.cpp new file mode 100644 index 00000000..2f4f6e61 --- /dev/null +++ b/Week_04/number-of-islands/Solution.cpp @@ -0,0 +1,36 @@ +// +// Created by HaigCode. +// + +class Solution { +private: + void dfs(vector>& grid, int r, int c) { + int nr = grid.size(); + int nc = grid[0].size(); + + grid[r][c] = '0'; + if (r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r - 1, c); + if (r + 1 < nr && grid[r+1][c] == '1') dfs(grid, r + 1, c); + if (c - 1 >= 0 && grid[r][c-1] == '1') dfs(grid, r, c - 1); + if (c + 1 < nc && grid[r][c+1] == '1') dfs(grid, r, c + 1); + } + +public: + int numIslands(vector>& grid) { + int nr = grid.size(); + if (!nr) return 0; + int nc = grid[0].size(); + + int num_islands = 0; + for (int r = 0; r < nr; ++r) { + for (int c = 0; c < nc; ++c) { + if (grid[r][c] == '1') { + ++num_islands; + dfs(grid, r, c); + } + } + } + + return num_islands; + } +}; diff --git a/Week_04/search-in-rotated-sorted-array/Solution.cpp b/Week_04/search-in-rotated-sorted-array/Solution.cpp new file mode 100644 index 00000000..c6d2caa2 --- /dev/null +++ b/Week_04/search-in-rotated-sorted-array/Solution.cpp @@ -0,0 +1,35 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + int search(vector& nums, int target) { + int n = (int)nums.size(); + if (!n) { + return -1; + } + if (n == 1) { + return nums[0] == target ? 0 : -1; + } + int l = 0, r = n - 1; + while (l <= r) { + int mid = (l + r) / 2; + if (nums[mid] == target) return mid; + if (nums[0] <= nums[mid]) { + if (nums[0] <= target && target < nums[mid]) { + r = mid - 1; + } else { + l = mid + 1; + } + } else { + if (nums[mid] < target && target <= nums[n - 1]) { + l = mid + 1; + } else { + r = mid - 1; + } + } + } + return -1; + } +}; diff --git a/Week_04/walking-robot-simulation/Solution.cpp b/Week_04/walking-robot-simulation/Solution.cpp new file mode 100644 index 00000000..ff291d9b --- /dev/null +++ b/Week_04/walking-robot-simulation/Solution.cpp @@ -0,0 +1,35 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int robotSim(vector& commands, vector>& obstacles) + { + int ans = 0; + unordered_map> ob; + for (auto& p : obstacles) + { + ob[p[0]].insert(p[1]); + } + + vector> dd = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} }; + int dir = 0; + vector pos = { 0,0 }; + + for (auto& c : commands) + { + dir = (c == -2) ? (dir + 4 - 1) % 4 : dir; + dir = (c == -1) ? (dir + 1) % 4 : dir; + for (int i = 0; i < c; i++) + { + vector next = { pos[0] + dd[dir][0], pos[1] + dd[dir][1] }; + if (ob[next[0]].count(next[1]) != 0) break; + pos = next; + ans = max(ans, pos[0] * pos[0] + pos[1] * pos[1]); + } + } + + return ans; + } +}; + diff --git a/Week_04/word-ladder-ii/Solution.cpp b/Week_04/word-ladder-ii/Solution.cpp new file mode 100644 index 00000000..73dfa66c --- /dev/null +++ b/Week_04/word-ladder-ii/Solution.cpp @@ -0,0 +1,78 @@ +// +// Created by HaigCode. +// +const int INF = 1 << 20; + +class Solution { +private: + unordered_map wordId; + vector idWord; + vector> edges; +public: + vector> findLadders(string beginWord, string endWord, vector& wordList) { + int id = 0; + for (const string& word : wordList) { + if (!wordId.count(word)) { + wordId[word] = id++; + idWord.push_back(word); + } + } + if (!wordId.count(endWord)) { + return {}; + } + if (!wordId.count(beginWord)) { + wordId[beginWord] = id++; + idWord.push_back(beginWord); + } + edges.resize(idWord.size()); + for (int i = 0; i < idWord.size(); i++) { + for (int j = i + 1; j < idWord.size(); j++) { + if (transformCheck(idWord[i], idWord[j])) { + edges[i].push_back(j); + edges[j].push_back(i); + } + } + } + const int dest = wordId[endWord]; + vector> res; + queue> q; + vector cost(id, INF); + q.push(vector{wordId[beginWord]}); + cost[wordId[beginWord]] = 0; + while (!q.empty()) { + vector now = q.front(); + q.pop(); + int last = now.back(); + if (last == dest) { + vector tmp; + for (int index : now) { + tmp.push_back(idWord[index]); + } + res.push_back(tmp); + } else { + for (int i = 0; i < edges[last].size(); i++) { + int to = edges[last][i]; + if (cost[last] + 1 <= cost[to]) { + cost[to] = cost[last] + 1; + vector tmp(now); + tmp.push_back(to); + q.push(tmp); + } + } + } + } + return res; + } + + bool transformCheck(const string& str1, const string& str2) { + int differences = 0; + for (int i = 0; i < str1.size() && differences < 2; i++) { + if (str1[i] != str2[i]) { + ++differences; + } + } + return differences == 1; + } +}; + + diff --git a/Week_04/word-ladder/Solution.cpp b/Week_04/word-ladder/Solution.cpp new file mode 100644 index 00000000..59adce7a --- /dev/null +++ b/Week_04/word-ladder/Solution.cpp @@ -0,0 +1,62 @@ +// +// Created by HaigCode.. +// + +class Solution { +public: + unordered_map wordId; + vector> edge; + int nodeNum = 0; + + void addWord(string& word) { + if (!wordId.count(word)) { + wordId[word] = nodeNum++; + edge.emplace_back(); + } + } + + void addEdge(string& word) { + addWord(word); + int id1 = wordId[word]; + for (char& it : word) { + char tmp = it; + it = '*'; + addWord(word); + int id2 = wordId[word]; + edge[id1].push_back(id2); + edge[id2].push_back(id1); + it = tmp; + } + } + + int ladderLength(string beginWord, string endWord, vector& wordList) { + for (string& word : wordList) { + addEdge(word); + } + addEdge(beginWord); + if (!wordId.count(endWord)) { + return 0; + } + vector dis(nodeNum, INT_MAX); + int beginId = wordId[beginWord], endId = wordId[endWord]; + dis[beginId] = 0; + + queue que; + que.push(beginId); + while (!que.empty()) { + int x = que.front(); + que.pop(); + if (x == endId) { + return dis[endId] / 2 + 1; + } + for (int& it : edge[x]) { + if (dis[it] == INT_MAX) { + dis[it] = dis[x] + 1; + que.push(it); + } + } + } + return 0; + } +}; + diff --git a/Week_06/221.maximal-square/Solution.cpp b/Week_06/221.maximal-square/Solution.cpp new file mode 100644 index 00000000..473c2fda --- /dev/null +++ b/Week_06/221.maximal-square/Solution.cpp @@ -0,0 +1,43 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int maximalSquare(vector>& matrix) { + if (matrix.size() == 0 || matrix[0].size() == 0) { + return 0; + } + int maxSide = 0; + int rows = matrix.size(), columns = matrix[0].size(); + for (int i = 0; i < rows; i++) { + for (int j = 0; j < columns; j++) { + if (matrix[i][j] == '1') { + // 遇到一个 1 作为正方形的左上角 + maxSide = max(maxSide, 1); + // 计算可能的最大正方形边长 + int currentMaxSide = min(rows - i, columns - j); + for (int k = 1; k < currentMaxSide; k++) { + // 判断新增的一行一列是否均为 1 + bool flag = true; + if (matrix[i + k][j + k] == '0') { + break; + } + for (int m = 0; m < k; m++) { + if (matrix[i + k][j + m] == '0' || matrix[i + m][j + k] == '0') { + flag = false; + break; + } + } + if (flag) { + maxSide = max(maxSide, k + 1); + } else { + break; + } + } + } + } + } + int maxSquare = maxSide * maxSide; + return maxSquare; + } +}; \ No newline at end of file diff --git a/Week_06/312.burst-balloons/Solution.cpp b/Week_06/312.burst-balloons/Solution.cpp new file mode 100644 index 00000000..3cb9256b --- /dev/null +++ b/Week_06/312.burst-balloons/Solution.cpp @@ -0,0 +1,35 @@ +// +// Created by HaigCode. +// +class Solution { +public: + vector> rec; + vector val; + +public: + int solve(int left, int right) { + if (left >= right - 1) { + return 0; + } + if (rec[left][right] != -1) { + return rec[left][right]; + } + for (int i = left + 1; i < right; i++) { + int sum = val[left] * val[i] * val[right]; + sum += solve(left, i) + solve(i, right); + rec[left][right] = max(rec[left][right], sum); + } + return rec[left][right]; + } + + int maxCoins(vector& nums) { + int n = nums.size(); + val.resize(n + 2); + for (int i = 1; i <= n; i++) { + val[i] = nums[i - 1]; + } + val[0] = val[n + 1] = 1; + rec.resize(n + 2, vector(n + 2, -1)); + return solve(0, n + 1); + } +}; \ No newline at end of file diff --git a/Week_06/32.longest-valid-parentheses/Solution.cpp b/Week_06/32.longest-valid-parentheses/Solution.cpp new file mode 100644 index 00000000..b4052b6a --- /dev/null +++ b/Week_06/32.longest-valid-parentheses/Solution.cpp @@ -0,0 +1,31 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int longestValidParentheses(string s) { + int size = s.length(); + vector dp(size, 0); + + int maxVal = 0; + for(int i = 1; i < size; i++) { + if (s[i] == ')') { + if (s[i - 1] == '(') { + dp[i] = 2; + if (i - 2 >= 0) { + dp[i] = dp[i] + dp[i - 2]; + } + } else if (dp[i - 1] > 0) { + if ((i - dp[i - 1] - 1) >= 0 && s[i - dp[i - 1] - 1] == '(') { + dp[i] = dp[i - 1] + 2; + if ((i - dp[i - 1] - 2) >= 0) { + dp[i] = dp[i] + dp[i - dp[i - 1] - 2]; + } + } + } + } + maxVal = max(maxVal, dp[i]); + } + return maxVal; + } +}; \ No newline at end of file diff --git a/Week_06/363.max-sum-of-rectangle-no-larger-than-k/Solution.cpp b/Week_06/363.max-sum-of-rectangle-no-larger-than-k/Solution.cpp new file mode 100644 index 00000000..385bed48 --- /dev/null +++ b/Week_06/363.max-sum-of-rectangle-no-larger-than-k/Solution.cpp @@ -0,0 +1,43 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int maxSumSubmatrix(vector>& matrix, int k) { + int row=matrix.size(); + if (row==0) + return 0; + int column=matrix.at(0).size(); + int ans=INT_MIN; + for (int left=0;left row_sum(row,0); + for (int right=left;right helper_set; + helper_set.insert(0); + int prefix_row_sum=0; + for (int i=0;ians) + ans=temp; + } + } + if (ans==k) + return k; + } + } + return ans; + } +}; + diff --git a/Week_06/403.frog-jump/Solution.cpp b/Week_06/403.frog-jump/Solution.cpp new file mode 100644 index 00000000..0c730bdd --- /dev/null +++ b/Week_06/403.frog-jump/Solution.cpp @@ -0,0 +1,29 @@ +// +// Created by HaigCode. +// +class Solution { +public: + bool canCross(vector& stones) { + unordered_set h; + for(auto x:stones) h.insert(x); + unordered_map memo; + + function dfs = [&] (int x, int y) { + if(y<=0 || !h.count(x)) return false; + if(x==1&&y==1) return true; + + long long t = (long long)x<<32|y; + if(memo.count(t)) return memo[t]; + + if(dfs(x-y,y)||dfs(x-y,y-1)||dfs(x-y,y+1)) + return memo[t] = true; + return memo[t] = false; + }; + + for(int i = 1 ; i <= 1001 ; i ++) + if(dfs(stones.back(),i)) + return true; + return false; + } +}; + diff --git a/Week_06/410.split-array-largest-sum/Solution.cpp b/Week_06/410.split-array-largest-sum/Solution.cpp new file mode 100644 index 00000000..f7d63c8a --- /dev/null +++ b/Week_06/410.split-array-largest-sum/Solution.cpp @@ -0,0 +1,24 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int splitArray(vector& nums, int m) { + int n = nums.size(); + vector> f(n + 1, vector(m + 1, LLONG_MAX)); + vector sub(n + 1, 0); + for (int i = 0; i < n; i++) { + sub[i + 1] = sub[i] + nums[i]; + } + f[0][0] = 0; + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= min(i, m); j++) { + for (int k = 0; k < i; k++) { + f[i][j] = min(f[i][j], max(f[k][j - 1], sub[i] - sub[k])); + } + } + } + return (int)f[n][m]; + } +}; + diff --git a/Week_06/552.student-attendance-record-ii/Solution.cpp b/Week_06/552.student-attendance-record-ii/Solution.cpp new file mode 100644 index 00000000..db8a8fdc --- /dev/null +++ b/Week_06/552.student-attendance-record-ii/Solution.cpp @@ -0,0 +1,29 @@ +// +// Created by HaigCode. +// +class Solution { +public: + long mod = 1e9 + 7; + int checkRecord(int n) { + if(n == 1) return 3; + vector> dp(2, vector(3, 0)); + dp[0][0] = 1; + dp[0][1] = 1; + dp[0][2] = 0; + dp[1][0] = 1; + dp[1][1] = 0; + dp[1][2] = 0; + vector> cur(2, vector(3, 0)); + for(long i = 2; i <= n; i++){ + cur[0][0] = (dp[0][0] + dp[0][1] + dp[0][2]) % mod; + cur[0][1] = dp[0][0]; + cur[0][2] = dp[0][1]; + cur[1][0] = (dp[0][0] + dp[0][1] + dp[0][2] + dp[1][0] + dp[1][1] + dp[1][2]) % mod; + cur[1][1] = dp[1][0]; + cur[1][2] = dp[1][1]; + dp = cur; + } + int ans = (dp[0][0] + dp[0][1] + dp[0][2] + dp[1][0] + dp[1][1] + dp[1][2]) % mod; + return ans; + } +}; diff --git a/Week_06/621.task-scheduler/Solution.cpp b/Week_06/621.task-scheduler/Solution.cpp new file mode 100644 index 00000000..b7e02650 --- /dev/null +++ b/Week_06/621.task-scheduler/Solution.cpp @@ -0,0 +1,15 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int leastInterval(vector& tasks, int n) { + int len=tasks.size(); + vector vec(26); + for(char c:tasks) ++vec[c-'A']; + sort(vec.begin(),vec.end(),[](int& x,int&y){return x>y;}); + int cnt=1; + while(cnt>& grid) { + if (grid.size() == 0 || grid[0].size() == 0) { + return 0; + } + int rows = grid.size(), columns = grid[0].size(); + auto dp = vector>(rows, vector (columns)); + dp[0][0] = grid[0][0]; + for (int i = 1; i < rows; i++) { + dp[i][0] = dp[i - 1][0] + grid[i][0]; + } + for (int j = 1; j < columns; j++) { + dp[0][j] = dp[0][j - 1] + grid[0][j]; + } + for (int i = 1; i < rows; i++) { + for (int j = 1; j < columns; j++) { + dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; + } + } + return dp[rows - 1][columns - 1]; + } +}; diff --git a/Week_06/674.palindromic-substrings/Solution.cpp b/Week_06/674.palindromic-substrings/Solution.cpp new file mode 100644 index 00000000..dbe1859d --- /dev/null +++ b/Week_06/674.palindromic-substrings/Solution.cpp @@ -0,0 +1,18 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int countSubstrings(string s) { + int n = s.size(), ans = 0; + for (int i = 0; i < 2 * n - 1; ++i) { + int l = i / 2, r = i / 2 + i % 2; + while (l >= 0 && r < n && s[l] == s[r]) { + --l; + ++r; + ++ans; + } + } + return ans; + } +}; diff --git a/Week_06/72.edit-distance/Solution.cpp b/Week_06/72.edit-distance/Solution.cpp new file mode 100644 index 00000000..c7d1c0c5 --- /dev/null +++ b/Week_06/72.edit-distance/Solution.cpp @@ -0,0 +1,38 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int minDistance(string word1, string word2) { + int n = word1.length(); + int m = word2.length(); + + // 有一个字符串为空串 + if (n * m == 0) return n + m; + + // DP 数组 + int D[n + 1][m + 1]; + + // 边界状态初始化 + for (int i = 0; i < n + 1; i++) { + D[i][0] = i; + } + for (int j = 0; j < m + 1; j++) { + D[0][j] = j; + } + + // 计算所有 DP 值 + for (int i = 1; i < n + 1; i++) { + for (int j = 1; j < m + 1; j++) { + int left = D[i - 1][j] + 1; + int down = D[i][j - 1] + 1; + int left_down = D[i - 1][j - 1]; + if (word1[i - 1] != word2[j - 1]) left_down += 1; + D[i][j] = min(left, min(down, left_down)); + + } + } + return D[n][m]; + } +}; + diff --git a/Week_06/76.minimum-window-substring/Solution.cpp b/Week_06/76.minimum-window-substring/Solution.cpp new file mode 100644 index 00000000..b6829e4d --- /dev/null +++ b/Week_06/76.minimum-window-substring/Solution.cpp @@ -0,0 +1,19 @@ +// +// Created by HaigCode.. +// +class Solution { +public: + string minWindow(string s, string t) { + vector worker(128, 0); + for (char &c : t) ++worker[c]; + int l = 0, r = 0, n = t.size(), len = INT_MAX, start = 0; + while (r < s.size()) { + if (worker[s[r++]]-- > 0) --n; // 非t类字母永远 >= 0 + while (!n) { + if (r - l < len) len = r - (start = l); + if (++worker[s[l++]] > 0) ++n; // t类字母永远 <= 0 + } + } + return len == INT_MAX ? "" : s.substr(start, len); + } +}; diff --git a/Week_06/91.decode-ways/Solution.cpp b/Week_06/91.decode-ways/Solution.cpp new file mode 100644 index 00000000..5a803049 --- /dev/null +++ b/Week_06/91.decode-ways/Solution.cpp @@ -0,0 +1,21 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int numDecodings(string s) { + if (s[0] == '0') return 0; + int pre = 1, curr = 1;//dp[-1] = dp[0] = 1 + for (int i = 1; i < s.size(); i++) { + int tmp = curr; + if (s[i] == '0') + if (s[i - 1] == '1' || s[i - 1] == '2') curr = pre; + else return 0; + else if (s[i - 1] == '1' || (s[i - 1] == '2' && s[i] >= '1' && s[i] <= '6')) + curr = curr + pre; + pre = tmp; + } + return curr; + } +}; + diff --git a/Week_06/README.md b/Week_06/README.md index 50de3041..b9bdbac1 100644 --- a/Week_06/README.md +++ b/Week_06/README.md @@ -1 +1,185 @@ -学习笔记 \ No newline at end of file +# 12.第五、六周 动态规划 +## 12.1.动态规划的实现及其特点 +### 12.1.1.总结数据结构&方法 +- Array 数组 +- Linked List 链表 +- Stack 栈 +- Queue 队列 +- HashTable 哈希表 +- Set 、 Map +- Tree 二叉树 +- BST 二叉搜素树 +- Search 查询 +- Recursion 查询 +- DFS 深度优先搜索 +- BFS 广度优先搜索 +- Divide & Conquer 分治 +- Backtracking 回溯 +- Greedy 贪心 +- Binary Search 二叉查找 + +### 12.1.2.地址 +- [数据结构与算法运行流程图](visualgo.net/en) + +### 12.1.2.思考纬度 +- 感触 + - 1.人肉递归低效,很累 + - 2.找到最近最简单方法,将其拆解成可重复解决的问题 + - 3.数学归纳法思维(抵制人肉递归的诱惑) +- 本质: + 寻找重复性——> 计算机指令集 + +## 12.2.动态规划Dynamic Programming(动态递推) +### 12.2.1.定义:只关注 最优解,最大值,最优子结构 +- 1.wiki定义 +- 2.Simplifying a complicated problem by breaking it down into simpler subproblems.(in a recursive manner) +- 3.Divide & Conquer + Optimal substructure 分治 + 最优子结构 + +### 12.2.2.关键点 +- 动态规划 和 递归或者分治 没有根本上的区别(关键看有无最优子结构) +- 共性: 找到重复子问题 +- 差异性:最优子结构、中途可以淘汰次优解 + +## 12.3.递归代码模板 +### 12.3.1.Python 代码模板 +``` +# Pythondef recursion(level, param1, param2, ...): +  # recursion terminator +   if level > MAX_LEVEL: + process_result + return +   # process logic in current level +   process(level, data...) +   # drill down +   self.recursion(level + 1, p1, ...) +   # reverse the current level status if needed +``` + +### 12.3.2.Java 代码模板 +``` +// Java +public void recur(int level, int param) { +   // terminator +   if (level > MAX_LEVEL) { +   // process result +     return; +   } +  // process current logic +   process(level, param); +   // drill down +   recur( level: level + 1, newParam); +   // restore current status +} +``` + +### 12.3.3.C/C++ 代码模板 +``` +// C/C++void recursion(int level, int param) { +   // recursion terminator +  if (level > MAX_LEVEL) { +   // process result +     return ; +   } +  // process current logic +   process(level, param); +  // drill down +   recursion(level + 1, param); +  // reverse the current level status if needed +} +``` + +### 12.3.4.JavaScript代码模板 +``` +// JavaScript +const recursion = (level, params) =>{ +   // recursion terminator + if(level > MAX_LEVEL){ + process_result + return + } + // process current level + process(level, params) + //drill down + recursion(level+1, params) + //clean current level status if needed +} +``` + +## 12.4.分治代码模板 +### 12.4.1.python代码模板 +``` +# Pythondef divide_conquer(problem, param1, param2, ...): +  # recursion terminator +  if problem is None: +  print_result +  return +  # prepare data +  data = prepare_data(problem) +  subproblems = split_problem(problem, data) +  # conquer subproblems +  subresult1 = self.divide_conquer(subproblems[0], p1, ...) +  subresult2 = self.divide_conquer(subproblems[1], p1, ...) +  subresult3 = self.divide_conquer(subproblems[2], p1, ...) +  … + # process and generate the final result +  result = process_result(subresult1, subresult2, subresult3, …) + # revert the current level states +``` + +### 12.4.2.C/C++代码模板 +``` +C/C++ +int divide_conquer(Problem *problem, int params) { +  // recursion terminator +  if (problem == nullptr) { +    process_result +    return return_result; +  } +  // process current problem +  subproblems = split_problem(problem, data) +  subresult1 = divide_conquer(subproblem[0], p1) +  subresult2 = divide_conquer(subproblem[1], p1) +  subresult3 = divide_conquer(subproblem[2], p1) +  ...  + // merge + result = process_result(subresult1, subresult2, subresult3) + // revert the current level status + return 0; +} +``` + +### 12.4.3.Java代码模板 +``` +Java +private static int divide_conquer(Problem problem, ) { + if (problem == NULL) { + int res = process_last_result(); + return res; + } + subProblems = split_problem(problem) + res0 = divide_conquer(subProblems[0]) + res1 = divide_conquer(subProblems[1]) + result = process_result(res0, res1); + return result; +} +``` + +### 12.4.4.JavaScript 代码模板 +``` +//Javascriptconst divide_conquer = (problem, params) => { +  // recursion terminator +  if (problem == null) { +    process_result +    return +  } +  // process current problem +  subproblems = split_problem(problem, data) +  subresult1 = divide_conquer(subproblem[0], p1) +  subresult2 = divide_conquer(subproblem[1], p1) +  subresult3 = divide_conquer(subproblem[2], p1) +  ...  + // merge  + result = process_result(subresult1, subresult2, subresult3)  + // revert the current level status +} +``` \ No newline at end of file diff --git a/Week_07/127.word-ladder/Solution.cpp b/Week_07/127.word-ladder/Solution.cpp new file mode 100644 index 00000000..61dca94b --- /dev/null +++ b/Week_07/127.word-ladder/Solution.cpp @@ -0,0 +1,60 @@ +// +// Created by HaigCode. +// +class Solution { +public: + unordered_map wordId; + vector> edge; + int nodeNum = 0; + + void addWord(string& word) { + if (!wordId.count(word)) { + wordId[word] = nodeNum++; + edge.emplace_back(); + } + } + + void addEdge(string& word) { + addWord(word); + int id1 = wordId[word]; + for (char& it : word) { + char tmp = it; + it = '*'; + addWord(word); + int id2 = wordId[word]; + edge[id1].push_back(id2); + edge[id2].push_back(id1); + it = tmp; + } + } + + int ladderLength(string beginWord, string endWord, vector& wordList) { + for (string& word : wordList) { + addEdge(word); + } + addEdge(beginWord); + if (!wordId.count(endWord)) { + return 0; + } + vector dis(nodeNum, INT_MAX); + int beginId = wordId[beginWord], endId = wordId[endWord]; + dis[beginId] = 0; + + queue que; + que.push(beginId); + while (!que.empty()) { + int x = que.front(); + que.pop(); + if (x == endId) { + return dis[endId] / 2 + 1; + } + for (int& it : edge[x]) { + if (dis[it] == INT_MAX) { + dis[it] = dis[x] + 1; + que.push(it); + } + } + } + return 0; + } +}; diff --git a/Week_07/130.surrounded-regions/Solution.cpp b/Week_07/130.surrounded-regions/Solution.cpp new file mode 100644 index 00000000..40d13761 --- /dev/null +++ b/Week_07/130.surrounded-regions/Solution.cpp @@ -0,0 +1,44 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int n, m; + + void dfs(vector>& board, int x, int y) { + if (x < 0 || x >= n || y < 0 || y >= m || board[x][y] != 'O') { + return; + } + board[x][y] = 'A'; + dfs(board, x + 1, y); + dfs(board, x - 1, y); + dfs(board, x, y + 1); + dfs(board, x, y - 1); + } + + void solve(vector>& board) { + n = board.size(); + if (n == 0) { + return; + } + m = board[0].size(); + for (int i = 0; i < n; i++) { + dfs(board, i, 0); + dfs(board, i, m - 1); + } + for (int i = 1; i < m - 1; i++) { + dfs(board, 0, i); + dfs(board, n - 1, i); + } + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (board[i][j] == 'A') { + board[i][j] = 'O'; + } else if (board[i][j] == 'O') { + board[i][j] = 'X'; + } + } + } + } +}; + diff --git a/Week_07/200.number-of-islands/Solution.cpp b/Week_07/200.number-of-islands/Solution.cpp new file mode 100644 index 00000000..b20d4f45 --- /dev/null +++ b/Week_07/200.number-of-islands/Solution.cpp @@ -0,0 +1,39 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int numIslands(vector>& grid) { + int count = 0; + // 遍历每一块 + for(int i=0;i>& grid, int i, int j){ + // 遍历到岛屿块 + if(inArea(i,j,grid.size(),grid[0].size()) && grid[i][j]=='1'){ + // visited 标为2 避免重复计算 + grid[i][j]='2'; + // 访问周边领接位置 + search(grid, i, j+1); + search(grid, i, j-1); + search(grid, i+1, j); + search(grid, i-1, j); + } + } + // 边界判断 + bool inArea(int i, int j, int row, int col){ + return (0<=i)&&(iinsert(word); + * bool param_2 = obj->search(word); + * bool param_3 = obj->startsWith(prefix); + */ + +class Trie { +private: + bool isEnd; + Trie* next[26]; +public: + Trie() { + isEnd = false; + memset(next, 0, sizeof(next)); + } + + void insert(string word) { + Trie* node = this; + for (char c : word) { + if (node->next[c-'a'] == NULL) { + node->next[c-'a'] = new Trie(); + } + node = node->next[c-'a']; + } + node->isEnd = true; + } + + bool search(string word) { + Trie* node = this; + for (char c : word) { + node = node->next[c - 'a']; + if (node == NULL) { + return false; + } + } + return node->isEnd; + } + + bool startsWith(string prefix) { + Trie* node = this; + for (char c : prefix) { + node = node->next[c-'a']; + if (node == NULL) { + return false; + } + } + return true; + } +}; + diff --git a/Week_07/212.word-search-ii/Solution.cpp b/Week_07/212.word-search-ii/Solution.cpp new file mode 100644 index 00000000..ba031b7b --- /dev/null +++ b/Week_07/212.word-search-ii/Solution.cpp @@ -0,0 +1,59 @@ +// +// Created by HaigCode. +// + +class TrieNode{ +public: + string word = ""; + vector nodes; + TrieNode():nodes(26, 0){} +}; + +class Solution { + int rows, cols; + vector res; +public: + vector findWords(vector>& board, vector& words) { + rows = board.size(); + cols = rows ? board[0].size():0; + if(rows==0 || cols==0) return res; + + //建立字典树的模板 + TrieNode* root = new TrieNode(); + for(string word:words){ + TrieNode *cur = root; + for(int i=0; inodes[idx]==0) cur->nodes[idx] = new TrieNode(); + cur = cur->nodes[idx]; + } + cur->word = word; + } + + //DFS模板 + for(int i=0; i>& board, TrieNode* root, int x, int y){ + char c = board[x][y]; + //递归边界 + if(c=='.' || root->nodes[c-'a']==0) return; + root = root->nodes[c-'a']; + if(root->word!=""){ + res.push_back(root->word); + root->word = ""; + } + + board[x][y] = '.'; + if(x>0) dfs(board, root, x-1, y); + if(y>0) dfs(board, root, x, y-1); + if(x+1> cache[100] = {nullptr}; +public: + shared_ptr> generate(int n) { + if (cache[n] != nullptr) + return cache[n]; + if (n == 0) { + cache[0] = shared_ptr>(new vector{""}); + } else { + auto result = shared_ptr>(new vector); + for (int i = 0; i != n; ++i) { + auto lefts = generate(i); + auto rights = generate(n - i - 1); + for (const string& left : *lefts) + for (const string& right : *rights) + result -> push_back("(" + left + ")" + right); + } + cache[n] = result; + } + return cache[n]; + } + vector generateParenthesis(int n) { + return *generate(n); + } +}; \ No newline at end of file diff --git a/Week_07/36.valid-sudoku/Solution.cpp b/Week_07/36.valid-sudoku/Solution.cpp new file mode 100644 index 00000000..37ee6d6d --- /dev/null +++ b/Week_07/36.valid-sudoku/Solution.cpp @@ -0,0 +1,29 @@ +// +// Created by HaigCode. +// +class Solution { +public: + bool isValidSudoku(vector>& board) { + int row[9][10] = {0};// 哈希表存储每一行的每个数是否出现过,默认初始情况下,每一行每一个数都没有出现过 + // 整个board有9行,第二维的维数10是为了让下标有9,和数独中的数字9对应。 + int col[9][10] = {0};// 存储每一列的每个数是否出现过,默认初始情况下,每一列的每一个数都没有出现过 + int box[9][10] = {0};// 存储每一个box的每个数是否出现过,默认初始情况下,在每个box中,每个数都没有出现过。整个board有9个box。 + for(int i=0; i<9; i++){ + for(int j = 0; j<9; j++){ + // 遍历到第i行第j列的那个数,我们要判断这个数在其所在的行有没有出现过, + // 同时判断这个数在其所在的列有没有出现过 + // 同时判断这个数在其所在的box中有没有出现过 + if(board[i][j] == '.') continue; + int curNumber = board[i][j]-'0'; + if(row[i][curNumber]) return false; + if(col[j][curNumber]) return false; + if(box[j/3 + (i/3)*3][curNumber]) return false; + + row[i][curNumber] = 1;// 之前都没出现过,现在出现了,就给它置为1,下次再遇见就能够直接返回false了。 + col[j][curNumber] = 1; + box[j/3 + (i/3)*3][curNumber] = 1; + } + } + return true; + } +}; diff --git a/Week_07/433.minimum-genetic-mutation/Solution.cpp b/Week_07/433.minimum-genetic-mutation/Solution.cpp new file mode 100644 index 00000000..f7bf91a5 --- /dev/null +++ b/Week_07/433.minimum-genetic-mutation/Solution.cpp @@ -0,0 +1,36 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + int Dif_num(string& s1, string& s2){ + int cnt = 0; + for (int i = 0; i < s1.size(); i++){ + if (s1[i] != s2[i])cnt++; + } + return cnt; + } + int minMutation(string start, string end, vector& bank) { + queuequ; + qu.push(start); + int res = 0; + unordered_mapvis; + while (!qu.empty()){ + int size = qu.size(); + for (int i = 0; i < size; i++){ + string cur = qu.front(); + vis[cur] = 1; + qu.pop(); + if (cur == end)return res; + for (int i = 0; i < bank.size(); i++){ + if (Dif_num(cur, bank[i]) == 1 && !vis[bank[i]]){ + qu.push(bank[i]); + } + } + } + res++; + } + return -1; + } +}; \ No newline at end of file diff --git a/Week_07/51.n-queens/Solution.cpp b/Week_07/51.n-queens/Solution.cpp new file mode 100644 index 00000000..730cf0c2 --- /dev/null +++ b/Week_07/51.n-queens/Solution.cpp @@ -0,0 +1,55 @@ +// +// Created by HaigCode. +// +class Solution { +public: + vector> solveNQueens(int n) { + auto solutions = vector>(); + auto queens = vector(n, -1); + auto columns = unordered_set(); + auto diagonals1 = unordered_set(); + auto diagonals2 = unordered_set(); + backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2); + return solutions; + } + + void backtrack(vector> &solutions, vector &queens, int n, int row, unordered_set &columns, unordered_set &diagonals1, unordered_set &diagonals2) { + if (row == n) { + vector board = generateBoard(queens, n); + solutions.push_back(board); + } else { + for (int i = 0; i < n; i++) { + if (columns.find(i) != columns.end()) { + continue; + } + int diagonal1 = row - i; + if (diagonals1.find(diagonal1) != diagonals1.end()) { + continue; + } + int diagonal2 = row + i; + if (diagonals2.find(diagonal2) != diagonals2.end()) { + continue; + } + queens[row] = i; + columns.insert(i); + diagonals1.insert(diagonal1); + diagonals2.insert(diagonal2); + backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2); + queens[row] = -1; + columns.erase(i); + diagonals1.erase(diagonal1); + diagonals2.erase(diagonal2); + } + } + } + + vector generateBoard(vector &queens, int n) { + auto board = vector(); + for (int i = 0; i < n; i++) { + string row = string(n, '.'); + row[queens[i]] = 'Q'; + board.push_back(row); + } + return board; + } +}; diff --git a/Week_07/547.number-of-provinces/Solution.cpp b/Week_07/547.number-of-provinces/Solution.cpp new file mode 100644 index 00000000..6c249cf3 --- /dev/null +++ b/Week_07/547.number-of-provinces/Solution.cpp @@ -0,0 +1,28 @@ +// +// Created by HaigCode. +// +class Solution { +public: + void dfs(vector>& isConnected, vector& visited, int provinces, int i) { + for (int j = 0; j < provinces; j++) { + if (isConnected[i][j] == 1 && !visited[j]) { + visited[j] = 1; + dfs(isConnected, visited, provinces, j); + } + } + } + + int findCircleNum(vector>& isConnected) { + int provinces = isConnected.size(); + vector visited(provinces); + int circles = 0; + for (int i = 0; i < provinces; i++) { + if (!visited[i]) { + dfs(isConnected, visited, provinces, i); + circles++; + } + } + return circles; + } +}; + diff --git a/Week_07/70.climbing-stairs/Solutions.cpp b/Week_07/70.climbing-stairs/Solutions.cpp new file mode 100644 index 00000000..661c2e8d --- /dev/null +++ b/Week_07/70.climbing-stairs/Solutions.cpp @@ -0,0 +1,15 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int climbStairs(int n) { + int p = 0, q = 0, r = 1; + for (int i = 1; i <= n; ++i) { + p = q; + q = r; + r = p + q; + } + return r; + } +}; diff --git a/Week_07/README.md b/Week_07/README.md index 50de3041..4c0da480 100644 --- a/Week_07/README.md +++ b/Week_07/README.md @@ -1 +1,50 @@ -学习笔记 \ No newline at end of file +# 13.字典树和并查集 +# 13.1.字典树Trie +- 1.字典树的数据结构 +- 2.字典树的核心思想 +- 3.字典树的基本性质 +# 13.1.1.基本结构 +字典树,即Trie树,又称单词查找树,是一种树形结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。 +优点:最大限度的减少无谓的字符串的比较,查询效率比哈希表高。 +# 13.1.2.基本性质 +- 1.节点本身不存完整单词 +- 2.从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; +- 3.每个节点的所有子节点路径代表的字符都不相同。 +- 注:节点可以存储额外信息,字符落下在这个节点的统计信息 +# 13.1.3.核心思想 +- Trie的核心思想是空间换时间 +- 利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。 +# 13.2.并查集 Disjoint Set +# 13.2.1.适用场景 +- 组团、配对问题 +- Group or not? +# 13.2.2.基本操作 +- makeSet(s): 建立一个新的并查集,其中包含s个单元素集合。 +- unionSet(x,y):把元素x和元素y所在的集合合并,要求x和y所在的集合不相交,如果相交则不合并。 +- find(x):找到元素x所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。 +# 13.2.3.初始化、查询、合并、路径压缩 +- parent[i] = i 集合领头元素 + +# 14.高级搜索 +- 剪枝 +- 双向BFS +- 启发式搜索(A*) +# 14.1.初级搜索 +- 1.朴素搜索 +- 2.优化方式:不重复(fibonacci)、剪枝(生成括号问题) +- 3.搜索方向: + - DFS :depth first search: 深度优先搜索 + - BFS: breadth first search : 广度优先搜索 + - 双向搜索、启发式搜索 +# 14.2.双向BFS(Breadth First Search) +- Two-ended BFS 双向BFS +# 14.3.启发式搜索(A*) +- 基于BFS代码 +- pop时加入智能 +- 优先级函数:估价函数 +# 14.3.1.估价函数 +- 启发式函数:h(n),它用来评价哪些节点最优希望的是一个我们要找的节点,h(n)会返回一个非负实数,也可以认为节点n的目标点路径的估计成本。 +- 启发式函数是一种告知搜索方向的方法。它提供了一种明智的方法来猜测哪个邻接点会导向一个目标。 +# 15.AVL树和红黑树 +- 15.1.AVL树 +- 1.Balance Factor(平衡因子):是它的左子树的高度减去它的右子树的高度(有时相反),balance factor = {-1,0,1} \ No newline at end of file diff --git a/Week_08/1122.relative-sort-array/Solution.cpp b/Week_08/1122.relative-sort-array/Solution.cpp new file mode 100644 index 00000000..8ded2a4b --- /dev/null +++ b/Week_08/1122.relative-sort-array/Solution.cpp @@ -0,0 +1,21 @@ +// +// Created by HaigCode. +// +class Solution { +public: + vector relativeSortArray(vector& arr1, vector& arr2) { + unordered_map rank; + for (int i = 0; i < arr2.size(); ++i) { + rank[arr2[i]] = i; + } + sort(arr1.begin(), arr1.end(), [&](int x, int y) { + if (rank.count(x)) { + return rank.count(y) ? rank[x] < rank[y] : true; + } + else { + return rank.count(y) ? false : x < y; + } + }); + return arr1; + } +}; diff --git a/Week_08/1244.design-a-leaderboard/Solution.cpp b/Week_08/1244.design-a-leaderboard/Solution.cpp new file mode 100644 index 00000000..e4de6599 --- /dev/null +++ b/Week_08/1244.design-a-leaderboard/Solution.cpp @@ -0,0 +1,41 @@ +// +// Created by HaigCode. +// +class Leaderboard { +public: + vector scoreVec; + map idScore; + Leaderboard() { + scoreVec.clear(); + idScore.clear(); + } + + void addScore(int playerId, int score) { + if (idScore.count(playerId) != 0) { + int oldscore = idScore[playerId]; + idScore[playerId] += score; + vector::iterator it = find(scoreVec.begin(), scoreVec.end(), oldscore); + *it += score; + } + else { + idScore[playerId] = score; + scoreVec.push_back(score); + } + } + + int top(int K) { + sort(scoreVec.begin(), scoreVec.end(),greater()); + int sum = 0; + for (int i = 0; i < scoreVec.size() && i < K; i++) { + sum += scoreVec[i]; + } + return sum; + } + + void reset(int playerId) { + int score = idScore[playerId]; + idScore.erase(playerId); + vector::iterator it = find(scoreVec.begin(), scoreVec.end(), score); + scoreVec.erase(it); + } +}; diff --git a/Week_08/146.lru-cache/Solution.cpp b/Week_08/146.lru-cache/Solution.cpp new file mode 100644 index 00000000..89dcd5ba --- /dev/null +++ b/Week_08/146.lru-cache/Solution.cpp @@ -0,0 +1,89 @@ +// +// Created by HaigCode. +// +struct DLinkedNode { + int key, value; + DLinkedNode* prev; + DLinkedNode* next; + DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {} + DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {} +}; + +class LRUCache { +private: + unordered_map cache; + DLinkedNode* head; + DLinkedNode* tail; + int size; + int capacity; + +public: + LRUCache(int _capacity): capacity(_capacity), size(0) { + // 使用伪头部和伪尾部节点 + head = new DLinkedNode(); + tail = new DLinkedNode(); + head->next = tail; + tail->prev = head; + } + + int get(int key) { + if (!cache.count(key)) { + return -1; + } + // 如果 key 存在,先通过哈希表定位,再移到头部 + DLinkedNode* node = cache[key]; + moveToHead(node); + return node->value; + } + + void put(int key, int value) { + if (!cache.count(key)) { + // 如果 key 不存在,创建一个新的节点 + DLinkedNode* node = new DLinkedNode(key, value); + // 添加进哈希表 + cache[key] = node; + // 添加至双向链表的头部 + addToHead(node); + ++size; + if (size > capacity) { + // 如果超出容量,删除双向链表的尾部节点 + DLinkedNode* removed = removeTail(); + // 删除哈希表中对应的项 + cache.erase(removed->key); + // 防止内存泄漏 + delete removed; + --size; + } + } + else { + // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部 + DLinkedNode* node = cache[key]; + node->value = value; + moveToHead(node); + } + } + + void addToHead(DLinkedNode* node) { + node->prev = head; + node->next = head->next; + head->next->prev = node; + head->next = node; + } + + void removeNode(DLinkedNode* node) { + node->prev->next = node->next; + node->next->prev = node->prev; + } + + void moveToHead(DLinkedNode* node) { + removeNode(node); + addToHead(node); + } + + DLinkedNode* removeTail() { + DLinkedNode* node = tail->prev; + removeNode(node); + return node; + } +}; + diff --git a/Week_08/190.reverse-bits/Solution.cpp b/Week_08/190.reverse-bits/Solution.cpp new file mode 100644 index 00000000..9118c5fd --- /dev/null +++ b/Week_08/190.reverse-bits/Solution.cpp @@ -0,0 +1,16 @@ +// +// Created by HaigCode. +// +class Solution { +public: + uint32_t reverseBits(uint32_t n) { + uint32_t ret = 0, power = 31; + while (n != 0) { + ret += (n & 1) << power; + n = n >> 1; + power -= 1; + } + return ret; + } +}; + diff --git a/Week_08/191.number-of-1-bits/Solution.cpp b/Week_08/191.number-of-1-bits/Solution.cpp new file mode 100644 index 00000000..6f9600f4 --- /dev/null +++ b/Week_08/191.number-of-1-bits/Solution.cpp @@ -0,0 +1,16 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + int hammingWeight(uint32_t n) { + int count = 0; + while(n != 0) + { + if(n & 1) count ++; + n >>= 1; + } + return count; + } +}; \ No newline at end of file diff --git a/Week_08/231.power-of-two/Solution.cpp b/Week_08/231.power-of-two/Solution.cpp new file mode 100644 index 00000000..bfb492e6 --- /dev/null +++ b/Week_08/231.power-of-two/Solution.cpp @@ -0,0 +1,5 @@ +// +// Created by HaigCode. +// + + diff --git a/Week_08/242.valid-anagram/Solution.cpp b/Week_08/242.valid-anagram/Solution.cpp new file mode 100644 index 00000000..ba56b554 --- /dev/null +++ b/Week_08/242.valid-anagram/Solution.cpp @@ -0,0 +1,15 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + bool isAnagram(string s, string t) { + if (s.length() != t.length()) { + return false; + } + sort(s.begin(), s.end()); + sort(t.begin(), t.end()); + return s == t; + } +}; \ No newline at end of file diff --git a/Week_08/493.reverse-pairs/Solution.cpp b/Week_08/493.reverse-pairs/Solution.cpp new file mode 100644 index 00000000..9d69eef2 --- /dev/null +++ b/Week_08/493.reverse-pairs/Solution.cpp @@ -0,0 +1,52 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int reversePairsRecursive(vector& nums, int left, int right) { + if (left == right) { + return 0; + } else { + int mid = (left + right) / 2; + int n1 = reversePairsRecursive(nums, left, mid); + int n2 = reversePairsRecursive(nums, mid + 1, right); + int ret = n1 + n2; + + // 首先统计下标对的数量 + int i = left; + int j = mid + 1; + while (i <= mid) { + while (j <= right && (long long)nums[i] > 2 * (long long)nums[j]) j++; + ret += (j - mid - 1); + i++; + } + + // 随后合并两个排序数组 + vector sorted(right - left + 1); + int p1 = left, p2 = mid + 1; + int p = 0; + while (p1 <= mid || p2 <= right) { + if (p1 > mid) { + sorted[p++] = nums[p2++]; + } else if (p2 > right) { + sorted[p++] = nums[p1++]; + } else { + if (nums[p1] < nums[p2]) { + sorted[p++] = nums[p1++]; + } else { + sorted[p++] = nums[p2++]; + } + } + } + for (int i = 0; i < sorted.size(); i++) { + nums[left + i] = sorted[i]; + } + return ret; + } + } + + int reversePairs(vector& nums) { + if (nums.size() == 0) return 0; + return reversePairsRecursive(nums, 0, nums.size() - 1); + } +}; diff --git a/Week_08/51.n-queens/Solution.cpp b/Week_08/51.n-queens/Solution.cpp new file mode 100644 index 00000000..730cf0c2 --- /dev/null +++ b/Week_08/51.n-queens/Solution.cpp @@ -0,0 +1,55 @@ +// +// Created by HaigCode. +// +class Solution { +public: + vector> solveNQueens(int n) { + auto solutions = vector>(); + auto queens = vector(n, -1); + auto columns = unordered_set(); + auto diagonals1 = unordered_set(); + auto diagonals2 = unordered_set(); + backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2); + return solutions; + } + + void backtrack(vector> &solutions, vector &queens, int n, int row, unordered_set &columns, unordered_set &diagonals1, unordered_set &diagonals2) { + if (row == n) { + vector board = generateBoard(queens, n); + solutions.push_back(board); + } else { + for (int i = 0; i < n; i++) { + if (columns.find(i) != columns.end()) { + continue; + } + int diagonal1 = row - i; + if (diagonals1.find(diagonal1) != diagonals1.end()) { + continue; + } + int diagonal2 = row + i; + if (diagonals2.find(diagonal2) != diagonals2.end()) { + continue; + } + queens[row] = i; + columns.insert(i); + diagonals1.insert(diagonal1); + diagonals2.insert(diagonal2); + backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2); + queens[row] = -1; + columns.erase(i); + diagonals1.erase(diagonal1); + diagonals2.erase(diagonal2); + } + } + } + + vector generateBoard(vector &queens, int n) { + auto board = vector(); + for (int i = 0; i < n; i++) { + string row = string(n, '.'); + row[queens[i]] = 'Q'; + board.push_back(row); + } + return board; + } +}; diff --git a/Week_08/52.n-queens-ii/Solution.cpp b/Week_08/52.n-queens-ii/Solution.cpp new file mode 100644 index 00000000..017fdc5b --- /dev/null +++ b/Week_08/52.n-queens-ii/Solution.cpp @@ -0,0 +1,24 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int totalNQueens(int n) { + return solve(n, 0, 0, 0, 0); + } + + int solve(int n, int row, int columns, int diagonals1, int diagonals2) { + if (row == n) { + return 1; + } else { + int count = 0; + int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2)); + while (availablePositions != 0) { + int position = availablePositions & (-availablePositions); + availablePositions = availablePositions & (availablePositions - 1); + count += solve(n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1); + } + return count; + } + } +}; diff --git a/Week_08/56.merge-intervals/Solution.cpp b/Week_08/56.merge-intervals/Solution.cpp new file mode 100644 index 00000000..c8b8f563 --- /dev/null +++ b/Week_08/56.merge-intervals/Solution.cpp @@ -0,0 +1,24 @@ +// +// Created by HaigCode. +// +class Solution { +public: + vector> merge(vector>& intervals) { + if (intervals.size() == 0) { + return {}; + } + sort(intervals.begin(), intervals.end()); + vector> merged; + for (int i = 0; i < intervals.size(); ++i) { + int L = intervals[i][0], R = intervals[i][1]; + if (!merged.size() || merged.back()[1] < L) { + merged.push_back({L, R}); + } + else { + merged.back()[1] = max(merged.back()[1], R); + } + } + return merged; + } +}; + diff --git a/Week_08/README.md b/Week_08/README.md index 50de3041..db50f557 100644 --- a/Week_08/README.md +++ b/Week_08/README.md @@ -1 +1,166 @@ -学习笔记 \ No newline at end of file +# 16.位运算 + +- [如何从十进制转换为二进制](https://zh.wikihow.com/%E4%BB%8E%E5%8D%81%E8%BF%9B%E5%88%B6%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BA%8C%E8%BF%9B%E5%88%B6) +- [N 皇后位运算代码示例](https://shimo.im/docs/YzWa5ZZrZPYWahK2/read) + +``` +# Python +def totalNQueens(self, n): +  if n < 1: return [] +  self.count = 0 +  self.DFS(n, 0, 0, 0, 0) +  return self.count + +def DFS(self, n, row, cols, pie, na): +  # recursion terminator +  if row >= n: +  self.count += 1 +  return + + bits = (~(cols | pie | na)) & ((1 << n) — 1)# 得到当前所有的空位 + + while bits: +   p = bits & —bits # 取到最低位的1 + bits = bits & (bits — 1) # 表示在p位置上放入皇后 + self.DFS(n, row + 1, cols | p, (pie | p) << 1, (na | p) >> 1) + # 不需要revert  cols, pie, na 的状态 +``` + +// Java +``` +class Solution { + private int size; + private int count; + private void solve(int row, int ld, int rd) { +  if (row == size) { +  count++; +  return; +  } + int pos = size & (~(row | ld | rd)); + while (pos != 0) { +   int p = pos & (-pos); +   pos -= p; // pos &= pos - 1; + solve(row | p, (ld | p) << 1, (rd | p) >> 1); + }  + }  + public int totalNQueens(int n) { +   count = 0; +   size = (1 << n) - 1; +   solve(0, 0, 0); +   return count; + } +} +``` + +// C/C++ +``` +//C/C++class Solution { +public: +    int totalNQueens(int n) { +        dfs(n, 0, 0, 0, 0); +        return this->res; +    } +    void dfs(int n, int row, int col, int ld, int rd) { +        if (row >= n) { res++; return; } +        // 将所有能放置 Q 的位置由 0 变成 1,以便进行后续的位遍历 +        int bits = ~(col | ld | rd) & ((1 << n) - 1); +        while (bits > 0) { +            int pick = bits & -bits; // 注: x & -x +            dfs(n, row + 1, col | pick, (ld | pick) << 1, (rd | pick) >> 1); +            bits &= bits - 1; // 注: x & (x - 1) +        } +    }private: +    int res = 0; +}; +``` + +// Javascript +``` +var totalNQueens = function(n) { +  let count = 0; +  void (function dfs(row = 0, cols = 0, xy_diff = 0, xy_sum = 0) { +    if (row >= n) { +      count++; +      return; +    } +    // 皇后可以放的地方 +    let bits = ~(cols | xy_diff | xy_sum) & ((1 << n) - 1); +    while (bits) { +      // 保留最低位的 1 +      let p = bits & -bits; +      bits &= bits - 1; +      dfs(row + 1, cols | p, (xy_diff | p) << 1, (xy_sum | p) >> 1); +    } +  })(); +  return count; +}; +``` +# 17.布隆过滤器、LRU Cache +## 17.1.布隆过滤器 +- [布隆过滤器的原理和实现](https://www.cnblogs.com/cpselvis/p/6265825.html) +- [使用布隆过滤器解决缓存击穿、垃圾邮件识别、集合判重](https://blog.csdn.net/tianyaleixiaowu/article/details/74721877) +- [布隆过滤器 Python 代码示例](https://shimo.im/docs/UITYMj1eK88JCJTH/read) +- [布隆过滤器 Python 实现示例](https://www.geeksforgeeks.org/bloom-filters-introduction-and-python-implementation/) +- [高性能布隆过滤器 Python 实现示例](https://github.com/jhgg/pybloof) +- [布隆过滤器 Java 实现示例 1](https://github.com/lovasoa/bloomfilter/blob/master/src/main/java/BloomFilter.java) +- [布隆过滤器 Java 实现示例 2](https://github.com/Baqend/Orestes-Bloomfilter) + +## 17.2.LRU Cache +- [Understanding the Meltdown exploit](https://www.sqlpassion.at/archive/2018/01/06/understanding-the-meltdown-exploit-in-my-own-simple-words/) +- [替换算法总揽](https://en.wikipedia.org/wiki/Cache_replacement_policies) +-[LRU Cache Python 代码示例](https://shimo.im/docs/CoyPAyXooGcDuLQo/read) + + +# 18.初级排序和高级排序的实现和特性 +# 18.1.排序 +# 18.1.1.比较类排序 +- 通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn), 因此也称为非线性时间比较类排序。 +# 18.1.2.非比较类排序 +- 不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。 +# 18.1.3.分类 +- 排序算法 + - 比较类排序 + - 交换排序 + - 冒泡排序 + - 快速排序`*` + - 插入排序 + - 简单插入排序 + - 希尔排序 + - 选择排序 + - 简单选择排序 + - 堆排序`*` + - 归并排序`*` + - 二路归并排序 + - 多路归并排序 + - 非比较类排序 + - 计数排序 + - 桶排序 + - 基数排序 +# 18.1.4.初级排序O(n^2) +- 1.选择排序(selection sort) + 每次找最小值,然后放入到待排序数组的起始位置。 +- 2.插入排序(Insrtion Sort) + 从前到后逐构建有序序列;对于为排序数据,在已排序序列中从后向前扫描,找到对应位置并插入。 +- 3.冒泡排序(Bubble Sort) + 嵌套循环,每次查找相邻的元素如果逆序,则交换。 +# 18.1.5.高级排序O(N*LogN) +- 1.快速排序(quick sort) + 数组取标杆pivot, 将小元素放pivot左边,大元素放右侧,然后依次对右边和右边的子数组继续快排;以达到整个序列有序。 +- 2.归并排序(Merge Sort)——分治 + - 1.把长度为n的序列分成两个长度为n/2的子序列; + - 2.对这两个子序列分别采用归并排序; + - 3.将两个排序好的子序列合并成一个最终的排序序列。 +- 3.归并和快速排序具有相似性,但步骤序列相反 + - 归并:先排序左右子序列,然后合并两个有序子序列 + - 快排:先调配出左右子序列,然后对于左右子序列数组进行排序 +# 18.1.6.特殊排序O(n) +- 1.计数排序(Counting Sort) +- 2.桶排序(Bucket Sort) +- 3.基数排序(Radix Sort) + +- [十大经典排序算法](https://www.cnblogs.com/onepixel/p/7674659.html) +- [快速排序代码示例](https://shimo.im/docs/TX9bDbSC7C0CR5XO/read) +- [归并排序代码示例](https://shimo.im/docs/sDXxjjiKf3gLVVAU/read) +- [堆排序代码示例](https://shimo.im/docs/M2xfacKvwzAykhz6/read) +- [9 种经典排序算法可视化动画](https://www.bilibili.com/video/av25136272) +- [6 分钟看完 15 种排序算法动画展示](https://www.bilibili.com/video/av63851336) \ No newline at end of file diff --git a/Week_08/sort/CMakeLists.txt b/Week_08/sort/CMakeLists.txt new file mode 100644 index 00000000..78c56478 --- /dev/null +++ b/Week_08/sort/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.14) +project(sort) + +set(CMAKE_CXX_STANDARD 14) + +add_executable(sort main.cpp sort.cpp) \ No newline at end of file diff --git a/Week_08/sort/cmake-build-debug/CMakeFiles/clion-log.txt b/Week_08/sort/cmake-build-debug/CMakeFiles/clion-log.txt new file mode 100644 index 00000000..c7ba5e8f --- /dev/null +++ b/Week_08/sort/cmake-build-debug/CMakeFiles/clion-log.txt @@ -0,0 +1 @@ +Toolchains are not configured Configure diff --git a/Week_08/sort/main.cpp b/Week_08/sort/main.cpp new file mode 100644 index 00000000..842c69be --- /dev/null +++ b/Week_08/sort/main.cpp @@ -0,0 +1,181 @@ +// +// Created by HaigCode. +// +#include +#include +#include "sort.h" + +using namespace sort_alg; + +int main() +{ + std::vector data = {629,345,465,24,2,71,630}; + std::vector unsorted_data = data; + + // 1 测试选择排序. + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_select(unsorted_data); + std::cout << "result of select sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 2 测试插入排序. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_insert(unsorted_data); + std::cout << "result of insert sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 3 测试冒泡排序. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_bubble(unsorted_data); + std::cout << "result of bubble sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 4 测试希尔排序. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_shell(unsorted_data); + std::cout << "result of shell sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 5.1 测试归并排序的递归实现. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_merge_recursive(unsorted_data, 0, 6); + std::cout << "result of recursive merge sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 5.2 测试归并排序的非递归实现. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_merge_non_recursive(unsorted_data); + std::cout << "result of non recursive merge sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 6.1 测试快速排序-递归实现. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_quick_recursive(unsorted_data, 0, 6); + std::cout << "result of recursive quick sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 6.2 测试快速排序-非递归实现. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_quick_non_recursive(unsorted_data, 0, 6); + std::cout << "result of noe recursive quick sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 7 测试堆排序. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_heap(unsorted_data); + std::cout << "result of heap sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 8 测试计数排序. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_count(unsorted_data); + std::cout << "result of count sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 9 测试桶排序. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_bucket(unsorted_data); + std::cout << "result of bucket sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + + // 9 测试基数排序. + unsorted_data = data; + std::cout << "original sequence:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } + sort::sort_radix(unsorted_data); + std::cout << "result of radix sort:" << std::endl; + for(auto value : unsorted_data) + { + std::cout << value << std::endl; + } +} diff --git a/Week_08/sort/sort.cpp b/Week_08/sort/sort.cpp new file mode 100644 index 00000000..253446ff --- /dev/null +++ b/Week_08/sort/sort.cpp @@ -0,0 +1,621 @@ +// +// Created by HaigCode. +// + +#include "sort.h" + +namespace sort_alg +{ + void sort::sort_select(std::vector& data) + { + // 思想: + // 在原始待排序列上做修改; + // 首先找出待排序列中最小的数,然后用这个数和原序列中的第一个数交换位置; + // 其次,找出第二小的和原第二个数交换位置; + // 依次顺序直到找到第二大的数,之后原数列完全变成有序数列. + + //获取待排序列的长度,长度小于2则序列肯定为有序的. + auto length = data.size(); + if(length < 2) + { + return; + } + //开始选择 + for(unsigned long i = 0; i < length - 1; i++) + { + // 找到无序序列中最小的数的下标. + unsigned long min = i; + for(unsigned long j = i + 1; j < length; j++) + { + if(data.at(min) > data.at(j)) + { + min = j; + } + } + // 此时的min是无序序列中最小值的下标,将其放入有序序列区. + auto temp = data.at(i); + data.at(i) = data.at(min); + data.at(min) = temp; + } + } + + void sort::sort_insert(std::vector& data) + { + // 思想: + // 在原始待排序列上做修改; + // 将待排序序列分为有序区和无序区; + // 最开始的待排序列只有第一个元素是有序的; + // 从第二个元素开始,向有序区往前查找合适的位置插入; + // 直到最后一个元素找到了合适的位置插入时原待排序列变为有序序列. + + // 获取待排序列的长度,长度小于2则序列肯定为有序的. + auto length = data.size(); + if(length < 2) + { + return; + } + // 从序列中第二个元素开始遍历 + for(unsigned long i = 1; i < length; i++) + { + // 本轮要插入的元素. + int key = data.at(i); + // k从最后一个有序元素(i前一个)往前遍历查找插入位置. + // 此处k的类型不能是unsigned long,因为k可能会等于-1, + // 如何为unsigned long的话会出现out of range 的错误. + int k = i - 1; + while(k >= 0 && data.at(k) > key) + { + // 将大于key的元素往后移,给key腾出位置插入. + data.at(k+1) = data.at(k); + k--; + } + // 已经找到要插入的位置为k+1,将key插入. + data.at(k+1) = key; + } + } + + void sort::sort_bubble(std::vector &data) + { + // 思想: + // 在原始待排序序列上操作; + // 将原始待排序序列分成无序区和有序区两部分; + // 初始状态下整个原始序列为无序区,每遍历一遍就浮现出无序区最大的元素放在有序区; + // 随着遍历,最终无序区长度变为0,整个原始序列变为有序序列. + + // 设置一个监控位,用于记录是否无序区碰巧为有序的,那么便免于对已经有序的序列排序. + int flag = 0; + + // 获取序列元素个数. + auto length = data.size(); + if(length < 2) + { + return; + } + // 开始遍历查找无序区最大值. + for(unsigned long i = 0; i < length; i++) + { + flag = 0; + for(unsigned long j = 0; j < length-i-1; j++) + { + if(data.at(j+1) < data.at(j)) + { + // 无序区前一个元素大于它的后一个元素,需要交换位置以及设置本轮的监控位. + flag = 1; + auto temp = data.at(j); + data.at(j) = data.at(j+1); + data.at(j+1) = temp; + } + } + if(flag == 0) + { + // 如果监控位为0,则说明无序区已经是有序的了,排序完毕 . + return; + } + } + } + + void sort::sort_shell(std::vector &data) + { + // 思想: + // 还是在原待排序序列上进行操作; + // 该算法是插入排序的变种和优化,当无序序列不理想,简单的插入排序会有很多的交换操作,影响效率; + // 希尔排序做的改进是将原始待排序序列以一个偏移量h进行逻辑上的分组,对组内的元素采用插入排序,使得原始序列基本有序; + // 偏移量逐渐减小,每减小一次进行组内插入排序,最后一轮则以1为偏移量,也就是将原书待排序列分为同一组进行组内排序,也就是简单的插入排序; + // 该排序算法对于无序序列很不理想的情况有利. + // 注:一般将h初始值设为length/2,h逐倍递减直至1,其实还有更理性的分组方法,对最终效率也有所优化. + // 对于分组方法,可以戳链接:https://mp.weixin.qq.com/s/4kJdzLB7qO1sES2FEW0Low + + int length = data.size(); + if(length < 2) + { + return; + } + // 组内排序,注意排序方法是对各个分组进行交替排序. + for(int h = length/2; h > 0; h /= 2) + { + // 对偏移量为h的分组交替进行插入排序. + // i = h是第一组的第二个元素,仔细想想. + for(int i = h; i < length; i++) + { + insert_i(data,h,i); + } + } + } + + void sort::insert_i(std::vector &data, int h, int i) + { + // 该部分代码其实就是插入排序的代码. + // 将第i个元素插入到以h为偏移量分的组的有序区的适当位置. + int temp = data.at(i); + int k; + for(k = i - h; k >= 0 && data.at(k) > temp; k -= h) + { + // 腾出插入位置. + data.at(k+h) = data.at(k); + } + // 已找到要插入的位置为k+h,插入. + data.at(k+h) = temp; + } + + void sort::sort_merge_recursive(std::vector &data, int left, int right) + { + // 思想: + // 归并排序这里不再是直接在原始序列上进行操作; + // 归并排序利用的是分而治之的思想,将序列分成两个子序列,将两个子序列排好序后合并为有序的序列; + // 而对两个子序列进行排序同样用分而治之,如此递归下去; + // 归并分为三步:分,治,合;治是关键,而治又是最简单的,将序列分为只有一个元素的两个子序列后自然变得有序; + // 所以归并可以分为两步:将序列一直分成只有一个元素的子序列,然后将这些有序的子序列合并. + + if(left < right) + { + // 将序列一分为二并获取中间位置. + int mid = (left + right)/2; + // 递归处理两个子序列使之有序. + sort_merge_recursive(data, left, mid); + sort_merge_recursive(data, mid + 1, right); + // 合并两个有序子序列后结束归并排序. + merge(data, left, mid, right); + } + } + + void sort::merge(std::vector &data, int left, int mid, int right) + { + // 将有序的两个子序列合并. + // 获取两个子序列的第一个元素. + int i = left; + int j = mid + 1; + // 创建临时容器来保存合并结果,同时指定容器大小. + std::vector temp; + temp.resize(right - left + 1); + // 开始合并. + int k = 0; + while((i <= mid) && (j <= right)) + { + if(data.at(i) <= data.at(j)) + { + temp.at(k++) = data.at(i++); + } + else + { + // data.at(j) < data.at(i); + temp.at(k++) = data.at(j++); + } + } + // 到此为止肯定有一个子序列已经完全放到临时容器里,现在将另子序列的元素放入临时容器. + while(i <= mid) + { + temp.at(k++) = data.at(i++); + } + while(j <= right) + { + temp.at(k++) = data.at(j++); + } + // 最后将临时容器里的元素复制到原容器完成合并. + // 切记这里不能直接使用赋值:data = temp; + // 因为这是递归操作,如果这样赋值,那么data的长度会变成2(思考一下为什么是2), + // 那么后续操作中会导致"std::out_of_range"错误. + for(int n = 0; n < k; n++) + { + data.at(left++) = temp.at(n); + } + } + + void sort::sort_merge_non_recursive(std::vector &data) + { + // 思想: + // 归并排序这里不再是直接在原始序列上进行操作; + // 归并排序利用的是分而治之的思想,将序列分成两个子序列,将两个子序列排好序后合并为有序的序列; + // 而对两个子序列进行排序同样用分而治之,如此递归下去; + // 归并分为三步:分,治,合;治是关键,而治又是最简单的,将序列分为只有一个元素的两个子序列后自然变得有序; + // 所以归并可以分为两步:将序列一直分成只有一个元素的子序列,然后将这些有序的子序列合并. + + // 获取序列长度. + int length = data.size(); + // 开始合并子序列,子序列长度为1,2,4,8....成倍增长(初始子序列长度必须为1,思考为什么). + for(int i = 1; i < length; i *= 2) + { + // 划分子序列. + int left = 0; + int mid = left + i - 1; + int right = mid + i; + // 开始合并. + // 注意此处while语句的结合条件,思考有什么问题. + while(right < length) + { + // 合并函数和递归实现的合并函数一样. + merge(data, left, mid, right); + // 更新left,mid,right以合并下一对序列. + left = right +1; + mid = left + i -1; + right = mid + i; + } + // while条件是有问题的,因为right = mid + i,随意可能出现情况: right < length < right + i; + // 这样会导致right后面到length可能有元素被遗漏,没有对他们进行排序,此处要单独处理. + if((left < length) && (mid < length)) + { + merge(data,left,mid,length - 1); + } + } + } + + void sort::sort_quick_recursive(std::vector &data, int left, int right) + { + // 思想: + // 在元素序列上直接操作; + // 每次在无序序列中选取一个数,一般称之为中轴数, + // 将元素序列分成两个部分,使得一部分的元素全都小于等于另一部分的所有元素; + // 也就是说将序列分成小于等于中轴数和大于等于中轴数的两部分,使得中轴数变为有序; + // 再递归的对分成的两部分进行划分操作. + + if(left < right) + { + // 找到中轴数的索引. + int index = partition(data,left,right); + // 以中轴数的索引为界递归处理两个部分的序列. + sort_quick_recursive(data,left,index - 1); + sort_quick_recursive(data,index + 1,right); + } + } + + int sort::partition(std::vector &data, int left, int right) + { + // 找到中轴数的正确位置,同时将序列划分为两部分. + // 中轴数有很多种取法,我们这里采用《算法导论》里的选取方法,即取序列最后一个元素. + int key = data.at(right); + // 此处设置两个索引i和j,区间[left,i]为小于中轴数的序列, + // 区间[j,right-1]为大于中轴数的序列. + int i = left - 1; + for(int j = left; j < right; j++) + { + if(data.at(j) <= key) + { + // 大于中轴数的元素让它继续待在[j,right-1]区间什么也不做; + // 小于中轴数的元素全部从[j,right-1]区间放到[left,i]区间去. + ++i; + int temp = data.at(i); + data.at(i) = data.at(j); + data.at(j) = temp; + } + } + // 此时中轴数的正确位置应该在i+1,将其归位. + // 思考为什么是i+1而不是i. + int temp = data.at(i+1); + data.at(i+1) = data.at(right); + data.at(right) = temp; + // 返回中轴数的正确索引. + return i+1; + } + + void sort::sort_quick_non_recursive(std::vector &data, int left, int right) + { + // 思想: + // 在元素序列上直接操作; + // 每次在无序序列中选取一个数,一般称之为中轴数, + // 将元素序列分成两个部分,使得一部分的元素全都小于等于另一部分的所有元素; + // 也就是说将序列分成小于等于中轴数和大于等于中轴数的两部分,使得中轴数变为有序; + // 再递归的对分成的两部分进行划分操作. + + // 非递归利用栈来实现. + // 利用栈来存储子序列的起点后终点(其实递归也是通过调用系统堆栈来保护调用现场的). + std::stack s; + if(left < right) + { + // 找到中轴数的索引. + int index = partition(data, left, right); + // 如果中轴数索引两个分区存在,则将起点和终点入栈. + if(index - 1 > left) + { + // 下面的入栈顺序要和此处一致. + s.push(left); + s.push(index - 1); + } + if(index + 1 < right) + { + s.push(index + 1); + s.push(right); + } + // 从栈里面取出序列并找到该序列中轴数的正确索引. + while(!s.empty()) + { + // 注意顺序. + int r = s.top(); + s.pop(); + int l = s.top(); + s.pop(); + index = partition(data, l, r); + // 将新的序列区间入栈. + if(index - 1 > l) + { + s.push(l); + s.push(index - 1); + } + if(index + 1 < r) + { + s.push(index + 1); + s.push(r); + } + } + } + } + + void sort::sort_heap(std::vector &data) + { + // 思想: + // 在原始序列上直接操作. + // 利用二叉堆的数据结构来实现排序,二叉堆是一个完全二叉数. + // 二叉堆的特点是堆顶元素是一个最值(大顶堆的堆顶为最大值,小顶堆的堆顶为最小值); + // 大顶堆:任意节点的元素都要大于他的子节点元素,小顶堆:任意节点的元素都要小于他的子节点元素. + // 排序方法是将堆顶元素和最后一个元素交换,然后恢复大/小顶堆;再将堆顶元素和最后第二个元素交换,以此类推. + // 排序第一步是需要将原始数据构建乘二叉堆(升序排序构建大顶堆,降序排序构建小顶堆),我们升序排序所以构建大顶堆; + // 第二步是调整使之重新成为大顶堆,我们使用从最后一个非叶子节点往上进行下沉处理(down_adjust()). + // 此处我们提供了二叉堆的插入元素后维护二叉堆的操作,采用上浮处理(up_adjust). + + // 建堆:从最后一个非叶子节点往上做下沉操作. + int length = data.size(); + // 思考为什么是减2. + for(int i = (length - 2)/2; i >= 0; i--) + { + down_adjust(data,i,length -1); + } + // 开始堆排序 + for(int i = length -1; i > 0; i--) + { + // 将堆顶节点与节点i交换. + int temp = data.at(0); + data.at(0) = data.at(i); + data.at(i) = temp; + // 恢复二叉堆. + down_adjust(data,0,i - 1); + } + } + + void sort::down_adjust(std::vector &data, int parent, int length) + { + // 对parent节点做下沉操作. + // 临时保存要下沉的节点. + int key = data.at(parent); + // 定位左子节点. + int child = 2 * parent + 1; + while(child <= length) + { + // 定位parent的较大的子节点. + if(child + 1 <= length && data.at(child) < data.at(child + 1)) + { + ++child; + } + // 如果parent节点的元素大于子节点的元素,则下沉完成. + if(key >= data.at(child)) + { + // 注意此处是break而不是return(想想为什么). + break; + } + // 需要下沉,将child节点放到父节点. + data.at(parent) = data.at(child); + // 更新parent和child,以便继续下沉操作. + parent = child; + child = 2 * parent +1; + } + // 找到下沉位置后将下沉元素放到正确位置上后完成操作. + data.at(parent) = key; + } + + void sort::up_adjust(std::vector &data, int length) + { + // 将末节点上浮到适当位置. + // 定位父子节点. + int child = length - 1; + int parent = (child - 1)/2; + // 临时保存需要上浮的节点. + int key = data.at(child); + // 开始上浮(大顶堆) + while(child > 0 && key > data.at(parent)) + { + // 如果要上浮的节点元素大于父节点,则将parent节点放到其子节点上. + data.at(child) = data.at(parent); + // 更新parent和child,以继续上浮操作. + child = parent; + parent = (child - 1)/2; + } + // 找到上浮位置后,将要上浮的节点放进去. + data.at(child) = key; + } + + void sort::sort_count(std::vector &data) + { + // 思想: + // 计数排序的思想比较新颖,不再是基于元素之间的比较,而是将待排序序列的元素当作容器的索引,记录索引出现的次数; + // 比如临时容器的a[i] = n,表示元素i出现n次; + // 记录玩出现次数之后,将临时容器从小到大将元素汇总起来,则变为有序; + // 我的方法是有所改进,临时容器记录的不是元素出现的次数,而是记录小于等于该元素的元素个数; + // 这样做的优点是可以保证稳定排序. + // 计数排序的缺点:只能对整数序列进行排序,而且不适合最大元素和最小元素差得很大的情况(为什么呢). + + // 1.遍历待排序数组,获取最大和最小元素,并求得插值d. + int max = data.at(0); + int min = data.at(0); + int length = static_cast(data.size()); + for(int i = 1; i < length; i++) + { + if(data.at(i) > max) + { + max = data.at(i); + } + if(data.at(i) < min) + { + min = data.at(i); + } + } + int d = max - min; + // 2.创建统计容器,并遍历待排序序列进行统计元素出现的次数. + // 容器元素默认值为0. + std::vector count_data; + count_data.resize(d+1); + for(int i = 0; i < length; i++) + { + // min值作为一个偏移量的角色. + ++count_data.at(data.at(i) - min); + } + // 3.改进以实现稳定排序,对统计容器做变形,统计容器元素存的不再是待排序元素出现的次数, + // 而是记录小于等于该索引的元素个数. + int sum = 0; + for(auto &value : count_data) + { + sum += value; + value = sum; + } + // 4.倒序遍历原始待排序序列,从统计容器中找到正确位置输出到结果容器, + // 如果没有第3步,那么就是简单的输出,是非稳定的. + std::vector sorted_data; + sorted_data.resize(data.size()); + // 注意:此处的i的类型不能用auto来排段,因为size()返回的类型为unsigned long,又因为是i--, + // 所以如果是用auto的活,这个将是一个死循环,而且基本上会造成std::out_of_range错误. + for(int i = static_cast(data.size() - 1); i >= 0; i--) + { + // 代码有点绕: + // data.at(i)-min是当前元素与最小值的差值, + // 以差值作为count_data的索引值,则count_data.at(data.at(i)-min)代表小于等于当前元素data.at(i)的个数, + // 所以count_data(data.at(i)-min)-1表示当前元素data.at(i)的升序序号. + // 将当前元素data.at(i)放入sorted_data的正确的位置上. + sorted_data.at(count_data.at(data.at(i) - min) - 1) = data.at(i); + // 随后更新统计容器的元素值,这和倒序遍历是实现稳定排序的关键. + --count_data.at(data.at(i)-min); + } + // 5.最后将已排序容器赋给原始序列,排序结束. + data = sorted_data; + } + + void sort::sort_bucket(std::vector &data) + { + // 思想: + // 桶排序是计数排序的一个优化,它解决了计数排序只适用于整数排序的限制,更是解决了不适用于最大值和最小值差值很大的情况. + // 桶排序的思想就是对最小值和最小值之间的元素进行瓜分,将他们分为多个区间,每个区间用一个桶(其实就是一个容器)来装. + // 前一个桶里的元素全都小于后一个桶里的元素,只需要利用别的常规排序算法将桶里面的元素进行排序使之有序即可使得原序列有序. + // 这里我们使用快速排序(非递归)对桶内的元素进行排序. + // 1.遍历待排序序列获取其中最大值和最小值,并计算他们的差值d. + int max = data.at(0); + int min = data.at(0); + for(int i = 1; i < static_cast(data.size()); i++) + { + if(max < data.at(i)) + { + max = data.at(i); + } + if(min > data.at(i)) + { + min= data.at(i); + } + } + int d = max - min; + // 2.初始化桶,桶因为要频繁插入元素,所以用List数据结构,然后所有的桶放在vector容器中. + std::vector> bucket_list; + // 我们将桶的个数设置为原序列元素个数. + int bucket_num = data.size(); + bucket_list.resize(bucket_num); + // 3.遍历原序列,将元素放到合适的桶中. + for(int value : data) + { + // 定位元素所属的桶的索引. + // 桶所有的桶平均划分最大值和最小值d的区间,value-min是当前元素与最小值的差值(区间). + // bucket_num-1是总的区间个数,则d/(bucket_num-1)代表一个区间的长度. + // 那么整个表达式得到的index则为当前元素value所跨越的区间个数,也就是当前元素所在的桶的索引. + int index = (value-min)/(d/(bucket_num-1)); + // 将当前元素放进桶里面去. + bucket_list.at(index).push_back(value); + } + // 4.对每个桶进行排序,我们采用快速排序. + // 依次将每个桶里的元素排好序后放入sorted_sequence中. + std::vector sorted_sequence; + for(auto bucket : bucket_list) + { + // 因为我们之前写的快排是对vector进行排序,所以我们使用一个辅助容器. + // 我们完全可以重新写一个针对list的快排算法,这样会提高时间和空间复杂度,此处我们就使用现成的(非递归快排). + std::vector auxiliary; + auxiliary.assign(bucket.begin(),bucket.end()); + sort_quick_non_recursive(auxiliary,0, static_cast(auxiliary.size()-1)); + // 将当前桶内元素排好序后放入sorted_sequence容器尾部. + sorted_sequence.insert(sorted_sequence.end(),auxiliary.begin(),auxiliary.end()); + } + // 5.将有序序列赋给data. + data = sorted_sequence; + } + + void sort::sort_radix(std::vector &data) + { + // 思想: + // 使用了桶排序中桶的思想,但它比桶排序更精明,它只需要十个桶,因为他的排序思想是分别对元素中的个位,十位,百位....进行排序. + // 也就是说,首先对所有数以个位数的大小进行排序,然后再对所有数以他们的十位数进行排序,依次类推. + // 在整个过程中会使得原始序列逐渐趋近有序,待将最高位排完之后完全有序. + // 想想为什么是从个位开始而不是从最高位开始呢,按道理从最高位开始的话每次都能得出一部分数的正确大小关系. + // 确实可以从最高位开始,而且可以减少比较次数,但是从最高位开始会有一个致命缺点,那就是在如果从高位开始,在对高位相同的 + // 数继续排序时,又需要另外创建十个桶对他们排序,其实也就是说最终的结果就是真多每一个不同的元素都会为它创建一个桶, + // 如果待排序序列有10000个不同的元素,那么从高位开始比较的方法就需要创建10000个桶,而从个位开始比较的方法可以重复使用 + // 那10个桶,如果序列个数更多那么这样的性能差异就更明显,所以虽然减少了比较次数但浪费了非常多的空间,得不偿失. + // 所以我们说基数排序的话都默认的是从个位开始比较的. + if(data.size() < 2) + { + return; + } + // 遍历待排序序列获取最大值,并计算出最大值的位数digits,这决定我们要循环排序的次数. + int length = static_cast(data.size()); + int max = data.at(0); + for(int i = 1; i < length; i++) + { + if(data.at(i) > max) + { + max = data.at(i); + } + } + // 计算最大值位数. + int digits = 1; + while(max/10 > 0) + { + ++digits; + max /= 10; + } + // 创建10个桶,因为需要频繁地往桶里面插入元素,所以我们使用list容器,将十个桶放入vector中. + std::vector> bucket_list; + bucket_list.resize(10); + // 从个位开始进行每一趟的排序,其实就是将待排序序列的元素放入当前排序位数(个/十/百...)数字对应的桶中. + for(int i = 1; i <= digits; i++) + { + for(int j = 0; j < length; j++) + { + // 计算出当前元素data.at(j)在本轮属于哪一个桶. + // pow()函数需要include. + int radix = static_cast(data.at(j)/pow(10,i-1)) % 10; + bucket_list.at(radix).push_back(data.at(j)); + } + // 每完成一轮便将桶里的元素按顺序合并放入原序列. + int k = 0; + for(int n = 0; n < 10; n++) + { + for(auto value : bucket_list.at(n)) + { + data.at(k++) = value; + } + // 同时需要将桶清空以便下一轮的排序. + bucket_list.at(n).clear(); + } + } + } +} \ No newline at end of file diff --git a/Week_08/sort/sort.h b/Week_08/sort/sort.h new file mode 100644 index 00000000..e9ae8771 --- /dev/null +++ b/Week_08/sort/sort.h @@ -0,0 +1,69 @@ +// +// Created by HaigCode. +// +#ifndef SORT_SORT_H +#define SORT_SORT_H + +#include +#include +#include +#include + +namespace yis +{ + class sort + { + // 十种排序算法 + // 算法实现的都是升序排序 + + public: + // 1 选择排序. + static void sort_select(std::vector& data); + + // 2 插入排序. + static void sort_insert(std::vector& data); + + // 3 冒泡排序. + static void sort_bubble(std::vector& data); + + // 4 希尔排序. + static void sort_shell(std::vector& data); + static void insert_i(std::vector& data,int h,int i); + + // 5.1 归并排序-递归实现. + static void sort_merge_recursive(std::vector& data,int left,int right); + static void merge(std::vector& data,int left,int mid,int right); + + // 5.2 归并排序-非递归实现. + static void sort_merge_non_recursive(std::vector& data); + // 非递归用到的merge()函数和递归使用的merge()是一样的. + + // 6.1 快速排序-递归实现. + static void sort_quick_recursive(std::vector& data, int left, int right); + static int partition(std::vector& data, int left, int right); + + // 6.2 快速排序-非递归实现. + static void sort_quick_non_recursive(std::vector& data, int left, int right); + // 非递归用到的partition()函数和递归使用的partition()是一样的. + + // 7 堆排序. + static void sort_heap(std::vector& data); + static void down_adjust(std::vector& data, int parent, int length); + // 堆排序没有用到上浮函数,它用于在堆尾插入元素后进行上浮以维护二叉堆的特性. + static void up_adjust(std::vector& data, int length); + + // 8 计数排序. + static void sort_count(std::vector& data); + + // 9 桶排序. + static void sort_bucket(std::vector& data); + // 会用到非递归快速排序函数:sort_quick_non_recursive(). + + // 10 基数排序. + static void sort_radix(std::vector& data); + }; +} + + +#endif //SORT_SORT_H + diff --git a/Week_09/115.distinct-subsequences/Solution.cpp b/Week_09/115.distinct-subsequences/Solution.cpp new file mode 100644 index 00000000..5f504d4a --- /dev/null +++ b/Week_09/115.distinct-subsequences/Solution.cpp @@ -0,0 +1,19 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + int numDistinct(string s, string t) { + vector dp(t.size()+1,0); + dp[0] = 1; + for(int i=0; i < s.size(); i++) { + for(int j=t.size(); j > 0; j--) { + if(s[i] == t[j-1]) { + dp[j] += dp[j-1]; + } + } + } + return (int)dp.back(); + } +}; diff --git a/Week_09/151.reverse-words-in-a-string/Solution.cpp b/Week_09/151.reverse-words-in-a-string/Solution.cpp new file mode 100644 index 00000000..45bf3580 --- /dev/null +++ b/Week_09/151.reverse-words-in-a-string/Solution.cpp @@ -0,0 +1,32 @@ +// +// Created by HaigCode. +// +class Solution { +public: + string reverseWords(string s) { + // 反转整个字符串 + reverse(s.begin(), s.end()); + + int n = s.size(); + int idx = 0; + for (int start = 0; start < n; ++start) { + if (s[start] != ' ') { + // 填一个空白字符然后将idx移动到下一个单词的开头位置 + if (idx != 0) s[idx++] = ' '; + + // 循环遍历至单词的末尾 + int end = start; + while (end < n && s[end] != ' ') s[idx++] = s[end++]; + + // 反转整个单词 + reverse(s.begin() + idx - (end - start), s.begin() + idx); + + // 更新start,去找下一个单词 + start = end; + } + } + s.erase(s.begin() + idx, s.end()); + return s; + } +}; + diff --git a/Week_09/205.isomorphic-strings/Solution.cpp b/Week_09/205.isomorphic-strings/Solution.cpp new file mode 100644 index 00000000..c861e1bd --- /dev/null +++ b/Week_09/205.isomorphic-strings/Solution.cpp @@ -0,0 +1,20 @@ +// +// Created by HaigCode. +// +class Solution { +public: + bool isIsomorphic(string s, string t) { + unordered_map s2t; + unordered_map t2s; + int len = s.length(); + for (int i = 0; i < len; ++i) { + char x = s[i], y = t[i]; + if ((s2t.count(x) && s2t[x] != y) || (t2s.count(y) && t2s[y] != x)) { + return false; + } + s2t[x] = y; + t2s[y] = x; + } + return true; + } +}; diff --git a/Week_09/300.longest-increasing-subsequence/Solution.cpp b/Week_09/300.longest-increasing-subsequence/Solution.cpp new file mode 100644 index 00000000..22a502b6 --- /dev/null +++ b/Week_09/300.longest-increasing-subsequence/Solution.cpp @@ -0,0 +1,33 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int lengthOfLIS(vector& nums) { + int len = 1, n = (int)nums.size(); + if (n == 0) { + return 0; + } + vector d(n + 1, 0); + d[len] = nums[0]; + for (int i = 1; i < n; ++i) { + if (nums[i] > d[len]) { + d[++len] = nums[i]; + } else { + int l = 1, r = len, pos = 0; // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 pos 设为 0 + while (l <= r) { + int mid = (l + r) >> 1; + if (d[mid] < nums[i]) { + pos = mid; + l = mid + 1; + } else { + r = mid - 1; + } + } + d[pos + 1] = nums[i]; + } + } + return len; + } +}; + diff --git a/Week_09/32.longest-valid-parentheses/Solution.cpp b/Week_09/32.longest-valid-parentheses/Solution.cpp new file mode 100644 index 00000000..358392f5 --- /dev/null +++ b/Week_09/32.longest-valid-parentheses/Solution.cpp @@ -0,0 +1,32 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int longestValidParentheses(string s) { + int size = s.length(); + vector dp(size, 0); + + int maxVal = 0; + for(int i = 1; i < size; i++) { + if (s[i] == ')') { + if (s[i - 1] == '(') { + dp[i] = 2; + if (i - 2 >= 0) { + dp[i] = dp[i] + dp[i - 2]; + } + } else if (dp[i - 1] > 0) { + if ((i - dp[i - 1] - 1) >= 0 && s[i - dp[i - 1] - 1] == '(') { + dp[i] = dp[i - 1] + 2; + if ((i - dp[i - 1] - 2) >= 0) { + dp[i] = dp[i] + dp[i - dp[i - 1] - 2]; + } + } + } + } + maxVal = max(maxVal, dp[i]); + } + return maxVal; + } +}; + diff --git a/Week_09/387.first-unique-character-in-a-string/Solution.cpp b/Week_09/387.first-unique-character-in-a-string/Solution.cpp new file mode 100644 index 00000000..2344416d --- /dev/null +++ b/Week_09/387.first-unique-character-in-a-string/Solution.cpp @@ -0,0 +1,19 @@ +// +// Created by HaigCode. +// + +class Solution { +public: + int firstUniqChar(string s) { + unordered_map frequency; + for (char ch: s) { + ++frequency[ch]; + } + for (int i = 0; i < s.size(); ++i) { + if (frequency[s[i]] == 1) { + return i; + } + } + return -1; + } +}; \ No newline at end of file diff --git a/Week_09/438.find-all-anagrams-in-a-string/Solution.cpp b/Week_09/438.find-all-anagrams-in-a-string/Solution.cpp new file mode 100644 index 00000000..5882c902 --- /dev/null +++ b/Week_09/438.find-all-anagrams-in-a-string/Solution.cpp @@ -0,0 +1,25 @@ +// +// Created by HaigCode. +// +class Solution { +public: + vector findAnagrams(string s, string p) { + vector res; + int len=p.size(),slen=s.size(); + if(slen> dp(m + 1, vector(n + 1)); + dp[0][0] = true; + for (int i = 1; i <= n; ++i) { + if (p[i - 1] == '*') { + dp[0][i] = true; + } + else { + break; + } + } + for (int i = 1; i <= m; ++i) { + for (int j = 1; j <= n; ++j) { + if (p[j - 1] == '*') { + dp[i][j] = dp[i][j - 1] | dp[i - 1][j]; + } + else if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } + } + } + return dp[m][n]; + } +}; diff --git a/Week_09/5.longest-palindromic-substring/Solution.cpp b/Week_09/5.longest-palindromic-substring/Solution.cpp new file mode 100644 index 00000000..8d573d26 --- /dev/null +++ b/Week_09/5.longest-palindromic-substring/Solution.cpp @@ -0,0 +1,27 @@ +// +// Created by HaigCode. +// +class Solution { +public: + string longestPalindrome(string s) { + int n = s.size(); + vector> dp(n, vector(n)); + string ans; + for (int l = 0; l < n; ++l) { + for (int i = 0; i + l < n; ++i) { + int j = i + l; + if (l == 0) { + dp[i][j] = 1; + } else if (l == 1) { + dp[i][j] = (s[i] == s[j]); + } else { + dp[i][j] = (s[i] == s[j] && dp[i + 1][j - 1]); + } + if (dp[i][j] && l + 1 > ans.size()) { + ans = s.substr(i, l + 1); + } + } + } + return ans; + } +}; diff --git a/Week_09/541.reverse-string-ii/Solution.cpp b/Week_09/541.reverse-string-ii/Solution.cpp new file mode 100644 index 00000000..52c5878e --- /dev/null +++ b/Week_09/541.reverse-string-ii/Solution.cpp @@ -0,0 +1,24 @@ +// +// Created by HaigCode. +// +class Solution { +public: + void reverse(string& s, int start, int end) { + for (int i = start, j = end; i < j; i++, j--) { + swap(s[i], s[j]); + } + } + string reverseStr(string s, int k) { + for (int i = 0; i < s.size(); i += (2 * k)) { + // 1. 每隔 2k 个字符的前 k 个字符进行反转 + // 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符 + if (i + k <= s.size()) { + reverse(s, i, i + k - 1); + continue; + } + // 3. 剩余字符少于 k 个,则将剩余字符全部反转。 + reverse(s, i, s.size() - 1); + } + return s; + } +}; diff --git a/Week_09/557.reverse-words-in-a-string-iii/Solution.cpp b/Week_09/557.reverse-words-in-a-string-iii/Solution.cpp new file mode 100644 index 00000000..1e7f4cb8 --- /dev/null +++ b/Week_09/557.reverse-words-in-a-string-iii/Solution.cpp @@ -0,0 +1,25 @@ +// +// Created by HaigCode. +// +class Solution { +public: + string reverseWords(string s) { + string ret; + int length = s.length(); + int i = 0; + while (i < length) { + int start = i; + while (i < length && s[i] != ' ') { + i++; + } + for (int p = start; p < i; p++) { + ret.push_back(s[start + i - 1 - p]); + } + while (i < length && s[i] == ' ') { + i++; + ret.push_back(' '); + } + } + return ret; + } +}; diff --git a/Week_09/63.unique-paths-ii/Solution.cpp b/Week_09/63.unique-paths-ii/Solution.cpp new file mode 100644 index 00000000..2e24bc86 --- /dev/null +++ b/Week_09/63.unique-paths-ii/Solution.cpp @@ -0,0 +1,26 @@ +// +// Created by HaigCode. +// +class Solution { +public: + int uniquePathsWithObstacles(vector>& obstacleGrid) { + int n = obstacleGrid.size(), m = obstacleGrid.at(0).size(); + vector f(m); + + f[0] = (obstacleGrid[0][0] == 0); + for (int i = 0; i < n; ++i) { + for (int j = 0; j < m; ++j) { + if (obstacleGrid[i][j] == 1) { + f[j] = 0; + continue; + } + if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0) { + f[j] += f[j - 1]; + } + } + } + + return f.back(); + } +}; + diff --git a/Week_09/680.valid-palindrome-ii/Solution.cpp b/Week_09/680.valid-palindrome-ii/Solution.cpp new file mode 100644 index 00000000..dfbb1a22 --- /dev/null +++ b/Week_09/680.valid-palindrome-ii/Solution.cpp @@ -0,0 +1,29 @@ +// +// Created by HaigCode. +// +class Solution { +public: + bool checkPalindrome(const string& s, int low, int high) { + for (int i = low, j = high; i < j; ++i, --j) { + if (s[i] != s[j]) { + return false; + } + } + return true; + } + + bool validPalindrome(string s) { + int low = 0, high = s.size() - 1; + while (low < high) { + char c1 = s[low], c2 = s[high]; + if (c1 == c2) { + ++low; + --high; + } + else { + return checkPalindrome(s, low, high - 1) || checkPalindrome(s, low + 1, high); + } + } + return true; + } +}; diff --git a/Week_09/8.string-to-integer-atoi/Solution.cpp b/Week_09/8.string-to-integer-atoi/Solution.cpp new file mode 100644 index 00000000..5f22a70d --- /dev/null +++ b/Week_09/8.string-to-integer-atoi/Solution.cpp @@ -0,0 +1,43 @@ +// +// Created by HaigCode. +// + +class Automaton { + string state = "start"; + unordered_map> table = { + {"start", {"start", "signed", "in_number", "end"}}, + {"signed", {"end", "end", "in_number", "end"}}, + {"in_number", {"end", "end", "in_number", "end"}}, + {"end", {"end", "end", "end", "end"}} + }; + + int get_col(char c) { + if (isspace(c)) return 0; + if (c == '+' or c == '-') return 1; + if (isdigit(c)) return 2; + return 3; + } +public: + int sign = 1; + long long ans = 0; + + void get(char c) { + state = table[state][get_col(c)]; + if (state == "in_number") { + ans = ans * 10 + c - '0'; + ans = sign == 1 ? min(ans, (long long)INT_MAX) : min(ans, -(long long)INT_MIN); + } + else if (state == "signed") + sign = c == '+' ? 1 : -1; + } +}; + +class Solution { +public: + int myAtoi(string str) { + Automaton automaton; + for (char c : str) + automaton.get(c); + return automaton.sign * automaton.ans; + } +}; \ No newline at end of file diff --git a/Week_09/818.race-car/Solution.cpp b/Week_09/818.race-car/Solution.cpp new file mode 100644 index 00000000..3909f613 --- /dev/null +++ b/Week_09/818.race-car/Solution.cpp @@ -0,0 +1,24 @@ +// +// Created by HaigCode. +// +#include +class Solution { +public: + int dp[10001]; + int racecar(int target) { + if (dp[target] > 0) return dp[target]; + int n = floor(log2(target)) + 1; + if (target + 1 == (1<= '1' && s[i] <= '6')) + curr = curr + pre; + pre = tmp; + } + return curr; + } +}; diff --git a/Week_09/917.reverse-only-letters/Solution.cpp b/Week_09/917.reverse-only-letters/Solution.cpp new file mode 100644 index 00000000..ed7cd96f --- /dev/null +++ b/Week_09/917.reverse-only-letters/Solution.cpp @@ -0,0 +1,19 @@ +// +// Created by HaigCode. +// +class Solution { +public: + string reverseOnlyLetters(string S) { + int begin = 0,end=S.length()-1; + while(begin 计算机指令集 +## 1.2.动态规划 Dynamic Programming +- 1."Simplifying a complicated problem by breaking it down into simpler sub-problems"(in a recursive manner) +- 2.Divide & Conquer + Optimal substructure(分治 + 最优子结构) +- 3.顺推形式:动态递归 +- DP顺推模板: +``` +function DP(): + dp = [][] # 二维情况 DP 状态定义复杂 + for i 0...M{ + for j=0...N{ + dp[i][j] = _Function(dp[i'][j']...) # 状态转移方程复杂 + } + } + return dp[M][N]; +``` +- 关键点: +动态规划和递归或者分治 没有根本上的区别(关键看有无最优的子结构) +拥有共性:找到重复子问题 +差异性:最优子结构、中途可以淘汰次优解 + +# 20.字符串基础知识 +# 20.1. 字符串遍历、比较 +# 20.2..高级字符串算法 +- 和动态规划结合 +- 最长子串、子序列问题:最长子串、最长子序列、编辑距离 +- 字符串 + DP问题:正则表达式匹配、通配符匹配、不通的子序列 +# 20.3.字符串匹配算法 +- 1.暴力法(brute force) +- 2.Rabin-Karp 算法 +- 3.KMP算法 + +# 20.4.参考链接 +- [Boyer-Moore 算法](https://www.ruanyifeng.com/blog/2013/05/boyer-moore_string_search_algorithm.html) +- [Sunday 算法](https://blog.csdn.net/u012505432/article/details/52210975) +- [字符串匹配暴力法代码示例](https://shimo.im/docs/8G0aJqNL86wWrPUE) +- [Rabin-Karp 代码示例](https://shimo.im/docs/1wnsM7eaZ6Ab9j9M) +- [KMP 字符串匹配算法视频](https://www.bilibili.com/video/av11866460?from=search&seid=17425875345653862171) +- [字符串匹配的 KMP 算法](http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html) + + +# 20.5.unique-paths-ii 状态转移方程 +- 我们用 f(i, j)f(i,j) 来表示从坐标 (0, 0)(0,0) 到坐标 (i, j)(i,j) 的路径总数,u(i, j)u(i,j) 表示坐标 (i, j)(i,j) 是否可行,如果坐标 (i, j)(i,j) 有障碍物,u(i, j) = 0u(i,j)=0,否则 u(i, j) = 1u(i,j)=1。 +- 因为「机器人每次只能向下或者向右移动一步」,所以从坐标 (0, 0)(0,0) 到坐标 (i, j)(i,j) 的路径总数的值只取决于从坐标 (0, 0)(0,0) 到坐标 (i - 1, j)(i−1,j) 的路径总数和从坐标 (0, 0)(0,0) 到坐标 (i, j - 1)(i,j−1) 的路径总数,即 f(i, j)f(i,j) 只能通过 f(i - 1, j)f(i−1,j) 和 f(i, j - 1)f(i,j−1) 转移得到。当坐标 (i, j)(i,j) 本身有障碍的时候,任何路径都到到不了 f(i, j)f(i,j),此时 f(i, j) = 0f(i,j)=0;下面我们来讨论坐标 (i, j)(i,j) 没有障碍的情况:如果坐标 (i - 1, j)(i−1,j) 没有障碍,那么就意味着从坐标 (i - 1, j)(i−1,j) 可以走到 (i, j)(i,j),即 (i - 1, j)(i−1,j) 位置对 f(i, j)f(i,j) 的贡献为 f(i - 1, j)f(i−1,j),同理,当坐标 (i, j - 1)(i,j−1) 没有障碍的时候,(i, j - 1)(i,j−1) 位置对 f(i, j)f(i,j) 的贡献为 f(i, j - 1)f(i,j−1)。 + +- f(i,j) = 0, u(i,j) = 0 +- f(i,j) = f(i-1,j) + f(i,j-1), u(i,j) != 0 +