Python技术迷

500万的房子400万卖了,200万又买回来,亏了还是赚了?

刚看到个贴子,说有人500万买的房子,400万卖掉,过两年又花200万买回来,问亏了还是赚了。网友回帖有的说整体只花了300万买到同一套,血赚;有的说中介税费加上时间成本,肯定是亏麻了。

Image

我觉得这事吧,先别算账面价,先想两个问题:第一,中间那段时间,你是不是确实需要那400万去救急、创业或者换城市?如果那时候不卖,后面很多机会可能都没了,那些看不见的收益也得算在里头。第二,再买回来时,这套房是不是当下最合适的选择?地段、学区、通勤都满足需求,那多花一点,就当是为人生重新“买门票”。

房子本质是生活工具,不是K线图。只盯着亏赚,很容易被房价牵着情绪走。

力

算法题:阶乘函数后 K 个零

昨天晚上十一点多,我在电脑前正打算关机睡觉,结果微信蹦出来一条消息: “东哥,你讲讲那个《阶乘函数后 K 个零》呗,看题解看懵了……”

好嘛,本来想摸鱼,结果又被你们按回算法频道了 😂

那就边聊天边说说这道题,顺便用 Python 写个你以后面试能直接拿出来用的模板。

题目名字听着有点严肃:“阶乘函数后 K 个零”。 实际意思是这样的:

  • 定义一个函数 f(n) 表示 n! 这个数尾巴上有多少个零

  • 比如:

    • 5! = 120,最后 1 个零,所以 f(5) = 1
    • 10! = 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 这种会多贡献一个 5
  • n / 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) = 1
  • trailing_zeros(10) = 2
  • trailing_zeros(25) = 6(因为 25=5²,会多贡献一个 5)

到这一步为止,你已经搞清楚“算 f(n)”了。 但题目真正恶心人的地方在后面:给你 k,反过来问你多少个 n 满足 f(n) = k。

f(n) 有什么怪性格?

简单捋一下 f(n) 的特点:

  1. n 越大,n! 越大,尾零一定不会变少 👉 所以 f(n) 是一个单调不减函数

  2. 不是每个整数 k 都一定能取到 比如有些区间是“跳过去”的,这也是为什么答案有时候是 0

  3. 很关键的一点(这题核心结论):

    对于任意一个 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