大家好,我是吴师兄。
每年校招季,最扎心的不是「投了简历没人看」,也不是「面试被问哭」,而是
“这年头算法岗到底怎么卷?”
。
今年依然有不少同学问我:「师兄,哪个方向最好上岸?」「后端岗会认可客户端的实习吗?」。
今天咱们就围绕这些问题,聊聊算法校招的那些事儿。
客户端实习,后端岗也认?
这个问题,答案是肯定的。
客户端和后端在校招层面没有你想象的壁垒那么高。
特别是在大厂,校招的重点是看你的潜力,而不是死磕方向。很多后端团队反而欣赏有客户端经验的候选人:你会从更贴近用户的角度考虑问题,对业务的理解可能更深。至于实习经历,那就像张小龙说的——“用得上的能力,才是值得学的。”所以有机会去做客户端实习,不要犹豫,后端岗还是有戏的。
最容易上岸的算法方向?
还是大模型和搜索推广。
老实说,算法岗「两极分化」很严重。业务导向的岗位需求量大,竞争相对小,比如搜索推广。相比之下,偏学术的方向(比如CV/NLP的纯研究岗)竞争异常激烈。这背后有现实原因:公司烧钱的时代过去了,现在大家更关心
“能不能直接赚钱?”
搜索推广就是那个
「永远不会过时的刚需」
。更神奇的是,不少人即使一开始没做这个方向,最终也会慢慢靠近。要么是主动转岗,要么是被业务需求推动。
市场需要,才是最终决定你去哪里的主旋律。
校招选公司,氛围真的很重要
除了方向,更让人头疼的是选公司。
很多人都问:「选大公司还是选小公司?」「重技术还是重业务?」其实规模和技术方向相差不大的情况下,
团队氛围才是决定你能不能开心工作的关键。
一个良好的团队氛围,不仅能让你干活不累,还能让你在行业里建立靠谱的人脉。反之,进了个气氛组,每天办公室弥漫着压抑的「职场丛林法则」,你可能干着干着就怀疑人生了。
从内容理解到搜索推广:转岗真的难吗?
如果你是做内容理解的,不必担心方向的局限性。
很多搜索推广团队都有「内容理解」相关的项目组,转岗平滑得像地铁换乘。甚至有些大厂专门会留这种内部流动的通道,帮助你实现职业方向的「升级打怪」。
算法岗的竞争从来不是绝路,关键是怎么找自己的优势和市场需求的结合点。
结尾的那点人生感悟
校招这件事,其实是一场「理性和感性的对话」。
理性上,选方向要看清市场的需求,别和现实较劲。
感性上,选团队一定要选让自己舒服的环境,长远来看,这才是稳定输出的关键。
最后借用一个朋友的话送给正在迷茫的校招生:
“人生没有白走的路,每一步都算数。”
也许今天的选择会影响几年后的轨迹,但不管方向如何,只要你认真走下去,都能找到属于自己的光。
回归我们公众号的主题。
继续来一道和「算法岗校招」相关的算法原题。
题目描述
公司老板做了一笔大生意,想要给每位员工分配一些奖金,想通过游戏的方式来决定每个人分多少钱。
按照员工的工号顺序,每个人随机抽取一个数字。按照工号的顺序往后排列,遇到
第一个数字比自己数字大的
,那么,前面的员工就可以获得
“距离*数字差值”
的奖金。如果遇不到比自己数字大的,就给自己分配随机数数量的奖金。
例如,按照工号顺序的随机数字是:
2,10,3
。那么第
2
个员工的数字
10
比第1个员工的数字
2
大,所以,第
1
个员工可以获得
1*(10-2)=8
。第
2
个员工后面没有比他数字更大的员工,所以,他获得他分配的随机数数量的奖金,就是
10
。第
3
个员工是最后一个员工,后面也没有比他更大数字的员工,所以他得到的奖金是
3
。
请帮老板计算一下每位员工最终分到的奖金都是多少钱。
输入描述
第一行
n
表示员工数量
第二是每位员工分配的随机数字
输出描述
最终每位员工分到的奖金数量
注:随机数字不重复,员工数量范围
1~10000
,随机数范围
1~100000
示例
输入
3
2 10 3
输出
8 10 3
解题思路
每一个员工本质上是要找到
位于其右边第一个大于它的元素
,并且求出它们之间的大小差值和距离差值。显然应该立刻想到使用单调栈来完成。
无论是正序还是逆序,都可以完成这个题目。
代码
解法一:逆序遍历
Python
# 输入员工个数n
n = int(input())
# 输入N个员工的随机数数组
nums = list(map(int, input().split()))
# 构建一个单调栈,用来存放不同随机数的索引
# 栈中储存的索引所对应在height中的元素大小,从栈底至栈顶单调递减
# 即更大的数(的下标)位于栈底
stack = list()
# 构建列表ans,用来保存输出结果
# 初始化其中所有的元素和原数组一样,注意此处要使用拷贝
ans = nums.copy()
# 逆序遍历nums中的每一个随机数
for i in range(n-1, -1, -1):
num = nums[i]
# 第i个员工拿到的的随机数num,需要不断地与栈顶元素比较
# 如果栈顶元素存在并且num【大于等于】栈顶元素stack[-1]这个下标对应的数字
# 说明栈顶元素stack[-1]这个下标对的数字,并不是随机数num右边最近的比num更大的元素
# 需要将栈顶元素弹出,继续寻找比num大的栈顶元素
while len(stack) > 0 and num >= nums[stack[-1]]:
# 栈顶元素下标对应的数字不大于当前数字num,不是符合要求的更大数字,弹出
stack.pop()
# 完成弹出后,如果栈顶仍存在元素,说明stack[-1]所对应的数字,是严格比当前数字num大的下一个数字
if len(stack) > 0:
# stack[-1]即为i这个索引所对应的,下一个最近的更大的随机数的下标
# 它们之间的下标距离为stack[-1]-i,数字大小差值为nums[stack[-1]]-num
# 相乘后在ans中修改,作为答案
ans[i] = (stack[-1]-i)*(nums[stack[-1]]-num)
# 再把当前下标i储存入栈中
# 注意:所储存的是下标i,而不是随机数num
stack.append(i)
# ans中的int元素转成str后才能合并成字符串
print(" ".join(map(str, ans)))
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
// 创建Scanner对象用于输入
Scanner sc = new Scanner(System.in);
// 输入员工个数n
int n = sc.nextInt();
// 输入N个员工的随机数数组
int[] nums = new int[n];
for (int i = 0; i nums[i] = sc.nextInt();
}
// 构建一个单调栈,用来存放不同随机数的索引
// 栈中储存的索引所对应在nums中的元素大小,从栈底至栈顶单调递减
// 即更大的数(的下标)位于栈底
Stack stack = new Stack<>();
// 构建列表ans,用来保存输出结果
// 初始化其中所有的元素和原数组一样,注意此处要使用拷贝
int[] ans = Arrays.copyOf(nums, n);
// 逆序遍历nums中的每一个随机数
for (int i = n - 1; i >= 0; i--) {
int num = nums[i];
// 第i个员工拿到的的随机数num,需要不断地与栈顶元素比较
// 如果栈顶元素存在并且num【大于等于】栈顶元素stack[-1]这个下标对应的数字
// 说明栈顶元素stack[-1]这个下标对的数字,并不是随机数num右边最近的比num更大的元素
// 需要将栈顶元素弹出,继续寻找比num大的栈顶元素
while (!stack.isEmpty() && num >= nums[stack.peek()]) {
// 栈顶元素下标对应的数字不大于当前数字num,不是符合要求的更大数字,弹出
stack.pop();
}
// 完成弹出后,如果栈顶仍存在元素,说明stack[-1]所对应的数字,是严格比当前数字num大的下一个数字
if (!stack.isEmpty()) {
// stack.peek()即为i这个索引所对应的,下一个最近的更大的随机数的下标
// 它们之间的下标距离为stack.peek()-i,数字大小差值为nums[stack.peek()]-num
// 相乘后在ans中修改,作为答案
ans[i] = (stack.peek() - i) * (nums[stack.peek()] - num);
}
// 再把当前下标i储存入栈中
// 注意:所储存的是下标i,而不是随机数num
stack.push(i);
}
// 输出结果,确保数字之间用空格分隔,没有尾部空格
for (int i = 0; i if (i > 0) System.out.print(" ");
System.out.print(ans[i]);
}
System.out.println();
}
}
C++
#include
#include
#include
using namespace std;
int main() {
// 输入员工个数n
int n;
cin >> n;
// 输入N个员工的随机数数组
vector<int> nums(n);
for (int i = 0; i cin >> nums[i];
}
// 构建一个单调栈,用来存放不同随机数的索引
// 栈中储存的索引所对应在nums中的元素大小,从栈底至栈顶单调递减
// 即更大的数(的下标)位于栈底
stack<int> s;
// 构建列表ans,用来保存输出结果
// 初始化其中所有的元素和原数组一样,注意此处要使用拷贝
vector<int> ans = nums;
// 逆序遍历nums中的每一个随机数
for (int i = n - 1; i >= 0; i--) {
int num = nums[i];
// 第i个员工拿到的的随机数num,需要不断地与栈顶元素比较
// 如果栈顶元素存在并且num【大于等于】栈顶元素stack[-1]这个下标对应的数字
// 说明栈顶元素stack[-1]这个下标对的数字,并不是随机数num右边最近的比num更大的元素
// 需要将栈顶元素弹出,继续寻找比num大的栈顶元素
while (!s.empty() && num >= nums[s.top()]) {
// 栈顶元素下标对应的数字不大于当前数字num,不是符合要求的更大数字,弹出
s.pop();
}
// 完成弹出后,如果栈顶仍存在元素,说明stack[-1]所对应的数字,是严格比当前数字num大的下一个数字
if (!s.empty()) {
// s.top()即为i这个索引所对应的,下一个最近的更大的随机数的下标
// 它们之间的下标距离为s.top()-i,数字大小差值为nums[s.top()]-num
// 相乘后在ans中修改,作为答案
ans[i] = (s.top() - i) * (nums[s.top()] - num);
}
// 再把当前下标i储存入栈中
// 注意:所储存的是下标i,而不是随机数num
s.push(i);
}
// 输出结果,确保数字之间用空格分隔,没有尾部空格
for (int i = 0; i if (i > 0) cout <" ";
cout < }
cout
return 0;
}
C
#include
#include
// 定义一个栈的数据结构
#define MAX_SIZE 100000
int stack[MAX_SIZE];
int top = -1;
// 向栈中压入元素
void push(int index) {
stack[++top] = index;
}
// 从栈中弹出元素
int pop() {
return stack[top--];
}
// 查看栈顶元素
int peek() {
return stack[top];
}
int main() {
int n;
// 输入员工个数n
scanf("%d", &n);
// 输入N个员工的随机数数组
int nums[n];
for (int i = 0; i scanf("%d", &nums[i]);
}
// 构建数组ans,用来保存输出结果
int ans[n];
for (int i = 0; i ans[i] = nums[i];
}
// 逆序遍历nums中的每一个随机数
for (int i = n - 1; i >= 0; i--) {
int num = nums[i];
// 第i个员工拿到的的随机数num,需要不断地与栈顶元素比较
while (top >= 0 && num >= nums[stack[top]]) {
// 弹出栈顶元素
pop();
}
// 如果栈顶仍存在元素,说明stack[top]所对应的数字,是严格比当前数字num大的下一个数字
if (top >= 0) {
ans[i] = (stack[top] - i) * (nums[stack[top]] - num);
}
// 把当前下标i储存入栈中
push(i);
}
// 输出结果,确保数字之间用空格分隔,没有尾部空格
for (int i = 0; i if (i > 0) {
printf(" ");
}
printf("%d", ans[i]);
}
printf("\n");
return 0;
}
Node JavaScript
const readline = require('readline');
// 创建接口读取输入
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// 读取输入
rl.question('', (n) => {
rl.question('', (line) => {
const nums = line.split(' ').map(Number);
const ans = [...nums]; // 拷贝一份nums数组
// 单调栈
const stack = [];
// 逆序遍历
for (let i = n - 1; i >= 0; i--) {
const num = nums[i];
// 比较栈顶元素与当前num的大小
while (stack.length > 0 && num >= nums[stack[stack.length - 1]]) {
stack.pop();
}
// 如果栈顶仍然有元素,说明栈顶对应的数字是比当前num大的下一个数字
if (stack.length > 0) {
ans[i] = (stack[stack.length - 1] - i) * (nums[stack[stack.length - 1]] - num);
}
// 将当前下标i压入栈中
stack.push(i);
}
// 输出结果,确保数字之间用空格分隔,且没有尾部空格
console.log(ans.join(' '));
rl.close(); // 关闭接口
});
});
Go
package main
import (
"fmt"
)
func main() {
var n int
// 输入员工个数n
fmt.Scan(&n)
// 输入N个员工的随机数数组
nums := make([]int, n)
for i := 0; i fmt.Scan(&nums[i])
}
// 构建一个单调栈
stack := []int{}
ans := make([]int, n)
copy(ans, nums) // 初始化结果数组
// 逆序遍历nums中的每一个随机数
for i := n - 1; i >= 0; i-- {
num := nums[i]
// 比较栈顶元素与当前num的大小
for len(stack) > 0 && num >= nums[stack[len(stack)-1]] {
// 弹出栈顶元素
stack = stack[:len(stack)-1]
}
// 如果栈顶仍然有元素,说明栈顶对应的数字是比当前num大的下一个数字
if len(stack) > 0 {
ans[i] = (stack[len(stack)-1] - i) * (nums[stack[len(stack)-1]] - num)
}
// 将当前下标i压入栈中
stack = append(stack, i)
}
// 输出结果,确保数字之间用空格分隔,且没有尾部空格
for i := 0; i if i > 0 {
fmt.Print(" ")
}
fmt.Print(ans[i])
}
fmt.Println()
}
解法二:正序遍历
Python
# 输入员工个数n
n = int(input())
# 输入N个员工的随机数数组
nums = list(map(int, input().split()))
# 构建一个单调栈,用来存放不同随机数的索引
# 栈中储存的索引所对应在height中的元素大小,从栈底至栈顶单调递减
# 即更大的数(的下标)位于栈底
stack = list()
# 构建列表ans,用来保存输出结果
# 初始化其中所有的元素和原数组一样,注意此处要使用拷贝
ans = nums.copy()
# 从头开始正序遍历nums中的每一个随机数
for i, num in enumerate(nums):
# 第i个员工拿到的的随机数num,需要不断地与栈顶元素比较
# 如果栈顶元素存在并且num【大于】栈顶元素stack[-1]这个下标对应的数字
# 意味着nums中下标为stack[-1]的数字,找到了右边最近的比它更大的数字num
while len(stack) > 0 and num > nums[stack[-1]]:
# 首先获取栈顶元素的值,也就是要修改的位置
preIndex = stack.pop()
# i即为preIndex这个索引所对应的,下一个最近的更大的随机数的下标
# 它们之间的下标距离为i-preIndex,数字大小差值为num-nums[preIndex]
# 相乘后在ans中修改,作为答案
ans[preIndex] = (i-preIndex)*(num-nums[preIndex])
# 再把当前下标i储存入栈中
# 注意:所储存的是下标i,而不是随机数num
stack.append(i)
# ans中的int元素转成str后才能合并成字符串
print(" ".join(map(str, ans)))
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 输入员工个数n
int n = sc.nextInt();
// 输入N个员工的随机数数组
int[] nums = new int[n];
for (int i = 0; i nums[i] = sc.nextInt();
}
// 构建一个单调栈,用来存放不同随机数的索引
// 栈中储存的索引所对应的元素大小,从栈底至栈顶单调递减
// 即更大的数(的下标)位于栈底
Stack stack = new Stack<>();
// 构建列表ans,用来保存输出结果
// 初始化其中所有的元素和原数组一样,注意此处要使用拷贝
int[] ans = Arrays.copyOf(nums, n);
// 从头开始正序遍历nums中的每一个随机数
for (int i = 0; i int num = nums[i];
// 第i个员工拿到的的随机数num,需要不断地与栈顶元素比较
// 如果栈顶元素存在并且num【大于】栈顶元素stack[-1]这个下标对应的数字
// 意味着nums中下标为stack[-1]的数字,找到了右边最近的比它更大的数字num
while (!stack.isEmpty() && num > nums[stack.peek()]) {
// 首先获取栈顶元素的值,也就是要修改的位置
int preIndex = stack.pop();
// i即为preIndex这个索引所对应的,下一个最近的更大的随机数的下标
// 它们之间的下标距离为i-preIndex,数字大小差值为num-nums[preIndex]
// 相乘后在ans中修改,作为答案
ans[preIndex] = (i - preIndex) * (num - nums[preIndex]);
}
// 再把当前下标i储存入栈中
// 注意:所储存的是下标i,而不是随机数num
stack.push(i);
}
// 输出结果,避免最后的空格
for (int i = 0; i if (i > 0) {
System.out.print(" "); // 在输出之前先输出一个空格
}
System.out.print(ans[i]);
}
System.out.println();
sc.close();
}
}
C++
#include
#include
#include
using namespace std;
int main() {
int n;
// 输入员工个数n
cin >> n;
// 输入N个员工的随机数数组
vector<int> nums(n);
for