程序员老鬼

晚上7点多,部门主管突然微信跟我说,有一笔1500元的团队激励奖,要跟工资一起发到我卡上,让我收到后再转给人事~

晚上7点多,主管突然发微信,说有一笔1500元团队激励奖已经跟工资一起打到我卡里,让我到账后再转给人事文员分给其他同事。

Image

钱既然能发到我卡上,说明财务、人事系统里已经能操作到个人账户。那为什么不直接一人一笔,非要绕我这一手?这种“先到你卡,再转别人”的流程,最麻烦的不是转账,是责任也跟着落到你头上。

要我说真碰到这种事,别急着回“收到”。先走两步:第一,要求主管在公司正式群里说,带上人事和财务;第二,直接打公司公开电话核实。

凡是奖金、补贴、报销,流程越反常,越要多问一句。很多坑,都是不好意思开口那一下掉进去的。

算法题:分数加减运算

字符串一长,很多人这题就开始上模板了:先通分,再相加,再约分。结果一写就乱,尤其是负号、整数部分、-1/2+1/2 这种边界,一不留神就翻车。

“分数加减运算”这题,其实不难,麻烦点全在细节。题目通常会给你一个表达式,比如:

-1/2+1/2+1/3

要求输出最简结果。能约分就约分;如果最后是整数,就直接输出整数;如果是 0,也别给我整成 0/1 之外的怪东西。

这类题我一般不急着拆一堆对象,先抓主线:整个表达式本质上就是一串分数累加。那就维护两个量:

  • 当前分子 num
  • 当前分母 den

每读到一个新的分数 a/b,就做一次合并:

num = num * b + a * den;
den = den * b;

然后立刻约分,别等到最后。因为中间值会越来越大,虽然这题数据范围通常不大,但早点收缩总没坏处。

关键代码先看一眼:

privatestaticlonggcd(long a, long b){
    a = Math.abs(a);
    b = Math.abs(b);
while (b != 0) {
long t = a % b;
        a = b;
        b = t;
    }
return a;
}

约分就靠它。每次算完新分子分母,顺手做一下:

long g = gcd(num, den);
num /= g;
den /= g;

还有个细节,分母最好始终保持正数,不然后面输出很别扭:

if (den < 0) {
    num = -num;
    den = -den;
}

完整写法我会更偏这种,直接扫字符串,不额外切太多片段:

classSolution{
public String fractionAddition(String expression){
long num = 0;
long den = 1;
int i = 0, n = expression.length();

while (i < n) {
int sign = 1;
if (expression.charAt(i) == '+' || expression.charAt(i) == '-') {
                sign = expression.charAt(i) == '-' ? -1 : 1;
                i++;
            }

long a = 0;
while (i < n && Character.isDigit(expression.charAt(i))) {
                a = a * 10 + (expression.charAt(i) - '0');
                i++;
            }

            i++; // 跳过 '/'

long b = 0;
while (i < n && Character.isDigit(expression.charAt(i))) {
                b = b * 10 + (expression.charAt(i) - '0');
                i++;
            }

            a *= sign;

            num = num * b + a * den;
            den = den * b;

long g = gcd(num, den);
            num /= g;
            den /= g;

if (den < 0) {
                num = -num;
                den = -den;
            }
        }

return num + "/" + den;
    }

privatelonggcd(long a, long b){
        a = Math.abs(a);
        b = Math.abs(b);
while (b != 0) {
long t = a % b;
            a = b;
            b = t;
        }
return a;
    }
}

这段代码的路子很直。

先读符号,再读分子,再跳过 /,再读分母。每拿到一个分数,就跟当前结果合并。这样不用先把所有分数存起来,边读边算就行。

拿 -1/2+1/2+1/3 走一遍:

第一步:0/1 + (-1/2) = -1/2第二步:-1/2 + 1/2 = 0/1第三步:0/1 + 1/3 = 1/3

最后输出 1/3。

这题真正容易错的地方就三个。

一个是第一个分数可能带负号,所以开头就得先判断 + 或 -。 一个是每次都要约分,不然中间分子分母膨胀得很快。 还有一个是0 的处理,约分后 0/x 最终都会收敛成 0/1,这一点用上面的写法天然就处理掉了。

算法复杂度也没什么花活,整体就是扫一遍字符串,时间复杂度 O(n),额外空间基本是 O(1)。

这种题放在算法里不算难题,但很适合看一个人的代码习惯。写得太急,就会在符号、约分、边界上丢分。看着只是分数相加,真到提交的时候,往往不是思路错,是手太快。