大家好,我是吴师兄。
前几天,一份从脉脉流出的最新大厂薪资地图,让社交圈炸了锅。
不少人感叹,“难怪年轻人削尖脑袋往大厂挤,就这薪资,搁谁不心动?”
但也有人说,大厂的高薪不过是
“卖命的代价”
。
先来看看研发岗的薪资,
大厂一线岗位是真的高
。
应届生动辄月薪20k起,3年经验月薪能到35k以上,10年往上直接奔着100k+。
更别提15薪、16薪,干12个月能拿15个月的钱,
年终奖一发,直接笑出声
。
有朋友戏称,
“年终奖不是奖励,是续命。”
在高压的大厂环境中,很多人指望年终奖来给自己一个交代。
也有人说,
“只有熬过年终奖那一天,你才能体会什么叫真正的松了一口气。”
但问题来了:
这高薪,是“高福利”还是“高代价”?
再看看应届生,
薪资直接吊打同龄人
。
有人调侃:
“大厂的校招面试现场,和考公务员的考场没区别。”
确实,谁不想年纪轻轻就月入20k?这不仅是薪资,还有一种“社会标签”:
“我进了大厂,我成功了。”
但别忘了,
这门票的代价可不低
。
拿Offer之前,要过简历筛选、技术面试、综合面试三道关,八股文刷上几百道都不稀奇。能挤进来的,都是硬实力+运气兼备的“勇士”。
可问题是,
勇士进了大厂,能不能全身而退?
为什么这么多人宁愿拼命也要进大厂?
有人说,这是“性价比”问题:
年轻人想用最好的年华换取最值钱的薪资
。
30岁之前进大厂,挣够一套房的首付,然后撤退。这套逻辑,听起来确实很香。
但问题是,
现实真的能让你体面地撤退吗?
越来越多的“过来人”表示,
大厂的经历确实是块敲门砖,但同时也是一把双刃剑
。
在大厂,
你能快速成长,但也可能迅速耗尽激情
;
你能拿到高薪,但也可能换来高压和“社畜化”
。
有个朋友总结得很扎心:
“大厂的薪资地图确实诱人,但你得先问自己——能跑多久?”
说到底,大厂的薪资只是个工具,
生活的质量才是真正的目的
。
冲刺大厂无可厚非,但记得提醒自己,不要被“高薪幻觉”绑架。
年终奖再高,也填不满空洞的内心;工资再多,也换不回丢失的健康。
最后,
祝大家2025年都能找到适合自己的节奏
:
拿得下高薪,撑得起生活;进得了大厂,出得了自由!
你怎么看大厂的薪资?有自己的故事和观点吗?评论区聊聊吧!
回归我们公众号的主题,
每天掌握一道算法题
。
继续来一道和「校招」相关的算法原题。
本原题练习网址:www.algomooc.com
题目描述
一农夫带着
m
只羊,
n
只狼过河,农夫有一条可载
x
只狼/羊的船
农夫在时或者羊的 数量大于狼时,狼不会攻击羊;农夫在不损失羊的情况下,运输几次可以完成运输?
只计算农夫去对岸的次数,回程时农夫不会运送羊和狼。
输入描述
输入参数为
m
,
n
,
x
m
为羊的数量、
n
为狼的数量、
x
为可载狼和羊的数量
输出描述
返回运输次数即可
如果无法完成运输返回
0
补充说明
农夫在或农夫离开后羊的数量大于狼的数量时,狼不会攻击羊。农夫不占用船的容量
示例
输入
5 3 3
输出
3
说明
第一次运
2
只狼
第二次运
3
只羊
第三次运
2
只羊和
1
只狼
解题思路
非常有意思的好题,很少见到这么好的题目了。
转化为搜索状态树最小层数问题
本题乍一看其实很难看出是一道图搜索题目,但如果我们从做出整个运送过程的示意图,
把每次选择后此岸情况、彼岸情况的状态当作一个节点,而每次选择都会转移到下一层节点更新到下一个状态
,就会发现这实际上是一个状态树寻找最小搜索层数的问题。
以题目提供的示例为例,我们做出示意图
显然我们可以看出几个要点
这是一个搜索过程,每一次运送过程,我们都有多种可能的运送方案
有一些方案可能会和之前出现过的方案重复,可以进行排除,无需做重复计算
本题的要求是要找到最少运送次数,即是
找到这棵状态树的最小搜索层数
,所以使用BFS要使用DFS更优。
节点的设计以及更新
确定这是一个状态树搜索问题之后,我们就需要设计出节点的储存方式了。
很显然,每一个节点状态仅由
4
个数据来表示,
此岸羊的数量、此岸狼的数量、彼岸羊的数量、彼岸狼的数量。
因此
4
个变量或者一个长度为
4
的列表来表示一个节点状态。
# 分别用索引0 1 2 3来表示此岸羊的数量、此岸狼的数量、彼岸羊的数量、彼岸狼的数量 node = [sheep_here, wolf_here, sheep_there, wolf_there]
假设我们使用长度为
4
的列表来表示当前节点状态
node
,那么当我们从一个节点更新到下一个节点的时候(即一次运送过程),还需要满足以下几个限制条件:
我们可以构建出如下函数
def update (X, node) : # 遍历本次运送的羊的数量,为船的最大载重量和此岸羊的数量的较小值 for sheep_num in range(min(X+1 , node[0 ]+1 )): # 遍历本次运送的狼的数量 # 由于羊已经选择了sheep_num只,所以狼的数量上限为X-sheep_num只和此岸狼的数量的较小值 for wolf_num in range(min(X+1 -sheep_num, node[1 ]+1 )): # 得到本次运送后的下一个节点状态 # 此岸减少sheep_num只羊和wolf_num只狼 # 彼岸增加sheep_num只羊和wolf_num只狼 nxt = [node[0 ]-sheep_num, node[1 ]-wolf_num, node[2 ]+sheep_num, node[3 ]+wolf_num] # 如果这个方案使得此岸羊的数量小于等于狼的数量,则不可以选择,直接跳过 # 注意,如果本次方案使得此岸的羊的数量为0,是可以选择的 if nxt[0 ] != 0 and nxt[0 ] <= nxt[1 ]: continue # 如果这个方案使得彼岸羊的数量小于等于狼的数量,则不可以选择,直接跳过 # 注意,如果本次运送后,彼岸的羊的数量仍然为0,是可以选择的 if nxt[2 ] != 0 and nxt[2 ] <= nxt[3 ]: continue
我们就完成了从当前状态穷举得到下一个状态的过程。
重复状态的排除
另一个重要的问题是,我们可能会经历非常多的重复状态,而这些状态的重复计算是没有必要的。
因为BFS就决定了,
一旦某一个状态在之前遇到过,则当前搜索到的状态一定不会比之前那次搜索的层数更低
。
此处重复状态的排除,类似于我们在常规的二维网格搜索过程中构建的那个
check_list
。
我们可以构建一个
seen
哈希集合,储存所有已经出现过的状态。
要注意哈希集合中只能储存不可变类型的数据结构,譬如字符串或者元组。
此处使用字符串来储存
nxt_str = "," .join(map(str, nxt))
我们可以在上述的代码中再加上关于
seen
的代码,得到更新的代码如下
def update (X, node, seen) : # 遍历本次运送的羊的数量,为船的最大载重量和此岸羊的数量的较小值 for sheep_num in range(min(X+1 , node[0 ]+1 )): # 遍历本次运送的狼的数量 # 由于羊已经选择了sheep_num只,所以狼的数量上限为X-sheep_num只和此岸狼的数量的较小值 for wolf_num in range(min(X+1 -sheep_num, node[1 ]+1 )): # 得到本次本次运送后的下一个节点状态 # 此岸减少sheep_num只羊和wolf_num只狼 # 彼岸增加sheep_num只羊和wolf_num只狼 nxt = [node[0 ]-sheep_num, node[1 ]-wolf_num, node[2 ]+sheep_num, node[3 ]+wolf_num] # 如果这个方案使得此岸羊的数量小于等于狼的数量,则不可以选择,直接跳过 # 注意,如果本次方案使得此岸的羊的数量为0,是可以选择的 if nxt[0 ] != 0 and nxt[0 ] <= nxt[1 ]: continue # 如果这个方案使得彼岸羊的数量小于等于狼的数量,则不可以选择,直接跳过 # 注意,如果本次运送后,彼岸的羊的数量仍然为0,是可以选择的 if nxt[2 ] != 0 and nxt[2 ] <= nxt[3 ]: continue # 用字符串nxt_str表示下一个节点的状态 nxt_str = "," .join(map(str, nxt)) # 如果这个状态曾遇到过,则无需选择,直接跳过 if nxt_str in seen: continue # 如果没有遇到过这个状态,则直接储存如哈希集合seen中 seen.add(nxt_str)
代入BFS代码框架
核心过程基本已经完成,进一步优化
update()
函数,将队列
q
和判断答案的代码加入,即可得到以下函数
# 计算下一个状态nxt并且更新队列q的函数 def update (X, node, seen, q) : # 设置全局变量isFind,用来标记是否找到了解 global isFind # 遍历本次运送的羊的数量,为船的最大载重量和此岸羊的数量的较小值 for sheep_num in range(min(X+1 , node[0 ]+1 )): # 遍历本次运送的狼的数量 # 由于羊已经选择了sheep_num只,所以狼的数量上限为X-sheep_num只和此岸狼的数量的较小值 for wolf_num in range(min(X+1 -sheep_num, node[1 ]+1 )): # 得到本次运送后的下一个节点状态 # 此岸减少sheep_num只羊和wolf_num只狼 # 彼岸增加sheep_num只羊和wolf_num只狼 nxt = [node[0 ]-sheep_num, node[1 ]-wolf_num, node[2 ]+sheep_num, node[3 ]+wolf_num] # 如果下一个状态的羊和狼在此岸的数量均为0,则找到了最小搜索层数的解,修改isFind且退出函数 if nxt[0 ] == 0 and nxt[1 ] == 0 : isFind = True return # 如果这个方案使得此岸羊的数量小于等于狼的数量,则不可以选择,直接跳过 # 注意,如果本次方案使得此岸的羊的数量为0,是可以接受的 if nxt[0 ] != 0 and nxt[0 ] <= nxt[1 ]: continue # 如果这个方案使得彼岸羊的数量小于等于狼的数量,则不可以选择,直接跳过 # 注意,如果本次运送后,彼岸的羊的数量仍然为0,是可以接受的 if nxt[2 ] != 0 and nxt[2 ] <= nxt[3 ]: continue # 用字符串nxt_str表示下一个节点的状态 nxt_str = "," .join(map(str, nxt)) # 如果这个状态曾遇到过,则无需选择,直接跳过 if nxt_str in seen: continue # 如果没有遇到过这个状态,则直接储存如哈希集合seen中 seen.add(nxt_str) # 将下一个节点状态nxt储存入队列q中 q.append(nxt)
进行一些变量初始化并将
update()
函数在BFS代码框架中使用,则基本可以得到完整的代码
# 初始化用于BFS过程的队列 q = deque()# 最初的状态,此岸有n只羊和m只狼,彼岸有0只羊和0只狼 q.append([n, m, 0 , 0 ])# 初始化检查状态是否出现过的哈希集合seen seen = set()# BFS过程的层数,初始化为0 level = 0 # 构建变量isFind,表示最终是否找到解 isFind = False # BFS过程 while q: # 搜索层数+1 level += 1 # 获得当前层的节点个数 qSize = len(q) # 遍历当前层的所有的节点node for _ in range(qSize): # 弹出队头元素,获得当前节点node node = q.popleft() # 遍历下一个状态,排除重复状态,更新队列q update(X, node, seen, q) # 如果找到解,退出上层循环 if isFind: break # 如果找到解,退出上层循环 if
isFind: break # 如果有解,则isFind为True,输出level,否则输出0 print(level if isFind else 0 )
代码
Python
# www.algomooc.com from collections import deque# 输入羊的初始数量n,狼的初始数量m,船的最大载重量X n, m, X = map(int, input().split())# 计算下一个状态nxt并且更新队列q的函数 def update (X, node, seen, q) : # 设置全局变量isFind,用来标记是否找到了解 global isFind # 遍历本次运送的羊的数量,为船的最大载重量和此岸羊的数量的较小值 for sheep_num in range(min(X+1 , node[0 ]+1 )): # 遍历本次运送的狼的数量 # 由于羊已经选择了sheep_num只,所以狼的数量上限为X-sheep_num只和此岸狼的数量的较小值 for wolf_num in range(min(X+1 -sheep_num, node[1 ]+1 )): # 得到本次运送后的下一个节点状态 # 此岸减少sheep_num只羊和wolf_num只狼 # 彼岸增加sheep_num只羊和wolf_num只狼 nxt = [node[0 ]-sheep_num, node[1 ]-wolf_num, node[2 ]+sheep_num, node[3 ]+wolf_num] # 如果下一个状态的羊和狼在此岸的数量均为0,则找到了最小搜索层数的解,修改isFind且退出函数 if nxt[0 ] == 0 and nxt[1 ] == 0 : isFind = True return # 如果这个方案使得此岸羊的数量小于等于狼的数量,则不可以选择,直接跳过 # 注意,如果本次方案使得此岸的羊的数量为0,是可以接受的 if nxt[0 ] != 0 and nxt[0 ] <= nxt[1 ]: continue # 如果这个方案使得彼岸羊的数量小于等于狼的数量,则不可以选择,直接跳过 # 注意,如果本次运送后,彼岸的羊的数量仍然为0,是可以接受的 if nxt[2 ] != 0 and nxt[2 ] <= nxt[3 ]: continue # 用字符串nxt_str表示下一个节点的状态 nxt_str = "," .join(map(str, nxt)) # 如果这个状态曾遇到过,则无需选择,直接跳过 if nxt_str in seen: continue # 如果没有遇到过这个状态,则直接储存如哈希集合seen中 seen.add(nxt_str) # 将下一个节点状态nxt储存入队列q中 q.append(nxt)# 初始化用于BFS过程的队列 q = deque()# 最初的状态,此岸有n只羊和m只狼,彼岸有0只羊和0只狼 q.append([n, m, 0 , 0 ])# 初始化检查状态是否出现过的哈希集合seen seen = set()# BFS过程的层数,初始化为0 level = 0 # 构建变量isFind,表示最终是否找到解 isFind = False # BFS过程 while q: # 搜索层数+1 level += 1 # 获得当前层的节点个数 qSize = len(q) # 遍历当前层的所有的节点node for _ in range(qSize): # 弹出队头元素,获得当前节点node node = q.popleft() # 遍历下一个状态,排除重复状态,更新队列q update(X, node, seen, q) # 如果找到解,退出上层循环 if isFind: break # 如果找到解,退出上层循环 if isFind: break # 如果有解,则isFind为True,输出level,否则输出0 print(level if isFind else 0 )
Java
//www.algomooc.com import java.util.*;public class Main { public static void main (String[] args) { // 读取输入:羊的数量n,狼的数量m,船的最大载重量X Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); // 初始羊的数量 int m = scanner.nextInt(); // 初始狼的数量 int X = scanner.nextInt(); // 船的最大载重量 // 初始化队列,用于BFS过程 Queue<int []> q = new LinkedList<>(); // 最初的状态:此岸有n只羊和m只狼,彼岸有0只羊和0只狼 q.offer(new int []{n, m, 0 , 0 }); // 初始化检查状态是否出现过的哈希集合seen Set seen = new HashSet<>(); // BFS过程的层数,初始化为0 int level = 0 ; // 构建变量isFind,表示最终是否找到解 boolean isFind = false ; // BFS过程 while (!q.isEmpty()) { // 搜索层数+1 level++; // 获得当前层的节点个数 int qSize = q.size(); // 遍历当前层的所有的节点node for (int i = 0 ; i < qSize; i++) { // 弹出队头元素,获得当前节点node int [] node = q.poll(); // 遍历下一个状态,排除重复状态,更新队列q isFind = update(X, node, seen, q); // 如果找到解,退出上层循环 if (isFind) break ; } // 如果找到解,退出上层循环 if (isFind) break ; } // 如果有解,则isFind为true,输出level,否则输出0 System.out.println(isFind ? level : 0 ); } // 计算下一个状态nxt并且更新队列q的函数 private static boolean update (int X, int [] node, Set seen, Queue<int []> q) { // 遍历本次运送的羊的数量 for (int sheepNum = 0 ; sheepNum <= Math.min(X, node[0 ]); sheepNum++) { // 遍历本次运送的狼的数量 for (int wolfNum = 0 ; wolfNum <= Math.min(X - sheepNum, node[1 ]); wolfNum++) { // 得到本次运送后的下一个节点状态 int [] nxt = { node[0 ] - sheepNum, // 此岸减少羊的数量
node[1 ] - wolfNum, // 此岸减少狼的数量 node[2 ] + sheepNum, // 彼岸增加羊的数量 node[3 ] + wolfNum // 彼岸增加狼的数量 }; // 如果下一个状态的羊和狼在此岸的数量均为0,则找到了解 if (nxt[0 ] == 0 && nxt[1 ] == 0 ) { return true ; } // 检查此岸羊的数量是否小于等于狼的数量(但不为0) if (nxt[0 ] != 0 && nxt[0 ] <= nxt[1 ]) { continue ; } // 检查彼岸羊的数量是否小于等于狼的数量(但不为0) if (nxt[2 ] != 0 && nxt[2 ] <= nxt[3 ]) { continue ; } // 用字符串表示下一个节点的状态 String nxtStr = nxt[0 ] + "," + nxt[1 ] + "," + nxt[2 ] + "," + nxt[3 ]; // 如果这个状态曾遇到过,则直接跳过 if (seen.contains(nxtStr)) { continue ; } // 如果没有遇到过这个状态,则储存到哈希集合seen中 seen.add(nxtStr); // 将下一个节点状态nxt储存到队列q中 q.offer(nxt); } } return false ; } }
C++
#include #include #include #include #include #include using namespace std ;// 将状态转化为字符串形式,用于哈希表存储 string stateToString (vector <int >& state) { ostringstream oss; for (size_t i = 0 ; i < state.size(); i++) { if (i > 0 ) oss << "," ; oss << state[i]; } return oss.str(); }// 更新队列的函数 bool update (int X, vector <int >& node, unordered_set <string >& seen, queue <vector <int >>& q) { for (int sheep_num = 0 ; sheep_num <= min(X, node[0 ]); sheep_num++) { for (int wolf_num = 0 ; wolf_num <= min(X - sheep_num, node[1 ]); wolf_num++) { vector <int > nxt = { node[0 ] - sheep_num, // 此岸减少羊的数量 node[1 ] - wolf_num, // 此岸减少狼的数量 node[2 ] + sheep_num, // 彼岸增加羊的数量 node[3 ] + wolf_num // 彼岸增加狼的数量 }; if (nxt[0 ] == 0 && nxt[1 ] == 0 ) { return true ; // 找到解 } if (nxt[0 ] != 0 && nxt[0 ] <= nxt[1 ]) continue ; // 此岸条件不合法 if (nxt[2 ] != 0 && nxt[2 ] <= nxt[3 ]) continue ; // 彼岸条件不合法 string nxt_str = stateToString(nxt); if (seen.count(nxt_str)) continue ; // 状态已访问过 seen.insert(nxt_str); q.push(nxt); } } return false ; }int main () { int n, m, X; cin >> n >> m >> X; queue <vector <int >> q; unordered_set <string > seen; q.push({n, m, 0 , 0 }); seen.insert(stateToString(q.front())); int level = 0 ; bool isFind = false ; while (!q.empty()) { level++; int qSize = q.size(); for (int i = 0 ; i < qSize; i++) { vector <int > node = q.front(); q.pop(); if (update(X, node, seen, q)) { isFind = true ; break ; } } if (isFind) break ; } cout << (isFind ? level : 0 ) << endl ; return 0 ; }
C
#include #include #include #include // 定义常量 #define MAX_STATE 10000 // 最大状态数量 #define MAX_STR_LEN 50 // 状态字符串的最大长度 // 定义队列结构 typedef struct { int data[MAX_STATE][4 ]; // 存储状态数组 int front, rear; // 队列头和尾 } Queue;// 队列初始化 void initQueue (Queue *q) { q->front = q->rear = 0 ; }// 队列是否为空 bool isEmpty (Queue *q) { return q->front == q->rear; }// 队列入队 void enqueue (Queue *q, int state[4 ]) { for (int i = 0 ; i < 4 ; i++) { q->data[q->rear][i] = state[i]; } q->rear++; }// 队列出队 void dequeue (Queue *q, int state[4 ]) { for (int i = 0 ; i < 4 ; i++) { state[i] = q->data[q->front][i]; } q->front++; }// 哈希表,用于记录已访问的状态 typedef struct { char keys[MAX_STATE][MAX_STR_LEN]; int size; } HashSet;// 哈希表初始化 void initHashSet (HashSet *set ) { set ->size = 0 ; }// 检查哈希表中是否包含某个状态 bool contains (HashSet *set , char *key) { for (int i = 0 ; i < set ->size; i++) { if (strcmp (set ->keys[i], key) == 0 ) { return true ; } } return false ; }// 向哈希表中添加状态 void add (HashSet *set , char *key) { if (!contains(set , key)) { strcpy (set ->keys[set ->size++], key); } }// 将状态转化为字符串形式 void stateToString (int state[4 ], char *result) { sprintf (result, "%d,%d,%d,%d" , state[0 ], state[1 ], state[2 ], state[3 ]); }// 更新队列的函数 bool update (int X, int node[4 ], HashSet *seen, Queue *q) { for (int sheep_num = 0 ; sheep_num <= X && sheep_num <= node[0 ]; sheep_num++) { for (int wolf_num = 0 ; wolf_num <= X - sheep_num && wolf_num <= node[1 ]; wolf_num++) { int nxt[4 ] = { node[0 ] - sheep_num, // 此岸减少羊的数量 node[1 ] - wolf_num, // 此岸减少狼的数量