专栏名称: 吴师兄学算法
和程序员小吴一起从初学者的角度学习算法,以动画的形式呈现解题的思路。每周四篇原创文章,期待你的鉴赏!
目录
相关文章推荐
北京晚报  ·  补时遭绝杀!遗憾……但要看到希望 ·  8 小时前  
北京晚报  ·  补时遭绝杀!遗憾……但要看到希望 ·  8 小时前  
广电独家  ·  新集团挂牌成立!贵州广电改革再行一步 ·  3 天前  
51好读  ›  专栏  ›  吴师兄学算法

不愧是大厂!字节跳动今年的薪资,直接看傻了……

吴师兄学算法  · 公众号  ·  · 2024-12-10 11:30

正文


大家好,我是吴师兄。

网友总结了一些今年字节的真实 Offer,看完真的让人直呼: “有钱真好。”

  1. 后端开发:32k * 15(月薪 x 15 月)

  • 学历:硕士,985
  • 等级:SSP
  • 工作地点:上海
  • 算法工程师:总包 100w + 股票

    • 学历:博士,海归
    • 等级:SSP
    • 工作地点:深圳
  • 后端开发:37k * 15 + 10w 签字费 + 1.5k 每月补贴

    • 学历:硕士,985
    • 等级:SSP
    • 工作地点:北京
  • 后端开发:30k * 15(月薪 x 15 月)

    • 学历:硕士,211
    • 等级:白菜价
    • 工作地点:北京

    估计有不少人感慨:“ 白菜价的标准都快追上我的梦想了!

    字节跳动的薪资,为啥总能卷到极致?

    1. 拿命卷的公司,当然敢给

    你没看错,字节是真的“拿命”在工作。从飞书到抖音,从火山到今日头条,哪一个产品不是在市场杀出血路?

    有句话很经典: “如果你拿着高薪,但公司在摸鱼,其实就是白嫖股东的钱;但如果你在字节,恭喜,你是在用高薪创造新神话。”

    2. SSP 是啥?全是钱的味道

    大厂的 SSP(Super Senior Package),本质上就是 顶级待遇

    但这也意味着, 工作强度和能力要求直接拉满

    SSP 的门槛在哪里?

    你需要拥有 硬核的技能、丰富的项目经验,以及足够亮眼的面试表现

    简单来说:能在激烈竞争中脱颖而出,证明“我就是那个最强王者”。


    如何成为 SSP 收割机?老司机划重点

    爆料网友也总结了一些“拿下字节 Offer”的秘籍, 把握以下几条,或许你也能“鲤鱼跃龙门”:

    1. 实习是敲门砖

    特别是 大厂实习 ,它直接决定了你的简历能不能过筛子。

    有些应届生早早在字节、阿里、美团等公司实习,等到校招时,优势直接拉满。

    2. 八股 + 算法 = 基础能力标配

    别指望“靠嘴炮打天下”,字节面试偏好 动手能力和逻辑思维

    • 八股文 :操作系统、网络、数据库、框架等,必须了如指掌;
    • 算法 :刷题量至少 400 起步,每道题目争取都能完美 AC。

    3. 项目经验要有亮点

    不要烂大街的“学生管理系统”或“电商商城”,那是 HR 的免疫范围。

    项目经历越能 体现解决问题的能力 ,越能吸引面试官的目光。

    4. 运气也是实力的一部分

    字节面试官的口碑里,有人说“碰到对的人和对的题”,感觉像中彩票。

    遇到聊得来的面试官,说不定直接一轮过。

    最后,咱普通人该怎么看待大厂?

    有人说:“高薪背后是无尽的 KPI 和无休止的熬夜。”的确,大厂不是天堂,但 对有准备的人来说,它就是通向高阶职场的电梯

    1. 看看自己的目标是什么

    钱、成长、还是生活平衡?

    • 如果你追求高薪,且有拼搏精神,大厂是个好选择;
    • 如果你更看重稳定和舒适,选择中小型企业也未尝不可。

    2. 不用一味羡慕,努力更重要

    看别人月薪 30k,背后是无数通宵刷题和数千行代码。
    天上掉馅饼之前,你得有接住的盘子。


    然后,回归我们公众号的主题, 每天掌握一道算法题

    继续来一道和「校招」相关的算法原题。

    本原题练习网址:https://www.algomooc.com/problem/P4204

    题目描述

    给定一个二维整数矩阵,要在这个矩阵中。选出一个子矩阵,使得这个子矩阵内所有的数字和尽量大

    我们把这个子矩阵称为 “和最大子矩阵” ,子矩阵的选取原则,是原矩阵中一段相互连续的矩形区域

    输入描述

    输入的第一行包含两个整数 N,M

    (1 <= N, M <= 10)

    表示一个 N 行 M 列的矩阵

    下面有 N 行 每行有 M 个整数

    同一行中每两个数字之间有一个空格

    最后一个数字后面没有空格

    所有的数字得在 -1000 ~ 1000 之间

    输出描述

    输出一行,一个数字。表示选出的“和最大子矩阵”内所有数字的和

    示例

    输入

    3 4
    -3 5 -1 5
    2 4 -2 4
    -1 3 -1 3

    输出

    20

    说明

    一个 3*4 的矩阵中

    后面 3 列的和为 20 ,和最大

    解题思路

    如何表示一个子矩阵

    一个子矩阵可以由四个参数决定,分别为上底、下底、左宽、右宽,分别用变量 a b c d 表示的话,如下图中灰色区域为通过四个参数所确定的矩形。

    如果我们想要枚举所有子矩阵,只需要分别枚举 a b c d ,写一个 4 层嵌套的 for 循环即可。

    for a in range(n):
         for b in range(a, n):
             for c in range(m):
                 for d in range(c, m):
                    pass

    暴力解法

    暴力解法是很容易想到的,我们只需要枚举所有的子矩阵,然后对每一个子矩阵进行矩阵内所有元素求和即可。其核心代码为

    for a in range(n):
         for b in range(a, n):
             for c in range(m):
                 for d in range(c, m):
                     submat_sum = 0
                     for i in range(a, b+1):
                         for j in range(c, d+1):
                             submat_sum += mat[i][j]
                     ans = max(submat_sum, ans)

    注意到会出现 6 for 循环嵌套,时间复杂度为

    。由于数据范围为 (1 <= n, m <= 10) ,故取最大值时复杂度约为
    。在数据量较大的情况下,可能无法通过全部用例,故应该思考如何优化。

    二维前缀和优化

    注意,该方法和LeetCode 304、二维区域和检索 - 矩阵不可变 是类似的。

    注意到每一个子矩阵的计算都可以用以下方式进行拆解。

    拆解后的四个区域具有一个共同的特点: 它们的上底均为上边界、左宽均为左边界

    因此需要考虑类似一维前缀和的方法,将所有的上底为上边界、左宽为左边界(即 a = 0 c = 0 )的子矩阵的和提前记录在二维前缀和矩阵 pre_sum_mat 中。

    pre_sum_mat 是一个大小为 (n+1)*(m+1) 的矩阵, pre_sum_mat[i][j] 表示以第 0 行、第 0 列为开头(取得到的开区间),第 i 行、第 j 列为结尾(取不到的闭区间)的子矩阵的和。

    上述的四个区域的和,就可以分别使用 pre_sum_mat[b+1][d+1] pre_sum_mat[b+1][c] pre_sum_mat[a][d+1] pre_sum_mat[a][c] 来表示了。

    这里对开/闭区间的理解是非常重要的,如果想不清楚的话,后面的代码很容易出错。如果把子矩阵用一种类似切片的方法表示(并不严谨的写法)为 mat[a:b+1][c:d+1] 。那么上述的分析过程可以写为

    sum(mat[a:b+1][c:d+1])
    = sum(mat[:b+1][:d+1]) + sum(mat[:a][:c]) - sum(mat[:b+1][:c]) - sum(mat[:a][:d+1])

    = pre_sum_mat[b+1][d+1] + pre_sum_mat[a][c] - pre_sum_mat[b+1][c] - pre_sum_mat[a][d+1]

    那么,在原矩阵 mat 中,分别以 a b c d 为上底、下底、左宽、右宽的子矩阵的和,就可以记为

    submat_sum = (pre_sum_mat[b+1][d+1] + pre_sum_mat[a][c] -
                  pre_sum_mat[b+1][c] - pre_sum_mat[a][d+1])

    上述计算的时间复杂度为 O(1) ,因此这种做法 规避了暴力解对子矩阵求和时出现的反复计算,降低了最内层求和时时间复杂度 。如果把外部的循环体加上,代码为

    for a in range(n):
        for b in range(a, n):
            for c in range(m):
                for d in range(c, m):
                    submat_sum = pre_sum_mat[b+1][d+1] + pre_sum_mat[a][c] - \
                                 pre_sum_mat[b+1][c] - pre_sum_mat[a][d+1]
                    ans = max(submat_sum, ans)

    如果不想让最内层的索引出现 +1 ,则可以修改for循环的范围,代码变为

    for a in range(n):
        for b in range(a+1, n+1):
            for c in range(m):
                for d in range(c+1, m+1):
                    submat_sum = pre_sum_mat[b][d] + pre_sum_mat[a][c] - \
                                 pre_sum_mat[b][c] - pre_sum_mat[a][d]
                    ans = max(submat_sum, ans)

    上述过程的时间复杂度为

    。当 n m 取最大值时复杂度约为
    ,可以通过全部用例。

    二维前缀和矩阵的构建

    二维前缀和矩阵 pre_sum_mat 的构建也要用到类似上述的拆分过程,其核心代码如下

    pre_sum_mat = [[0] * (m+1for _ in range(n+1)]
    for i in range(1, n+1):
        for j in range(1, m+1):
            pre_sum_mat[i][j] = pre_sum_mat[i-1][j] + pre_sum_mat[i][j-1] - \
                                pre_sum_mat[i-1][j-1] + mat[i-1][j-1]

    要特别注意二维前缀和 pre_sum_mat 的大小,在两个维度上均比原矩阵矩阵 mat 1

    该过程的时间复杂度为

    代码

    解法一:二维前缀和

    Python

    # 题目练习网址:https://www.algomooc.com/problem/P4204


    from math import inf


    n, m = map(int, input().split())
    mat = list()
    for _ in range(n):
        row = list(map(int, input().split()))
        mat.append(row)

    # 构建二维前缀和数组
    pre_sum_mat = [[0] * (m+1for _ in range(n+1)]
    for i in range(1, n+1):
        for j in range(1, m+1):
            pre_sum_mat[i][j] = pre_sum_mat[i-1][j] + pre_sum_mat[i][j-1] - \
                                pre_sum_mat[i-1][j-1] + mat[i-1][j-1]

    # 初始化答案为负无穷小
    ans = -inf

    # 枚举上底a
    for a in range(n):
        # 枚举下底b
        for b in range(a, n):
            # 枚举左宽c
            for c in range(m):
                # 枚举右宽d
                for d in range(c, m):
                    # 此时四个参数能够表示一个子矩阵
                    # 根据式子计算子矩阵和,更新ans
                    submat_sum = pre_sum_mat[b+1][d+1] + pre_sum_mat[a][c] - \
                                 pre_sum_mat[b+1][c] - pre_sum_mat[a][d+1]
                    ans = max(submat_sum, ans)

    print(ans)

    Java

    import java.util.Scanner;

    public class Main {
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
            int n = scanner.nextInt();
            int m = scanner.nextInt();
            int[][] mat = new int[n][m];

            for (int i = 0; i             for (int j = 0; j                 mat[i][j] = scanner.nextInt();
                }
            }

            int[][] preSumMat = new int[n + 1][m + 1];

            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= m; j++) {
                    preSumMat[i][j] = preSumMat[i - 1][j] + preSumMat[i][j - 1] - preSumMat[i - 1][j - 1] + mat[i - 1][j - 1];
                }
            }

            int ans = Integer.MIN_VALUE;

            for (int a = 0; a             for (int b = a; b                 for (int c = 0; c                     for (int d = c; d                         int submatSum = preSumMat[b + 1][d + 1] + preSumMat[a][c] - preSumMat[b + 1][c] - preSumMat[a][d + 1];
                            ans = Math.max(submatSum, ans);
                        }
                    }
                }
            }

            System.out.println(ans);
        }
    }

    C++

    #include 
    #include 
    #include 

    using namespace std;

    int main() {
        int n, m;
        cin >> n >> m;
        vector<vector<int>> mat(n, vector<int>(m));

        for (int i = 0; i         for (int j = 0; j             cin >> mat[i][j];
            }
        }

        vector<vector<int>> pre_sum_mat(n + 1vector<int>(m + 10));

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                pre_sum_mat[i][j] = pre_sum_mat[i - 1][j] + pre_sum_mat[i][j - 1] - pre_sum_mat[i - 1][j - 1 ] + mat[i - 1][j - 1];
            }
        }

        int ans = numeric_limits<int>::min();

        for (int a = 0; a         for (int b = a; b             for (int c = 0; c                 for (int d = c; d                     int submat_sum = pre_sum_mat[b + 1][d + 1] + pre_sum_mat[a][c] - pre_sum_mat[b + 1][c] - pre_sum_mat[a][d + 1];
                        ans = max(submat_sum, ans);
                    }
                }
            }
        }

        cout     return 0;
    }

    C

    #include 
    #include   // 用于定义负无穷

    int main() {
        int n, m;
        scanf("%d %d", &n, &m);

        int mat[n][m];
        // 读取矩阵数据
        for (int i = 0; i         for (int j = 0; j             scanf("%d", &mat[i][j]);
            }
        }

        // 构建二维前缀和数组
        int pre_sum_mat[n+1][m+1];
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                pre_sum_mat[i][j] = 0;
            }
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                pre_sum_mat[i][j] = pre_sum_mat[i-1][j] + pre_sum_mat[i][j-1] - pre_sum_mat[i-1][j-1] + mat[i-1][j-1];
            }
        }

        // 初始化答案为负无穷小
        int ans = INT_MIN;

        // 枚举上底a
        for (int a = 0; a         // 枚举下底b
            for (int b = a; b             // 枚举左宽c
                for (int c = 0; c                 // 枚举右宽d
                    for (int d = c; d                     // 计算子矩阵的和
                        int submat_sum = pre_sum_mat[b+1][d+1] + pre_sum_mat[a][c] - pre_sum_mat[b+1][c] - pre_sum_mat[a][d+1];
                        // 更新答案
                        if (submat_sum > ans) {
                            ans = submat_sum;
                        }
                    }
                }
            }
        }

        printf("%d\n", ans);
        return 0;
    }

    Node JavaScript

    const readline = require('readline');
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });

    let input = [];
    rl.on('line', line => {
      input.push(line);
    }).on('close', () => {
      let [n, m] = input[0].split(' ').map(Number);
      let mat = [];
      for (let i = 1; i <= n; i++) {
        mat.push(input[i].split(' ').map(Number));
      }

      // 构建二维前缀和数组
      let pre_sum_mat = Array.from({ length: n + 1 }, () => Array(m + 1).fill(0));

      for (let i = 1; i <= n; i++) {
        for (let j = 1; j <= m; j++) {
          pre_sum_mat[i][j] = pre_sum_mat[i - 1][j] + pre_sum_mat[i][j - 1] - pre_sum_mat[i - 1][j - 1] + mat[i - 1][j - 1];
        }
      }

      let ans = -Infinity;

      // 枚举所有子矩阵
      for (let a = 0; a     for (let b = a; b       for (let c = 0; c         for (let d = c; d           let submat_sum = pre_sum_mat[b + 1][d + 1] + pre_sum_mat[a][c] - pre_sum_mat[b + 1][c] - pre_sum_mat[a][d + 1];
              ans = Math.max(submat_sum, ans);
            }
          }
        }
      }

      console.log(ans);
    });

    Go

    package main

    import (
            "fmt"
            "math"
    )

    func main() {
            var n, m int
            fmt.Scanf("%d %d", &n, &m)

            // 读取矩阵数据
            mat := make([][]int, n)
            for i := 0; i                 mat[i] = make([]int, m)
                    for j := 0; j                         fmt.Scanf("%d", &mat[i][j])
                    }
            }

            // 构建二维前缀和数组
            preSumMat := make([][]int, n+1)
            for i := 0; i <= n; i++ {
                    preSumMat[i] = make([]int, m+1)
            }

            for i := 1; i <= n; i++ {
                    for j := 1; j <= m; j++ {
                            preSumMat[i][j] = preSumMat[i-1][j] + preSumMat[i][j-1] - preSumMat[i-1][j-1] + mat[i-1][j-1]
                    }
            }

            // 初始化答案为负无穷
            ans := math.MinInt

            // 枚举所有子矩阵
            for a := 0; a                 for b := a; b                         for c := 0; c                                 for d := c; d                                         submatSum := preSumMat[b+1][d+1] + preSumMat[a][c] - preSumMat[b+1][c] - preSumMat[a][d+1]
                                            if submatSum > ans {
                                                    ans = submatSum
                                            }
                                    }
                            }
                    }
            }

            // 输出结果
            fmt.Println(ans)
    }

    时空复杂度

    时间复杂度:

    空间复杂度:







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