为什么现在很难看到一个温柔安静不虚荣不花里胡哨单纯有灵气的姑娘?
刚看到个贴子,网友问为什么现在很难遇到那种温柔安静、不虚荣、不花哨、单纯有灵气的姑娘。底下有人说:不是没了,是你所在的阶层配不上。听着刺耳,但有点现实。
我觉得这话的核心在“土壤”。温柔安静需要家庭氛围稳定,父母心平气和;不虚荣要有见识和安全感;不花里胡哨靠审美;单纯有灵气,则得从小被温柔地对待。可现实是,很多人生活在焦虑和压力里,大家都在为生存奔波,哪还有那么多余力去保持“灵气”。
网友有人反驳,说好姑娘也有,只是大家都不懂珍惜。我挺认同这句。毕竟环境确实重要,但自己也能选择——选择不被戾气裹挟,选择做个温柔又清醒的人。
算法题:前缀和后缀搜索
给一堆单词,多次查询:给你 prefix 和 suffix,要返回满足这俩条件的最后出现(或权重最大)那个单词的下标。暴力每次扫一遍肯定慢。
常见三种套路
哈希拼接法:对每个单词 w,把所有后缀 + '#' + 整词作为键插入,再去查suffix + '#' + prefix。查出来的路径上记录“最新下标”,就能 O(len(prefix)+len(suffix)) 回答。双Trie:建前缀Trie和后缀Trie,各自保存下标集合,查询时做集合交集。实现麻烦、内存也较大。 笛卡尔展开字典:对每个单词把所有 (prefix, suffix)二元组都塞进 map,值是下标。查询 O(1),但空间爆炸,长度 N 的词要插 N² 对。
综合权衡,哈希拼接 + 单Trie够稳、够快、空间也相对可控。
为什么“后缀#整词”这招有效
我们希望同时限定“开头是 prefix”和“结尾是 suffix”。把所有 suffix#word 丢进一棵 Trie,上查询时走 suffix#prefix 这条路径:
suffix决定路径的前半段;#当作分隔,避免歧义;prefix决定后半段“word 必须以 prefix 开头”。 插入时每个 Trie 节点都更新“经过这里的最大下标”,于是查到路径末端的节点,保存的就是答案。
import java.util.*;
publicclassPrefixSuffixSearch{
// Trie节点
staticclassNode{
Node[] next = new Node[27]; // 26字母 + '#'
int best = -1; // 经过此节点的最大下标
}
privatefinal Node root = new Node();
// 把字符映射到 [0..26]
privateintidx(char c){
return (c == '#') ? 26 : (c - 'a');
}
// 往 Trie 里插入一个字符串,并把best更新为index
privatevoidinsert(String s, int index){
Node cur = root;
cur.best = Math.max(cur.best, index);
for (int i = 0; i < s.length(); i++) {
int k = idx(s.charAt(i));
if (cur.next[k] == null) cur.next[k] = new Node();
cur = cur.next[k];
cur.best = Math.max(cur.best, index);
}
}
// 构建:对每个单词word,枚举所有后缀 suf,插入 "suf#word"
publicPrefixSuffixSearch(String[] words){
for (int i = 0; i < words.length; i++) {
String w = words[i];
// 优化:若词很长,可限制枚举长度上限以控内存
for (int cut = 0; cut <= w.length(); cut++) {
String suf = w.substring(cut); // 从cut到末尾
insert(suf + "#" + w, i);
}
}
}
// 查询:在Trie里走 "suffix#prefix" 这条路径,拿末节点best
publicintf(String prefix, String suffix){
String key = suffix + "#" + prefix;
Node cur = root;
for (int i = 0; i < key.length(); i++) {
int k = idx(key.charAt(i));
if (cur.next[k] == null) return -1;
cur = cur.next[k];
}
return cur.best;
}
// --- demo ---
publicstaticvoidmain(String[] args){
String[] words = {"apple", "apply", "apt", "maple"};
PrefixSuffixSearch ps = new PrefixSuffixSearch(words);
System.out.println(ps.f("ap", "le")); // "apple" vs "maple" -> 下标更大的是 3
System.out.println(ps.f("ap", "ly")); // 命中 "apply" -> 1
System.out.println(ps.f("a", "t")); // 命中 "apt" -> 2
System.out.println(ps.f("zz", "zz")); // -1
}
}
复杂度与边界
设单词平均长度为 L,词数为 N。构建会插入约 ∑(L_i+1)条键,每条键长度约L_i + 1 + L_i,总体近似 **O(N·L²)**。查询是走一条路径,**O(|prefix| + |suffix|)**。 字母表只有小写 + #,我们用定长 27 分支的数组,比Map<Character,Node>更省常数。
边界点随手记:
空前缀/空后缀要支持; 重复单词按“后出现”的下标覆盖; 若字符集不是纯小写,改成 Map或扩大分支;大数据集时可给枚举后缀设一个长度上限(比如只枚举后缀长 ≤32),以换取更小内存。
什么时候换方案
若查询极多、内存足:可以考虑“笛卡尔展开字典”(所有 (pre,suf)入 map),查询 O(1)。若必须支持删除:Trie 节点要改成“堆/多值结构”或计数,删除时回收 best;或用双Trie+交集并维护倒排表。
就这样,思路落地、代码能跑,面试里讲清“为什么是 suffix#word、为什么节点要存 best、复杂度咋来的”,基本就稳了。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html