500万的房子400万卖了,200万又买回来,亏了还是赚了?
刚看到个贴子,说有人500万买的房子,400万卖掉,过两年又花200万买回来,问亏了还是赚了。网友回帖有的说整体只花了300万买到同一套,血赚;有的说中介税费加上时间成本,肯定是亏麻了。
我觉得这事吧,先别算账面价,先想两个问题:第一,中间那段时间,你是不是确实需要那400万去救急、创业或者换城市?如果那时候不卖,后面很多机会可能都没了,那些看不见的收益也得算在里头。第二,再买回来时,这套房是不是当下最合适的选择?地段、学区、通勤都满足需求,那多花一点,就当是为人生重新“买门票”。
房子本质是生活工具,不是K线图。只盯着亏赚,很容易被房价牵着情绪走。
力
算法题:阶乘函数后 K 个零
昨天晚上十一点多,我在电脑前正打算关机睡觉,结果微信蹦出来一条消息: “东哥,你讲讲那个《阶乘函数后 K 个零》呗,看题解看懵了……”
好嘛,本来想摸鱼,结果又被你们按回算法频道了 😂
那就边聊天边说说这道题,顺便用 Python 写个你以后面试能直接拿出来用的模板。
题目名字听着有点严肃:“阶乘函数后 K 个零”。 实际意思是这样的:
定义一个函数
f(n)表示n!这个数尾巴上有多少个零比如:
5! = 120,最后 1 个零,所以f(5) = 110! = 3628800,最后 2 个零,所以f(10) = 2
题目给你一个整数 k,问你:
有多少个非负整数
n,让f(n) = k?
注意哈,是“有多少个 n”,不是“求这个 n 是多少”。 这个就是 LeetCode 那道挺经典的题。
这个你可以这么想: 一个整数末尾有多少个零,其实就是里面能拆出多少个 10。 而 10 = 2 * 5,在 n! 里面,2 的个数远远比 5 多,所以决定零的个数的是质因子 5 的个数。
也就是说:
f(n) = n!里面一共包含多少个因子 5
那怎么算呢? 不能一个个数,那样太折磨。
有个很经典的公式:
f(n) = floor(n / 5) + floor(n / 25) + floor(n / 125) + ...
意思就是:
n / 5:统计有多少个数是 5 的倍数(每个至少贡献一个 5)n / 25:因为像 25、50 这种会多贡献一个 5n / 125:像 125 这种会贡献三个 5,前面只算了两个,还差一个一直除下去,直到结果为 0
这个公式就已经把 f(n) 搞定了。
先写个统计尾零的小函数(Python)
这个函数你以后遇到任何“n! 的尾零个数”都能直接用:
deftrailing_zeros(n: int) -> int:
"""
返回 n! 末尾有多少个 0
"""
res = 0
while n > 0:
n //= 5
res += n
return res
随便试几个:
trailing_zeros(5) = 1trailing_zeros(10) = 2trailing_zeros(25) = 6(因为 25=5²,会多贡献一个 5)
到这一步为止,你已经搞清楚“算 f(n)”了。 但题目真正恶心人的地方在后面:给你 k,反过来问你多少个 n 满足 f(n) = k。
f(n) 有什么怪性格?
简单捋一下 f(n) 的特点:
n越大,n!越大,尾零一定不会变少 👉 所以f(n)是一个单调不减函数不是每个整数 k 都一定能取到 比如有些区间是“跳过去”的,这也是为什么答案有时候是 0
很关键的一点(这题核心结论):
对于任意一个 k,要么没有 n 让
f(n)=k, 要么正好有 5 个连续的 n 满足f(n)=k
也就是说:
要么答案是 0 要么答案是 5
这也是题目名字里那个“K 个零”的真正含义:让你求的是“原像大小”(preimage size)。
怎么从 k 反推回 n?
既然 f(n) 单调不减,我们最顺手的工具就是——二分搜索。
你可以先想一件事: 大概 n 多大的时候,f(n) 会到 k 呢?
每 5 个数里至少贡献一个 5,所以很粗糙估计:
5 * k这个位置,尾零至少有 k 个
实际上:trailing_zeros(5 * k) >= k 这句话是肯定成立的。
那我们可以把搜索区间写成:
left = 0
right = 5 * (k + 1) # 稍微放宽一点,绝对够用了
二分的目标是:看看区间里有没有某个 n,使得 trailing_zeros(n) == k。
如果有,我们知道这样的 n 会连着 5 个,所以答案是 5; 如果没有,那就是 0。
二分查 k 的“原像”
把刚才的想法翻成 Python 代码,大概是这样:
defpreimage_size_fzf(k: int) -> int:
"""
返回满足 trailing_zeros(n) == k 的非负整数 n 的个数
结论:要么是 0,要么是 5
"""
# 搜索区间 [0, 5 * (k + 1)]
left, right = 0, 5 * (k + 1)
while left <= right:
mid = (left + right) // 2
z = trailing_zeros(mid)
if z < k:
left = mid + 1
elif z > k:
right = mid - 1
else:
# 一旦找到一个 mid 使得 f(mid) == k
# 根据性质,一定有连续 5 个,这里直接返回 5
return5
# 整个区间都没找到,说明没有 n 让 f(n) == k
return0
这段代码干了几件事:
用 trailing_zeros(mid)算当前中点的尾零数尾零太少就往右找,太多就往左找,刚好等于就成功 因为 f(n)单调不减,二分是安全的,不会漏
时间复杂度大概是:
每次 trailing_zeros是O(log₅ n)二分本身是 O(log n)合起来 O((log n)²),实际跑起来很快,在线评测完全没压力。
顺带:真想把那 5 个 n 找出来也行
虽然题目只问有几个,但你要是好奇,想把那 5 个 n 具体求出来,也不难。
思路很简单:
先用上面那套二分,找出最小的 n,使得 f(n) = k那后面的 n+1, n+2, n+3, n+4也都会满足f(n) = k
再做一次“左边界二分”就行,这里就不展开啰嗦了,你面试要真遇到,再在这一层上加个二分即可。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB