每周完成一个 ARTS:
Algorithm: 每周至少做一个 LeetCode 的算法题
Review: 阅读并点评至少一篇英文技术文章
Tips: 学习至少一个技术技巧
Share: 分享一篇有观点和思考的技术文章
题图:君の名は
题目:234. Palindrome Linked List
难度:Easy
题意:Given a singly linked list, determine if it is a palindrome.
示例 1:
Input: 1->2
Output: false
示例 2:
Input: 1->2->2->1
Output: true
Follow up:
Could you do it in O(n) time and O(1) space?
解法 1:
我们首先试一个稍微简单的方法。
判断回文问题,核心思想是从中间向两端扩展。我们可以使用双指针法找到中间节点,慢指针此时经过的部分就是前半部分,我们可以把这些部分保存起来,再与慢指针之后将要经过的部分作比较,就可以判断这个单链表是否为回文链表了。
代码:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
slow, fast = head, head
stack = []
while fast and fast.next:
stack.append(slow.val)
slow = slow.next
fast = fast.next.next
# When the length of the linked list is an odd number, we need to forward slow by one step
if fast:
slow = slow.next
while slow:
top = stack.pop()
if top != slow.val:
return False
slow = slow.next
return True
注意一个问题,如果链表的长度是奇数,我们找到的中间节点是正好的中间节点,为了往后继续比较,我们需要把慢指针再往后移一位,再与之前保存的数组相比较。
这样的解法时间复杂度和空间复杂度都是 O(n)。
解法 2:
上面第一种解法,虽然是以数组的形式存储的前半部分,但本质上还是反转前半部分的链表,我们还可以写一个反转链表的函数,来帮助我们反转,比较方便的办法是反转后半部分:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# When the length of the linked list is an odd number, we need to forward slow by one step
if fast:
slow = slow.next
left, right = head, self.reverse(slow)
while right:
if left.val != right.val:
return False
left = left.next
right = right.next
return True
def reverse(self, head: ListNode) -> ListNode:
pre, cur = None, head
while cur:
next = cur.next
cur.next = pre
pre = cur
cur = next
return pre
这样我们就只需要保存反转之后后半部分链表的头结点即可,实现 O(1) 的空间复杂度。
本周要分享的英文文章是 LessWrong! 上的一篇文章,《100 Tips for a Better Life》。文章中,作者 Ideopunk 给出了他认为能够给我们生活带来提升的 100 条建议,有如下若干个方向:物品、烹饪、生产力、身体、成功、理性、自我提升、危险行为、杂项、人际关系、同理心、快乐。
我随便截取几个放到这里:
文章中大部分建议都给出了相应的参考链接,有理有据,值得一读。
最近发现一个 Notion 的插件,叫 Indify,它为 Notion 提供了 8 个实用的小组件,可以让你在你的 Notion 页面中嵌入:时间、倒计时、计数器、谷歌日历、增强的图片库、生命进度条、名人名言、天气这八个小组件。
我目前是用了倒计时和天气这两个小组件:
你可以在 Indify.co 注册你的账号并生成自己的小组件,然后在 Notion 中嵌入它们。
最近写论文要画流程图,之前一直是使用「PPT 画图 -> 导出为 PDF -> 嵌入图像」这样的流程,但有时会遇到导出图像的字体和 PPT 中的字体大小不一致的情况,于是想着试试用 TikZ 在 LaTeX 中直接画图的方式。
TikZ 是 LaTeX 的一个包,用它可以制作各种复杂但高质量的图。这里我简单介绍一下如何用 TikZ 来画流程图。
要使用 TikZ 画流程图,第一步是调用 tikz
包,同时调用 TikZ 的两个标准库 shapes.geometric
和 arrows
:
\usepackage{tikz}
\usetikzlibrary{shapes.geometric, arrows}
第二步,我们需要对流程图的一些基本元素的形状、颜色等属性进行定义:
\tikzstyle{startstop} = [rectangle, rounded corners, minimum width=3cm, minimum height=0.5cm, text centered, draw=black, fill=red!30]
上面这条语句,我们将「开始结束」模块定义如下:圆角矩形,最小宽度 3 厘米,最小高度 0.5 厘米,文本居中显示,黑色边框,30% 浓度红色(简单理解)填充。
\tikzstyle{io} = [trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=0.5cm, text centered, draw=black, fill=blue!30]
上面这条语句,我们定义了「输入输出」模块:梯形,左边倾斜角 70 度,右边倾斜角 110 度(这样我们就得到一个平行四边形),最小宽度 3 厘米,最小高度 0.5 厘米,文本居中显示,黑色边框,30% 浓度蓝色填充。
\tikzstyle{process} = [rectangle, minimum width=3cm, minimum height=0.5cm, text centered, draw=black, fill=orange!30]
上面这条语句,我们定义了「步骤」模块:矩形,最小宽度 3 厘米,最小高度 0.5 厘米,文本居中显示,黑色边框,30% 浓度橙色填充。
\tikzstyle{decision} = [diamond, minimum width=3cm, minimum height=0.5cm, text centered, draw=black, fill=green!30]
上面这条语句,我们定义了「判断」模块:菱形,最小宽度 3 厘米,最小高度 0.5 厘米,文本居中显示,黑色边框,30% 浓度绿色填充。
\tikzstyle{arrow} = [thick,->,>=stealth]
这条语句定义了「箭头」:粗,箭头方向是 ->,箭头类型 stealth。
上面两步需要在 \begin{document}
之前完成。
完成上面两步之后,在正文部分,我们就可以开始画图了,下面介绍一下基本的画法。
我们需要使用 \begin{tikzpicture}
开始画图,注意这个语句最好是放在 \begin{figure}
内部,这样可以进行居中操作、添加描述与标签。下面是一个常用的格式:
\begin{figure}
\centering
\begin{tikzpicture}[node distance=2cm]
...
\end{tikzpicture}
\caption{这是一个图片的描述}
\label{fig:1}
上面的格式完成之后,我们就可以开始添加元素了,我们先添加几个常用的节点:
\begin{figure}
\centering
\begin{tikzpicture}[node distance=2cm]
\node (start) [startstop] {Start};
\node (proc1) [process, below of=input] {Process 1};
\node (dec) [decision, below of=proc1] {Decision};
\node (proc2a) [process, right of=dec] {Process 2a};
\node (proc2b) [process, below of=dec] {Process 2b};
\node (output) [io, below of=proc2b] {Output};
\node (stop) [startstop, below of=output] {Stop};
\end{tikzpicture}
\caption{Flowchart Example.}
\label{fig:1}
\end{figure}
排版的结果如下:
我们可以看到,添加节点的语句很容易懂:
\node (node 名称) [node 类型, node 的相对位置, 其他参数] {node 中的文字};
但是有一个问题,我们的 Process 2a 与 Decision 重叠了,这个时候,我们可以在其他参数
中加上 xshift=2cm
来将其往右移动 2 厘米,如果是 xshift=-2cm
就是向左移动 2 厘米了,yshift
的用法同理。这样对应的代码就变成:
\node (proc2a) [process, right of=dec, xshift=2cm] {Process 2a};
而节点的位置也正常了:
下面我们来添加箭头。我们只需要在 node
下面继续添加 arrow
即可:
\draw [arrow] (start) -- (input);
\draw [arrow] (input) -- (proc1);
\draw [arrow] (proc1) -- (dec);
\draw [arrow] (dec) -- node[anchor=south] {no} (proc2a);
\draw [arrow] (dec) -- node[anchor=east] {yes} (proc2b);
\draw [arrow] (proc2a) -- (proc1);
\draw [arrow] (proc2b) -- (output);
\draw [arrow] (output) -- (stop);
添加箭头的语句也很容易明白:
\draw [arrow] (起点) -- (终点);
而如果要在箭头旁边添加文字说明的话,就要在 --
和 (终点)
后面添加 node[anchor=箭头的相对文字的位置(东南西北)] {文字内容}
:
\draw [arrow] (dec) -- node[anchor=south] {no} (proc2a);
\draw [arrow] (dec) -- node[anchor=east] {yes} (proc2b);
最后一点,我们可以看到,从 Process 2a 到 Process 1 的箭头是一条直线,并不美观,我们可以把它改成折线。
只需要把 --
改成 |-
就可以了,它表示箭头会先竖着走,再横着走:
\draw [arrow] (proc2a) |- (proc1);
最终的代码如下:
\begin{figure}
\centering
\begin{tikzpicture}[node distance=2cm]
\node (start) [startstop] {Start};
\node (proc1) [process, below of=input] {Process 1};
\node (dec) [decision, below of=proc1] {Decision};
\node (proc2a) [process, right of=dec] {Process 2a};
\node (proc2b) [process, below of=dec] {Process 2b};
\node (output) [io, below of=proc2b] {Output};
\node (stop) [startstop, below of=output] {Stop};
\draw [arrow] (start) -- (input);
\draw [arrow] (input) -- (proc1);
\draw [arrow] (proc1) -- (dec);
\draw [arrow] (dec) -- node[anchor=south] {no} (proc2a);
\draw [arrow] (dec) -- node[anchor=east] {yes} (proc2b);
\draw [arrow] (proc2a) |- (proc1);
\draw [arrow] (proc2b) -- (output);
\draw [arrow] (output) -- (stop);
\end{tikzpicture}
\caption{Flowchart Example.}
\label{fig:1}
\end{figure}
最后的生成的流程图如下: