程序员老鬼

技术大牛朋友,年包大概80万,为了冲刺百万年薪,他去竞聘了Team Leader,工作半年下面人不服他,领导觉得他能力不行

网上有个网友吐槽:他那位技术大牛朋友年包80万,眼看就差“一小步”到百万,咬牙去竞聘Team Leader。结果干了半年,下面人不服,领导还觉得他带不动队伍。

Image

评论区可热闹了。有网友说:“技术强不等于会带人,写代码是单机游戏,带团队是多人副本。”还有人补刀:“TL最难的技能是背锅与沟通,BUG反而排第三。”

我觉得这事挺真实的。很多程序员以为升职=加薪,其实是换赛道:从‘我把活干完’变成‘我让别人把活干完’。

真想冲百万,除了技术,开会控场、拆任务、给反馈、扛压力这些软技能得一起点满。

走管理线的好处也明显:影响力放大,资源更多,涨薪空间也更大,只是代价是你得学会和人打交道,代码写得再漂亮也不一定救场。

面试题:灯泡开关

那天加班到快十二点,领导甩过来一道题,说很简单:“灯泡开关,会的不?顺手写下。”说完人就下班了,办公室只剩我和天花板那排惨白的灯泡,对着题目一看,笑出了声:这不就我租的那个长长的走廊嘛。

题目大概是这样哈:走廊上有 n 个灯泡,一开始全关着。 第 1 轮,从第 1 个开始,每个灯泡都切换一次(关就开,开就关)。 第 2 轮,从第 2 个开始,每隔 2 个切一次。 第 3 轮,从第 3 个开始,每隔 3 个切一次…… 一直到第 n 轮,问最后有多少个灯是亮的。

你们第一反应是不是跟我一样:for 套 for,全部模拟,搞定收工对吧。

我当时就啪的一下敲了个最直男版本的代码出来,长这样:

publicclassBulbSwitcherSlow{

// 暴力模拟版,能跑但不建议上生产
publicintbulbSwitch(int n){
// false 代表关灯,true 代表开灯
boolean[] bulbs = newboolean[n + 1]; // 从 1 开始方便一点

for (int round = 1; round <= n; round++) {
for (int i = round; i <= n; i += round) {
                bulbs[i] = !bulbs[i]; // 切换开关
            }
        }

int count = 0;
for (int i = 1; i <= n; i++) {
if (bulbs[i]) count++;
        }
return count;
    }

publicstaticvoidmain(String[] args){
        System.out.println(new BulbSwitcherSlow().bulbSwitch(10)); // 打个样
    }
}

跑一下小数据,没毛病。 然后我手贱就在本机随手测了个 n = 10_0000_000,风扇呼呼直转,那声音,真有点当年给 Postgres 和 MySQL 压测时的 PTSD 即视感……

回过神一算,这玩意时间复杂度接近 O(n²),面试官要是看到你写这个,估计会把笔一合:“下一个”。

我当时脑子已经有点糊了,就在那儿来回走,顺便数办公室那排灯:第 1 个灯,会被哪些轮次按到?1 的倍数嘛,1、2、3……全都按。 第 6 个灯呢?会在这些轮次被按:1、2、3、6。 你仔细想一下,其实每个灯被按几次,完全取决于“它有多少个约数”。

然后就冒出一个关键句:被按奇数次的灯才会亮。

那问题马上变形了: 哪些数字的约数个数是奇数?

这时候脑子里那个小学数学老师突然上线了: 一个数的约数,基本都是成对出现的。比如 12:

  • 1 和 12
  • 2 和 6
  • 3 和 4

成双成对,所以约数个数一般是偶数。 除非有那么一种情况:有一对其实是一个数自己跟自己,也就是 √n * √n = n,这个约数只算一次。 也就是说——只有完全平方数的约数个数是奇数。

所以灯泡 i 最后是不是亮,只取决于 i 是不是完全平方数。 那 1 到 n 之间有多少个完全平方数? 很简单嘛,就是: 1²,2²,3²,…,k² ≤ n 所以 k = ⌊√n⌋

也就是说,答案直接就是 sqrt(n) 向下取整,压根不用开灯关灯这么累……

我当场把刚才那坨 for 循环删了,换成了两行半的版本:

publicclassBulbSwitcher{

// 正解版:O(1) 时间复杂度
publicintbulbSwitch(int n){
// 强转一下就行了,Math.sqrt 返回的是 double
return (int) Math.sqrt(n);
    }

publicstaticvoidmain(String[] args){
        BulbSwitcher s = new BulbSwitcher();
        System.out.println(s.bulbSwitch(3));   // 结果 1 -> 只有灯 1 亮
        System.out.println(s.bulbSwitch(10));  // 结果 3 -> 1,4,9 亮
        System.out.println(s.bulbSwitch(9999)); // 心里有数就行
    }
}

写完我自己都笑了,干了半天,发现真正有用的代码就这一行:

return (int) Math.sqrt(n);

其他全是为了显得自己很努力。

顺便说一下,这种题在面试里特别有“欺负人”的那种坏心眼: 一上来给你个看起来很模拟、很工程的问题,你本能就会往 for 循环那边冲; 但面试官其实是在等你那个“等等,好像不对”的停顿—— 看你能不能从“写代码”切到“想问题”,从“操作灯泡”切到“数约数”。