公司新来80后大叔,,直接把公司搁了三年的烂尾项目给盘活了,软硬通吃,没过多久,大叔又拉来自己前同事,俩人直接扛起了整个研发部。
刚看到个贴子,说公司招来个80后大叔,本来行政嫌岁数大不想要,是技术总监强撑着给了试用。结果人家进来十来天,把躺了三年的破项目啃下来了,软硬件一串儿搞定。
后面还把自己老战友也拉来,两个人直接扛起整个研发,原来那些小年轻,一边喊师傅一边心里打鼓:这下真得好好干了。
我觉得这事吧,最扎心的一点是:职场从来只看“能不能干成事”。你多年轻、多会说,都不如真把烂摊子收拾了更管用。
对公司来说,别把“年轻化”当口号,真正稀缺的是能救火的人;对年轻人来说,慌没用,赶紧趁身边有高手,多学两招,比在那儿喊“卷死了”实在多了。说到底,年纪只是标签,能创造价值的人,在哪儿都不怕没饭碗。
力
算法题:多米诺和托米诺平铺
这道「多米诺和托米诺平铺」,本质上就是一道非常标准、但细节有点绕的动态规划题,用 Python 写起来其实就十几行。
那天晚上快十一点,我在公司楼下便利店泡面还没泡开,我们组那个小李微信上丢过来一张图:一块 2×n 的长条棋盘,问我怎么数“所有用骨牌、L 型小板”铺满的方式。
你脑子里先想象一下这个棋盘:高度固定是 2,长度是 n。可以用的砖有两种:
多米诺骨牌:2×1 或 1×2 的小长条 托米诺:一个 2×2 的正方形去掉一个角,像个 L
要求就是:刚好铺满,不重叠、不越界,一共有多少种铺法。
说白了,就是问你:“有多少种拼法,把一个 2×n 的洞给填死?”
暴力枚举肯定是能做的,回溯一顿 DFS,看到空格就随便放一个砖,能放就继续,放不下就回退……但你想啊,n 要是上来给你个 30、50,状态数直接炸到天上去,人都算吐了,更别说 Python 代码跑不跑得动了。
这种“前面怎么摆,后面怎么接”的问题,十有八九是动态规划。套路是:我别管整块棋盘,先想“2×n 的答案能不能用 2×(n-1)、2×(n-2) 之类的答案拼出来”。
所以我们先定义一个最核心的东西:
dp[n]:铺满一块 2×n 棋盘的方案数
然后你看小规模的情况:
n = 0:空棋盘,算 1 种(什么都不放)
n = 1:只能竖着放一块 2×1 多米诺,所以是 1
n = 2:情况就多一点了:
两块竖着:|| 两块横着:= 再加上一些 L 型组合,但要仔细想
这里如果你硬去列 n = 3、4 的所有图案,肯定能列出来,但会非常容易漏情况,而且没啥一般性规律。所以更聪明的做法是:承认这个问题有点绕,我们引入一个“中间状态”来帮忙想。
直觉上,当你右边一列“缺一块”的时候,接下来肯定要用一个 L 形托米诺把它补回去。于是很多官方解法会引入一个“带缺口的状态”,比如:
full[n]:铺满 2×n 的方案数(我们刚才叫dp[n])gap[n]:铺到第 n 列时,棋盘右边还缺一个小格子的方案数(上缺或下缺,数量是一样的,就合在一起算)
然后去推转移方程:从 full[n-1]、full[n-2] 和各种 gap[...] 组合到 full[n],再从老的 gap 组合到新的 gap。推着推着你会发现一个很优雅的结果:
可以把所有“缺口状态”都消掉,最后只用
dp[n]一个数组,就能写出一个简单的式子:当 n ≥ 3 时:
dp[n] = 2 * dp[n-1] + dp[n-3]
这个式子什么意思?你可以简单这么理解(别往死里较真细节,就当个直觉):
2 × dp[n-1]:右边最后一块区域,有两大类摆法能“往前延伸一个格子”,所以是乘 2 dp[n-3]:还有一类“直接用三个列宽的花样 L 组合”从 n-3 跳到 n
细节展开会很长,但记住这个递推就够做题了。
前几个值我们手算一下:
dp[0] = 1(空板) dp[1] = 1 dp[2] = 2 dp[3] = 2×dp[2] + dp[0] = 4 + 1 = 5 dp[4] = 2×dp[3] + dp[1] = 10 + 1 = 11
跟网上那道经典题(LeetCode 790)给的样例是一致的。
实战里一般还会要求取模,比如经常见的 10^9 + 7,不然 n 稍微大一点,数字直接飞出地球。
用 Python 写一版很朴素但完全够用的代码,大概就是这样:
MOD = 10 ** 9 + 7
defnum_tilings(n: int) -> int:
# 特殊情况先兜住
if n == 0:
return1
if n == 1:
return1
if n == 2:
return2
dp = [0] * (n + 1)
dp[0] = 1
dp[1] = 1
dp[2] = 2
for i in range(3, n + 1):
dp[i] = (2 * dp[i - 1] + dp[i - 3]) % MOD
return dp[n]
如果你面试的时候想显得再骚一点,还可以把空间优化成 O(1),反正只用到 i-1、i-2、i-3 这几个位置,完全没必要开数组:
MOD = 10 ** 9 + 7
defnum_tilings(n: int) -> int:
if n == 0:
return1
if n == 1:
return1
if n == 2:
return2
# 对应 dp[0], dp[1], dp[2]
dp0, dp1, dp2 = 1, 1, 2
# 从 i = 3 开始往后推
for i in range(3, n + 1):
dpi = (2 * dp2 + dp0) % MOD
dp0, dp1, dp2 = dp1, dp2, dpi
return dp2
时间复杂度 O(n),空间你要数组就是 O(n),要装逼就是 O(1),在面试或者刷题里都非常稳。
最后一个小经验:像这种“拼小板块”的题,一旦你发现:
高度是定值(比如固定 2 行) 只有有限几种小砖 问的是“有多少种拼法”
脑子里就可以条件反射:先试着列出前几个 n 的情况,看能不能凑出一个简单的递推公式。推不出来,再考虑加“带缺口的中间状态”,大概率能搞定。
行了,我先去把刚才那桶泡面吃了,你要是愿意,可以自己拿纸画一画 n=3、4 的所有拼法,对着上面 dp 的值数一数,看是不是对得上。