0%

持续更新题目列表

Outline

• 什么时候使用 BFS

• 二叉树上的 BFS

• 图上的 BFS

• 矩阵上的 BFS

• 拓扑排序 Topological Sorting

什么时候应该使用BFS?

图的遍历 Traversal in Graph

• 层级遍历 Level Order Traversal

• 由点及面 Connected Component:其实就是判断点有没有联通

• 拓扑排序 Topological Sorting

简单图最短路径 Shortest Path in Simple Graph

非递归的方式找所有方案 Iteration solution for all possible results

P.S. 如果题目问最短路径 除了BFS还可能是什么算法? 如果问最长路径呢?

最短路径

简单图 → BFS

复杂图 → Dijkstra, SPFA, Floyd(一般面试不考这个)

最长路径

图可以分层 → Dynamic Programming

分层:比如走到第i一定会经过第 i-1 层(棋盘格子图是典型的例子)

不可以分层 → DFS

Code Template

我一般使用双端队列,没试过用普通Queue去做。按照大佬的说法,那个Queue主要是服务于多线程。如果队列里没有东西然后POP的话,会block在那儿,结果就是超时TLE。所以一般还是用collections.deque。

P.S. 双端队列内部实现使用的是双向链表。队列用的链表,栈用的数组,因为它就是在尾巴上进行操作。或者两个队列轮着换来做,这样相当于栈。

关于deque一般也就用popleft和append,很少用到它的appendleft和pop。用deque两个好处,一个就是快,Queue它服务于多线程,会加锁,所以会慢一些。以上说的是python,Java的queue没问题。

然后BFS的思路用语言说可以总结成这么两步:

创建一个队列,并把起点放置进去

while队列不空,弹出队头,将它的邻居加入队列。(如果是图需要哈希表来防止访问已经到过的节点)

1
2
3
4
5
6
7
8
9
10
11
from collections import deque
if not root:
return []

queue = deque([root])

while len(queue):
head = queue.popleft()
for neighbor in head.neighbors:
do_sth_with(neighbor)
queue.append(neighbor)

If we care about level

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import deque
if not root:
return []

queue = deque([root])

while len(queue):
currenlevel = []
for _ in range(len(queue)):
head = queue.popleft()
currenlevel.append(head.val)
for neighbor in head.neighbors:
do_sth_with(neighbor)
queue.append(neighbor)

BFS Key Points

使用队列作为主要的数据结构 Queue

思考:用栈(Stack)是否可行?为什么行 or 为什么不行?

是否需要实现分层?

需要分层的算法比不需要分层的算法多一个循环

Java / C++: size=queue.size()

如果直接 for (int i = 0; i < queue.size(); i++) 会怎么样?

BFS in Binary Tree

Binary Tree Level Order Traversal : https://leetcode.com/problems/binary-tree-level-order-traversal/

Binary Tree Serialization (M+Y) https://leetcode.com/problems/serialize-and-deserialize-binary-tree/

P.S. 什么是序列化

将“内存”中结构化的数据变成“字符串”的过程。

序列化:object to string

反序列化:string to object

什么时候需要序列化?

  1. 将内存中的数据持久化存储时

    内存中重要的数据不能只是呆在内存里,这样断电就没有了,所需需要用一种方式写入硬盘,在需要 的时候,能否再从硬盘中读出来在内存中重新创建

  2. 网络传输时

    机器与机器之间交换数据的时候,不可能互相去读对方的内存。只能讲数据变成字符流数据(字符串) 后通过网络传输过去。接受的一方再将字符串解析后到内存中。

    常用的一些序列化手段:

    • XML

    • Json

    • Thrift (by Facebook)

    • ProtoBuf (by Google)

序列化算法

一些序列化的例子:

• 比如一个数组,里面都是整数,我们可以简单的序列化为”[1,2,3]”

• 一个整数链表,我们可以序列化为,”1->2->3”

• 一个哈希表(HashMap),我们可以序列化为,”{\”key\”: \”value\”}”

序列化算法设计时需要考虑的因素:

• 压缩率。对于网络传输和磁盘存储而言,当然希望更节省。

​ • 如 Thrift, ProtoBuf 都是为了更快的传输数据和节省存储空间而设计的。

• 可读性。我们希望开发人员,能够通过序列化后的数据直接看懂原始数据是什么。

​ • 如 Json,LintCode 的输入数据

Binary Tree Level Order Traversal II https://leetcode.com/problems/binary-tree-level-order-traversal-ii/

Binary Tree Zigzag Level Order Traversal https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/

Flatten Binary Tree to Linked List https://leetcode.com/problems/flatten-binary-tree-to-linked-list/

BFS in Graph

和树上有什么区别?

Ans: 图意味着有环,所以需要一个哈希表来记录已访问的节点,防止重复访问。

Problems:

Clone Graph (F) https://leetcode.com/problems/clone-graph

Word Ladder https://leetcode.com/problems/word-ladder/

Word Ladder II https://leetcode.com/problems/word-ladder-ii/

最典型的BFS问题 —— 隐式图 (Implicit Graph) 最短路径

BFS in Matrix

矩阵与图有什么区别呢?

图 Graph

N个点,M条边

M最大是 O(N^2) 的级别

图上BFS时间复杂度 = O(N + M)

• 说是O(M)问题也不大,因为M一般都比N大

所以最坏情况可能是 O(N^2)

矩阵 Matrix

R行C列

RC个点,2RC 条边(每个点上下左右4条边,每条边被2个点共享)。

矩阵中BFS时间复杂度 = O(R * C)

Problems:

Number of Islands https://leetcode.com/problems/number-of-islands/

Topological Sorting

几乎每个公司都有一道拓扑排序的面试题。

插一嘴,能够用 BFS 解决的问题,不要用 DFS 去做。

因为用 Recursion 实现的 DFS 可能造成 StackOverflow! (Iteration 的 DFS 一来你不会写,二来面试官也看不懂)

算法:

入度(In-degree):

有向图(Directed Graph)中指向当前节点的点的个数(或指向当前节点的边的条数)

算法描述:

  1. 统计每个点的入度

  2. 将每个入度为 0 的点放入队列(Queue)中作为起始节点

  3. 不断从队列中拿出一个点,去掉这个点的所有连边(指向其他点的边),其他点的相应的入度 - 1

  4. 一旦发现新的入度为 0 的点,丢回队列中

拓扑排序并不是传统的排序算法 一个图可能存在多个拓扑序(Topological Order),也可能不存在任何拓扑序

拓扑排序的四种不同问法

求任意1个拓扑序(Topological Order)用上面的BFS思路即可

问是否存在拓扑序(是否可以被拓扑排序) 用上面的BFS思路即可

求所有的拓扑序 DFS

求是否存在且仅存在一个拓扑序 Queue中最多同时只有1个节点

还有一种:

求最小拓扑序(alien dictionary)

Problems:

Course Schedule https://leetcode.com/problems/course-schedule/

Course Schedule II https://leetcode.com/problems/course-schedule-ii/

Alien Dictionary https://leetcode.com/problems/alien-dictionary/

考点1:如何构建图 考点2:如何存储图 考点3:如何拓扑排序

Conclusion

• 能用 BFS 的一定不要用 DFS(除非面试官特别要求)

• BFS 的两个使用条件

​ • 图的遍历(由点及面,层级遍历)

​ • 简单图最短路径

• 是否需要层级遍历

​ • size = queue.size()

• 拓扑排序须掌握

• 坐标变换数组

​ • deltaX, deltaY

​ • inBound

持续施工中

What is mono stack?

Monotonic stack is actually a stack. It just uses some ingenious logic to keep the elements in the stack orderly (monotone increasing or monotone decreasing) after each new element putting into the stack.

本质上来说,单调栈就是一个栈。只不过我们在元素加入过程中会进行pop操作来保证栈的单调性。

Advantage of mono stack:

Linear time complexity, all elements will only be put into the stack once, and once they are out of the stack, they will not come in again.

使用单调栈往往意味着线性复杂度解决问题,所以如果你发现了题目有着单调栈的pattern,那么就可以尝试使用单调栈优化算法。

Properties of monotonic stack

  1. The elements in the monotonic stack are monotonic

  2. Before elements are added to the stack, all elements that destroy the monotonicity of the stack will be deleted at the top of the stack

What kind of problem use mono stack?

The problems that cares about the first element smaller/greater than it in the left/right.

很关键的一个问题,什么样的问题能够使用单调栈呢?

其实我们会发现,单调栈只擅长解决很小的一类问题,它的pattern十分明显:

那就是如果问题关心第一个左边/右边比它小/大的元素,那么就可以使用单调栈。

Intuition behind mono stack

Suppose we want to solve this question:

Given an array, we want to output the first element smaller than it in the left for every number in that array.

What is the naive way?

Just use two for/while loop to solve it brute forcely.

But clearly there are many repeating computation. So how to solve it efficiently?

We store elements before ai into a stack:[a0~ai-1] .

And each time search through this stack.

But what if there are a pair aj >= ak(j < k):**

Then aj will never be chosen.

Thus for all j < k, aj < ak. AKA monotonic**.

好了,说了半天,我们来举一个例子吧。

考虑这样一个问题:

给定一个数组,对于该数组中的每个数字,我们要输出的左边第一个比它小的数字。

什么是“天真”的方式?

只需使用两个for / while循环即可强行解决它。

但是显然有很多重复的计算。 那么如何有效地解决呢?

就是使用单调栈

Code template

1
2
3
4
5
6
stk = []
for i in range(len(nums)):
while (stk && check(stk[-1], nums[i])): stk.pop()
stk.append(nums[i])
def check(num1, num2):
return num1 >= num2

Extension

We just solved smaller/left.

How about greater or right?

for i in range(len(nums) - 1, -1, -1):

return num1 <= num2

我们可以轻松地扩展单调栈应用到比右边或者第一个比它大的数。

Problems

496 Next Greater Element I: https://leetcode.com/problems/next-greater-element-i/

503 Next Greater Element II: https://leetcode.com/problems/next-greater-element-ii/.

42 Trapping Rain Water: https://leetcode.com/problems/trapping-rain-water/.

84 Largest Rectangle in Histogram: https://leetcode.com/problems/largest-rectangle-in-histogram/

Reference

  1. https://github.com/labuladong/fucking-algorithm/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%B3%BB%E5%88%97/%E5%8D%95%E8%B0%83%E6%A0%88.md

  2. 单调栈的介绍以及一些基本性质_多反思,多回顾,要坚持。

  3. https://blog.csdn.net/qq_17550379/article/details/86519771

最近阅读论文十分痛苦,一方面是因为英文文献阅读本就不易,另一方面是我深感论文的阅读技巧这一块还不到位。于是在知乎找了篇回答学习一下:如何快速阅读英文论文并提取重要信息? - 科研Up主小陈的回答 - 知乎 https://www.zhihu.com/question/42920162/answer/1288743308。

分为五个部分:

1. 两分钟就可以完成的文献泛读

2.可能是最简单的文献精读方法

3.怦然心动的精读文献收纳整理法

4.最高效的阅读,是边写边读

5.要点总结

1. 两分钟就可以完成的文献泛读

阅读文献的流程:搜索→泛读→下载→精读→整理笔记→开始写作输出。

对于理工科来说,泛读关注这几个部分:一头一尾一图

  • 摘要
  • 总结
  • 图表

当然,并不需要每篇文章都看这几个部分,摘要是必须要读的,其他的部分可以酌情省略。

这个阶段只关心一件事:本文对我接下来的研究有没有帮助?


2.可能是最简单的文献精读方法:摘录+划重点

泛读选好了需要进一步了解的文章后,便进入了精读阶段。精读也是有门道的。

很多时候阅读论文都是读了后面便忘了前面,这样来回反复的阅读,极大打击了我们的积极性,让人沮丧。

按照答主的分析,这是因为我们没有给自己建立正反馈机制,没有一种明确的“我读完了读懂了”的成就感。为了克服这一问题,提出了一种高效的阅读文献的方法:就是一边看一边摘录信息——对于需要通读全文的非综述类的文章,我会用一个Excel表来简单记录“这几十篇文献说了什么”。

一般来说,表头会有:第一作者,年份,题目,方法,论文结果,论文优势,论文缺点,还有一两句话的论文评论。

其中,方法这一项对于理工科来说可能很多,可以分为几步概括。

这一两句话的评论就是你看完之后对这篇文章的评价,所以可以口语化表达。

当你对这个方法熟练了以后,通常对一篇英文文献做一个简单的摘抄不会超过20分钟,快的话10分钟就结束了。

另外,在摘录的过程中,如果我看到了有用的信息,我还会做的一件事是划重点。我一般用两种颜色来划重点,红色表示接下来论文写作/输出时会用到的内容,黄色表示这个信息要配合后续搜索/还没有理解(当后续搜索完成了,会再用黑笔打勾表示完成,如图)。

通常如果这个内容重点比较模糊,我不确定之后再回看这篇文章这个部分会不会回忆到这个重点是为什么画的,我会在旁边加上一句表示“画重点的目的”

大家其实可以看到我的笔记其实做得很简单,但是这样读文献会比较轻松。天下精读文献,唯快不破,因为读文献本身不是目的,读文献最终的目的就是写文章,所以要让信息为自己所用。不要让“读文献”这件事占用太多时间,读到怀疑人生,反而消耗了太多精力和时间,消磨了自己做学术的热情。

这种摘录+划重点的笔记方式是我自己实践过最快速,最简单方便,也是最有效率的笔记方法,喜欢用其他笔记方法的同学,可以参考一下,或者在评论区留言分享自己的经验和心得。


3.怦然心动的精读文献收纳整理法

一篇文献,绝对不是你像上面那样读完就结束的,我认为,无论是本科生,研究生,还是博士生,我推荐大家一定要做好的是文献的收纳和整理,这个对于后续的写作和之后对于项目的follow up都至关重要。

但是我目前发现比找文献更麻烦的,是去找自己读过有印象也引用了但是因为各种原因(文献管理软件崩了,自己没做好文献插入等等)而找不到的文献(不要问我是怎么知道的)。

光用文字很难去描述我是怎么做文献的收纳整理的,下图是我收纳已读文献的一个方法。

1)我会把精读文献的pdf下载好放到同一个“子项目文献”文件夹中,阅读时画的重点,可以直接保存;

2)刚刚我们做好的重要文献的Excel 摘录,我们可以把它放在“共同文件”文件夹中,当我们的读过的文献有更新的时候,再将它们记录在案;当子项目增加时,可以用新增工作簿来重新再做摘录;

3)【文献管理进阶】在下载这些文献的同时,我也会下载它们的RIS文件,然后用Endnote library打开,在Endnote中进行文献的管理。不同的子项目,在Endnote中做一个基本的分组。

网络上关于Endnote入门的用法的帖子有很多,迟点我写关于系统性综述的文章的时候也再写写。关于如何使用Endnote的视频链接我贴在这里,EndNote X9 快速上手官方视频教程(6分钟),不懂的大家可以摸索一下(如果是用mendeley的同学,其实方法也差不多)。

4)对于已经放到了“子项目1-文献”文件夹的文献,用自己看得懂的方法命名,我比较喜爱的方法是:第一作者姓氏+出版年份+关键词1+关键词2(如图)。这样,当我们回去看表格里我们的笔记的时候,也可以很轻松找到我们画过重点的pdf。


4.最高效的阅读,是边写边读

其实按照这种方法整理好论文之后,我的建议是,不妨打开你存pdf的文件夹,回头看看自己下载过的这些文献。

简单地说,就是当你在写哪个部分的时候,就去看别人的哪个部分是怎么写的

  • 当你写Introduction的时候,就去看那些你觉得Introduction写得好的文章,看看人家的开篇结构,摆的数据来源,引用的文章是哪些,有没有提及哪篇综述;
  • 写Method和Result的时候,参考那些跟你用相同或者类似方法的文章,看一下人家是怎么样将实验的流程,仪器,数据之类的复杂东西写清楚写明白的;
  • 写Discussion的时候,参考那些你觉得discussion写得好的文章,看看有没有什么别人讨论过的问题你还没讨论的,然后也看看那些跟你用类似方法的文章,是不是可以将人家的文章的结果跟你的做一个对比,等等。
  • 写conclusion的时候,就去看那些你觉得conclusion写得好的文章。

​ 好的文章大多数是相似的,而差的文章各有各的差法。当你读得越多,你越能会体会到好的论文应该是怎样的。

这时候阅读文献,相当于一个学习写作的过程,除了模仿别人的语法和句式结构,也要模仿别人的推理演绎的逻辑过程,去体会什么是一篇结构优良的文章,在写作过程中,怎么兼顾科学性和可读性(句子之间的关系,文章的起承转合等等)。


5.要点总结

  1. 读文献的流程:搜索→泛读→下载→精读→整理笔记→开始写作

  2. 先泛读,需要的文章再下载,只看题目和摘要,泛读过程2分钟就可以了

  3. 精读时,边读边摘抄必要信息,整理到Excel表中

  4. 除了必要信息外,用红黄两色记重点,红色记有用于写作的内容,黄色记需要后续搜寻的内容

  5. 画过重点的文献根据内容命名,之后分类放好在文件夹中以免日后找不到笔记;有用文献管理软件的同学,Endnote也要更新。

  6. 写作时,回到文件夹中,写哪个部分就看参考文献的那个部分是怎么写的,边模仿边学习。

通过Hexo + Github page部署了自己的第一个博客,接下来会在这里记录学习甚至是其他方面的东西。

无总结则无进步嘛,之前学过的很多东西,经历过的很多的事,没有见诸文字,也就慢慢地忘记了。这也是部署这个博客很重要的一个原因。

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment