Python技术迷

今天给一个候选人走背调,他33岁,P7,总包谈好了78万,一切顺利。结果我打给他前司的HR,对方说他离职前跟直属leader吵过架

这事看得我有点无语,前面都谈到78万了,级别也对得上,眼看就要入职,结果一个背调电话直接翻车。

前公司HR说他临走前跟直属上级闹得挺僵,最后几个月绩效也一般,系统里还留了个“协作能力需要加强”。这话一传到用人部门,领导基本没犹豫:别要了,现在名额少,没必要赌。

Image

最难受的就是这种,能力、面试、薪资全过了,最后被前司一句评价卡死。至于当时到底是谁的问题,已经没人愿意再查。HC紧的时候,公司挑人就是这样,稍微有点争议,直接换下一个。打工人离职时那口气,有时候真不能乱出。

今日算法题

Python 处理分数加减,别急着把除法写进去

3/4 - 5/6,结果不是 Python 直接算出来的那个小数。

这类题我第一眼先看两件事:分母怎么通,结果怎么约分。至于浮点数,能不碰就别碰。你把 1/3 转成小数之后,后面跟着的精度问题会越来越别扭,算法题没必要给自己挖这个坑。

分数加减只有一条路:

a/b + c/d = (a*d + c*b) / (b*d)
a/b - c/d = (a*d - c*b) / (b*d)

比如:

3/4 - 5/6
= (3×6 - 5×4) / (4×6)
= -2/24
= -1/12

算到 -2/24 还不算结束。题目通常要求最简分数,所以后面必须求最大公约数,再同时除掉分子和分母。

Python 代码我会这么写:

from math import gcd


defmerge_fraction(left_top, left_bottom,
                   right_top, right_bottom, operator)
:

if left_bottom == 0or right_bottom == 0:
raise ValueError("分母不能为 0")

    common_bottom = left_bottom * right_bottom

if operator == "+":
        merged_top = (
            left_top * right_bottom
            + right_top * left_bottom
        )
elif operator == "-":
        merged_top = (
            left_top * right_bottom
            - right_top * left_bottom
        )
else:
raise ValueError("只支持加法和减法")

if merged_top == 0:
return0, 1

    divisor = gcd(abs(merged_top), abs(common_bottom))
    merged_top //= divisor
    common_bottom //= divisor

# 负号统一放在分子上,避免出现 1/-2
if common_bottom < 0:
        merged_top = -merged_top
        common_bottom = -common_bottom

return merged_top, common_bottom


top, bottom = merge_fraction(3, 4, 5, 6, "-")
print(f"{top}/{bottom}")

输出:

-1/12

这里有几个地方容易写漏。

第一个是 gcd() 要对分子取绝对值。减法可能得到负数,比如 1/3 - 5/6,这时分子是 -3。约分看的是数值大小,不应该让负号干扰最大公约数。

第二个是结果为零时,直接返回 0/1。虽然 0/7、0/19 在数学上都没错,但算法题输出一般要求统一,留着那个分母没什么意义。

第三个是负号位置。1/-2 和 -1/2 数值相同,但输出格式未必都认。我的习惯是把分母整理成正数,负号只放分子上,后面判断和打印都省事。

如果题目一次输入一整行,例如:

7/10-3/5

可以再加一层解析:

import re


defcalculate(expression):
    pattern = r"\s*([+-]?\d+)/(\d+)\s*([+-])\s*(\d+)/(\d+)\s*"
    matched = re.fullmatch(pattern, expression)

ifnot matched:
raise ValueError("表达式格式不正确")

    a, b, operator, c, d = matched.groups()

    top, bottom = merge_fraction(
        int(a), int(b),
        int(c), int(d),
        operator
    )
returnf"{top}/{bottom}"


print(calculate("7/10-3/5"))

结果是:

1/10

这道题计算量不大,时间复杂度主要落在最大公约数上,欧几里得算法大约是 O(log n)。真正容易丢分的不是性能,而是忘记约分、分母为零、结果为零,以及负号位置。

分数题看着像小学数学,代码里该补的边界一个都不能少。