程序员老鬼

我妹国企的工资,看完一点都不羡慕了

国企这滤镜,真该碎一碎了。 网友晒妹妹工资条,实发1930,评论区一堆人先是愣住,接着开始自我安慰:这点钱,在大城市连像样点的房租都扛不住,还谈什么“稳定体面”。也有人说,国企看的是长线,五险一金、假期、食堂、福利,不能只盯着到手那点。

Image

但话说回来,到手1930这事,真不是“别只看眼前”就能糊弄过去的。工资低成这样,连基本生活都得精打细算,稳定听着像优点,落到本人头上,可能就是不敢辞职,也不敢生病。很多人羡慕国企,其实羡慕的是想象里的国企,不是这种月底一看余额心口发紧的版本。

说难听点,1930的“稳定”,更像把人稳定在不太敢折腾的状态里。HR看完估计都得装作没看见,这数字发出来,滤镜确实掉一地。

面试题:倒水

两个桶摆在那儿,一个 5 升,一个 3 升,目标是倒出 4 升。题看着像小学奥数,真写代码时,很多人第一反应还是一通模拟:装满、倒空、互相倒,分支越写越乱,最后自己都不敢改。

这种题我一般不先盯着“怎么倒”,先盯“状态”。 因为倒水过程本质上不是动作问题,是状态转移问题。你手上真正关心的,只有两个数:a 桶里还剩多少水,b 桶里还剩多少水。只要状态能表达清楚,后面就顺了。

比如 (0,0) 表示两个桶都空,(5,0) 表示 5 升桶满了,3 升桶空着,(2,3) 表示 5 升桶还有 2 升,3 升桶装满。 那从一个状态出发,能做的事情其实就 6 种:

  1. 装满 A
  2. 装满 B
  3. 倒空 A
  4. 倒空 B
  5. A 往 B 倒
  6. B 往 A 倒

到这题就别再硬模拟步骤了,直接上 BFS。原因也很现实: 题目通常不光要你判断“能不能倒出来”,还经常顺手问“最少几步”。最短步数,BFS 天然合适。

Java 我会这样写,代码不长,但味道要对,核心是“状态去重 + 六种转移”。

import java.util.*;

publicclassWaterJug{

staticclassState{
int a;
int b;
int step;

        State(int a, int b, int step) {
this.a = a;
this.b = b;
this.step = step;
        }
    }

publicstaticintminSteps(int capA, int capB, int target){
        Queue<State> queue = new LinkedList<>();
boolean[][] visited = newboolean[capA + 1][capB + 1];

        queue.offer(new State(0, 0, 0));
        visited[0][0] = true;

while (!queue.isEmpty()) {
            State cur = queue.poll();

if (cur.a == target || cur.b == target || cur.a + cur.b == target) {
return cur.step;
            }

            List<State> nextList = nextStates(cur, capA, capB);
for (State next : nextList) {
if (!visited[next.a][next.b]) {
                    visited[next.a][next.b] = true;
                    queue.offer(next);
                }
            }
        }
return -1;
    }

privatestatic List<State> nextStates(State s, int capA, int capB){
        List<State> list = new ArrayList<>();

        list.add(new State(capA, s.b, s.step + 1)); // 装满A
        list.add(new State(s.a, capB, s.step + 1)); // 装满B
        list.add(new State(0, s.b, s.step + 1));    // 倒空A
        list.add(new State(s.a, 0, s.step + 1));    // 倒空B

int moveAtoB = Math.min(s.a, capB - s.b);
        list.add(new State(s.a - moveAtoB, s.b + moveAtoB, s.step + 1));

int moveBtoA = Math.min(s.b, capA - s.a);
        list.add(new State(s.a + moveBtoA, s.b - moveBtoA, s.step + 1));

return list;
    }

publicstaticvoidmain(String[] args){
        System.out.println(minSteps(5, 3, 4)); // 6
    }
}

这段代码里,最容易写错的不是 BFS,本身就那点东西;真正容易翻车的是“倒水”这一步。 比如 A 往 B 倒,不是简单做 a--、b++,也不是一口气全倒完,而是只能倒到两种边界之一:

要么 A 倒空。 要么 B 装满。

所以才有这句:

int moveAtoB = Math.min(s.a, capB - s.b);

这地方我第一眼就会盯住,因为很多人代码能跑,但状态转移是歪的,样例一多马上露馅。

再往前走一步,其实这题还有个更省的判断: 如果目标值 target 大于两个桶容量之和,那肯定不可能。 再狠一点,若 target 不是 gcd(capA, capB) 的倍数,也不可能倒出来。这个是数学结论,能先剪掉不少无效情况。

if (target > capA + capB) return -1;
if (target % gcd(capA, capB) != 0) return -1;

算法题里,“倒水”不算难,难的是别把自己写进细节泥潭里。 动作看着很多,状态其实很少。把状态抽出来,剩下就是标准图搜索。很多题都一个路数:表面在演过程,骨子里在跑状态机。