├── .gitignore ├── README.md ├── leetcode ├── Cargo.lock ├── Cargo.toml └── src │ ├── array │ ├── todo108.rs │ ├── todo118.rs │ ├── todo1984.rs │ ├── todo287.rs │ ├── todo303.rs │ ├── todo442.rs │ ├── todo448.rs │ ├── todo485.rs │ ├── todo566.rs │ ├── todo654.rs │ ├── todo697.rs │ └── todo766.rs │ ├── bfs │ ├── 102.rs │ └── 1161.rs │ ├── dfs │ ├── 397.rs │ ├── 669.rs │ └── todo783.rs │ ├── main.rs │ ├── stack │ └── 20.rs │ └── two_pointers │ └── todo283.rs ├── src ├── code │ └── top100 │ │ ├── 128.cpp │ │ └── 49.cpp ├── cpp │ ├── smart_ptr.cpp │ └── typeid.cpp ├── latex_file ├── note.md └── resume_v1.pdf └── weekly_contest └── 2024_4_21 ├── cpp ├── a.out ├── q1.cpp ├── q2.cpp └── q3.cpp └── rust ├── Cargo.lock ├── Cargo.toml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | target 3 | *.out 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # interview-note 2 | 3 | ## 目前状况 4 | 5 | 已转正智臾科技的 C++ 后端开发岗,主要负责 SQL Parser 和一些插件支持的工作。 6 | 7 | 14 | 15 | [最新简历 Resume](./src/resume_v1.pdf) (2022/4) 16 | 17 | 18 | 19 | ## 面试记录 20 | 21 | ### 23秋招 (2022/9 - 2022/10) 22 | 23 | 因为工作累加上感觉学历差和竞争力不够就没投别的公司,直接在智臾转正 C++ 后端开发岗了。 24 | 25 |
26 | 27 | ### 23暑期实习 (2022/4 - 2022/5) 28 | 29 | 简历挂:OPPO、携程、贝壳、网易、英雄游戏、拼多多、深信服、NVIDIA、快手、完美、淘乐、七牛云、毫末智行、哲库科技、CVTE、虹软、搜狐畅游、旷视 30 | 31 | 放弃:XSKY、字节、美团、美的、海康威视 32 | 33 | |公司|岗位|笔试|一面|二面|三面|HR面|状态| 34 | |:-|:-|:-|:-|:-|:-|:-|:-| 35 | |米哈游|云游戏后台研发(C++)|4/10|X|X|X|X|笔试挂| 36 | |阿里|测试开发(C++)|4/15|X|X|X|X|笔试挂| 37 | |百度|软件开发(C++)|4/12|4/17|X|X|X|面试挂| 38 | |趋势科技|软件开发(C++)|4/22|X|X|X|X|笔试挂| 39 | |腾讯|软件开发(C++)|4/24|X|X|X|X|笔试挂| 40 | |微众银行|数据库(C++)|4/20|-|-|-|-|等待| 41 | |京东|测试开发(C++)|4/29|-|-|-|-|等待| 42 | |智臾|软件开发(C++)|4/20|4/26|5/7|-|-|已接受OFFER| 43 | 44 | ## 工作经历 45 | 46 | ### 浙江智臾科技(2022/7 - 2022/12) 47 | 48 | C++后端开发实习生 49 | 1. DolphinDB Python 语法脚本支持 Pandas 和 Numpy 库 50 | 2. 负责 Parser 部分,支持 SQL-92 的标准语法 51 | 52 |
53 | 54 | ## 数据结构算法 55 | 56 | 1. 数据结构 57 | - [数组](#数组) 58 | - [栈](#栈) 59 | - [栈/递归](#栈递归) 60 | - [单调栈](#单调栈) 61 | - [队列](#队列) 62 | - [普通队列](#普通队列) 63 | - [堆(优先队列)](#堆优先队列) 64 | - [单调队列](#单调队列) 65 | - [链表](#链表) 66 | - [字符串](#字符串) 67 | - [常规字符串](#常规字符串) 68 | - [大数问题](#大数问题) 69 | - [哈希表](#哈希表) 70 | - [树](#树) 71 | - [普通题型](#普通题型) 72 | - [遍历](#遍历) 73 | - [BST(二叉搜索树)](#BST(二叉搜索树)) 74 | - [Trie(字典树、前缀树)](#Trie(字典树前缀树)) 75 | - [N叉树](#N叉树) 76 | - [图](#图) 77 | - [拓扑排序](#拓扑排序) 78 | - [位运算](#位运算) 79 | 80 | 2. 算法 81 | - [搜索](#搜索) 82 | - [DFS](#DFS) 83 | - [BFS](#BFS) 84 | - [多源BFS](#多源BFS) 85 | - [回溯](#回溯) 86 | - [双指针](#双指针) 87 | - [排序](#排序) 88 | - [简单的排序](#简单的其他排序) 89 | - [快速排序](#快速排序) 90 | - [堆排序](#堆排序) 91 | - [桶排序](#桶排序) 92 | - [归并排序](#归并排序) 93 | 96 | - [并查集](#并查集) 97 | - [动态规划](#动态规划) 98 | - [记忆化搜索](#记忆化搜索) 99 | - [简单一维DP问题](#简单一维DP问题) 100 | - [简单二维DP问题](#简单二维DP问题) 101 | - [数组区间问题](#数组区间问题) 102 | - [字符串问题](#字符串问题) 103 | - [最长递增子序列](#最长递增子序列) 104 | - [最长公共子序列](#最长公共子序列) 105 | - [数值拆分问题](#数值拆分问题) 106 | - [背包问题](#背包问题) 107 | - [贪心思想](#贪心思想) 108 | - [二分思想](#二分思想) 109 | - [分而治之](#分而治之) 110 | - [数学问题](#数学问题) 111 | 116 | - [进制转换](#进制转换) 117 | - [阶乘](#阶乘) 118 | - [相遇问题](#相遇问题) 119 | - [LRU](#LRU) 120 | - [滑动窗口](#滑动窗口) 121 | 122 | 3. 其他 123 | - [输入输出格式](#输入输出格式) 124 | 125 | ### **数组** 126 | 127 | 水题:LC108 Easy、LC118 Easy、LC240 Medium、LC252 Easy、LC448 Easy、LC485 Easy、LC566 Easy、LC654 Medium、LC697 Easy、LC766 Easy、LC1984 Easy 128 | 129 | |题号|笔记| 130 | |:-|:-| 131 | |LC287 Medium|| 132 | |LC303 Easy|记录从下标 0 到每个元素的数值和,这样一个区间内的数值和就可以转换为两个数值和的差值。| 133 | |LC442 Medium|| 134 | 135 | ### **栈** 136 | 137 | #### 栈/递归 138 | 139 | 水题:LC20 Easy、LC225 Easy、LC232 Easy 140 | 141 | |题号|笔记| 142 | |:-|:-| 143 | |LC155 Easy|运用辅助栈,多使用一个栈来保存别的信息,让辅助栈来跟随主栈push\pop操作,并保存每一层的最小值情况。| 144 | |LC394 Medium|解析上一层需要前需要先解析下一层得到信息,明显就是递归,将 [ 和 ] 分别作为递归的开启与终止条件。| 145 | 146 | #### 单调栈 147 | 148 | 要求是每次入栈的元素必须要有序(如果新元素入栈不符合要求,循环将之前的元素出栈,直到符合要求再入栈),使之形成单调递增/单调递减的一个栈。当要入栈新元素的时候,我们发现如果栈不为空,那么这个栈顶元素就是我们要找的向前/向后(通过循环从前往后或者从后往前来控制)第一个大于/小于/大于等于/小于等于(通过改变内部入栈比较条件来控制)的元素,如果栈为空那么说明没有。关键词就是最近/后面/前面遇到的第一个的大于/小于/大于等于/小于等于的元素。 149 | 150 | ```c++ 151 | 152 | // 模板举例 153 | while (!st.empty() && num[i] >= st.top()) 154 | st.pop(); 155 | 156 | ans[i] = st.empty() ? -1 : st.top(); // code here 此处是通过栈顶元素获得结果的部分 157 | st.push(num[i]); 158 | 159 | ``` 160 | 161 | 162 | 163 | |题号|笔记| 164 | |:-|:-| 165 | |LC42 Hard|运用单调栈,每一次比较大于,得知有一个洼地,可能形成积水,通过不断出栈中间洼地或者比两端低的高墙的元素,计算新增加的积水(下层的积水之前已经计算完成),然后把积水加起来即可。另外一点注意,因为我们要用索引做差求距离,所以单调栈里面存的是索引。| 166 | |LC496 Easy|子问题是下一个更大的元素,明显是单调栈。然后总体问题通过哈希+单调栈解决,因为元素各异,所以可以用哈希表存结果。| 167 | |LC503 Medium|循环数组拉直,破环成链,通过下标取模,使得范围从 0 到 2 * sz - 1 完成拉直工作,然后单调栈做法即可。第二点返回向量答案,但我们是逆序遍历得到答案的,为了避免逆序,我们定义vector ret(sz),直接初始化好对应的大小,逆序遍历直接通过索引ret\[i\] = x来存答案,避免多余的reverse函数。| 168 | |LC739 Medium|单调栈问题,要注意一下这道题单调栈必须存索引,不能直接存元素,因为答案是索引做差得到的天数。| 169 | 170 | ### **队列** 171 | 172 | #### 普通队列 173 | 174 | 水题:LC281 Medium 175 | 176 | #### 堆(优先队列) 177 | 178 | 有时候我们需要比较自定义类型,这个时候需要我们手写比较类,下面是一个例子。 179 | 180 | ```c++ 181 | struct cmp 182 | { 183 | bool operator()(const ListNode *l1, const ListNode *l2) 184 | { 185 | return l1->val > l2->val; 186 | } 187 | }; 188 | 189 | //使用时 190 | priority_queue, cmp> pq; 191 | ``` 192 | 193 | prob 218 194 | |题号|笔记| 195 | |:-|:-| 196 | |LC253 Medium|to do| 197 | 198 | 跳转:[堆排序](#堆排序) 199 | 200 | #### 单调队列 201 | 202 | |题号|笔记| 203 | |:-|:-| 204 | |LC239 Hard|to do| 205 | |LC264 Medium|直接从堆里取出一个,然后加入 2x、3x、5x,这个的正确性是没问题的,因为当前点的下一层的三个点一定比这个大,排除,所以可以得证。另外注意,为了去重,我们使用 unordered_set,不断插入,如果 count 发现已存在,则直接丢弃。| 206 | |LC1438 Medium|to do| 207 | 208 | ### **链表** 209 | 210 | 水题:LC2 Medium、LC21 Easy、LC24 Medium、LC83 Easy、LC206 Easy、LC328 Medium、LC725 Medium 211 | 212 | |题号|笔记| 213 | |:-|:-| 214 | |LC160 Easy|两个链表相交点,不使用额外空间的方法,就是利用 a+b+c = c+b+a,其中b是相交之后共同的部分,a和c分别是两个链表相交之前的部分,利用这个数学原理得到,走完a+b+c后,下一个一定是b的第一个,也就是相交的第一个节点;另外如果两个链表不相交,那么就是a+c=c+a,下一个都是nullptr,虽然不相交但也可以通过 l1 == l2 来终止循环,无需额外考虑。| 215 | |LC234 Easy|不使用额外空间的做法,快慢指针找中间节点,然后后半个链表再反转(使用前中后三个节点来实现反转),然后比较这两个链表。 216 | |LC445 Medium|这道题本身很简单,难点在于得到逆序的数值,关于逆序我们首先就要想到栈,使用栈很简单,比反转链表方便多了。| 217 | 218 | ### **字符串** 219 | 220 | #### 常规字符串 221 | 222 | 水题:LC6 Medium、LC9 Easy、LC14 Easy、LC28 Easy、LC205 Easy、LC242 Easy、LC409 Easy、LC551 Easy 223 | 224 | |题号|笔记| 225 | |:-|:-| 226 | |LC49 Medium|注意包含相同字母(个数也相同)的单词,可以经过排序,得到一个相同的单词作为key,然后使用哈希。| 227 | |LC266 Easy|利用回文字符串的特性+哈希表,奇数次数的字符小于等于 1 则可以通过排列变成回文数。| 228 | |LC696 Easy|一些时候可以可以将同字符的子字符串转为单纯的一个数字,然后利用数学关系简化复杂度。| 229 | 230 | #### 大数问题 231 | 232 | |题号|笔记| 233 | |:-|:-| 234 | |LC43 Medium|大数乘法,直接模拟的话,就是一轮一轮乘,然后把每一轮的结果再通过大数相加得到最终答案,复杂度(mn + n^2),复杂度和O(mn)或者O(n^2)差不多| 235 | |LC415 Easy|从低位到高位模拟运算即可,注意字符串加的顺序,str = 新的高位 + str,再就是注意下进位。| 236 | 237 | ### **哈希表** 238 | 239 | 水题:LC217 Easy 240 | 241 | |题号|笔记| 242 | |:-|:-| 243 | |LC1 Easy|注意这道题是无序的,而且排序后下标也变了,所以这道题不是双指针类型的题,不要被误导。为实现O(log n)的复杂度,我们先使用哈希表记录下每个元素对应的下标,然后循环find得到target-nums\[i\]的迭代器,注意考虑重复元素的问题,判断条件中需要判断这两个不是同一个元素。| 244 | |LC219 Easy|注意使用 unordered_map 记录元素和对应下标,另外注意如果第一个重复的元素不满足条件,那么应该更换映射的下标,因为随着向右遍历,离旧下标距离越来越远,根本不可能,所以一旦不满足,则换映射的下标。| 245 | |LC594 Easy|哈希表直接通过 key + 1 找到目标的元素。| 246 | 247 | ### **树** 248 | 249 | #### 普通题型 250 | 251 | 水题:LC112 Easy、LC404 Easy、LC106 Medium、LC107 Medium、LC04.02 Easy、LC563 Easy 252 | 253 | |题号|笔记| 254 | |:-|:-| 255 | |LC100 Easy|判断两个树是否相同,递归dfs,然后分类讨论p、q一个为空为假,两个都空为真,两个都不空就判断值即可。| 256 | |LC101 Easy|对称,让根节点的左右节点,如此check(p->left, q->right) && check(p->right, q->left)递归比较;或者是先反转任一一个子树,然后递归比较相同。| 257 | |LC103 Easy|bfs内部for循环来分层,加一个标志位控制插入deque的方向,用deque来存每层的结果,这样就不用reverse了。| 258 | |LC104 Easy|求树的高度。| 259 | |LC105 Medium|中序配合后序遍历生成二叉树,后序的最后一个节点是根节点(对应前序的第一个节点是根节点),然后根据根节点值再中序遍历的位置,分割得到左右子树,然后递归处理。注意要点就是可以建立map,记录中序遍历值对应所在的下标,这样我们不用每次取得根节点值再搜索得到下标,而是可以直接从这个map得到。| 260 | |LC111 Easy|求树的最小深度,注意不同于普通的求高度,当叶节点情况返回1,当本身是nullptr说明这里无该节点,这里的深度无意义,返回一个INT32_MAX舍去这种无意义情况。| 261 | |LC110 Easy|自顶而下递归比较左右两子树的高度很简单,但是内部有重复,复杂度是O(n^2);与自定而下相对应的就是自底向上,复杂度仅O(n),思路参见该表格中下面的LC543题的笔记解释。| 262 | |LC226 Easy|反转二叉树。| 263 | to do另一种方法|LC437 Medium|路径求和,可以遍历并对每个节点来一次递归求解操作,复杂度O(n^2),写法简单;另一种利用前缀和,降低复杂度只有O(n)。| 264 | |LC235 Easy|BST下找最近公共父结点,分类讨论,利用分叉点一定最近来只需要O(n)的复杂度;另一种做法是分别保存根节点到p、q的两个路径,然后比较得到。| 265 | |LC236 Medium|| 266 | |LC543 Easy|求树的直径,转换成对每个节点求左子树和右子树的高度和,返回最大的即可。简单的做法是双重递归,对每个节点都递归求解;但是更高效的一种方法,我们发现求深度的递归函数中,内部会获取左子树和右子树的高度,利用这里可以可以求最大直径,而这里的方向是从下往上,因为是深度遍历,最开始得到左右子树两个深度的情况是最底层,然后再return返回到上一层继续比较,这就是自底向上的思想。| 267 | to do另一种方法|LC572 Easy|利用一个性质,那就是相同树结构的先序遍历结果相同(相同的结果一定相同树吗??)| 268 | |LC617 Easy|合并二叉树。| 269 | |LC669 Medium|注意处理根节点的情况,发现根节点不满足问题就变成求它的一个子树的范围。(迭代写了三十行,递归只要十行无语。。)| 270 | |LC671 Easy|| 271 | 272 | #### 遍历 273 | 274 | prob 297 331 275 | 276 | 水题:LC94 Easy、LC116 Medium、LC144 Easy、LC145 Easy、LC700 Easy、LC513 Medium、LC671 Easy 277 | 278 | |题号|笔记| 279 | |:-|:-| 280 | |LC74 Medium|从右上角来看,你会发现这是一个BST,初始定义右上角坐标,然后循环比较 target 比较即可。| 281 | |LC94 Easy|二叉树的中序遍历,递归自然不用说,迭代实现是借助栈来完成,先将当前节点的所有左子树压入栈,压到没有为止;将最后一个压入的节点弹出(栈顶元素),加入答案;将当前弹出的节点的右节点作为当前节点,重复上述步骤。注意迭代的终止条件,不光栈为空,而且当前指针也指向空才对(意味着接下来没有入栈的)。| 282 | |LC117 Medium|层序遍历,prev 初始化为 nullptr,如果非空则 prev->next = p 连接下一个同层节点,内部 for 循环当 i == sz - 1 知道这是该层最后一个元素,把这个元素的 next 设为 nullptr 即可。方法二,建立第 i+1 层时第 i 层的 next 已经设置完成,我们在第 i 层通过 next 遍历,并记录下第一个节点的左节点,即第 i+1 层的第一个元素,初始化为 last 和 next_start 然,然后不断地把第 i 层的元素放到 last->next = p; last = p; 构建,第 i+1 层构建完成后,start = next_start 然后循环进行,直到 start 为 nullptr,这种方法时间复杂度仍然是 O(n) 但是不需要额外的空间,空间复杂度 O(1)。| 283 | |LC173 Medium|笨办法需要 O(n) 复杂度,中序遍历结果放到向量里即可,不需要解释。另一种方式是通过栈的思路,to do| 284 | |LC199 Medium|二叉树的右侧视角(从右面看),即每层最中最后的那个节点,层序遍历bfs即可完成,再记一下bfs内部for循环完成一层的遍历。同理左侧视角就是层序遍历每层最左面的节点了。| 285 | |LC270 Easy|查找离目标值最近的节点值,注意目标值是 double,所以要小心类型问题。另外 abs() 返回的是 int 类型,所以不能用在 double 上,需要手动去绝对值。| 286 | |LC298 Medium|-| 287 | |LC536 Medium|原理就是一个简单的递归,但是实现起来比较难,需要定位最外侧括号,可以通过经过(计数加1,经过)计数减1,为0说明经过了一次最外侧的(),然后递归处理这部分字符串返回作为左/右孩子即可。| 288 | |LC606 Easy|这道题要利用递归函数的返回值,返回 string 代表处理的该个子树的结果,这样就可以把一个父问题变为处理 root 和两个子树的递归问题了,注意要用 string 返回值代表处理的子问题的结果,这样写起来会很简洁。| 289 | |LC637 Easy|层次遍历,外层while条件是队列不为空,内部一个for循环是当前层的结点遍历,for循环的次数是通过队列size函数来得到的,即当前层结点数。| 290 | |LC637 Easy|层次遍历,外层while条件是队列不为空,内部一个for循环是当前层的结点遍历,for循环的次数是通过队列size函数来得到的,即当前层结点数。| 291 | |LC783 Easy|利用 BST 中序遍历的性质,直接在递归中做差取最小值即可。| 292 | |LC872 Easy|遍历树,内部用 !r->left && !r->right 对每一次递归的子树根节点判断是否是叶节点即可。| 293 | |LC897 Easy|笨办法是先中序遍历把每一个 root 存到 vector 里,然后再循环 vector 来构造。另一种方法不需要额外的空间复杂度,那就是中序遍历的时候记录上一个节点,让上一个节点的 right 是这一个节点即可,但这样我们需要额外考虑第一个节点的情况,一种好的方法是一开始 new 一个 dummy 节点作为前一个结点即可,这样不用考虑前一个结点为 nullptr 的情况。| 294 | |LC993 Easy|找到父节点和深度然后对比即可,可以使用参数来传递父节点,如果通过给一个变量不断赋值来记录父节点,要注意当找到答案后立马返回,避免又修改了。| 295 | |LC938 Easy|注意这是BST,递归思路,如果 root->val > high 则只可能左子树,反之则只能右子树,然后就是 root->val 和左右子树。| 296 | 297 | #### BST(二叉搜索树) 298 | 299 | |题号|笔记| 300 | |:-|:-| 301 | |LC99 Medium|to do| 302 | |LC230 Medium|BST按中序遍历会得到一个递增的序列,中序遍历+递归函数内部计录排位即可求得BST中第k小的元素了。因为中序遍历有个特性是先到最左面的叶子结点,之后是依序访问,那么复杂度是O(H+k),H是树的高度,所以如果我们将BST优化成AVL能把复杂度最小化成O(log N + k),因为AVL树的高度是log N。| 303 | |LC285 Medium|-| 304 | |LC501 Easy|利用BST中序遍历是非递减序列完成,注意通过非递减这个特性,我们可以不用哈希表,从而空间复杂度为O(1)。| 305 | |LC530 Easy|注意这是BST,所以一定有什么寓意,发现是利用BST最重要的那个性质,中序遍历得到非递减序列,依次做差即可。另外要注意 prev 一开始不能直接用,要一个 bool 变量标记,当 prev 被赋值后,更改那个 bool 标记才能用。| 306 | |LC538 Medium|这道题使用了逆中序遍历的思路,先右再左遍历。既然中序遍历是递增,那么逆中序自然是递减,方向是从右往左,累加和也变得可以计算了。| 307 | |LC653 Easy|用烂了的BST中序遍历+双指针即可。| 308 | 309 | #### Trie(字典树、前缀树) 310 | 311 | |题号|笔记| 312 | |:-|:-| 313 | |LC139 Medium|| 314 | |LC208 Medium|典型的字典树写法,另外注意下利用是否是叶节点的布尔变量来区分查前缀和搜索单词。| 315 | |LC677 Medium|字典序前缀和的问题,注意可以优化每个节点保存一个前缀和的方式,通过每次新插入增加路径前缀节点的值;另外注意通过哈希表记录以及插入的值,发生改变值的时候,我们就这次新值和之前值做差,然后增加前缀节点这个差值,完成更改。| 316 | 317 | #### N叉树 318 | 319 | 水题:LC589 Easy、LC590 Easy 320 | 321 | |题号|笔记| 322 | |:-|:-| 323 | |LC429 Medium|N叉树层序遍历,和常规的二叉树的层次遍历方法几乎一样,同样注意 while 内部用 for 一次遍历一层。| 324 | 325 | ### **图** 326 | 327 | #### 拓扑排序 328 | 329 | |题号|笔记| 330 | |:-|:-| 331 | |LC207 Medium|首先是通过依赖关系建立单向图 vector> gr,前置节点指向所依赖的节点(需要访问完前置节点才能访问所依赖的节点),然后记录下每个节点的入度,如果入度为 0 说明该节点无前置节点,可以直接被访问。创建一个队列,把所有不被依赖的节点(入度为 0)放入,然后取出访问并减小所指向节点的入度,入度降为 0 再加入队列。如果该单向图无环,那么访问的节点数等于所有节点数,如果有环(相互依赖的不可能情况),那么这种节点入度永远不可能降为 0,所以当队列为空结束时,访问的结点数小于所有节点数,可以据此判断。| 332 | |LC210 Medium|题和 LC207 几乎一样,不过这次我们用 DFS 来做这个拓扑排序题。我们一次 dfs 中如果两次相遇(搜索中状态 1)说明有环,如果正常结束我们把 1 设置成 -1 (已完成状态),此时已经把结果加入栈中,且不会当成一次 dfs 的两次相遇,因为已经搜索完成了,而上次搜索没有任何节点指向这个节点(否则也是已完成状态,无法 dfs 本节点了),所以加入这个节点指向那些已完成状态的节点也不可能有环。最后把从内到外 push_back 的结果向量颠倒即可。| 333 | |LC851 Medium|| 334 | |LC954 Medium|to do| 335 | 336 | #### 判断图是否有环 337 | 338 | |题号|笔记| 339 | |:-|:-| 340 | |LC802 Medium|、| 341 | 342 | ### **位运算** 343 | 344 | 水题: 345 | 346 | |题号|笔记| 347 | |:-|:-| 348 | ||| 349 | ||| 350 | ||| 351 | ||| 352 | ||| 353 | 354 | ### **搜索** 355 | 356 | #### DFS 357 | 358 | 水题 LC559 Easy 359 | 360 | prob 1723 1766 361 | 362 | |题号|笔记| 363 | |:-|:-| 364 | |LC130 Medium|涉及到边界问题,这道题的问题在于要区分含不含边界两种情况,聪明的办法就是我们先只对边界dfs,将元素改成一种新的标记,即可区分包不包含边界的情况,最后再改回即可。| 365 | |LC200 Medium|典型的棋盘问题,岛屿计数,遍历进行dfs,每一次遇到未访问的点加一计数即可。四个方向可以定义两个方向数组,写成循环减少代码量,而不用写四遍。| 366 | |LC257 Easy|典型的树的递归遍历,注意通过参数来保存当前深度的已经过的路径,递归通过参数保存当前状态,和向下再传递状态。| 367 | |LC386 Medium|按字典序用dfs遍历即可。| 368 | |LC417 Medium|类似130题型,dfs倒序从边界遍历,即“水往高处流”,分别遍历两种海洋,最后取交叉即可。| 369 | |LC547 Medium|题目很简单,是典型的邻接矩阵格式的图的dfs遍历写法;另外用bfs当然也可以,通过循环+队列实现。| 370 | |LC695 Medium|典型的棋盘问题,另外通过递归返回值作为结果可以减少代码量,尽量用这个。| 371 | |LC733 Medium|如果用bfs写,注意bfs和dfs一样需要vis标志是否访问过,不过很多题中不需要显式vis标记,比如这道题可以通过设置image\[x\]\[y\]为newColor隐式标记这个点已经访问过,如果不能标记,那么还是接着用vis标记即可。另外这道题注意判断新旧颜色相同的情况,避免造成无限递归。| 372 | |LC797 Medium|| 373 | |LC1020 Medium|记住,棋盘的问题,如果题干和边缘相关,那么我们就要考虑只对边界的点遍历即可的思路。我用的是笨办法,对每个点 dfs 然后看标志位是否经过边界,如果没有经过则 ret 加上 dfs 返回的经过的块数,这种方法写起来挺恶心的。而一个聪明的方法是只对边界的陆地点 dfs,然后把这些和边缘连通的陆地变成 2,最后记录 1 的数量即可。| 374 | |LC1034 Medium|注意如果直接在 dfs 中更改颜色,则会影响后序的判断,所以我们需要先记下所有要改的位置,但先不改,到最周再一起改。| 375 | |LC2049 Medium|to do| 376 | 377 | #### BFS 378 | 379 | 380 | 381 | prob 127 403 752 773 778 815 847 909 1034 1036 1345 1631 2039 2045 2059 LCP07 382 | 383 | |题号|笔记| 384 | |:-|:-| 385 | |LC863 Medium|求距离为 k 的点我们很容易想到 BFS,我们但是这个题是树而不是图,所以我们只需要通过树构造一个图即可,图我们使用邻接表的形式 vector>,首先来个先序遍历,对每个结点判断是否有子节点,有的话把这个边加入无向图(两个方向都加入)即可,不用担心加重复的问题,因为每个结点只能加它和它的子节点的边,不会加往上的边,所以不可能重复加入。构建好图后然后就对图用 BFS 遍历,依旧是内部 for 循环一层(注意同一层距离相同)的全部元素即可。| 386 | 387 | #### 多源BFS 388 | 389 | |题号|笔记| 390 | |:-|:-| 391 | |LC1162 Medium|to do| 392 | |LC1765 Medium|典型的多源 BFS 题型,注意我们对 0 BFS 遍历得到所有 1 的位置,剩下的与 1 相邻位置就必须是 2 了,因为所有必须是 1 的位置已经标出,这保证了正确性,BFS 遍历,直到队列为空即可。| 393 | 394 | #### 回溯 395 | 396 | 397 | 398 | 399 | 回溯算法是对树形或者图形结构执行一次深度优先遍历,在遍历的过程中寻找问题的解。当发现已不满足求解条件时,就返回,尝试别的路径。此时对象类型变量就需要重置成为和之前一样,称为“状态重置”,而这就是与DFS的区别。许多复杂的,规模较大的问题都可以使用回溯法,实际上,回溯算法就是暴力搜索算法。 400 | 401 | DFS使用的标记数组是不需要状态重置的,因为这是一个全局状态,全局只走一遍,比如遍历,从一个结点向右先走的路径,之后再从这个点向下走就不可能再走了,因为两个方向都是服务于同一个目标;而回溯不一样,对于回溯来说向右走的尝试和之后向下走的尝试是没有关系的,之后向下走完全是一个新的尝试,不受之前向右走尝试的影响,所以需要向右走完之后撤销状态。 402 | 403 | 状态撤销有两种,一种是自然的撤销操作,另一种是通过传值,使得递归调用的子函数改变的是副本从而不会影响到另一种尝试,当然传值拷贝的费用不低,所以要优先使用撤销的方式,这样我们传递引用避免拷贝的成本,而且可读性更好,用的人更多。 404 | 405 | 如果看见排序,组合,枚举,暴力搜索或类似的列出全部可能性的题,一般就是回溯题型了。 406 | 407 | 另外如果 TLE 考虑一下能不能剪枝(避免根本不可能得到答案的递归调用),若是还不行就转为记忆化搜索,然后还可以转 DP 来做。 408 | 409 | 水题:LC17 Medium 410 | 411 | |题号|笔记| 412 | |:-|:-| 413 | |LC31 Medium|to do返回当前排列的下一个字典序排列。| 414 | |LC37 Hard|to do| 415 | |LC39 Medium|、| 416 | |LC40 Medium|、| 417 | |LC46 Medium|不重复数组的全排列,我们要在每层取出一个数作为第一个数放入一个临时向量,然后递归子函数在剩余部分再选出下层的第一个数,递归即可,最后把这个临时向量放入答案,我们只需要额外定义一个 vis 向量来监视是否上层函数已经选过这个数当作第一个数即可实现,记得本层函数结束后要撤销对 vis 的操作;首先要注意得到剩余部分的时候,不要从向量中删除,因为会导致删除元素右面的所有元素的移动,代价太大;然后还有一个优化是通过交换来实现取本层的数,可以不用定义 vis 向量,然后 start + 1 来告知递归子函数要处理的剩余部分,注意本层交换操作回溯回来要撤销操作来重置状态,来将我们传递引用的nums数组恢复到操作前的状态。| 418 | |LC47 Medium|可重复数组的全排列,剪枝优化。涉及考虑重复元素,或者大小比较的情况,答案顺序任意,一定要考虑下排序,排序之后就可以更好的找到规律,更好找到剪枝等优化操作的规律。答案顺序任意,因而这道题排序后答案没问题,很容易发现,在同层中,重复元素选任何一个的结果都一样,从而找到剪枝条件(i > 0 && !vis\[i - 1\] && nums\[i\] == nums\[i - 1\])。| 419 | |LC51 Hard|N皇后问题,一行必须放一个,所以可以把行数当下标,然后列、左斜、右斜三个集合查集合回溯即可。注意通过行数下标减去列,可以作为左斜的集合的元素。| 420 | |LC77 Medium|组合问题,从n个数里面选k个,列出全部组合情况(一种组合中内部的顺序无意义),| 421 | |LC78 Medium|、| 422 | |LC79 Medium|、| 423 | |LC90 Medium|、| 424 | |LC93 Medium|字符串dfs遍历所有可能性即可,注意前导0格式问题。| 425 | |LC113 Medium|树的路径问题,注意我们无需对每一次路径都值传递拷贝一次,我们只需要一个全局的 vector path 记录路径,进入就 push,离开就 pop,只需要这一个就可以完成记录路径的作用了。| 426 | |LC131 Medium|、| 427 | |LC216 Medium|、| 428 | |LC267 Medium|、| 429 | |LC306 Medium|暴力回溯思路,但是由于数值可能很大,long long 也无法满足,所以我们可以通过 vector 从低位到高位(这样进位好写,如果最后有进位的多一位的 1 可以直接加在最后,不用全体右移一位)存入数值的每一位,然后实现一个函数实现两个这个相加和比较,本质和 string 加法一样,就是方便一些,除了有这个溢出问题,这道题和 LC842(这道题告诉你最多 32 位来保证了用 long long 完全足够,超过 INT32_MAX 直接舍弃,不会累加超出 long long 的范围) 本质一样。| 430 | |LC401 Easy|这道题是简单题是因为数值范围很小,我们可以直接穷举,算出对应的小时和分钟位数相加如果等于则 ++ret 即可。常规思路是回溯,中等难度,难点在于我们不能把小时位数和分钟分数分开想,要合起来想,想成一共 10 位,前 4 位代表小时,然后回溯把这一共的 10 位进行 k 次置位成 1,然后对每个结果的位数分析得到对应字符串。| 431 | |LC526 Medium|、| 432 | |LC797 Medium|这道题因为告诉你是有向无环图,所以非常简单,直接回溯做出来即可。| 433 | |LC842 Medium|回溯参数是新一个数字的开始下标,然后循环取该层的数值 n = n * 10 + num\[start + i\],注意如果是 0 那么只有一种可能,如果不满足要求要提前退出。| 434 | |LC1219 Medium|多源 DFS,对每一个非 0 的点进行回溯暴力做即可,另外这道题这么高的复杂度竟然没有超时。| 435 | |剑指38 Medium|字符串的排列,注意 string 和 vector 很像,都有 push_back() 和 pop_back(),所以完全可以看成 vector 即可。典型的题,vis 表示字符串的某位置元素是否已选取(不可重复),然后对于重复的答案,我们使用 set 来存,最终再用 set 构造 vector 返回即可。可以剪枝提高效率,先要排序(答案顺序任意所以不影响),这样让重复的元素在一起,这是最关键的一步,然后同层的重复元素我们选取最左面一个即可,最左面的那个的它自己的 vis 直接是 1 然后向下递归回溯,而后面的重复元素分成两种情况,如果是前一种情况的子递归,那么 vis\[i - 1\] 一定是 1,而如果是同层的重复元素,会发现 vis\[i - 1\] 是 0,因为前一个元素已经回溯完成了,1 又变回了 0,此时无需再次选中这个重复的元素了,剪枝即可。另外这道题更好的思路是用 LC31 的下一个字典序排列。| 436 | 437 | ### **双指针** 438 | 439 | 167 345 633 680 88 524 \ 11 31 42 75 148 581 440 | 441 | 水题:LC283 Easy 442 | 443 | |题号|笔记| 444 | |:-|:-| 445 | |LC15 Medium|三数之和,a + b + c = 0 即 a + b = c,我们只需要循环取 c 作为左端最小值(可通过反证法证明不会漏组合),设置 a = i + 1,就可以典型做法排序 + 双指针得到 a b 答案,另外注意 c 取一个就得到对应 a b 的全部答案,因为排序过了,所以直接 num\[i\] == num\[i - 1\] continue 即可,然后内部同理如果和下一个元素相同则 continue,复杂度 O(n^2)。| 446 | |LC16 Medium|同 LC15 三数之和做法,注意优化,i 是作为三个数中最小值的,我们的左右指针指的是第二小和第三小的值,所以左指针取的是 i + 1,如果假设 i 是随便一个数不是最小,那么一个结果的三个值都可以作为 i,而如果只能是最小值,那么 i 只能是最左侧的那个,这样我们避免了重复计算。证明:假设现在 i 之前有未加入的结果,但我们推出当时取那个之前位置作为最小的时候已经计算过,如果是结果那时候就已经加入了,所以现在不可能有 i 之前未加入的结果,矛盾。所以反证法可得这种优化不会漏结果。| 447 | |LC18 Medium|四数之和,to do| 448 | |LC19 Medium|链表倒数第n个节点,先让一个指针指向正向第n个,然后另一个指针指向头结点,当第一个指针到末尾时,第二个指针就到了倒数第n个节点上。| 449 | |LC56 Medium|首先排序,使得可能合并的元素靠在一起,这样才能遍历合并区间。然后根据后一个的 \[0\] 和 前一个的 \[1\] 比较合并即可,合并成的放入结果即可。可以使用双指针,一个是正在合并的元素,这个元素随着合并更改边界,另一个是待判断合并的元素,如果不能合并,则 a = b 开始下一轮的合并区间和 push_back 上个合并得到的结果即可。| 450 | |LC109 Medium|使用快慢指针找链表中间节点,另外不一定需要nullptr表示链表结束,也可以再定义函数,让形参为左右两个指针表示左右边界来降低难度。| 451 | |LC141 Easy| 环形链表,判断是否有环,使用快慢指针可以保证空间复杂度为O(1),因为两者速度差一,使得当两个指针都进入环内的初始位置后,每一步都会使得两指针距离减一,推得如果有环那么肯定会相交。| 452 | |LC142 Medium|环形链表,要找到环开始的节点,快慢指针很麻烦,使用vector来存节点,然后比较是否存在相同节点很简单,两者时间复杂度都是O(n),不过这样空间复杂度是O(n);如果要使用快慢指针,推出a=c+(n−1)(b+c)后,让新的指针从头走,slow指针接着走,发现当新指针走了a步,slow指针走了c步和n-1圈,刚好相交,这就是起始点,所以我们直接找新指针和slow指针第一次相交即可,此时就是起始点(https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/huan-xing-lian-biao-ii-by-leetcode-solution/)。| 453 | 454 | ### **并查集** 455 | 456 | |题号|笔记| 457 | |:-|:-| 458 | |LC399 Medium|| 459 | 460 | ### **排序** 461 | 462 | #### 简单的其他排序 463 | 464 | 1. **冒泡排序**,复杂度O(n^2),是一种稳定的排序(不会改变相同元素的相对位置)。每一轮内部循环通过交换把一个最大的元素交换到最后,形成{未排序|排序},然后重复这个过程。 465 | 466 | ```C++ 467 | 468 | void bubble_sort(int arr[], int len) { 469 | int i, j, change=1; 470 | for (i = 0; i < len - 1 && change != 0; ++i) 471 | { 472 | change=0; 473 | for (j = 0; j < len - 1 - i; ++j) 474 | if (arr[j] > arr[j + 1]) 475 | { 476 | swap(arr[j], arr[j + 1]); 477 | change = 1; 478 | } 479 | } 480 | } 481 | 482 | // 第一处是注意第二重循环只需要比较仍然无需的序列,也就是从 0 到 len - 1 - i 483 | // 第二处是注意通过标志位,避免对排好序了的数组再无用的遍历下去 484 | 485 | ``` 486 | 487 | 2. **选择排序**,复杂度O(n^2),不稳定。{排序|未排序},每次内部循环未排序部分选择到一个最小的元素,然后通过交换把这个最小的元素与未排序的第一个元素交换,重复这个过程。 488 | 489 | ```c++ 490 | 491 | void selection_sort(vector& arr) { 492 | for (int i = 0; i < arr.size() - 1; i++) { 493 | int min = i; 494 | for (int j = i + 1; j < arr.size(); j++) 495 | if (arr[j] < arr[min]) 496 | min = j; 497 | swap(arr[i], arr[min]); 498 | } 499 | } 500 | 501 | ``` 502 | 503 | 3. **插入排序**,复杂度O(n^2),稳定。{排序|未排序},每次内部循环,把未排序的第一个元素暂存,然后不懂右移排序部分的元素,直到找到合适的位置,把这个未排序的第一个元素插入到这里,重复这个过程。 504 | 505 | ```c++ 506 | 507 | void insertion_sort(int arr[],int len) 508 | { 509 | for(int i = 1; i < len; ++i) 510 | { 511 | int key = arr[i]; 512 | int j = i - 1; 513 | while((j >= 0) && (key < arr[j])) 514 | { 515 | arr[j + 1] = arr[j]; 516 | --j; 517 | } 518 | arr[j + 1] = key; 519 | } 520 | } 521 | 522 | // 原理就是将未排序序列的第一个元素(arr[i]),插入到已排序序列中合适的位置(通过key和从后往前的arr[j]的比较) 523 | 524 | ``` 525 | 526 | 4. **希尔排序**,复杂度O(n*log(n)^2)或n^(3/2),不稳定。 527 | 528 | 5. 其他排序: 529 | - 计数排序,元素是n个0到k之间的整数时,它的时间复杂度是Θ(n + k),空间复杂度是O(k),显然当n很小k很大的时候,比如比较1和10000000,这个算法既浪费了几乎全部开辟的空间,又慢的令人发指(因为需要遍历开辟的数组来排序,比较无数个arr\[i\]是否为0的情况),在绝大多数情况下是个无用的算法。仅仅在k很小,且n较大,分布比较均匀才有用。 530 | - 基数排序整数,按位数切割成不同的数字,然后按每个位数分别放入桶中进行排序,循环每一位进行比较。 531 | 532 | #### 快速排序 533 | 534 | |题号|笔记| 535 | |:-|:-| 536 | |LC912 Medium|时间复杂度O(log n),这是因为内部递归不断分别处理左右两个子序列。貌似面试考的次数挺多的,一定要会写。| 537 | 538 | ```c++ 539 | class Solution 540 | { 541 | public: 542 | vector sortArray(vector &nums) 543 | { 544 | quickSort(nums, 0, nums.size() - 1); 545 | return nums; 546 | } 547 | 548 | private: 549 | void quickSort(vector &nums, int l, int r) 550 | { 551 | if (l < r) 552 | { 553 | int index = partition(nums, l, r); 554 | quickSort(nums, l, index - 1); 555 | quickSort(nums, index + 1, r); 556 | } 557 | } 558 | 559 | int partition(vector &nums, int l, int r) 560 | { 561 | int p = (rand() % (r - l + 1)) + l; 562 | swap(nums[l], nums[p]); 563 | int s = nums[l]; 564 | int start = l; 565 | while (l < r) 566 | { 567 | /* 这里一定要先写 --r,因为开头的基准可以作为哨兵,当之后所有元素都 568 | * 小于基准的时候,r 会变成 start 正常停止; 569 | * 而如果先写 ++l,当基准后所有元素都小于等于基准时,l 的值就会变成 570 | * 最后一个小于等于的元素下标,和其他情况下的 l 终止状态(第一个大于 571 | * 的下标)不一样,导致错误。 572 | */ 573 | while (l < r && nums[r] > s) 574 | --r; 575 | while (l < r && nums[l] <= s) 576 | ++l; 577 | if (l < r) 578 | swap(nums[l], nums[r]); 579 | } 580 | swap(nums[start], nums[r]); 581 | return l; 582 | } 583 | 584 | void swap(int &left, int &right) 585 | { 586 | int t = left; 587 | left = right; 588 | right = t; 589 | } 590 | }; 591 | ``` 592 | 593 | #### 堆排序 594 | 595 | 水题:LC703 Easy 596 | 597 | |题号|笔记| 598 | |:-|:-| 599 | |LC215 Medium|注意这种求第k大或者前k大的一般都可以用堆排序,利用小顶堆(升序),当有限队列内元素数量超过k之后,删除掉堆顶这个最小的,最后剩下的就是那k个频率最大的元素了。| 600 | |LC451 Medium|注意这种有频率排序的可以用堆排序,先是哈希表统计频率,然后就是利用优先队列排序pair。| 601 | 602 | #### 桶排序 603 | 604 | |题号|笔记| 605 | |:-|:-| 606 | to do桶排序|LC347 Medium|求数组中频率前k高的元素集合,方法一:桶排序,;方法二:堆排序,利用小顶堆(升序),当有限队列内元素数量超过k之后,删除掉堆顶这个最小的,最后剩下的就是那k个频率最大的元素了;方法三:哈希表按value排序,操作是map的所有pair元素放到一个集合,然后手写cmp比较pair类型,然后sort快排即可。| 607 | ||| 608 | 609 | #### 归并排序 610 | 611 | ```c++ 612 | void MergeSort (int arr [], int low,int high) { 613 | if(low>=high) 614 | return; 615 | int mid = low + (high - low) / 2; 616 | MergeSort(arr,low,mid); 617 | MergeSort(arr,mid+1,high); 618 | merge(arr,low,mid,high); 619 | // 合并两个有序序列,中间需要额外空间存排序后的结果,然后再拷贝回原数组 620 | } 621 | ``` 622 | 623 | |题号|笔记| 624 | |:-|:-| 625 | |LC23 Hard|把 k 个链表一开始 push 进优先队列,之后循环取最小并如有下一个把下一个也 push 进入。注意对于自定义类型我们要自己手写比较类,然后注意为了避免讨论头指针,我们创建了一个 dummy 无用结点,最后返回 dummy.next 即可。| 626 | |LC148 Medium|| 627 | |LC912 Medium|| 628 | |LC1305 Medium|弱智的中序+归并做出,为什么用归并呢,因为中序后我们得到了两个分别有序的向量,显然归并 O(m + n),像快排就无法用到已经有序这个属性,复杂度 O(nlogn)。| 629 | 630 | ### **动态规划** 631 | 632 | prob 3 10 22 32 53 121 139 338 647 633 | 634 | 递推公式可能难以想出,我们可以先写回溯递归算法,然后改成记忆化搜索(注意递推改成记忆化搜索需要让递推函数通过返回值返回结果才可),然后我们抓住状态(一定注意状态可以是三个,甚至更多,缺少就会导致实际上达到的状态会在不同的缺少的那个状态条件下有多个结果)作为下标把返回值答案写入,最后在记忆化搜索的代码中推出 DP 即可(也可以直接记忆化搜索完事)。 635 | 636 | 必须要明确推导的方向(比如一层/两层循环的方向问题,是要从左向右还是从右向左,是要从下往上还是从上往下),这非常重要,因为动态规划必须是从已有的答案来推导出新的答案,因此我们必须确保我们用来作为已知的 dp\[x\]\[y\] 是已经推导得到结果的,才能推导新的未知。 637 | 638 | #### 记忆化搜索 639 | 640 | prob 87 403 552 691 913 1728 641 | 642 | |题号|笔记| 643 | |:-|:-| 644 | |LC375 Medium|每一层遍历猜的数,然后这个数加上另外两个范围的数的递归答案的最大值,遍历这一层猜的所有数的结果,得到最小值返回即可。为了优化我们记忆答案,注意 unordered_map 默认不能用 pair,因为没有关于 pair 的哈希要自己实现,另外直接用二维数组存答案也非常好,因为这道题的范围很小。| 645 | |LC464 Medium|一道博弈论的题,函数意思是判断输赢,这一步赢 (curr + i >= target) 或者对方输了 (!func(对方的调用)),返回 true,否则返回 false,另外再来个 vis 记录使用过的数。| 646 | |LC576 Medium|发现这个移动可以重复路径,所以不用 vis 也不是一个回溯,单纯的穷举即可,但单纯穷举会超时,这就要引入记忆化搜索,写的递归函数要返回结果,另外非常重要的是,我们的状态是三个,还要考虑剩余可走的步数,所以我们写了个三维数组。另外要注意加法求模的公式 (a+b)%c =(a%c)+(b%c))%c 防止溢出。| 647 | 648 | #### 简单一维DP问题 649 | 650 | 水题:LC509 Easy、LC1137 Easy 651 | 652 | |题号|笔记| 653 | |:-|:-| 654 | |LC53 Easy|注意这个题干要求是连续的,意思是 f(i) 必须代表着以 nums\[i\] 结尾的最好结果,这样后一个和前一个元素才能连在一起符合题意连续的序列,不能间断。f(i) = max(f(i - 1) + nums\[i\], nums\[i\]),一个是继续连续下去以 nums\[i\] 结尾的最好结果,一个是作为新的连续序列的开头。| 655 | |LC70 Easy|f(i) = f(i-2) + f(i-1) 最简单的DP题,注意因为之前的数值不再被需要,所以可以用两个变量代替整个dp数组,只记录最新的两个楼梯爬法的数值,这样优化空间复杂度为O(1),这就是滚动数组优化空间复杂度的思想,再注意一般一维dp数组都能通过这个滚动数组思想来优化空间。| 656 | |LC120 Medium|、| 657 | |LC198 Medium|f(i) = max(f(i-2) + nums\[i\], f(i - 1)) 找出递推关系有点难,另外注意dp\[i\]定义成偷前面i个屋子后,此时最多偷的金额,这个i代表前面i个屋,与第i个屋子本身偷不偷无关,是前面i个范围的最优解。| 658 | 659 | #### 简单二维DP问题 660 | 661 | |题号|笔记| 662 | |:-|:-| 663 | |LC62 Medium|f(i,j) = f(i-1,j) + f(i,j-1),发现初始值 f(0,j) = 1 和 f(i,0) = 1,分补对应一直向右和一直向下,只有一种方式,然后循环求值即可。| 664 | |LC64 Medium|f(i,j) = min(f(i-1,j), f(i,j-1)) + grip\[i\]\[j\],二维dp数组初始值最好设置\[i\]\[0\]和\[0\]\[j\],不要只给个\[0\]\[0\],这样两层for循环从i=1和j=1开始,完全避开了判断是否数组越界的情况,减少的代码量。| 665 | |LC375 Medium|递推式有点复杂 f(i, j)= min(i ≤ x ≤ j) {x + max(f(i, x-1) ,f(x+1, j))},涉及到每一层对猜想值 x 的遍历。另外要特别注意遍历的方向,因为动态规划必须是从已有的答案来推导出新的答案,因此我们必须确保我们用来推导未知的已知条件f(a, b)是已经得到的,看这里的 i, x-1 是同行左面,x+1, j 是下面同列,所以方向必须是从左下向右上推导的,即行从下到上,列从左到右才可以。| 666 | 667 | #### 数组区间问题 668 | 669 | |题号|笔记| 670 | |:-|:-| 671 | |LC413 Medium|| 672 | 673 | #### 字符串问题 674 | 675 | |题号|笔记| 676 | |:-|:-| 677 | |LC5 Medium|回文串的性质很容易发现 f(i,i) = true 和 f(i,j) = f(i+1,j-1),比较难想的是我们要用二维 DP,i 表示子串开头,j 表示字串结尾。另外要注意写循环的顺序,因为是 i+1 和 j-1,所以是从左下方位得到,所以我们循环要先得到左边的列,再得到右面的列,所以我们内部循环是 i,外侧可以循环子串的长度。| 678 | |LC72 Hard|这道题很难直接想出来状态转移方程,那么我们不如直接暴力回溯做出来一个超时的版本,然后选取合适的(这道题是两个字符串各自当前的长度)状态写一个数组,记录下答案,如果在表中已经计算过就直接取出答案,以此避免重叠子问题的计算。最后根据递归的规律可以再自底向上通过动态规划的方式(DP表)写出来,或者干脆直接用刚才记忆化搜索的方式提交均可。总而言之,要是能直接想出来方程就直接动态规划,不行就先暴力回溯然后找好状态加答案记录表写记忆化搜索。另外再注意一下有时候从右往左比从左往右写起来方便,不过这两种复杂度都一样,写哪个方向都可。| 679 | |LC91 Medium|字符串拆分问题,分类讨论,那就是当第i个字符加入的时候,既可能作为单字符又可能作为双字符解析,判断条件满足一个累加一个即可。另外注意字符串中的递归式 f(i) 的 i 是第几个字符,因为 0 要作为无字符情况是边界条件,这个边界值要手动赋值,这里初始是 f(0) 为 1,因为无字符也是一种解析,而不是零,所以 f(1) 对应的是 str\[0\] 第一个元素,这个写的时候要注意,字符串下标要减一。| 680 | |LC816 Medium|暴力枚举,循环拆字符串成两部分,先枚举 ',' 左右的字符串,再内部分别枚举左右字符串无小数点和有小数点(同样是循环拆开字符串)的情况,注意处理前导 0,小数后面 0 如 0.10 等恶心的格式问题。| 681 | 682 | #### 最长递增子序列 683 | 684 | |题号|笔记| 685 | |:-|:-| 686 | |LC300 Medium|| 687 | |LC376 Medium|| 688 | |LC646 Medium|| 689 | |LC673 Medium|| 690 | 691 | #### 最长公共子序列 692 | 693 | |题号|笔记| 694 | |:-|:-| 695 | |LC1143 Medium|、| 696 | 697 | #### 数值拆分问题 698 | 699 | |题号|笔记| 700 | |:-|:-| 701 | |LC279 Medium|| 702 | |LC343 Medium|| 703 | |LC279 Medium|f(i) 表示凑成整数 i 所需的完全平方数的最少数量,f(i) = min{ f(i - e) + 1 | e∈nums },比 322 题就是多了一个求有几种完全平方数的步骤。| 704 | |LC322 Medium|零钱兑换,也是凑数问题,问用其他价格的硬币为了凑出目标值,最小需要多少枚。这道题看起来用暴力搜索做很容易,但是这样会超时,所以还是要用DP的思路。f(i) 定义成凑成 i 块钱所需要的最小硬币数,f(i) = min(全部币值情况的f(i - coin)) + 1。| 705 | 706 | #### 背包问题 707 | 708 | |题号|笔记| 709 | |:-|:-| 710 | ||| 711 | 712 | #### 树形DP 713 | 714 | |题号|笔记| 715 | |:-|:-| 716 | |LC310 Medium|to do| 717 | 718 | ### **贪心思想** 719 | 720 | 贪心要需要证明该问题在局部情况不断取最优解,到了最后一定得到全局最优解,这样我们过程中只要不断取最优解即可(比如常见的局部最优解如给满足中的最差的,不断取左右两端最值,最大最小值等情况)。 721 | 722 | 水题:LC860 Easy、LC976 Easy 723 | 724 | |题号|笔记| 725 | |:-|:-| 726 | |LC53 Easy|最大连续子数组和,利用 sum 记录总和,如果是正那么要保留,因为之后有了这个会更大,如果是负那么直接舍弃然后重新计数,因为后面不可能需要这个。| 727 | |LC455 Easy|要明白为了尽可能给更多人饼干,要给每个人尽量小的,另外要注意排序胃口,这样小胃口满足不了后面大胃口更满足不了,直接终止循环。| 728 | |LC561 Easy|贪心思想,为了避免短板发现两个数尽量接近的情况下结果最大。| 729 | |LC605 Easy|贪心思想,能种花就种花即可,另外注意边界情况,可以在左右添加 0 来避免分类讨论。| 730 | |LC942 Easy|遇到 < 左侧放最小值,遇到 > 左侧放最大值即可,因为这样自动保证右侧无论放哪个剩余值都满足。| 731 | 732 | ### **二分思想** 733 | 734 | prob 153 34 735 | 736 | 注意二分查找的前提是向量已排好序,所以如果题目告诉你已排好序(或者你需要手动排序),然后查找某一个元素或者重复元素的最左、最右侧的元素,可以使用二分查找。 737 | 738 | 二分思想在于通过比较 nums\[mid\] 得到要查询的子范围(根据不同情况,改变左右边界,另外要注意子范围包不包含 mid 下标),从而再去查这个子范围,随着范围越来越小,最后会收敛在一个下标,即为答案。 739 | 740 | 水题:LC278 Easy、LC374 Easy、LC441 Easy 741 | 742 | |题号|笔记| 743 | |:-|:-| 744 | |LC69 Easy|平方根不要再用循环一个一个试了,这个复杂度是O(根号X);而使用二分则是O(logX),快非常多。| 745 | |LC74 Medium|| 746 | |LC378 Medium|| 747 | |LC540 Medium|二分查找,利用奇偶性发现数值对应的规律。为了避免溢出 mid 要写成这种形式 (high - low) / 2 + low。| 748 | |LC744 Easy|利用二分查找,由于是查找第一个比目标大的,所以小于等于目标都不符合范围要 l = mid + 1,而大于的可能是答案也可能在右侧,所以 r = mid,收敛到 l == r 时注意可能是没有答案要分类讨论,有答案就是 nums\[l\]。| 749 | 750 | ### **分而治之** 751 | 752 | 把一个复杂的问题分成两个或更多的相同或相似的子问题,再递归地把子问题分成更小的子问题,直到最后子问题可以简单的直接求解,最后原问题的解可以由子问题的解合并得到。 753 | 754 | 水题: 755 | 756 | |题号|笔记| 757 | |:-|:-| 758 | |LC95 Medium|| 759 | |LC241 Medium|| 760 | 761 | ### **数学问题** 762 | 763 | #### 简单的数学问题 764 | 765 | 水题:LC13 Easy、LC263 Easy 766 | 767 | #### 数值转换 768 | 769 | |题号|笔记| 770 | |:-|:-| 771 | |LC7 Medium|因为不让用超过32位,通过数学推导,我们可以推出 ret > INT32_MAX / 10 || ret < INT32_MIN / 10 的下一步会溢出[推导原理链接](https://leetcode-cn.com/problems/reverse-integer/solution/zheng-shu-fan-zhuan-by-leetcode-solution-bccn/)| 772 | |LC8 Medium|看起来简单,但是要处理前导 0,正负值,数值越界,非法字符(比较是否 ch >= '0' && ch <= '9'即可>)等情况。| 773 | |LC405 Easy|int变量里面本身就是补码,直接位运算即可,从高到低每四位转换一个十六进制数,另外注意从高位到低位的顺序配合上字符串是否为空可以方便去除前导0,再注意下(char) ('a' + val - 10)映射。| 774 | |LC504 Easy|采用模拟的思路,递归取余,不过首先注意负数的情况,再是注意0的情况,最后注意递归取的余数是从高位到低位的顺序。| 775 | 776 | #### 阶乘 777 | 778 | |题号|笔记| 779 | |:-|:-| 780 | |LC172 Medium|计算尾随零的数量,10的两个最基本的因子是2和5,我们只要分别计算能被2和5整除的次数,然后取min(num2,num5)即可得到可以凑出的10的个数,也就是尾随零的个数。| 781 | 782 | #### 相遇问题 783 | 784 | |题号|笔记| 785 | |:-|:-| 786 | |LC462 Medium|| 787 | 788 | ### **LRU** 789 | 790 | |题号|笔记| 791 | |:-|:-| 792 | |LC146 Medium|LRU(Least Recently Used)最近最少使用,关键是按上一次的访问时间排序,然后逐出的是最久未访问的数据。使用的是一个叫哈希链表的数据结构,即哈希表和双向链表,完成get和put都是O(1)的复杂度。注意unordered_map才是基于哈希的,map是基于红黑树的,查询复杂度是O(log n)。| 793 | |LC460 Hard|LFU()| 794 | 795 | ### **滑动窗口** 796 | 797 | 常用于处理连续字串、连续数组的问题,分为固定窗口大小和可变窗口大小两类。其中固定窗口的题很多要么题干告诉你,要么两个字符串之类的其中一个小字符串的长度。 798 | 799 | 如果题目中发现有说连续的子串、子数组,只要有连续二字,一定要思考是不是滑动窗口的题型,然后一般题型是求最值的问题,然后条件一些很典型的比如告诉你可以 k 次更改/反转/忽略某一个元素,这种题你就需要一个变量,用于记录当前窗口内已经调整元素的次数,然后内层循环作为条件与 k 比较。 800 | 801 | 对于可变窗口,发现了一个模型,问题一般是求最值,开始初始化 l 和 r 为 0,一个 sz 和一个记录条件的变量,外层循环不断增大 r 作为新加入窗口的元素,然后如果条件不满足那么内层循环不断增大 l 使得条件再次满足,之后当内层循环结束即条件满足的情况,更新最值即可,下面代码是随便举的一个例子(LC1208)。 802 | 803 | ```C++ 804 | class Solution { 805 | public: 806 | int equalSubstring(string s, string t, int maxCost) { 807 | int l = 0, r = 0; 808 | int sz = s.size(); 809 | int ret = INT32_MIN; 810 | int cost = 0; 811 | 812 | while(r < sz){ 813 | cost += abs(t[r] - s[r]); 814 | while(cost > maxCost){ 815 | cost -= abs(t[l] - s[l]); 816 | ++l; 817 | } 818 | ret = max(ret, r - l + 1); 819 | ++r; 820 | } 821 | 822 | return ret; 823 | } 824 | }; 825 | ``` 826 | 827 | 注意另一种题型问的是符合条件的个数而不是求最值,(举例LC1248,见题解),那么我们需要 to do 828 | 829 | 另外一种常见的题型是,让你不断选取/扔掉向量的最左或最右元素 k 次,求选取的/剩余的部分的最值,这个很容易地发现这是个固定窗口问题,size - k 就是连续子数组的固定窗口大小,然后求最值即可。另外注意选取部分的最值可以通过总和减去滑动窗口的最值来得到,需要先遍历求出元素总和多转换一步而已(LC1423,见题解)。 830 | 831 | prob 76 424 480 930 932 978 992 1610 1838 832 | 833 | 水题:LC187 Medium、LC487 Medium、LC1208 Medium 834 | 835 | |题号|笔记| 836 | |:-|:-| 837 | |LC3 Medium|通过 unordered_map 记录窗口内的元素,然后如果没有重复的话就右移 r,不断找最大值,直到重复,此时不断右移 l 直到不再重复。本质上来说遇到重复元素时肯定之前已经经过上一个最长连续不重复子串并记录下最大值了,所以正确性没问题。| 838 | |LC209 Medium|可变窗口的典型题,记录 l 到 r 范围内的总和 sum,不满足时 r 一直右移,这个写成外层 for 循环(或者 while 更容易理解),直到满足时为了尽可能小,内部再循环把 l 尽可能左移,找到最小值。| 839 | |LC220 Medium|to do 最简单的暴力遍历复杂度 O(nk) 会超时。| 840 | |LC239 Hard|容易想的方法是循环+堆排序,复杂度 O(n logn),注意堆里面用 pair 存下标,堆里面存着超出范围的元素没有问题,只要我们取出的时候循环判断一下是不是再窗口内即可,不在的话舍弃并再取,直到取出一个在范围内的,那就是这个窗口内的最大值。另一种方法是单调队列的解法,见[此链接](#单调队列)。难忘的一题,智臾一面出的题,然而我面完第二天就刷到了这个题,要是早一天就好了。。。| 841 | |LC396 Medium|| 842 | |LC438 Medium|这道题简单,因为窗口的长度是固定的,是 p 的长度,我们只需要不断右移整个窗口,并对比这26个字母的个数是否相同即可。可以使用 unordered_map 来比较(直接用 == 运算符即可,比较 key、key 和其对应的 value 是否全部相同,和顺序无关)),但一定要注意,map\[index\] 值为0和不存在这个 key 的元素是不一样的;也可以用vector来比较,下标对应字母即可。| 843 | |LC567 Medium|对于判断单词是否异构(即每个字符的个数相同)这种形式,我们可以直接用 unordered_map 来实现,但是注意没插入过字母和加入字母又删除字母导致值为 0 是两种不同的概念,这里需要注意当为 0 要 erase 对应的键值对。当然更简便和推荐的方式是直接使用 vector m(26),这样就不用区分 0 还是不存在的情况了。| 844 | |LC643 Easy|水题,给了 k,是固定窗口,一开始先计算前k个元素来初始化,然后从下标 k 开始循环 sum = sum - nums\[i - k\] + nums\[i\] 即可。| 845 | |LC904 Medium|典型的可变窗口题型,记录 l 和 r,外侧循环 r 不断右移,并不断比较最大值,直到不满足内部循环 l 不断右移扔掉最左侧的值,来让窗口符合要求。| 846 | |LC1004 Medium|可变窗口,另外需要记录窗口内 0 的个数,如果小于等于 k,那么不断外层循环右移 r 即可,如果大于 k,那么内层循环要不断右移 l,直到满足条件。| 847 | |LC1052 Medium|发现最终结果应该是不生气时的顾客总数,加上原本生气现在忍住的顾客数,题干告诉强忍时间是必须连续的,所以这道题是个固定滑动窗口大小的题,伴随忍住不生气的窗口移动即可。| 848 | |LC1234 Medium|to do| 849 | |LC1248 Medium|to do 注意这道题和别的滑动窗口题不一样,它问的是符合条件的个数,而不是求最值之类的问题,导致我们无法简单的套用模板做出。| 850 | |LC1423 Medium|不断选取向量的最左或最右元素 k 次,求选取的部分的最值,这个很容易地发现这是个固定窗口问题,size - k 就是连固定窗口大小,然后可以得到滑动窗口的最值。之后选取部分的最值通过总和减去滑动窗口的最值来得到,需要先遍历求出元素总和多转换一步而已。| 851 | |LC1438 Medium|这道题框架仍然是最典型的可变窗口,但难点在于如何快速得到窗口内最大值与最小值的差值,如果能得到这个差值,那么这道题就是一个送分题。我一开始写的是用两个堆,一个最大堆一个最小堆,里面元素是 pair 第二个 int 记录下标,然后我们明白就算是超出窗口的元素放到堆里面也是无妨的,只要我们取最大/最小时额外判断下 second >= l ,如果小于则 pop 即可,保证我们得到的确实是窗口内的最大和最小值,然后利用这两个值做差得到,但这个写法无疑有些麻烦。我们需要元素有序且可以重复,这样我们就可以用最后一个元素减去第一个元素即可得到差值,然后当要右移 l 时我们还需要能从容器中删除那个特定的元素,vector 可以但是太过麻烦,我们可以用 multiset 这个有序可重复集合,通过 begin 和 rbegin 获取第一个元素和最后一个元素,erase 删除时需要迭代器,那么我们就 find(value) 返回迭代器来让 erase 删除即可。这道题也可以用单调队列做,见[此链接](#单调队列)。| 852 | |LC1658 Medium|和 LC1423 同类型题目。一开始以为是个很简单写的回溯,但发现复杂度太高。发现扔掉最左或最右端的元素,最后得到的中间子序列一定是连续的,然后又发现可以先遍历求和,转化成判断窗口内和的值,这个题就变成了典型的求连续子数组的最大和,最后滑动窗口复杂度 O(n)。| 853 | |LC2024 Medium|典型的可变窗口题,但比较不同的是这道题我们无法判断最佳情况是 'T' 还是 'F',因此我们分类讨论,把滑动窗口的代码提取出来作为一个辅助函数,主函数分别调用这个辅助函数计算 'T' 和 'F' 的情况,然后取最大值即可。| 854 | 855 | ### **输入输出格式** 856 | 857 | 1. 目前用到的笔试是牛客网的在线笔试,https://www.nowcoder.com/test/27976983/summary#question 这个网站可以进行输入输出格式练习。 858 | 2. 获取一行用getline(输入流,字符串引用)(默认分割符为'\n'),然后通过stringstream字符流来输出分隔空格,如果要分隔的是别的符号比如',',那么就要再次getline(stringstream流,字符串引用,分割符)来分隔,因为c++标准库里面没有split,所以要通过这种方式来实现。 859 | 3. 注意审题,是多组数据还是一组数据,有时候题干是多组数据但示例给的只有一组,让人误解。 860 | 4. 注意审题数据范围,INT32_MAX 是 2^31-1 在 10^9 ~ 10^10 范围,别忘了改用 long,如果特意说是32位机器,long 和 int同大小要用 long long。 861 | 5. 另外是输出末尾不让有空格之类的问题,一般我们循环外输出第一个元素(别忘了额外判断是否为空),之后循环中输出空格加剩下元素即可。 862 | 6. 注意使用英文字符。 863 | 7. cin.ignore(n,终止字符) 函数作用是跳过输入流中n个字符,或在遇到指定的终止字符时提前结束(含终止字符)。cin 和 getline 混用,要注意 cin 按类型输入完会留下'\n',所以我们调用一个 cin.ignore() 即可,默认参数是 n=1 忽略一个 '\n' 即可。 864 | -------------------------------------------------------------------------------- /leetcode/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "leetcode" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /leetcode/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "leetcode" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /leetcode/src/array/todo108.rs: -------------------------------------------------------------------------------- 1 | // Definition for a binary tree node. 2 | #[derive(Debug, PartialEq, Eq)] 3 | pub struct TreeNode { 4 | pub val: i32, 5 | pub left: Option>>, 6 | pub right: Option>>, 7 | } 8 | 9 | impl TreeNode { 10 | #[inline] 11 | pub fn new(val: i32) -> Self { 12 | TreeNode { 13 | val, 14 | left: None, 15 | right: None, 16 | } 17 | } 18 | } 19 | struct Solution; 20 | 21 | use std::cell::RefCell; 22 | use std::rc::Rc; 23 | 24 | #[allow(dead_code)] 25 | impl Solution { 26 | pub fn sorted_array_to_bst(nums: Vec) -> Option>> { 27 | if nums.is_empty() { 28 | return None; 29 | } 30 | Solution::sorted_array_to_bst_helper(&nums) 31 | } 32 | 33 | fn sorted_array_to_bst_helper(nums: &[i32]) -> Option>> { 34 | let len = nums.len(); 35 | let mid = len / 2; 36 | let node_rc = Rc::new(RefCell::new(TreeNode::new(nums[mid]))); 37 | { 38 | let mut node = node_rc.borrow_mut(); 39 | if mid > 0 { 40 | node.left = Solution::sorted_array_to_bst_helper(&nums[0..mid]); 41 | } 42 | if mid + 1 < len { 43 | node.right = Solution::sorted_array_to_bst_helper(&nums[mid + 1..]); 44 | } 45 | } 46 | Some(node_rc) 47 | } 48 | } 49 | 50 | fn main() {} 51 | -------------------------------------------------------------------------------- /leetcode/src/array/todo118.rs: -------------------------------------------------------------------------------- 1 | struct Solution; 2 | fn main() {} 3 | 4 | #[allow(dead_code)] 5 | impl Solution { 6 | pub fn generate(num_rows: i32) -> Vec> { 7 | let mut res = vec![vec![1]]; 8 | for i in 1..num_rows as usize { 9 | let mut vec = vec![1]; 10 | for j in 1..=i as usize { 11 | let x1 = res.get(i - 1).unwrap().get(j - 1).unwrap(); 12 | if let Some(x2) = res.get(i - 1).unwrap().get(j) { 13 | vec.push(*x1 + *x2); 14 | } else { 15 | vec.push(*x1); 16 | } 17 | } 18 | res.push(vec); 19 | } 20 | res 21 | } 22 | } 23 | 24 | // 输入: numRows = 5 25 | // 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 26 | 27 | #[test] 28 | fn test1() { 29 | let v1 = Solution::generate(1); 30 | let v2 = Solution::generate(2); 31 | let v3 = Solution::generate(5); 32 | 33 | assert_eq!(v1, vec![vec![1]]); 34 | assert_eq!(v2, vec![vec![1], vec![1, 1]]); 35 | assert_eq!( 36 | v3, 37 | vec![ 38 | vec![1], 39 | vec![1, 1], 40 | vec![1, 2, 1], 41 | vec![1, 3, 3, 1], 42 | vec![1, 4, 6, 4, 1] 43 | ] 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /leetcode/src/array/todo1984.rs: -------------------------------------------------------------------------------- 1 | struct Solution; 2 | fn main() {} 3 | 4 | #[allow(dead_code)] 5 | impl Solution { 6 | pub fn minimum_difference(nums: Vec, k: i32) -> i32 { 7 | let mut nums = nums; 8 | nums.sort(); 9 | 10 | let mut min_diff = i32::MAX; 11 | let r = nums.len() - k as usize; 12 | for i in 0..=r { 13 | let diff = nums.get(i + k as usize - 1).unwrap() - nums.get(i).unwrap(); 14 | if diff < min_diff { 15 | min_diff = diff; 16 | } 17 | } 18 | min_diff 19 | } 20 | } 21 | 22 | #[test] 23 | fn test1() { 24 | assert_eq!(Solution::minimum_difference(vec![9, 4, 1, 7], 2), 2); 25 | } 26 | -------------------------------------------------------------------------------- /leetcode/src/array/todo287.rs: -------------------------------------------------------------------------------- 1 | struct Solution; 2 | fn main() {} 3 | 4 | #[allow(dead_code)] 5 | impl Solution { 6 | pub fn find_duplicate(nums: Vec) -> i32 { 7 | const PREFIX: i32 = 1000000; 8 | let mut nums = nums; 9 | let len = nums.len(); 10 | for i in 0..len { 11 | let num = nums.get(i).unwrap(); 12 | let idx = if *num > PREFIX { *num - PREFIX } else { *num } as usize; 13 | let value = nums.get_mut(idx).unwrap(); 14 | if *value > PREFIX { 15 | return idx as i32; 16 | } else { 17 | *value += PREFIX; 18 | } 19 | } 20 | -1 21 | } 22 | } 23 | 24 | #[test] 25 | fn test1() {} 26 | -------------------------------------------------------------------------------- /leetcode/src/array/todo303.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | 3 | #[allow(dead_code)] 4 | struct NumArray { 5 | sum_nums: Vec, 6 | } 7 | 8 | #[allow(dead_code)] 9 | impl NumArray { 10 | fn new(nums: Vec) -> Self { 11 | let mut sum_nums = Vec::with_capacity(nums.len()); 12 | let mut sum = 0; 13 | for num in nums { 14 | sum += num; 15 | sum_nums.push(sum); 16 | } 17 | NumArray { sum_nums } 18 | } 19 | 20 | fn sum_range(&self, left: i32, right: i32) -> i32 { 21 | if left >= 1 { 22 | *self.sum_nums.get(right as usize).unwrap() 23 | - *self.sum_nums.get(left as usize - 1).unwrap() 24 | } else { 25 | *self.sum_nums.get(right as usize).unwrap() 26 | } 27 | } 28 | } 29 | 30 | #[test] 31 | fn test1() { 32 | let obj = NumArray::new(vec![-4, -5]); 33 | assert_eq!(obj.sum_range(1, 1), -5); 34 | } 35 | -------------------------------------------------------------------------------- /leetcode/src/array/todo442.rs: -------------------------------------------------------------------------------- 1 | struct Solution {} 2 | fn main() {} 3 | 4 | #[allow(dead_code)] 5 | impl Solution { 6 | pub fn find_duplicates(nums: Vec) -> Vec { 7 | const PREFIX: i32 = 1000000; 8 | let mut nums = nums; 9 | let mut res = Vec::new(); 10 | 11 | let len = nums.len(); 12 | for i in 0..len { 13 | let num = nums.get(i).unwrap(); 14 | let idx = if *num > PREFIX { *num % PREFIX } else { *num } as usize - 1; 15 | 16 | let mut_val = nums.get_mut(idx).unwrap(); 17 | if *mut_val > PREFIX { 18 | res.push(idx as i32 + 1); 19 | } else { 20 | *mut_val += PREFIX; 21 | } 22 | } 23 | res 24 | } 25 | } 26 | 27 | #[test] 28 | fn test1() { 29 | assert_eq!( 30 | Solution::find_duplicates(vec![4, 3, 2, 7, 8, 2, 3, 1]), 31 | vec![2, 3] 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /leetcode/src/array/todo448.rs: -------------------------------------------------------------------------------- 1 | use std::vec; 2 | 3 | struct Solution; 4 | fn main() {} 5 | 6 | #[allow(dead_code)] 7 | impl Solution { 8 | pub fn find_disappeared_numbers(nums: Vec) -> Vec { 9 | const FLAG_NUM: i32 = 100000; 10 | let mut nums = nums; 11 | let size = nums.len(); 12 | 13 | for i in 0..size { 14 | let e = *nums.get(i).unwrap(); 15 | let index = if e >= FLAG_NUM { 16 | e - FLAG_NUM - 1 17 | } else { 18 | e - 1 19 | }; 20 | 21 | let target = nums.get_mut(index as usize).unwrap(); 22 | if *target < FLAG_NUM { 23 | *target += FLAG_NUM; 24 | } 25 | } 26 | 27 | let mut result = vec![]; 28 | for (i, &e) in nums.iter().enumerate() { 29 | if e < FLAG_NUM { 30 | result.push(i as i32 + 1); 31 | } 32 | } 33 | result 34 | } 35 | } 36 | 37 | #[test] 38 | fn test1() { 39 | assert_eq!( 40 | Solution::find_disappeared_numbers(vec![4, 3, 2, 7, 8, 2, 3, 1]), 41 | vec![5, 6] 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /leetcode/src/array/todo485.rs: -------------------------------------------------------------------------------- 1 | struct Solution; 2 | fn main() {} 3 | 4 | #[allow(dead_code)] 5 | impl Solution { 6 | pub fn find_max_consecutive_ones(nums: Vec) -> i32 { 7 | let mut max = 0; 8 | let mut count = 0; 9 | for e in nums { 10 | count = if e != 1 { 0 } else { count + 1 }; 11 | if count > max { 12 | max = count; 13 | }; 14 | } 15 | max 16 | } 17 | } 18 | 19 | #[test] 20 | fn test1() { 21 | assert_eq!( 22 | Solution::find_max_consecutive_ones(vec![1, 1, 0, 1, 1, 1]), 23 | 3 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /leetcode/src/array/todo566.rs: -------------------------------------------------------------------------------- 1 | struct Solution; 2 | fn main() {} 3 | 4 | #[allow(dead_code)] 5 | impl Solution { 6 | pub fn matrix_reshape(mat: Vec>, r: i32, c: i32) -> Vec> { 7 | let num_rows = mat.len(); 8 | let num_cols = mat.get(0).unwrap().len(); 9 | if num_rows * num_cols != (r * c) as usize { 10 | return mat; 11 | } 12 | 13 | let mut vec = Vec::with_capacity(r as usize); 14 | for i in 0..r { 15 | let mut row = Vec::with_capacity(c as usize); 16 | for j in 0..c { 17 | let nth = i * c + j; 18 | row.push( 19 | *mat.get(nth as usize / num_cols) 20 | .unwrap() 21 | .get(nth as usize % num_cols) 22 | .unwrap(), 23 | ); 24 | } 25 | vec.push(row); 26 | } 27 | vec 28 | } 29 | } 30 | 31 | #[test] 32 | fn test1() { 33 | assert_eq!( 34 | Solution::matrix_reshape(vec![vec![1, 2], vec![3, 4]], 1, 4), 35 | vec![vec![1, 2, 3, 4]] 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /leetcode/src/array/todo654.rs: -------------------------------------------------------------------------------- 1 | struct Solution; 2 | fn main() {} 3 | 4 | // Definition for a binary tree node. 5 | #[derive(Debug, PartialEq, Eq)] 6 | pub struct TreeNode { 7 | pub val: i32, 8 | pub left: Option>>, 9 | pub right: Option>>, 10 | } 11 | 12 | impl TreeNode { 13 | #[inline] 14 | pub fn new(val: i32) -> Self { 15 | TreeNode { 16 | val, 17 | left: None, 18 | right: None, 19 | } 20 | } 21 | } 22 | 23 | use std::cell::RefCell; 24 | use std::rc::Rc; 25 | 26 | #[allow(dead_code)] 27 | impl Solution { 28 | pub fn construct_maximum_binary_tree(nums: Vec) -> Option>> { 29 | Solution::construct_maximum_binary_tree_helper(&nums) 30 | } 31 | 32 | fn construct_maximum_binary_tree_helper(nums: &[i32]) -> Option>> { 33 | let max_tuple = nums.iter().enumerate().max_by_key(|&(_, item)| item); 34 | match max_tuple { 35 | Some((idx, &val)) => { 36 | let mut node = TreeNode::new(val); 37 | node.left = Solution::construct_maximum_binary_tree_helper(&nums[..idx]); 38 | node.right = Solution::construct_maximum_binary_tree_helper(&nums[idx + 1..]); 39 | Some(Rc::new(RefCell::new(node))) 40 | } 41 | None => None, 42 | } 43 | } 44 | } 45 | 46 | #[test] 47 | fn test1() { 48 | let tree = Solution::construct_maximum_binary_tree(vec![3, 2, 1, 6, 0, 5]); 49 | println!("{:?}", tree); 50 | } 51 | -------------------------------------------------------------------------------- /leetcode/src/array/todo697.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | struct Solution; 4 | fn main() {} 5 | 6 | #[allow(dead_code)] 7 | impl Solution { 8 | pub fn find_shortest_sub_array(nums: Vec) -> i32 { 9 | let mut l_map = HashMap::new(); 10 | let mut r_map = HashMap::new(); 11 | let mut fre_map = HashMap::new(); 12 | for (idx, num) in nums.iter().enumerate() { 13 | l_map.entry(num).or_insert(idx); 14 | r_map.insert(num, idx); 15 | let count = fre_map.entry(num).or_insert(0); 16 | *count += 1; 17 | } 18 | 19 | let mut max_fre = 0; 20 | let mut min_degree = usize::MAX; 21 | for (&num, fre) in fre_map.iter() { 22 | let degree = r_map.get(num).unwrap() - l_map.get(num).unwrap() + 1; 23 | if *fre > max_fre { 24 | max_fre = *fre; 25 | min_degree = degree; 26 | } else if *fre == max_fre && degree < min_degree { 27 | min_degree = degree; 28 | } 29 | } 30 | min_degree as i32 31 | } 32 | } 33 | 34 | #[test] 35 | fn test1() { 36 | assert_eq!(Solution::find_shortest_sub_array(vec![1, 2, 2, 3, 1]), 2); 37 | assert_eq!( 38 | Solution::find_shortest_sub_array(vec![1, 2, 2, 3, 1, 4, 2]), 39 | 6 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /leetcode/src/array/todo766.rs: -------------------------------------------------------------------------------- 1 | struct Solution; 2 | fn main() {} 3 | 4 | #[allow(dead_code)] 5 | impl Solution { 6 | pub fn is_toeplitz_matrix(matrix: Vec>) -> bool { 7 | let num_row = matrix.len(); 8 | let num_col = matrix.first().unwrap().len(); 9 | for i in 0..num_row - 1 { 10 | let row = matrix.get(i).unwrap(); 11 | let next_row = matrix.get(i + 1).unwrap(); 12 | if row[..num_col - 1] != next_row[1..] { 13 | return false; 14 | } 15 | } 16 | true 17 | } 18 | } 19 | 20 | #[test] 21 | fn test1() { 22 | assert!(Solution::is_toeplitz_matrix(vec![ 23 | vec![1, 2, 3, 4], 24 | vec![5, 1, 2, 3], 25 | vec![9, 5, 1, 2] 26 | ])); 27 | 28 | assert!(!Solution::is_toeplitz_matrix(vec![vec![1, 2], vec![2, 2]])); 29 | } 30 | -------------------------------------------------------------------------------- /leetcode/src/bfs/102.rs: -------------------------------------------------------------------------------- 1 | struct Solution {} 2 | fn main() {} 3 | 4 | // Definition for a binary tree node. 5 | #[derive(Debug, PartialEq, Eq)] 6 | pub struct TreeNode { 7 | pub val: i32, 8 | pub left: Option>>, 9 | pub right: Option>>, 10 | } 11 | 12 | impl TreeNode { 13 | #[inline] 14 | pub fn new(val: i32) -> Self { 15 | TreeNode { 16 | val, 17 | left: None, 18 | right: None, 19 | } 20 | } 21 | } 22 | 23 | use std::cell::RefCell; 24 | use std::collections::VecDeque; 25 | use std::rc::Rc; 26 | 27 | impl Solution { 28 | pub fn level_order(root: Option>>) -> Vec> { 29 | let mut ans = Vec::new(); 30 | let mut q = VecDeque::new(); 31 | if let Some(x) = root { 32 | q.push_back(x); 33 | } 34 | 35 | while !q.is_empty() { 36 | let n = q.len(); 37 | let mut vals = Vec::with_capacity(n); 38 | for _ in 0..n { 39 | if let Some(node) = q.pop_front() { 40 | let mut node_ref = node.borrow_mut(); 41 | vals.push(node_ref.val); 42 | 43 | if let Some(left) = node_ref.left.take() { 44 | q.push_back(left); 45 | } 46 | if let Some(right) = node_ref.right.take() { 47 | q.push_back(right); 48 | } 49 | } 50 | } 51 | ans.push(vals); 52 | } 53 | ans 54 | } 55 | } 56 | 57 | #[test] 58 | fn test() {} 59 | -------------------------------------------------------------------------------- /leetcode/src/bfs/1161.rs: -------------------------------------------------------------------------------- 1 | struct Solution {} 2 | fn main() {} 3 | 4 | // Definition for a binary tree node. 5 | #[derive(Debug, PartialEq, Eq)] 6 | pub struct TreeNode { 7 | pub val: i32, 8 | pub left: Option>>, 9 | pub right: Option>>, 10 | } 11 | 12 | #[allow(dead_code)] 13 | impl TreeNode { 14 | #[inline] 15 | pub fn new(val: i32) -> Self { 16 | TreeNode { 17 | val, 18 | left: None, 19 | right: None, 20 | } 21 | } 22 | } 23 | 24 | use std::cell::RefCell; 25 | use std::rc::Rc; 26 | 27 | #[allow(dead_code)] 28 | impl Solution { 29 | pub fn max_level_sum(root: Option>>) -> i32 { 30 | let mut q = std::collections::VecDeque::new(); 31 | q.push_back(root.unwrap()); 32 | 33 | let mut max_level = 0; 34 | let mut max = i32::MIN; 35 | let mut level = 1; 36 | while !q.is_empty() { 37 | let mut sum = 0; 38 | let size = q.len(); 39 | for _ in 0..size { 40 | let node = q.pop_front().unwrap(); 41 | let ref_node = node.borrow(); 42 | sum += ref_node.val; 43 | 44 | if let Some(l) = ref_node.left.clone() { 45 | q.push_back(l.clone()); 46 | } 47 | if let Some(r) = ref_node.right.clone() { 48 | q.push_back(r.clone()); 49 | } 50 | } 51 | 52 | if sum > max { 53 | max = sum; 54 | max_level = level; 55 | } 56 | level += 1; 57 | } 58 | max_level 59 | } 60 | } 61 | 62 | #[test] 63 | fn test() {} 64 | -------------------------------------------------------------------------------- /leetcode/src/dfs/397.rs: -------------------------------------------------------------------------------- 1 | struct Solution {} 2 | fn main() {} 3 | 4 | #[allow(dead_code)] 5 | struct MyStack {} 6 | 7 | use std::{cmp::min, collections::HashMap}; 8 | 9 | #[allow(dead_code)] 10 | impl Solution { 11 | pub fn integer_replacement_helper(n: i32, map: &mut HashMap) -> i32 { 12 | if n == 1 { 13 | return 0; 14 | } 15 | 16 | if let Some(&res) = map.get(&n) { 17 | return res; 18 | } 19 | 20 | let res = if n % 2 == 0 { 21 | Solution::integer_replacement_helper(n / 2, map) + 1 22 | } else { 23 | 2 + min( 24 | Solution::integer_replacement_helper(n / 2, map), 25 | Solution::integer_replacement_helper(n / 2 + 1, map), 26 | ) 27 | }; 28 | 29 | map.insert(n, res); 30 | res 31 | } 32 | 33 | pub fn integer_replacement(n: i32) -> i32 { 34 | Solution::integer_replacement_helper(n, &mut HashMap::new()) 35 | } 36 | } 37 | 38 | #[test] 39 | fn test() { 40 | println!("{}", Solution::integer_replacement(2147483647)); 41 | assert_eq!(Solution::integer_replacement(7), 4); 42 | assert_eq!(Solution::integer_replacement(4), 2); 43 | } 44 | -------------------------------------------------------------------------------- /leetcode/src/dfs/669.rs: -------------------------------------------------------------------------------- 1 | struct Solution {} 2 | fn main() {} 3 | 4 | // Definition for a binary tree node. 5 | #[derive(Debug, PartialEq, Eq)] 6 | pub struct TreeNode { 7 | pub val: i32, 8 | pub left: Option>>, 9 | pub right: Option>>, 10 | } 11 | 12 | impl TreeNode { 13 | #[inline] 14 | pub fn new(val: i32) -> Self { 15 | TreeNode { 16 | val, 17 | left: None, 18 | right: None, 19 | } 20 | } 21 | } 22 | 23 | use std::cell::RefCell; 24 | use std::rc::Rc; 25 | impl Solution { 26 | pub fn trim_bst( 27 | root: Option>>, 28 | low: i32, 29 | high: i32, 30 | ) -> Option>> { 31 | let root = root?; 32 | let val = root.borrow().val; 33 | if val < low { 34 | Solution::trim_bst(root.borrow_mut().right.take(), low, high) 35 | } else if val > high { 36 | Solution::trim_bst(root.borrow_mut().left.take(), low, high) 37 | } else { 38 | let left = Solution::trim_bst(root.borrow_mut().left.take(), low, high); 39 | let right = Solution::trim_bst(root.borrow_mut().right.take(), low, high); 40 | root.borrow_mut().left = left; 41 | root.borrow_mut().right = right; 42 | Some(root) 43 | } 44 | } 45 | } 46 | 47 | #[test] 48 | fn test() {} 49 | -------------------------------------------------------------------------------- /leetcode/src/dfs/todo783.rs: -------------------------------------------------------------------------------- 1 | struct Solution {} 2 | fn main() {} 3 | 4 | // Definition for a binary tree node. 5 | #[derive(Debug, PartialEq, Eq)] 6 | pub struct TreeNode { 7 | pub val: i32, 8 | pub left: Option>>, 9 | pub right: Option>>, 10 | } 11 | 12 | impl TreeNode { 13 | #[inline] 14 | pub fn new(val: i32) -> Self { 15 | TreeNode { 16 | val, 17 | left: None, 18 | right: None, 19 | } 20 | } 21 | } 22 | 23 | use std::cell::RefCell; 24 | use std::cmp::min; 25 | use std::rc::Rc; 26 | impl Solution { 27 | pub fn min_diff_in_bst(root: Option>>) -> i32 { 28 | if root.is_none() { 29 | return i32::MAX; 30 | } 31 | 32 | min( 33 | min( 34 | Solution::min_diff_in_bst_in_node(root.clone()), 35 | Solution::min_diff_in_bst(root.clone().unwrap().borrow().left.clone()), 36 | ), 37 | Solution::min_diff_in_bst(root.clone().unwrap().borrow().right.clone()), 38 | ) 39 | } 40 | 41 | fn min_diff_in_bst_in_node(root: Option>>) -> i32 { 42 | let mut min = i32::MAX; 43 | if root.is_none() { 44 | return min; 45 | } 46 | let root = root.unwrap().clone(); 47 | let val = root.borrow().val; 48 | 49 | if root.borrow().left.is_some() { 50 | let mut node: Rc> = root.borrow().left.clone().unwrap(); 51 | while node.borrow().right.is_some() { 52 | let right = node.borrow().right.clone().unwrap(); 53 | node = right; 54 | } 55 | let diff = (node.borrow().val - val).abs(); 56 | if min > diff { 57 | min = diff 58 | }; 59 | } 60 | 61 | if root.borrow().right.is_some() { 62 | let mut node = root.borrow().right.clone().unwrap(); 63 | while node.borrow().left.is_some() { 64 | let left = node.borrow().left.clone().unwrap(); 65 | node = left; 66 | } 67 | let diff = (node.borrow().val - val).abs(); 68 | if min > diff { 69 | min = diff 70 | }; 71 | } 72 | 73 | min 74 | } 75 | } 76 | 77 | #[test] 78 | fn test() {} 79 | -------------------------------------------------------------------------------- /leetcode/src/main.rs: -------------------------------------------------------------------------------- 1 | struct Solution {} 2 | fn main() {} 3 | 4 | // Definition for a binary tree node. 5 | #[derive(Debug, PartialEq, Eq)] 6 | pub struct TreeNode { 7 | pub val: i32, 8 | pub left: Option>>, 9 | pub right: Option>>, 10 | } 11 | 12 | #[allow(dead_code)] 13 | impl TreeNode { 14 | #[inline] 15 | pub fn new(val: i32) -> Self { 16 | TreeNode { 17 | val, 18 | left: None, 19 | right: None, 20 | } 21 | } 22 | } 23 | 24 | use std::cell::RefCell; 25 | use std::rc::Rc; 26 | 27 | #[allow(dead_code)] 28 | impl Solution { 29 | pub fn max_level_sum(root: Option>>) -> i32 { 30 | let mut q = std::collections::VecDeque::new(); 31 | q.push_back(root.unwrap()); 32 | 33 | let mut max_level = 0; 34 | let mut max = i32::MIN; 35 | let mut level = 1; 36 | while !q.is_empty() { 37 | let mut sum = 0; 38 | let size = q.len(); 39 | for _ in 0..size { 40 | let node = q.pop_front().unwrap(); 41 | let ref_node = node.borrow(); 42 | sum += ref_node.val; 43 | 44 | if let Some(l) = ref_node.left.clone() { 45 | q.push_back(l.clone()); 46 | } 47 | if let Some(r) = ref_node.right.clone() { 48 | q.push_back(r.clone()); 49 | } 50 | } 51 | 52 | if sum > max { 53 | max = sum; 54 | max_level = level; 55 | } 56 | level += 1; 57 | } 58 | max_level 59 | } 60 | } 61 | 62 | #[test] 63 | fn test() {} 64 | -------------------------------------------------------------------------------- /leetcode/src/stack/20.rs: -------------------------------------------------------------------------------- 1 | struct Solution {} 2 | fn main() {} 3 | 4 | #[allow(dead_code)] 5 | impl Solution { 6 | pub fn is_valid(s: String) -> bool { 7 | let mut stack = Vec::new(); 8 | 9 | for ch in s.chars() { 10 | match ch { 11 | '(' | '[' | '{' => stack.push(ch), 12 | ')' | ']' | '}' => { 13 | let top = stack.pop(); 14 | match (top, ch) { 15 | (Some('('), ')') | (Some('['), ']') | (Some('{'), '}') => continue, 16 | _ => return false, 17 | } 18 | } 19 | _ => (), 20 | } 21 | } 22 | stack.is_empty() 23 | } 24 | } 25 | 26 | #[test] 27 | fn test1() { 28 | assert!(Solution::is_valid(String::from("()[]{}"))); 29 | } 30 | -------------------------------------------------------------------------------- /leetcode/src/two_pointers/todo283.rs: -------------------------------------------------------------------------------- 1 | struct Solution; 2 | 3 | fn main() { 4 | let mut input = vec![1]; 5 | Solution::move_zeroes(&mut input); 6 | println!("{:?}", input); 7 | } 8 | 9 | impl Solution { 10 | pub fn move_zeroes(nums: &mut Vec) { 11 | let mut li = 0; 12 | let sz = nums.len(); 13 | for ri in 0..sz { 14 | if li >= sz { 15 | break; 16 | } 17 | 18 | while li < sz && nums[li] != 0 { 19 | li += 1; 20 | } 21 | 22 | if nums[ri] != 0 && ri > li { 23 | nums[li] = nums[ri]; 24 | nums[ri] = 0; 25 | li += 1; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/code/top100/128.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using std::string; 6 | using std::unordered_set; 7 | using std::vector; 8 | 9 | class Solution { 10 | public: 11 | int longestConsecutive(vector& nums) { 12 | if (nums.empty()) 13 | return 0; 14 | 15 | unordered_set set; 16 | for (const auto& num : nums) 17 | set.insert(num); 18 | 19 | int longest = 1; 20 | for (const auto& num : nums) { 21 | if (set.count(num - 1)) 22 | continue; 23 | 24 | int curr_long = 1; 25 | int key = num + 1; 26 | while (set.count(key)) { 27 | ++curr_long; 28 | key += 1; 29 | } 30 | if (curr_long > longest) 31 | longest = curr_long; 32 | } 33 | return longest; 34 | } 35 | }; 36 | 37 | int main() { 38 | vector arg = {0, 3, 7, 2, 5, 8, 4, 6, 0, 1}; 39 | Solution s; 40 | s.longestConsecutive(arg); 41 | return 0; 42 | } 43 | 44 | /** 时间复杂度为 O(n) 的算法解决数字连续的最长序列问题 45 | * O(n) 所以不能用排序,O(n) 不代表 1n,只要是常数 cn 即可,所以可以多次循环 46 | * 一遍构造哈希,另一边通过 O(1) 的不断寻找即可,因为只有从最小找才有意义,所以可以剪枝, 47 | * 这也可以保证绝大部分元素的探寻次数都是 O(1),所以 c 总体上是常数,所以是 O(n) 48 | */ 49 | -------------------------------------------------------------------------------- /src/code/top100/49.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using std::string; 7 | using std::unordered_map; 8 | using std::vector; 9 | 10 | class Solution { 11 | public: 12 | vector> groupAnagrams(vector& strs) { 13 | unordered_map> map; 14 | for (const auto& str : strs) { 15 | string tmp = str; 16 | std::sort(tmp.begin(), tmp.end()); 17 | map[tmp].push_back(str); 18 | } 19 | 20 | vector> ans; 21 | for (const auto& iter : map) { 22 | ans.push_back(iter.second); 23 | } 24 | return ans; 25 | } 26 | }; 27 | 28 | int main() { 29 | vector arg = {"eat", "tea", "tan", "ate", "nat", "bat"}; 30 | Solution s; 31 | s.groupAnagrams(arg); 32 | return 0; 33 | } 34 | 35 | /** 字母异位词 36 | * 将字母异位词排序,互为异位的单词能够得到相同的键,可以用哈希。 37 | */ 38 | -------------------------------------------------------------------------------- /src/cpp/smart_ptr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | template 5 | class smart_ptr 6 | { 7 | private: 8 | T *ptr; 9 | int *use_count; // 通过指针完成多个 smart_ptr 共享 10 | 11 | public: 12 | smart_ptr(T *p); 13 | smart_ptr(const smart_ptr &sp); 14 | ~smart_ptr(); 15 | 16 | T &operator*(); 17 | T *operator->(); 18 | smart_ptr &operator=(const smart_ptr &rhs); 19 | T *operator+(int i); 20 | int operator-(const smart_ptr &lhs, const smart_ptr &rhs); 21 | 22 | int get_count(); 23 | }; 24 | 25 | template 26 | smart_ptr::smart_ptr(T *p) 27 | { 28 | ptr = p; 29 | use_count = new int(1); 30 | } 31 | 32 | template 33 | smart_ptr::smart_ptr(const smart_ptr &sp) 34 | { 35 | ptr = sp.ptr; 36 | use_count = sp.use_count; 37 | ++(*use_count); 38 | } 39 | 40 | template 41 | smart_ptr::~smart_ptr() 42 | { 43 | if (--(*use_count) == 0) 44 | { 45 | delete ptr; 46 | ptr = nullptr; 47 | delete use_count; 48 | use_count = nullptr; 49 | } 50 | } 51 | 52 | template 53 | T &smart_ptr::operator*() 54 | { 55 | return *ptr; 56 | } 57 | 58 | template 59 | T *smart_ptr::operator->() 60 | { 61 | return ptr; 62 | } 63 | 64 | template 65 | smart_ptr &smart_ptr::operator=(const smart_ptr &rhs) 66 | { 67 | ++(*rhs.use_count); 68 | if (--(*use_count) == 0) 69 | { 70 | delete ptr; 71 | ptr = nullptr; 72 | delete use_count; 73 | use_count = nullptr; 74 | } 75 | 76 | ptr = rhs.ptr; 77 | use_count = rhs.use_count; 78 | return *this; 79 | } 80 | 81 | template 82 | T *smart_ptr::operator+(int i) 83 | { 84 | T *p = ptr + i; 85 | return p; 86 | } 87 | 88 | // template 89 | // int smart_ptr::operator-(const smart_ptr &lhs, const smart_ptr &rhs) 90 | // { 91 | // return lhs.ptr - rhs.ptr; 92 | // } 93 | 94 | template 95 | int smart_ptr::get_count() 96 | { 97 | return *use_count; 98 | } 99 | 100 | int main() 101 | { 102 | return 0; 103 | } -------------------------------------------------------------------------------- /src/cpp/typeid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | class A 5 | { 6 | public: 7 | virtual void f() 8 | { 9 | cout << "a" << endl; 10 | } 11 | }; 12 | 13 | class B : public A 14 | { 15 | public: 16 | void f() 17 | { 18 | cout << "b" << endl; 19 | } 20 | }; 21 | 22 | int main() 23 | { 24 | A a; 25 | B b; 26 | 27 | A *p1 = &a; 28 | cout << typeid(p1).name() << endl; 29 | cout << typeid(*p1).name() << endl; 30 | A *p2 = &b; 31 | cout << typeid(p2).name() << endl; 32 | cout << typeid(*p2).name() << endl; 33 | 34 | if (typeid(p1) == typeid(p2)) 35 | cout << "true" << endl; 36 | else 37 | cout << "false" << endl; 38 | 39 | if (typeid(*p1) == typeid(*p2)) 40 | cout << "true" << endl; 41 | else 42 | cout << "false" << endl; 43 | 44 | return 0; 45 | } -------------------------------------------------------------------------------- /src/latex_file: -------------------------------------------------------------------------------- 1 | % !TEX TS-program = xelatex 2 | % !TEX encoding = UTF-8 Unicode 3 | % !Mode:: "TeX:UTF-8" 4 | 5 | \documentclass{resume} 6 | \usepackage{zh_CN-Adobefonts_external} % Simplified Chinese Support using external fonts (./fonts/zh_CN-Adobe/) 7 | %\usepackage{zh_CN-Adobefonts_internal} % Simplified Chinese Support using system fonts 8 | \usepackage{linespacing_fix} % disable extra space before next section 9 | \usepackage{cite} 10 | 11 | \begin{document} 12 | \pagenumbering{gobble} % suppress displaying page number 13 | 14 | \name{孙慧泉} 15 | 16 | \basicInfo{ 17 | \phone{18353287416} 18 | \email{february2048@163.com} 19 | \github[github]{https://github.com/sunhuiquan} 20 | \homepage[blog]{https://sunhuiquan.github.io} 21 | } 22 | 23 | \section{简介} 24 | \datedsubsection{\textbf{青岛科技大学}, 青岛}{2019 -- 2023} 25 | 专业:计算机科学与技术 26 | 27 | \section{技能} 28 | \begin{itemize}[parsep=0.5ex] 29 | \item \textbf{编程语言}: 熟练使用 C C++,能够使用 C\# Java python。 30 | \item \textbf{Linux系统编程}:熟悉Linux常用系统调用和shell的常用命令,熟悉manpage,较为了解Linux系统编程知识如各种I/O模型、文件系统、多进程、多线程、信号、进程间通信/同步、socket基础等基础知识。 31 | \item \textbf{网络编程}:熟悉Socket编程,较为了解TCP、UDP的原理和行为,了解使用tcpdump或wireshark抓包分析,了解常用的netstat、nc、telnet、inetd指令。 32 | \item \textbf{开发环境}:熟悉Linux环境下编程,熟练使用git工具,较为了解vim、gcc、gdb、cmake等常见工具的使用。 33 | \item \textbf{其他}:完成一些国外名校公开课和对应的lab(CMU15-213,CS144,MIT6.S081),专业课基础较好;英语裸考过六级,能够较为顺畅地阅读和编写英文文档,熟练使用Google、github和StackOverflow。 34 | \end{itemize} 35 | 36 | \section{项目} 37 | \textbf{FUSE FileSystem}\hfill{https://github.com/sunhuiquan/fuse-toy-fs} 38 | 39 | 借鉴xv6的文件系统和ext2,实现一个支持并发、日志恢复的轻量级用户态文件系统,通过挂载到Linux上运行。 40 | \begin{itemize}[parsep=0.5ex] 41 | \item 通过libfuse库,使得内核态的linux VFS机制代码能与我们在用户态实现的文件系统沟通。 42 | \item 分层结构,设计磁盘层、日志层、inode层、位图层、块缓冲层这五层,降低耦合度和实现难度。 43 | \item 通过对写系统调用采用简单的事务批处理机制,保证数据一致性,并通过日志层对应的日志数据块存储的数据实现日志恢复功能。 44 | \end{itemize} 45 | 46 | \textbf{Toy TCP}\hfill{https://github.com/sunhuiquan/CS144\_lab} 47 | 48 | 读完《计算机网络:自顶向下》,完成CS144计网Lab,内容是使用C++实现了一个玩具级的TCP协议 49 | \begin{itemize}[parsep=0.5ex] 50 | \item 实现三次握手、四次挥手和TCP状态机的11种状态之间的切换。 51 | \item 实现接受端的流重组器,将到来的可能乱序的字节流重组,有序地放入接受缓冲区。 52 | \item 实现简化的累计确认重传机制,保证TCP的可靠性。 53 | \item 实现滑动窗口,来保证流量控制;实现慢启动和拥塞避免机制的来完成拥塞控制。 54 | \end{itemize} 55 | 56 | \textbf{TLPI learn note}\hfill{https://github.com/sunhuiquan/tlpi-learn} 57 | 58 | 因网上没有比较全的《UNIX/LINUX 系统编程手册》这本书的答案,所以我完成了几百道关于Linux系统编程各的题目并开源,并获得50+ stars。 59 | 60 | \textbf{MIT6.S081 Labs}\hfill{https://github.com/sunhuiquan/mit\_6.S081\_lab} 61 | 62 | 配合《操作系统概念》,完成MIT6.S081操作系统课大作业,阅读和修改xv6操作系统源码,通过为xv6实现页表、实现惰性内存分配、实现fork写时复制、扩大inode的索引层数等实验,较为深入地了解了操作系统的虚拟内存、进程切换、系统调用、内存分配、文件系统、锁机制等实现原理。 63 | 64 | \textbf{CSAPP Labs}\hfill{https://github.com/sunhuiquan/csapp\_lab} 65 | 66 | 读完《深入理解计算机系统》,完成CMU15-213课程及其lab,实现基于隐式空闲链表的简易动态内存分配器(malloclab),实现玩具shell程序(shlab)和玩具代理服务器(proxylab)。 67 | 68 | \textbf{C\# car-rental-app}\hfill{https://github.com/sunhuiquan/car-rental-app} 69 | 70 | 实现C/S架构的车位出租应用,使用C\#+Winform实现客户端,使用C\#socket和thread实现多线程网络服务器,云服务器上建立MySQL数据库,并在服务器上使用对数据库增删改查处理业务。 71 | 72 | \end{document} 73 | -------------------------------------------------------------------------------- /src/note.md: -------------------------------------------------------------------------------- 1 | 1. 无符号类型和有符号类型运算,有符号会自动转成无符号类型,此时要注意负数溢出的问题,涉及负数一定先转换成有符号类型. 2 | 2. 对于数组、字符串的题目,如果从前向后遍历操作会需要移动元素的话,可以考虑从后向前反向操作来避免移动元素,例如合并两个有序数组,而且是O(1)空间复杂度,怎么才能避免插入导致的移动,要从后面指向开始,这样可以避免覆盖还没用过的第一个数组,做过的双指针的题,两个指针分别指向尾部。 3 | 3. 对于排列组合、列出可能性、全排列的问题,可以使用递归的思路,第一层列出第一组的所有可能性,然后这些可能性,每一个都再分别各自列出自己的第二层的所有可能性,以此类推。注意这个递归全排列最后是有序的,可以利用这个性质解决一些问题。 4 | 4. 大数问题转成字符串计算问题。 5 | 5. 链表要么分开处理头结点和非头结点,要么加一个空头结点当哨兵,这样只用处理非头结点一种情况了,简化代码。 6 | 6. 用来追踪的二维数组,可以用一维数组表示,这样简单,动态分配还能完美确定大小,下标通过 行*列+列 得到对应二维的坐标。 7 | 7. 链表的快慢指针法,比如删除中间节点,让快指针走两步,慢指针走一步,这样快指针走到头走了n步,慢指针是n/2步正好是一半。 8 | 8. 写代码前先做一件事,那就是检测参数是否有效,提高鲁棒性。 9 | 9. T236的递归得到从根节点到目的节点的路径。 10 | 10. Partition函数实现快排和查找n个数中第k大的数字。 11 | 11. 使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇,数学上可以通过距离差证明,当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动 N 轮,这个N轮就是循环的次数也是这个环的长度,所以是O(N)。 12 | 12. bfs通过 迭代+队列+记录数组 实现,有拓展是增加数组维度来记录其他的状态信息。 13 | 13. 动态规划,注意剪枝来避免无用情况的计算(通过具体条件判断出什么时候是不可能达成的情况),与避免重复(通过数组/map记录已经计算的答案)有些不同。 14 | 14. bfs是一种应用情况是找最短路径,因为随着不断遍历,低层的永远比高层的先遍历到,低层代表距离起始点的层次少,也就是经过的边数少。 15 | 15. dfs通过 递归(栈)+标记的思想。 16 | 16. bfs中,对于一层的全部节点,while循环内部再来一个for循环,循环的次数就是上一层的结点数,通过对队列进行size()得到,这样可以分别得到每一层的结点。 17 | 17. 逆序先想想使用栈可不可以,一般这个是最简单的反转方式。 18 | 18. 做题前问一下返回的形式,比如返回的时候可不可以使用额外空间(比如new)。 19 | 19. 从链表形成子链表的时候,别忘了抽出来的节点的next都是有东西的,前面的节点会不断覆盖next,但尾节点没有覆盖,还是原先的内容,要注意给伪节点手动赋值nullptr。 20 | 20. 记住 logx 比 根号x 快非常多。 21 | 21. int * long 类型,首先是必须类型相同才能运算,int要变成long,注意int的变量和long的临时值(一个右值)是完全不同的两个东西,内存地址也完全不同,long是新在内存中分配的一块空间来放这个临时值,然后运算。 22 | 22. int * int 中途越界就会直接运行时错误提示,必须运算中就提升成更大范围的类型,才能正常运算。 23 | 23. 区分map和unordered_map,注意map的底层实现是红黑树,而unordered_map的底层原理才是哈希,哈希表在C++里面特指unordered_map(注hash_map已被unordered_map取代),除了实现原理不同外,红黑树map默认是按key排序的,而unordered_map是无序的,无法排序。 24 | 24. 优先队列比较类to do 25 | 25. 注意计算机内的值放的就是补码,比如一个int -10,int变量所在的地址里面放的就是补码,最高位就是1,只不过你用int格式会把这个最高位解释成负数,unsigned则解释为正常的数据位而已,所以直接位运算即可,里面的值本身就是补码,里面更不可能有'-'号。 26 | 26. C 风格字符串使用 atoi atol 之类的,stirng 需要 c_str,更好的是 string 直接用 stoi stol 之类的。 27 | 27. 字符串题注意前导0问题,一般前导0格式都是错误的。 28 | 28. 双向链表最好要采用空头结点的方式,来避免分类讨论是否为头结点的情况,头结点就是head->next,尾结点就是head->prev。另外插入节点一般是指插入作为第一个节点,为了避免写错,记得我们先设置要插入的节点的成员,再head->next->prev = node,再head->next = node,以这种顺序一定没错,要是随便写很可能由于粗心导致写错导致各种乱七八糟的错误。 29 | 29. rand()生成包含0到类型最大值的随机数 30 | 30. 对于数组过大的情况下,尤其是二维数组,写在栈内会栈错误,要写在堆上。另外上限空间很高但大部分测试需要的空间很低,这样就写动态分配确切的空间,免得浪费空间。 31 | 31. 动态规划问题,因为要设置初始化边界,所以要注意 n 为那几个初始边界大小的情况,要处理这个边界条件。 32 | 32. 对于需要记录下标的情况,可以考虑 pair (比如对于队列),map、unordered_map first 保存 nums\[i\],second 保存下标 i。 33 | 33. 对于求最值的题目,我喜欢初始化成 INT32_MIN 或 INT32_MAX 但是别忘了有些题对于没有结果的情况,要记得最后判断下返回 0 还是 -1 之类的。 34 | 34. map 的 count() 函数,如果在 map 中存在这个 key 的元素则返回 1,不存在返回 0,和 value 值无关。 35 | 35. 注意 0 除以、被求模都会运行出错,所以注意一下这个特殊输入。 36 | 36. vector 的 clear 函数是清除所有元素,而不是清零,size 会变成 0,如果不重新 resize 使用超过 size 的范围会产生未定义的行为,比如 vis 数组 clear 后要重新 resize,所以说还是用数组实现 vis 好,只需要 memset 好写效率也高。 37 | 37. 别忘了对于经常插入删除的要用list,那么list 顺序访问写法,用迭代器?,下标访问是随机访问不能这样写?,remove要先找到对应的元素,复杂度怎么样?,如果多个元素都删除吗?,erase迭代器删除 38 | 38. double 转 string 保留 n 位小数,通过 C 风格的 sprintf 的 "%.nlf" 实现,千万不要自己实现,涉及到复杂点的精度问题都是灾难。 39 | 39. C++ double 转 string 默认 6 位精度,std::stringstream ss; ss << std::setprecision(15) << d(d 是 double 变量); str = ss.str(); 这种方式可以设置精度。 38用这个方式呢? 40 | 40. 使用pair作为unordered_map的key时会错误,因为C++标准中没有为pair提供hash函数,所以在使用的时候需要手写一个。不过刷题情况下我们用一个二维数组当成 pair 进行索引即可,当然实际中显然不能接受如此大的空间浪费。 41 | 42 | ```C++ 43 | struct pair_hash 44 | { 45 | template 46 | std::size_t operator() (const std::pair& p) const 47 | { 48 | auto h1 = std::hash{}(p.first); 49 | auto h2 = std::hash{}(p.second); 50 | return h1 ^ h2; 51 | } 52 | }; 53 | 54 | unordered_map, int, pair_hash> um; //ok 55 | ``` 56 | 41. (a+b)%c =((a%c)+(b%c))%c; (a*b)%c = (a%c)*(b%c)%c 求模的犯溢出公式,注意最外侧也有一个求模。 57 | 58 | 42. string 里面有 insert 和 erase 用于插入删除字符串,比手动两次 substr 再合起来快,注意一下,要用自带的 insert 和 erase。 59 | 60 | 43. Contest297 的题2没改错 61 | 62 | 63 | prob: 64 | 1. AVL(平衡二叉树) 65 | 2. 手写堆 66 | 3. 手写哈希表 67 | 4. 双向链表 68 | 69 | # 面试题汇总 70 | 71 | ### C++ 72 | 73 | placement new 74 | 虚函数 75 | 具体怎么找虚函数表 76 | 虚函数可以是内联的吗 77 | 虚函数表存的是啥 78 | static函数作用 79 | 内联函数 80 | 智能指针 81 | 多态实现原理 82 | C++11的新特性 83 | public、private 84 | malloc和new的区别 85 | 左值、右值 86 | 4.虚析构函数 87 | 5.虚构造函数 88 | const在什么时期初始化?编译期。Q:怎么初始化啊?A:编译期啊!!!Q:不是直接初始化吗?A:。。。。。。 89 | 4.类不想被继承怎么办?final。C++11之前呢?A:私有构造 Q:为什么?A:这样子类就调用不了基类构造函数了 90 | 5.const存放在啥区?随口一编,代码区,因为想着常量不改变了,放在代码区挺合适的哈哈哈 91 | 6.bss懂吗?不会 Q:下去好好研究研究吧 92 | inline函数?直接在代码展开了,不需要出栈和进栈所以速度快,但是也会占更大的空间。inline函数里面不能有for循环和递归。谷歌规范,inline函数里10行为最佳。在对比一下define函数 93 | map和unordered_map区别?map是红黑树,红黑树根和孩子节点是黑色,两红色节点不能相邻,有左旋、右旋、变色操作,而且中序遍历也是有序的。unordered_map哈希冲突,开链法或者扩容解决,时间复杂度是O(1)的。需要排序的话就首先选红黑树 94 | static作用 95 | std::sort有没有遇到过问题 96 | 1. C++14有啥新特性?make_unique 97 | 2. C++17有啥新特性?auto[key,value] 遍历map,还有那啥标签来着 98 | 3. C++20有啥新特性?format、协程 99 | 4.lambda如何实现的? 我:? 100 | 5.shared_ptr介绍一下。引用计数器线程安全的。 101 | Q:引用计数器的值是堆上的还是栈上的? 102 | A:一开始说的是栈上,让我写一下实现,发现,唔应该是堆上的 103 | 6.你写的那个不是拷贝构造函数啊,Test(Test&)我写的是这样的。我说必须要加个const?面试官是的,你不加编译器也会默认生成一个带const的。面试的时候:真的吗?我现在实习写了一个月的PHP和JS你别骗我啊,就没battle。但是下来我发现,不写const可以啊,怎么可能会默认生成啊,const保证的是不可改变值啊。又用c++insights跑没生成const拷贝构造函数啊。 104 | 105 | 7.为什么不能返回局部的引用?返回值是栈上的值,函数结束,栈被系统回收,内存的值就不存在了。 106 | 8.为什么要operator=重载等于号,C++为啥要这么设计? 我:? 107 | 9.说一下push_back和emplace_back的区别?我:天啊,这是招语言学家吧。忘记emplace_back更方便转换对象了,当时没答上来。 108 | 10.插一下,让我写shared_ptr的时候,让我实现一下删除器功能。我:emm以前看C++primer还会,现在忘光了。仔细想了想其实也还可以设计出来的,类似回调函数设计吧 109 | 13.weak_ptr了解吗?解决循环引用 110 | 14.push_back插入均摊复杂度。真忘记了,事后想到是O(1)。 111 | 15.epoll的原理?红黑树加+链表。在对比一下select的效率 112 | 16 epoll为什么是红黑树?查询更稳定(对比哈希表,哈希表还存在扩容问题),内存占用更小(对比哈希表,哈希表一般占得空间更大) 113 | 114 | ### 并发 115 | 116 | 同步异步 117 | 阻塞非阻塞 118 | 可以同步非阻塞吗?我答得是可以 119 | poll、epoll和select区别 120 | epoll 底层原理 121 | epoll是同步的还是异步的? 122 | 协程? 123 | 对称协程和非对称协程?不会 124 | 协程为什么叫用户态线程 125 | 死锁概念 126 | one loop per thread 127 | epoll那个函数线程不安全 epoll_create epoll_wait epoll_ctl这些 我猜ctl,就跟多线程插入元素到数组。wait以前是有惊群的后面改了。所以wait应该是安全的 128 | 3.红黑树原理 129 | 4.乐观锁、悲观锁 130 | 5.乐观锁实现原理 131 | 132 | 133 | ### 操作系统 134 | 135 | 如何创建进程?我说./把可执行变成进程。告知不对。又说fork函数创建通过进程id判断一下。还不对。 136 | 进程布局?堆栈全局代码那些区 137 | ”hello world"存在那个区 138 | 线程私有区和共享区 139 | 函数压栈过程,包括函数带参数 140 | 是个全局变量但我又想是线程独有的怎么办?陈硕永远滴神。__thred关键字,有一小节整好叫善用__thread关键字 141 | 线程调度 142 | 原子怎么实现的?简单说了下CAS 原理 143 | 有多少锁 144 | 协程了解过吗?协程应用场景呢?有栈和无栈,以前实现过,linux用的那个ucontext族函数封装的 145 | 7.协程和线程的区别?硬编,我看项目里用线程的都自己封装协程了 146 | 进程和线程区别?聊切换进程的开销 147 | IO复用和多线程区别?我:我都是他俩结合使用,我也不知道,硬编,编到最后问面试官,你说说看吧,我不会了。面试官:"你面试还是我面试?" 我:"哈哈哈哈,相互探讨吗"。面试官提示分场景,看IO密集型还是cpu密集型。确实是这么考虑的哈哈哈哈 148 | 15.说说你会的linux的命令。开始吹cat less netstat ps top grep | & head -n 149 | select和epoll什么时候在用户和内核态拷贝数据 150 | 什么是内核态和用户态?什么是系统调用? 151 | 可重入和线程安全 152 | 8.什么是可重入 153 | 9.僵尸进程和孤儿进程 154 | 软链接硬链接 155 | 17.硬链接 inode是有多少个?不知道,事后学了学inode 156 | .IO重定向 157 | 19.无锁队列 158 | 20.死锁条件 159 | 21.死锁避免 160 | 22.发生死锁C++怎么感知 161 | fork的copy on write手法。进程都会共用那些区域 162 | 3. 栈区和堆区的区别?栈区更容易写进寄存器里,因为地址是连续的,CPU有预读功能,可以读一行数据。堆的空间不连续更连续 163 | 4. 以前是没有堆区的,为什么需要堆区?扯到链表去了,说这种结构不需要连续空间。面试官告诉我,差不多把,因为不是所有数据都需要先进后出 164 | 5. 内存模型都有什么区?栈区、代码区、堆区、静态存储区 165 | 6. 是否查看过内存泄露? 166 | gdb,比如调试正在运行的程序,先用ps -ef获取pid,然后使用gdb attach pid。调试多线程,我会先list看看在哪行源码,或者在那个cpp文件,break可以 xxx.cpp:30,在某个文件上第30行打个断点,然后run跑起来,info thread左上角的星号是正在执行的线程,可以用thread id来切换线程,还可以用set scheduler-locking来设置只让一根线程执行。 167 | 单核计算密集型任务,单线程还是多线程更好 168 | 169 | 170 | ### 计组 171 | 172 | 190 | 191 | ### MySQL 192 | 193 | mysql acid 194 | mysql mvcc 隔离级别实现原理什么一套都讲了 195 | mysql为啥要分库分表 196 | mysql除了myslam和innodb还知道啥引擎? 197 | 198 | ### 数据结构 199 | 200 | B+ B树 红黑树区别 201 | .B树适用场景 202 | LRU 203 | 动态规划和贪心区别 204 | 动态规划可以得到全局最优解吗 205 | 只有一个数字出现奇数剩下都是出现偶次数,如何快速得到值?异或 206 | 红黑树的介绍 207 | 哈希是无序的,那我想让他保证插入删除的顺序呢?LRU算法思想讲一遍就行(lc原题) 208 | 209 | ### 网络 210 | 211 | udp和tcp区别?讲完区别,又从wireshark讲起tcp,然后如何网络优化分析(因为这几天正好在学wireshark,吹了一吹) 212 | tcp三次握手、流量控制、拥塞控制、MSS那一套 213 | timewait 214 | 客户端握手发送SYN,TCP状态机,client变成什么状态? 215 | 快重传,MSL 216 | 主机断电会发生什么 217 | 2.什么是close_wait? 218 | 13.服务端什么时候进入close_wait?是发送ack还是发送FIN? 219 | 14什么是半连接? 220 | 15.HTTP1.1咋实现的长连接?不close套接字,被告知不是这个原理。这题不会,懂得大佬可以告诉下我,谢谢! 221 | 7.tcp超时?巴拉巴拉。那现在客户端断电了,我tcp怎么感知?A:断电操作系统就不会发送FIN,但tcp感知?emmmm send函数返回-1吧。Q:你确定吗?A:尬笑 Q:下去了好好研究研究吧 222 | 9.http和https区别 223 | 10.http和https的端口号 224 | 超时重传、快速重传、黏包 225 | 226 | ### Redis 227 | 228 | redis的跳表 229 | 20.redis几种基本数据结构? 230 | 17.redis可以做哪些事情?开吹,分布式锁、消息队列、缓存 231 | 18.redis基本类型?字符串、哈希、集合、压缩列表、跳表(人称小红黑树,介绍了一下时间复杂度等等)。又说了下集合可以做交集、并集、补集。还可以从集合中随机取数字 232 | 19.最后我还吹了吹redis和mysql数据一致性问题,先删缓存还是先更新数据库。延时双删策略 233 | 游戏排行榜怎么做?我问可以借助redis这种东西吗?可以的,那就跳表去做吧 234 | 跳表是啥?简单介绍了一下就是个带索引的链表,比红黑树实现简单一些,那会我也看了下跳表的缺点,插入太多元素会造成索引效率降低。 235 | 236 | ### 笔试题 237 | 238 | lc209题 239 | 相交链表 240 | 二叉树层序遍历反转一下 241 | 作者:dxgzg 242 | 8.大文件找次数出现最多的10个。我去我竟然蒙对了,哈希到同一个文件,然后在许多文件选出这个文件最前的10个出来。在把所有文件里的前十汇总起来找前10。 243 | 9.两个有序数组找中位数。 双指针。Q:还能在优化吗?内心:没合并数组在sort我都觉得自己很厉害了哈哈哈哈。A:反抗一会,说不会了。事后发现是lc一道题,尴尬lc打不开放不上连接了 244 | 5.A*算法 245 | 6.判断点在矩形 246 | 7.判断点三角形 247 | 248 | muduo 腾讯课堂有个课剖析源码 249 | -------------------------------------------------------------------------------- /src/resume_v1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunhuiquan/interview-note/ce5cf5cca672a0dab1c5e5cfa8bdff1078d35681/src/resume_v1.pdf -------------------------------------------------------------------------------- /weekly_contest/2024_4_21/cpp/a.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunhuiquan/interview-note/ce5cf5cca672a0dab1c5e5cfa8bdff1078d35681/weekly_contest/2024_4_21/cpp/a.out -------------------------------------------------------------------------------- /weekly_contest/2024_4_21/cpp/q1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using std::string; 4 | using std::unordered_set; 5 | 6 | class Solution { 7 | public: 8 | int numberOfSpecialChars(string word) { 9 | unordered_set m; 10 | unordered_set res; 11 | for (const auto &ch : word) { 12 | m.insert(ch); 13 | if ((ch >= 'a' && ch <= 'z' && m.find(std::toupper(ch)) != m.end()) || 14 | (ch >= 'A' && ch <= 'Z' && m.find(std::tolower(ch)) != m.end())) { 15 | res.insert(std::tolower(ch)); 16 | } 17 | } 18 | return res.size(); 19 | } 20 | }; 21 | 22 | #include 23 | using std::cout; 24 | using std::endl; 25 | 26 | int main() { 27 | Solution a; 28 | cout << a.numberOfSpecialChars("abBCab") << endl; 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /weekly_contest/2024_4_21/cpp/q2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using std::string; 5 | using std::unordered_map; 6 | using std::unordered_set; 7 | 8 | class Solution { 9 | public: 10 | int numberOfSpecialChars(string word) { 11 | unordered_set s; 12 | unordered_map res; 13 | for (const auto& ch : word) { 14 | if (ch >= 'a' && ch <= 'z') { 15 | s.insert(ch); 16 | } 17 | 18 | if (ch >= 'A' && ch <= 'Z') { 19 | if (s.find(std::tolower(ch)) != s.end()) { 20 | if (res.find(std::tolower(ch)) == res.end()) { 21 | res[std::tolower(ch)] = 1; 22 | } 23 | } else { 24 | res[std::tolower(ch)] = -1; 25 | } 26 | } 27 | 28 | if (ch >= 'a' && ch <= 'z' && res.find(ch) != res.end()) { 29 | res[ch] = -1; 30 | } 31 | } 32 | 33 | int ret = 0; 34 | for (const auto& p : res) { 35 | if (p.second == 1) { 36 | ++ret; 37 | } 38 | } 39 | return ret; 40 | } 41 | }; 42 | 43 | #include 44 | using std::cout; 45 | using std::endl; 46 | 47 | int main() { 48 | Solution a; 49 | cout << a.numberOfSpecialChars("AbcbDBdD") << endl; 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /weekly_contest/2024_4_21/cpp/q3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using std::min; 6 | using std::pair; 7 | using std::unordered_map; 8 | using std::unordered_set; 9 | using std::vector; 10 | 11 | class Solution { 12 | public: 13 | int minimumOperations(vector>& grid) { 14 | int ret = INT32_MAX; 15 | numrow_ = grid.size(); 16 | if (!numrow_) { 17 | return 0; 18 | } 19 | numcol_ = grid[0].size(); 20 | 21 | for (int i = 0; i < numrow_; ++i) { 22 | ret = min(ret, dfs(grid, 0, i)); 23 | } 24 | return ret; 25 | } 26 | 27 | private: 28 | int dfs(vector>& grid, int col, int row) { 29 | int idx = row * numcol_ + col; 30 | if (memory_.find(idx) != memory_.end()) { 31 | return memory_[idx]; 32 | } 33 | 34 | int current_ops = 0; 35 | int val = grid[row][col]; 36 | for (int i = 0; i < numrow_; ++i) { 37 | if (grid[i][col] != val) { 38 | ++current_ops; 39 | } 40 | } 41 | 42 | unordered_set s1; 43 | unordered_set s2; 44 | 45 | int ops = INT32_MAX; 46 | if (col + 1 < numcol_) { 47 | for (int i = 0; i < numrow_; ++i) { 48 | int v = grid[i][col + 1]; 49 | if (v == val) { 50 | continue; 51 | } 52 | 53 | if (s1.find(v) != s1.end()) { 54 | continue; 55 | } 56 | s1.insert(v); 57 | ops = min(ops, dfs(grid, col + 1, i)); 58 | } 59 | 60 | int spec_ops = INT32_MAX; 61 | if (col + 2 < numcol_) { 62 | for (int i = 0; i < numrow_; ++i) { 63 | int v = grid[i][col + 2]; 64 | if (s2.find(v) != s2.end()) { 65 | continue; 66 | } 67 | s2.insert(v); 68 | spec_ops = min(spec_ops, dfs(grid, col + 2, i)); 69 | } 70 | } 71 | if (spec_ops != INT32_MAX) { 72 | ops = min(ops, spec_ops + numrow_); 73 | } 74 | 75 | if (ops == INT32_MAX && col + 2 >= numcol_) { 76 | memory_[idx] = numrow_; 77 | } else { 78 | memory_[idx] = ops + current_ops; 79 | } 80 | } else { 81 | memory_[idx] = current_ops; 82 | } 83 | return memory_[idx]; 84 | } 85 | 86 | int numrow_; 87 | int numcol_; 88 | unordered_map memory_; 89 | }; 90 | 91 | #include 92 | using std::cout; 93 | using std::endl; 94 | 95 | int main() { 96 | Solution a; 97 | vector> input = {{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}}; 98 | cout << a.minimumOperations(input) << endl; 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /weekly_contest/2024_4_21/rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "rust" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /weekly_contest/2024_4_21/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /weekly_contest/2024_4_21/rust/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | --------------------------------------------------------------------------------