某大厂员工吐槽:跟父母交谈说今年钱不好挣,我妈说多的不说了,打工一年最少要挣个45十万...
刚刷到这个帖子,我先是笑了一下,随后又有点笑不出来。
孩子在那边铺垫半天,说今年行情一般,钱不好挣,结果妈妈一句“别的先不说,打工一年最少也得挣个四五十万吧”
真的,很多长辈对大厂工资的理解,还停留在“进去了就自动开金矿”那个阶段,默认你工牌一挂,年薪就得往上蹿。
问题是现在这环境,别说轻轻松松四五十万了,能稳稳当当把班上着、绩效别翻车,都已经算谢天谢地。最扎心的是,你还没法认真解释,解释了他们会觉得你在谦虚,或者干脆觉得你不上进。
HR听了沉默,员工听了头皮发麻,家里人还觉得这个目标很朴素。这个代沟,有时候真不是靠沟通能补的。
算法题:最大回文数乘积
数字一大,暴力解法就开始露馅。
“最大回文数乘积”这题,很多人第一反应都是双重循环:从大到小枚举两个数,乘出来以后再判断是不是回文。能做,但我第一眼就不太信这种写法。原因很简单,判断回文不贵,贵的是你把大量根本不可能成为答案的乘积也算了一遍。
这题更像线上排查慢 SQL:先别急着把所有可能都扫一遍,先想想哪些分支能提前砍掉。
假设题目要求:求两个 n 位数乘积形成的最大回文数。
最直的思路是这样:
defis_palindrome(x: int) -> bool:
s = str(x)
return s == s[::-1]
defmax_palindrome_product(n: int) -> int:
high = 10 ** n - 1
low = 10 ** (n - 1)
best = 0
for a in range(high, low - 1, -1):
for b in range(a, low - 1, -1):
val = a * b
if val <= best:
break
if is_palindrome(val):
best = val
break
return best
这版已经比无脑双循环顺眼一点了,至少加了一个 val <= best 就停的剪枝。因为 b 是递减的,后面只会更小,没必要继续试。
但还能再收一刀。
偶数位回文数有个很实用的性质:一定能被 11 整除。这个结论拿来做题很好使。像两个 n 位数乘积得到的最大回文,绝大多数时候我们盯的都是偶数位回文,那枚举因子时,至少有一个数得是 11 的倍数。这样循环能少掉一大截。
我平时更愿意写成下面这样:
defis_palindrome(x: int) -> bool:
text = str(x)
return text == text[::-1]
deflargest_palindrome(n: int) -> int:
if n == 1:
return9
upper = 10 ** n - 1
lower = 10 ** (n - 1)
ans = 0
for left in range(upper, lower - 1, -1):
if left * upper <= ans:
break
if left % 11 == 0:
right_start = upper
step = 1
else:
right_start = upper - (upper % 11)
step = 11
for right in range(right_start, left - 1, -step):
product = left * right
if product <= ans:
break
if is_palindrome(product):
ans = product
break
return ans
print(largest_palindrome(2)) # 9009
print(largest_palindrome(3)) # 906609
这段代码的关键不在“判断回文”,而在“少算很多没意义的组合”。
left * upper <= ans 是第一层止损。 如果当前左边这个数,哪怕配上最大的右边,也不可能超过已有答案,那这一整轮直接结束。
第二层是 11 的倍数剪枝。left 不是 11 的倍数时,right 就只枚举 11 的倍数。这个地方看着只是改了个步长,实际能省掉不少计算。
这题真正想明白的,不是怎么写回文判断,而是怎么利用数学性质缩搜索范围。很多算法题都这样,表面在考代码,实际上在考你会不会先怀疑暴力解法。
一上来就把所有组合全跑完,通常不是不会写,是没想着先剪枝。