专栏名称: 吴师兄学算法
和程序员小吴一起从初学者的角度学习算法,以动画的形式呈现解题的思路。每周四篇原创文章,期待你的鉴赏!
目录
相关文章推荐
一念行者  ·  以大海而存在,让波浪境生境灭 ·  昨天  
51好读  ›  专栏  ›  吴师兄学算法

2025最新大厂薪资一览表

吴师兄学算法  · 公众号  ·  · 2025-01-07 11:10

正文

大家好,我是吴师兄。

前几天,一份从脉脉流出的最新大厂薪资地图,让社交圈炸了锅。

不少人感叹,“难怪年轻人削尖脑袋往大厂挤,就这薪资,搁谁不心动?”

但也有人说,大厂的高薪不过是 “卖命的代价”

先来看看研发岗的薪资, 大厂一线岗位是真的高

应届生动辄月薪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 只狼

解题思路

非常有意思的好题,很少见到这么好的题目了。

转化为搜索状态树最小层数问题

本题乍一看其实很难看出是一道图搜索题目,但如果我们从做出整个运送过程的示意图, 把每次选择后此岸情况、彼岸情况的状态当作一个节点,而每次选择都会转移到下一层节点更新到下一个状态 ,就会发现这实际上是一个状态树寻找最小搜索层数的问题。

以题目提供的示例为例,我们做出示意图

显然我们可以看出几个要点

  1. 这是一个搜索过程,每一次运送过程,我们都有多种可能的运送方案
  2. 有一些方案可能会和之前出现过的方案重复,可以进行排除,无需做重复计算
  3. 本题的要求是要找到最少运送次数,即是 找到这棵状态树的最小搜索层数 ,所以使用BFS要使用DFS更优。

节点的设计以及更新

确定这是一个状态树搜索问题之后,我们就需要设计出节点的储存方式了。

很显然,每一个节点状态仅由 4 个数据来表示, 此岸羊的数量、此岸狼的数量、彼岸羊的数量、彼岸狼的数量。

因此 4 个变量或者一个长度为 4 的列表来表示一个节点状态。

# 分别用索引0 1 2 3来表示此岸羊的数量、此岸狼的数量、彼岸羊的数量、彼岸狼的数量
node = [sheep_here, wolf_here, sheep_there, wolf_there]

假设我们使用长度为 4 的列表来表示当前节点状态 node ,那么当我们从一个节点更新到下一个节点的时候(即一次运送过程),还需要满足以下几个限制条件:

  1. 本次运送的载重不能超过船的最大载重量 X
  2. 本次运送的羊或狼不能超过此岸的羊或狼的数量
  3. 运送后,此岸羊的数量必须多于狼
  4. 运送后,彼岸羊的数量必须多于狼

我们可以构建出如下函数

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, 00])
# 初始化检查状态是否出现过的哈希集合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, 00])
# 初始化检查状态是否出现过的哈希集合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, 00});
        // 初始化检查状态是否出现过的哈希集合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, 00});
    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 *setchar *key) {
    for (int i = 0; i < set->size; i++) {
        if (strcmp(set->keys[i], key) == 0) {
            return true;
        }
    }
    return false;
}

// 向哈希表中添加状态
void add(HashSet *setchar *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,  // 此岸减少狼的数量






请到「今天看啥」查看全文