40岁总监,拥有10年+经验的财务总监,在北京求职屡屡碰壁,甚至做好了“离开北京”的最坏打算。。
44岁,财务总监,10多年经验,按理说不该这么难看吧,结果在北京投简历投到怀疑人生,连“要不要离开北京”都开始想了。
你以为自己攒的是资历,公司那边看见的可能是年龄、薪资、稳定但不够便宜。财务岗位又不像销售那样立马拉业绩,很多老板嘴上说要经验,真到出钱的时候,又想找个年轻点、便宜点、能熬点的。
北京更现实,机会多,但筛人也狠。你稍微慢一步,就有人比你年轻、比你便宜,还愿意加班。中年人不是没能力,是突然发现自己被放到另一个价目表里了。
算法题:词典中最长的单词
banana 明明最长,代码却不该返回它。
这个题最容易写错的地方就在这:它问的不是“词典里长度最长的单词”,而是“能一步一步从短前缀长出来的最长单词”。
比如词典是这样:
words = ["a", "banana", "app", "ap", "appl", "apple", "apply"]
很多人第一眼就会写个排序,按长度倒序拿第一个。
这地方我第一眼就不太信。
因为 banana 虽然长,但它的前缀链不完整:
b
ba
ban
bana
banan
banana
这些前缀词典里都没有,所以它不合格。
而 apple 就不一样:
a
ap
app
appl
apple
每一层都在词典里。
这个题真正要查的是:一个单词的所有前缀,是否都出现过。
先看一个容易错的版本:
deflongest_word_bad(words):
words.sort(key=lambda x: len(x), reverse=True)
return words[0]
这段代码跑上面的数据,直接返回 banana。
看着短,错得也干脆。
这种写法只看长度,不看“生长路径”。题目里那个“built one character at a time”才是关键,不是装饰词。
我平时处理这种题,一般先把词典塞进 set。原因很简单,查前缀要频繁查,如果每次都在 list 里扫,数据一大就很难看。
第一版可以写得直接一点:
deflongest_word(words):
bag = set(words)
ans = ""
for word in words:
ok = True
for end in range(1, len(word)):
if word[:end] notin bag:
ok = False
break
ifnot ok:
continue
if len(word) > len(ans):
ans = word
elif len(word) == len(ans) and word < ans:
ans = word
return ans
这段代码有两个判断。
第一个判断长度:
len(word) > len(ans)
谁长用谁。
第二个判断字典序:
len(word) == len(ans) and word < ans
长度一样,取字典序更小的。
比如:
words = ["a", "ar", "art", "ap", "app"]
art 和 app 都是 3 个字符,如果它们的前缀都合法,那返回 app,不是 art。
这里别手滑写成 word > ans。这个坑很小,但测试用例会精准打脸。
跑一下:
cases = [
["w", "wo", "wor", "worl", "world"],
["a", "banana", "app", "ap", "appl", "apple", "apply"],
["z", "za", "zac", "a", "ap", "app"]
]
for item in cases:
print(item, "=>", longest_word(item))
输出应该是:
['w', 'wo', 'wor', 'worl', 'world'] => world
['a', 'banana', 'app', 'ap', 'appl', 'apple', 'apply'] => apple
['z', 'za', 'zac', 'a', 'ap', 'app'] => app
这版没问题,但我不太喜欢它。
每个单词都要切片查前缀,word[:end] 会生成新字符串。题目数据不大没事,真放到词库校验脚本里,我会稍微换个写法。
更顺手的办法是先排序。
按字典序排序后,再从短词往长词处理。只要一个单词去掉最后一个字符的前缀已经合法,那这个单词也合法。
比如 apple 只需要看 appl 合不合法,不用每次重新查 a、ap、app、appl。
代码这样写:
defpick_longest_word(words):
words.sort()
ready = {""}
best = ""
for word in words:
parent = word[:-1]
if parent notin ready:
continue
ready.add(word)
if len(word) > len(best):
best = word
return best
这里有个小细节:
ready = {""}
空字符串先放进去。
这样长度为 1 的单词,比如 a、b,它们的 word[:-1] 都是空字符串,可以自然通过,不用单独写一堆 if。
还有一个细节,为什么只判断长度,不判断字典序?
因为前面已经 words.sort() 了。
同样长度的单词,字典序小的会先被遍历到。后面字典序大的即使合法,长度也不会超过 best,所以不会替换。
这块当时我也怀疑过,会不会漏掉更小的答案。实际不会,因为排序已经把这个规则提前处理掉了。
拿刚才的数据再跑:
tests = [
["w", "wo", "wor", "worl", "world"],
["a", "banana", "app", "ap", "appl", "apple", "apply"],
["b", "br", "bre", "brea", "break", "a", "ar", "arc"]
]
for words in tests:
print(pick_longest_word(words))
输出:
world
apple
break
这版复杂度主要在排序,O(n log n)。
后面每个单词只查一次 set,整体比较稳。
不过有个前提要记住:它不是找“最长字符串”,它找的是“每一层前缀都能在词典里找到的最长字符串”。
所以看到这题,我一般先写两个反例:
["banana", "ban", "bana"]
不能返回 banana。
再写一个平局反例:
["a", "ap", "app", "b", "ba", "ban"]
应该返回 app,不是 ban。
这两个用例过了,基本方向就没歪。
最后把代码收一下:
deflongest_word_in_dictionary(words):
words.sort()
built = {""}
answer = ""
for word in words:
if word[:-1] notin built:
continue
built.add(word)
if len(word) > len(answer):
answer = word
return answer
这个题不难,难的是别被“最长”两个字带跑。
真正要看的,是这个单词有没有从第一块砖开始,一层一层垒起来。