程序员老鬼

昨天还在说没对象,今天工作都没了。 那我有什么?有房贷啊,还有一身过劳的臭毛病。

昨天还在群里哀嚎“没对象”,今天工牌都刷不开了,这剧情比相亲市场还狠。对象没着落,工作先黄了,成年人的安全感真是一天比一天抽象。

Image

最扎心的是,人家问你“那你现在还有什么”,你都不好意思沉默太久。有什么?有房贷。有花呗。有凌晨两点还睡不着的职业病。还有一身坐出来的腰疼、颈椎疼、脾气越来越差的臭毛病。恋爱没谈明白,班倒是先白加了。

你说职场给了我们什么,工资像安慰奖,压力像包月服务。年轻时候觉得熬一熬就过去了,熬到后面才发现,过去的是自己。最稳定的关系,可能真就是每个月那条还款短信。

面试题:金字塔转换矩阵

这题第一眼看着像回溯,真写起来,坑基本都在“怎么把下一层候选快速拿出来”这一步。题目给你一行底边,比如 BCD,再给一批三元规则,像 BCG、CDE 这种,意思是底下相邻两个字母如果是 BC,上面可以放 G。最后问一句,这个金字塔能不能一路往上搭到只剩一个字符。这个题要是上来就硬搜,分支一多,递归树马上就炸。真正该先做的,不是递归,而是先把规则表收拾干净。这个取舍就很关键,跟线上排查一个味道:先把索引建好,再谈扫描。整体写法我参考了那种“先落现象,再给关键判断”的技术表达气质,但内容和代码都是重新写的。

这题我一般先把 allowed 转成 Map<String, List<Character>>。比如 BCG 和 BCA,就记成 BC -> [G, A]。后面递归时,拿到底层相邻两个字符,直接查表,不要每次都去全表里扫。

代码别写得太教学,我贴现场能过题的关键版本:

import java.util.*;

publicclassSolution{
publicbooleanpyramidTransition(String bottom, List<String> allowed){
        Map<String, List<Character>> ruleMap = new HashMap<>();
for (String s : allowed) {
            String key = s.substring(0, 2);
            ruleMap.computeIfAbsent(key, k -> new ArrayList<>()).add(s.charAt(2));
        }
return dfs(bottom, ruleMap, new HashMap<>());
    }

privatebooleandfs(String row, Map<String, List<Character>> ruleMap, Map<String, Boolean> memo){
if (row.length() == 1) {
returntrue;
        }
if (memo.containsKey(row)) {
return memo.get(row);
        }

        List<String> nextRows = new ArrayList<>();
        buildNextRows(row, 0, new StringBuilder(), nextRows, ruleMap);

for (String next : nextRows) {
if (dfs(next, ruleMap, memo)) {
                memo.put(row, true);
returntrue;
            }
        }
        memo.put(row, false);
returnfalse;
    }

privatevoidbuildNextRows(String row, int idx, StringBuilder path,
                               List<String> nextRows, Map<String, List<Character>> ruleMap)
{
if (idx == row.length() - 1) {
            nextRows.add(path.toString());
return;
        }

        String key = row.substring(idx, idx + 2);
        List<Character> list = ruleMap.get(key);
if (list == null) {
return;
        }

for (char ch : list) {
            path.append(ch);
            buildNextRows(row, idx + 1, path, nextRows, ruleMap);
            path.deleteCharAt(path.length() - 1);
        }
    }
}

这版代码核心就两件事。

一个是预处理规则,不让递归阶段做重复劳动。另一个是记忆化,某一层字符串如果已经判过走不通,别再回来撞第二次。很多人卡超时,不是回溯不会写,是没做这层缓存。

再说下判断顺序。假设当前层是 BCD,那下一层的第一个位置来自 BC,第二个位置来自 CD。所以这一层其实不是只生成一个结果,而是把每个位置的候选做组合。这里最容易写歪的地方,是有人在生成下一层时,拿到一个可用字符就直接往下递归,结果把同层剩余位置漏掉了。