辛辛苦苦攒了300万,辞职全职投资,全仓买入ETH,亏掉60%只剩120万了,怎么办?不想上班,还能继续躺平吗?
这哪是躺平啊,这是把床搬到火山口上了。
辛辛苦苦攒了300万,辞职不干了,想着全职投资,结果一把全仓ETH,亏到只剩120万。说真的,这事最扎心的不是亏钱,是他还在问“还能不能继续躺平”。
投资本来就有波动,全仓一个币,还没工资现金流兜底,这不叫自由职业,叫裸奔。
不想上班我能理解,谁爱上班啊。但躺平也得有躺平的本钱。300万的时候可能还有点余地,120万再继续硬扛,心态先崩。每天盯盘,涨了幻想回本,跌了怀疑人生,比上班还累。
这时候最该想的不是“怎么翻盘”,而是先别让自己再被市场教育一遍。打工人看完血压都上来了,真的。
这题第一眼别去想什么最短路模板,也别急着 Floyd。 “访问所有节点”这几个字很容易把人带偏,以为要找一条真的路径。其实代码里要抓的是两个东西:人现在在哪个点,已经踩过哪些点。
题目是:给你一个无向图,从任意节点出发,可以重复走边,问最少走多少步,能把所有节点都访问到。
我一般看到这种“所有节点 + 最短步数”,会先怀疑状态压缩 BFS。因为普通 BFS 只记录当前节点不够,比如都走到 3 号节点,一个路径访问过 0、1、3,另一个路径访问过 2、3,这俩状态完全不是一回事。
状态写成这样:
int state = 1 << node;
比如有 4 个节点,1011 表示访问过 0、1、3。
队列里不能只放节点,要放:
当前位置 node
访问状态 mask
当前步数 step
所有节点都访问过时,mask 应该等于:
int finish = (1 << n) - 1;
这里有个小细节,起点不是固定的。题目允许从任意节点出发,所以 BFS 初始化时,要把所有节点都塞进去。这个地方少写一步,结果就会绕远。
完整代码我一般这么写,别搞太复杂:
import java.util.*;
classSolution{
publicintshortestPathLength(int[][] graph){
int n = graph.length;
if (n <= 1) return0;
int finish = (1 << n) - 1;
boolean[][] seen = newboolean[n][1 << n];
ArrayDeque<int[]> queue = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
int mask = 1 << i;
queue.offer(newint[]{i, mask, 0});
seen[i][mask] = true;
}
while (!queue.isEmpty()) {
int[] cur = queue.poll();
int node = cur[0];
int mask = cur[1];
int step = cur[2];
for (int next : graph[node]) {
int nextMask = mask | (1 << next);
if (nextMask == finish) {
return step + 1;
}
if (seen[next][nextMask]) {
continue;
}
seen[next][nextMask] = true;
queue.offer(newint[]{next, nextMask, step + 1});
}
}
return -1;
}
}
这段代码里最关键的是 seen[next][nextMask]。 不是防止某个节点重复访问,而是防止“同一个节点 + 同一份访问记录”重复进队列。
举个很小的图:
graph = [[1,2,3],[0],[0],[0]]
从 0 出发未必最优,因为你要访问三个叶子节点,来回折返。BFS 把 0、1、2、3 都作为起点一起推进,谁先凑齐全部节点,谁就是最短答案。
这题的节点数通常不大,否则状态压缩直接炸。复杂度大概是:
节点数 n
状态数 2^n
总状态 n * 2^n
所以时间复杂度可以看成 O(n * 2^n + 边数 * 2^n),面试里说到这个程度够了。
我不太建议这题上来写 DFS 回溯。回溯会把“路径”枚举得很热闹,但你很难保证第一次找到的就是最短。BFS 的优势就在这里:一步一步往外扩,第一次访问到全集状态,步数肯定最短。
这题真正考的不是图论多深,而是你能不能把“走到哪个点”和“已经访问过哪些点”绑成一个状态。绑住了,剩下就是 BFS。