程序员老鬼

我同事当了别人小三,全公司都知道了 就她觉得自己藏得挺好。 。

我同事当了别人小三,全公司都知道了 就她觉得自己藏得挺好。 。

Image

全公司都知道她跟一个四十多岁的男人走得近,就她自己还觉得藏得天衣无缝。人家一周开车来接她好几次,豪车停楼下,她还硬说是亲戚。问题是,哪个亲戚见面又搂又贴啊?同事随口点一句,她还急眼:别多管闲事。

更明显的是消费变化。以前中午还跟大家一起热饭,后来天天出去吃,包换新的,鞋也换新的,整个人像突然升职加薪了十倍。

公司工资大家心里都有数,五千多的班,硬是过出了高薪精英的日子。别人问钱哪来的,她就说自己存的。

行吧,存钱能存出这效果,那财务部都得来拜师。

面试题:字母移位

这题最容易写崩的地方,不是移位,是你真的一轮轮去改字符。

字符串 abc,移位数组是 [3,5,9]。意思不是第 0 个字符移 3 次、第 1 个移 5 次、第 2 个移 9 次这么简单,而是:

shifts[0] 作用在 s[0]
shifts[1] 作用在 s[0..1]
shifts[2] 作用在 s[0..2]

所以每个字符最后被移了多少次,得反着看。

c 只吃到 9。

b 吃到 5 + 9。

a 吃到 3 + 5 + 9。

这地方我第一眼就不会去模拟。模拟太蠢,尤其题目数据一大,字符挨个改,基本就是给自己挖坑。

比如你真这么写:

for (int i = 0; i < shifts.length; i++) {
for (int j = 0; j <= i; j++) {
// 每次都改 s[j]
    }
}

看着挺直观,提交时基本要凉。因为这是平方复杂度,字符串稍微长一点就开始卡。

这题正确的手感是:从右往左扫一遍,把后缀移位次数累加起来。

为什么从右往左?

因为第 i 个字符,会受到 shifts[i]、shifts[i+1]、shifts[i+2]... 这些操作影响。这个东西天然就是后缀和。

直接看代码:

classSolution{
public String shiftingLetters(String s, int[] shifts){
char[] arr = s.toCharArray();

long move = 0;

for (int i = arr.length - 1; i >= 0; i--) {
            move += shifts[i];
            move %= 26;

int oldPos = arr[i] - 'a';
int newPos = (int) ((oldPos + move) % 26);

            arr[i] = (char) ('a' + newPos);
        }

returnnew String(arr);
    }
}

这里有两个点别省。

第一个,move 我用了 long。

有些人会觉得反正最后 % 26,用 int 也没事。正常小数据确实没事,但算法题里数组长度和移位值一上来,累加先溢出,再取模,结果就已经脏了。

第二个,每次累加后马上 % 26。

字母只有 26 个,移 27 次和移 1 次一样。这个模运算不是为了装技巧,是为了把数字压住,后面计算干净一点。

拿 s = "abc",shifts = [3,5,9] 走一下:

i = 2, move = 9      c -> l
i = 1, move = 14     b -> p
i = 0, move = 17     a -> r

最后结果就是:

rpl

这题如果非要说坑,其实就两个。

一个是方向错了。很多人从左往右算,算着算着发现当前字符后面还有操作影响它,又回头补,代码越写越别扭。

另一个是把字符位移写复杂了。

我见过有人写一堆 if 判断:

if (ch > 'z') {
    ch = ...
}

没必要。

字符转成 0 ~ 25 的编号,算完再转回字符,最稳:

int pos = arr[i] - 'a';
arr[i] = (char) ('a' + (pos + move) % 26);

这个写法在线上写字段脱敏、批量编码转换时也常见。别直接拿字符做加减,除非你很确定边界不会绕回来。

这题最后复杂度就是:

时间复杂度:O(n)
空间复杂度:O(n)

空间这里主要是 char[]。Java 的字符串不可变,不转数组也能写,但会很难看。为了省那点空间,把代码写成一坨,不划算。

这类题看着像字符串,其实考的是“别模拟”。能把每个位置最终受到的影响一次算出来,就别一轮一轮去改。算法题里很多超时,都是从“我先照着题意模拟一下”开始的。