题目要求:
输入两个字符串名叫 needle
和 haystack
,找到 needle
出现在 haystack
中的位置,如果没有出现则返回 -1 。字符串取名出自英语的 Needle in a haystack 即“大海捞针”。
思路:
对于这样的问题,高级算法如 KMP 可以把复杂度控制在 $O(m+n)$ 。但是 KMP 极难理解,也很难在面试中一次写出不出 bug 。因此,解决这个问题时用比较朴素的比较算法即可。
1 | class Solution(object): |
输入两个字符串名叫 needle
和 haystack
,找到 needle
出现在 haystack
中的位置,如果没有出现则返回 -1 。字符串取名出自英语的 Needle in a haystack 即“大海捞针”。
对于这样的问题,高级算法如 KMP 可以把复杂度控制在 $O(m+n)$ 。但是 KMP 极难理解,也很难在面试中一次写出不出 bug 。因此,解决这个问题时用比较朴素的比较算法即可。
1 | class Solution(object): |
输入数组和一个目标数字,要求从数组中原位去掉全部目标数字,并返回新数组长度,不要使用额外的存储空间。
与 Leetcode 026 Remove Duplicates from Sorted Array 思路一致。采用快慢指针,快指针遍历数组,慢指针从零开始,遇到非目标数字时将快指针的数值复制到慢指针处,慢指针前进一位。
1 | class Solution(object): |
输入一个有序,并且可能含有重复内容的数组,原位删除重复内容,空间复杂度为常数级别,并返回无重复数组长度。无重复部分之外内容不限。
快慢指针,快指针遍历数组,慢指针从零开始,每当遇到一个新数字时将新数字复制到当前慢指针位置并前进一位,最后返回慢指针位置加 1 。
1 | class Solution(object): |
输入一个单向链表,对相邻的两个节点进行交换。如输入 1->2->3->4
输出 2->1->4->3
在开始时在链表头部建立一个额外的辅助节点,用 p
指向它。对每一对链表节点,用 q
标记出来保护引用位置不至于丢失。之后交换两个节点和相应的连接即可。
1 | class Solution(object): |
输入多个排好序的链表,合并成一个排好序的链表。
因为每个链表已经排好序,所以可以采用堆排序加快合并速度。首先将各个链表的表头值和链表编号放入最小堆,按表头值排序。每次取出一个最小值,将其对应的表头放入合并后的链表,并将对应编号的链表的表头向后移动一个位置,再将新表头值和编号放回最小堆。直至所有链表表头均为空,合并完毕。
1 | class Solution(object): |
Diceware是一种生成便于记忆并难以通过暴力穷举密码的方式。很长时间以来,网站都在建议人们使用更加复杂的密码,如:
几种常见的密码组合方式的组合数分别为:
密码成分 | 8位组合数 | 12位组合数 | 16位组合数 |
---|---|---|---|
a-z | $10^{11.3}$ | $10^{17.0}$ | $10^{22.6}$ |
a-z0-9 | $10^{12.5}$ | $10^{18.7}$ | $10^{24.9}$ |
a-zA-Z | $10^{13.7}$ | $10^{20.6}$ | $10^{27.5}$ |
a-zA-Z0-9 | $10^{14.7}$ | $10^{22.0}$ | $10^{29.3}$ |
a-zA-Z0-9& | $10^{15.8}$ | $10^{23.7}$ | $10^{31.6}$ |
这样的确可以生成复杂度极高的密码,但是这样的密码对用户非常不友好,难以记忆,所以事实上真正这样做的人并不多。毕竟,没有多少人真得能牢记形如 z28>+J]pFF22c+tj
、 ?WtdCW(a9j9u2+jw
、 @^xtE9am#(eFC9H8
、 T3sY]7]tnRc28U>}
、 i949re{NCjje%/*9
的密码,并且能分清分别对应哪个网站、哪个账户。
用 Diceware 生成密码可以在一定程度上解决这个问题。 Diceware 的原理是:对字典中的每一个单词用长度一定(如5位)的6进制数字编号,生成密码时,掷一枚均匀的六面骰子5次,得到一个5位的6进制数字,根据编号找到并记录这个单词。重复这样的操作3到5次,即可得到长度为3至5个单词的diceware密码。可以针对这几个单词编一个故事便于记忆。
电子前线基金会(EFF)在网站上给出了一个已经完成编号的单词列表,其中包含了 $6^5=7776$ 个单词,另外,在 github 上还可以找到包含近50万单词的词汇列表。根据 EFF 的词典生成的随机密码可以具有相当高的复杂度:
相对于记住十几位毫无规律的随机字符串,通过编故事记住几个形如 aloft-applied-embody
、 mug-gown-getaway
、 idiom-eggnog-sadden
的单词组合应该会容易的多。
利用 Python ,写一个生成 Diceware 密码的程序非常简单:
1 | import collections |
生成密码的部分还可以分别一行写完
1 | print(sep.join([diceware["".join([str(random.randint(1,6)) for i in range(5)])] for j in range(length)])) |
另外在实际应用中,要使用密码学安全伪随机数生成器(CSPRNG)
Coursera 是由斯坦福大学的教授吴恩达(Andrew Ng)和达芙妮·科勒联(Daphne Koller)合创建的一个营利性的教育科技公司,与很多大学都有合作。经过几个月的尝试,我终于在昨天晚上通过了Coursera 上由墨尔本大学提供的Discrete Optimization这一门课,拿到了课程认证。虽然最终的成绩离满分还很远,但是我觉得把我在解决作业问题中的思路记录下来是有一定意义的。我的作业代码保存在了 github 上,欢迎 star 、 fork 、发 pull request 。
假设我们有一个容量为 $K$ 的背包,以及 $n$ 个物品,每个物品有重量 $w_i$ 和价格 $v_i$ ,题目的目的是找到一个背包可以装下的物品组合(总体积不超过背包容量),并且总价格最高。
在作业中,输入数据为背包总容量 cap
和物品列表 items
每个物品有 value
、 weight
、 density
三个属性。输出数据为背包总价值 value
以及长度等于 items
的数组 taken
,其中元素为 1 则表示对应物品放入背包中,否则不放。
背包问题是NP完全问题,不存在“高效”的多项式时间的最优解法,但是可以通过动态规划实现比较高效的解法,或者用整数规划软件包高速求解。
求解背包问题最朴素的办法就是尝试所有可能的物品组合,找到符合要求的最优解。由于每个物品有两种状态,即放进背包和不放进背包,因此朴素解法需要尝试 $2^n$ 种不同的组合才能保证找到最优解,效率极低。
贪心法的原理就是在每一步的时候都采取当前状态下最优的选择,并期望最终能达到比较好的结果。这样做的有点是速度快,对于背包问题来讲,无论是基于物品价格的贪心还是价格密度的贪心,只要计算完成就可以不断向背包中添加物品直至无法添加任何一件新物品。缺点则是并不能达到最优,并且性能没有很好的保障,只能得到近似解或次优解。这里以密度贪心算法举例:
1 | Item = namedtuple("Item", ['index', 'value', 'weight', 'density']) |
动态规划可以比朴素算法更快地得到背包问题的最优解,但是对于规模较大的问题仍然性能极低并且有极高的内存占用。适用于动态规划的问题需要有这样的性质:
用动态规划求解背包问题的思路是:
这里用一组简单的数据举例:
k\j | 0 | 1 | 2 | 3 |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 3 |
3 | 0 | 0 | 0 | 3 |
4 | 0 | 5 | 5 | 5 |
5 | 0 | 5 | 6 | 6 |
6 | 0 | 5 | 6 | 8 |
7 | 0 | 5 | 6 | 9 |
8 | 0 | 5 | 6 | 9 |
9 | 0 | 5 | 11 | 11 |
经过列动态规划表之后,可以看出,最优解选择了前两个物品,包中物品总价值为 11
解题代码如下:
1 | Item = namedtuple("Item", ['index', 'value', 'weight', 'density']) |
背包问题的设定非常适合转换成 ILP 的标准形式之后用软件包直接求解。在上课的过程中,我发现了 Gurobi 这个数值优化神器,性能极高并且可以让在校生免费使用,并且具有非常简单易用的 python
接口。接下来我们只要根据输入数据对问题进行转换即可。
令 $x_i$ 为优化变量, $x_i=1$ 表示选择第 $i$ 个物品, $x_i=0$ 表示不选择第 $i$ 个物品,可以列出优化问题标准形式如下:
$$\begin{align}
\max & \sum x_i v_i \
s.t. & \sum w_i\leq K\
& x_i\in{0,1}
\end{align}$$
解题时,将问题数据转换成 Gurobi 所需的格式即可直接求解:
1 | def mip(cap, items, verbose=False, num_threads=None): |
输入整数 n
要求输出所有合理的由 n
对括号组成的字符串。
采用深度优先搜索(DFS),初始化时左右括号剩余数量均为 n
,搜索中,如果左右括号剩余数均为0,则找到一组解,记录在答案中;如果还剩余左括号,则在当前搜索字符串尾增加一个左括号并继续搜索;同时如果剩余左括号数量小于剩余右括号数量,则在当前搜索字符串尾增加一个右括号并继续搜索,直至所有情况搜索完成,返回答案
1 | class Solution(object): |
给定两个排好序的链表,要求合并成一个新链表,元素来自于两个输入链表并且完成排序。
用两个指针遍历两个链表,同时新建一个链表,每次对比两个指针所在节点的数字大小,将较小的那个添加到新链表中。当一个链表遍历结束后,直接将另一个链表的全部节点附加到新链表结尾即可。
1 | class Solution(object): |
输入一个完全由 '(', ')', '{', '}', '[', ']'
组成的字符串,判定字符串中的括号是不是有效的。
创建维护一个栈,遍历一遍字符串,遇到左括号时压栈,遇到右括号时,如果栈为空或者弹出的第一个字符不是对应的左括号,返回假。遍历结束没有遇到问题则返回真。
1 | class Solution(object): |