程序员老鬼

某东员工:干了一年,几乎天天10点下班,年底给了个B+,以为会涨薪,结果一分没有,从此以后早点走,!

某东有员工说,自己这一年基本没怎么准点走过,晚上十点下班都快成默认设置了。人嘛,一开始还挺天真,觉得我这么扛,年底怎么也得有点表示吧。

结果绩效出来,B+。

他还以为涨薪稳了,结果一问,好家伙,一分钱没动。那一刻估计人直接清醒了。

Image

这就很职场。你以为加班是在攒功劳,老板那边可能只是在记录:这个人挺能熬,下次继续用。

所以他后面也想明白了,到点差不多就撤。不是突然摆烂,是终于知道了,有些公司嘴上讲奋斗,发钱的时候又开始讲预算。

B+不涨薪,十点下班也不涨薪,那早点回家吃饭,好像也挺合理的。

面试题:1 比特与 2 比特字符

bits = [1, 1, 1, 0] 这个用例,很多人第一眼会判错。

最后一位确实是 0,但它不一定就是一个独立字符。

这题坑就坑在这里:不是看最后一位长什么样,而是看前面的解析有没有把它吃掉。

题目叫 1 比特与 2 比特字符。规则很少:

0 表示一个 1 比特字符。

10 或 11 表示一个 2 比特字符。

给你一个只包含 0 和 1 的数组,并且数组最后一位一定是 0,问最后这个 0 是不是一个单独的 1 比特字符。

别急着从后往前看。

我一般遇到这种编码解析题,第一反应不是找数学规律,而是按协议解析一遍。因为协议这东西,前面怎么读,会直接影响后面的位置。你不能盯着最后一个字节说它是不是独立的,得看指针最后是不是刚好落到它身上。

比如这个:

[1, 0, 0]

从第 0 位开始看:

1 0  -> 一个 2 比特字符
0    -> 一个 1 比特字符

最后这个 0 是独立的。

再看这个:

[1, 1, 1, 0]

解析过程是:

1 1  -> 一个 2 比特字符
1 0  -> 一个 2 比特字符

最后这个 0 被前面的 1 带走了,不是独立字符。

这地方如果只写一句“最后一个 0 前面有几个连续的 1”,当然也能做,但我不太喜欢一上来就这么写。面试题还好,线上协议解析你敢这么写,后面改规则的时候大概率把自己坑进去。

直接模拟,反而最稳。

代码可以这样写:

publicclassBitCharChecker{

publicbooleanisLastSingleBit(int[] bits){
if (bits == null || bits.length == 0) {
returnfalse;
        }

int pos = 0;
int last = bits.length - 1;

while (pos < last) {
if (bits[pos] == 0) {
                pos += 1;
            } else {
                pos += 2;
            }
        }

return pos == last;
    }
}

这段代码就干一件事:从头解析,别越过最后一位。

pos < last 这个条件要注意。

这里不是 pos < bits.length。

因为我们只关心最后一位有没有被单独解析到。只要指针还在最后一位前面,就继续按规则往后跳。

最后有两种情况:

pos == last

说明前面的字符都解析完了,指针刚好停在最后这个 0 上,那它就是单独的 1 比特字符。

pos > last

说明最后这个 0 已经被前面的 1 组成了 2 比特字符,不能单独算。

拿几个用例跑一下,别只看脑子里的感觉:

publicclassBitCharDemo{

publicstaticvoidmain(String[] args){
        BitCharChecker checker = new BitCharChecker();

int[][] cases = {
                {0},
                {1, 0, 0},
                {1, 1, 1, 0},
                {1, 1, 0},
                {0, 0},
                {1, 0}
        };

for (int[] bits : cases) {
            System.out.println(format(bits) + " -> " + checker.isLastSingleBit(bits));
        }
    }

privatestatic String format(int[] bits){
        StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < bits.length; i++) {
if (i > 0) {
                sb.append(", ");
            }
            sb.append(bits[i]);
        }
return sb.append("]").toString();
    }
}

输出大概是这样:

[0] -> true
[1, 0, 0] -> true
[1, 1, 1, 0] -> false
[1, 1, 0] -> true
[0, 0] -> true
[1, 0] -> false

其中 [1, 1, 0] 也容易误判。

它的解析是:

1 1 -> 2 比特字符
0   -> 1 比特字符

所以返回 true。

而 [1, 0] 是:

1 0 -> 2 比特字符

最后这个 0 就不是独立的。

这题的复杂度没什么花头,时间复杂度 O(n),空间复杂度 O(1)。

但真正容易错的不是复杂度,是边界。

尤其是这种写法:

while (pos < bits.length) {
if (bits[pos] == 0) {
        pos++;
    } else {
        pos += 2;
    }
}
return pos == bits.length - 1;

这代码看着像那么回事,实际已经晚了。

因为它会把最后一位也解析掉。假设输入是:

[0]

循环进去后 pos 从 0 变成 1,最后判断 pos == 0,直接返回 false。

这就是典型的指针边界没想清楚。

这类题不要急着压缩成一行,也不要为了显得聪明去背结论。按编码规则走一遍,指针停在哪,答案就在哪。规则题最怕猜,猜对一次没用,换个用例就露馅。