美团今年新招6000应届生!

美团

在过刚去的中秋,美团 CEO 王兴对内发布全员信:在感慨了美团这些年取得的成绩之际,也透露了美团 2025 届计划招募 6000 名应届毕业生,分布在数十个城市。

真不错,前些天雷军刚在 2024 届小米应届生迎新典礼上宣布校招加码(计划招聘 5000 名 2025 届毕业生),美团这就跟上了,2025 届是真幸福。

在内部信中,与「2025 届计划招募」相关的原话:

我们关注员工的招募和培养,从公司成立之初就坚持校园招聘,过去3年每年招聘超过5000名应届毕业生,2025届计划招募6000名,分布在数十个城市;

除此以外,内部信还分享了美团当前有近 70% 的管理者是从内部提拔,而非外部空降。

这意味着毕业加入美团,逐步往上晋升是一条普遍的道路。

将大厂作为跳板,通过"几年一跳"来实现涨薪是不少同学心中的一条"明道",但当求职者年龄到了某个阶段,该做法会遭到反噬,要么是没有厂接得住,或是干脆没有厂愿意接;此时在某个公司沉淀,逐步从研发转向管理,则是一条相对稳定的道路,美团则是推崇此类职业规划的公司。

...

回归主题。

来一道和「美团」相关的算法题。

题目描述

平台:LeetCode

题号:1208

给你两个长度相同的字符串,st

s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。

用于变更字符串的最大预算是 maxCost

在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。

如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。

如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。

示例 1:

输入:s = "abcd", t = "bcdf", maxCost = 3

输出:3

解释:s 中的 "abc" 可以变为 "bcd"。开销为 3,所以最大长度为 3。

示例 2:

输入:s = "abcd", t = "cdef", maxCost = 3

输出:1

解释:s 中的任一字符要想变成 t 中对应的字符,其开销都是 2。因此,最大长度为 1。

示例 3:

输入:s = "abcd", t = "acde", maxCost = 0

输出:1

解释:a -> a, cost = 0,字符串未发生变化,所以最大长度为 1。

提示:

  • s 和  t 都只含小写英文字母。

前缀和 + 二分 + 滑动窗口

给定了长度相同的 st,那么对于每一位而言,修改的成本都是相互独立而确定的。

我们可以先预处理出修改成本的前缀和数组 sum

当有了前缀和数组之后,对于任意区间 [i,j] 的修改成本,便可以通过 sum[j] - sum[i - 1] 得出。

那么接下来我们只需要找出成本不超过 maxCost 的最大长度区间,这个长度区间其实就是滑动窗口长度,滑动窗口长度的范围为 [1, n] (n 为字符串的长度)。

通过枚举来找答案可以吗?

我们可以通过数据范围大概分析一下哈,共有 n 个滑动窗口长度要枚举,复杂度为 ,对于每个滑动窗口长度,需要对整个前缀和数组进行滑动检查,复杂度为 。也就是整体复杂度是 的。

数据范围是 ,那么单个样本的计算量是 ,计算机单秒肯定算不完,会超时 ~

所以我们直接放弃通过枚举的朴素做法。

那么如何优化呢?其实有了对于朴素解法的分析之后,无非就是两个方向:

  1. 优化第一个 :减少需要枚举的滑动窗口长度
  2. 优化第二个 :实现不完全滑动前缀和数组,也能确定滑动窗口长度是否合法

事实上第 2 点是无法实现的,我们只能「减少需要枚举的滑动窗口长度」。

一个 的操作往下优化,通常就是优化成 基本上我们可以先猜一个「二分」查找。

然后我们再来分析是否可以二分:假设我们有满足要求的长度 ans,那么在以 ans 为分割点的数轴上(数轴的范围是滑动窗口长度的范围:[1, n]):

  1. 所有满足 <= ans 的点的修改成本必然满足 <= maxCost
  2. 所有满足 > ans 的点的修改成本必然满足 > maxCost (否则 ans 就不会是答案)

因此 ans 在数轴 [1, n] 上具有二段性,我们可以使用「二分」找 ans。得证「二分」的合理性。

「可见二分的本质是二段性,而非单调性。」

编码细节:
  1. 为了方便的预处理前缀和和减少边界处理,我会往字符串头部添加一个空格,使之后的数组下标从 1 开始
  2. 二分出来滑动窗口长度,需要在返回时再次检查,因为可能没有符合条件的有效滑动窗口长度

Java 代码:

class Solution {
    public int equalSubstring(String ss, String tt, int max) {
        int n = ss.length();
        ss = " " + ss; tt = " " + tt;
        char[] s = ss.toCharArray(), t = tt.toCharArray();
        int[] sum = new int[n + 1];
        for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + Math.abs(s[i] - t[i]);
        int l = 1, r = n;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (check(sum, mid, max)) l = mid;    
            else r = mid - 1;
        }
        return check(sum, r, max) ? r : 0;
    }
    boolean check(int[] nums, int mid, int max) {
        for (int i = mid; i < nums.length; i++) {
            int tot = nums[i] - nums[i - mid];
            if (tot <= max) return true;
        }
        return false;
    }
}

C++ 代码:

class Solution {
public:
    int equalSubstring(string ss, string tt, int max) {
        int n = ss.length();
        ss = " " + ss; tt = " " + tt;
        vector<intsum(n + 10);
        for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + abs(ss[i] - tt[i]);
        int l = 1, r = n;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (check(sum, mid, max)) l = mid;    
            else r = mid - 1;
        }
        return check(sum, r, max) ? r : 0;
    }
    bool check(const vector<int>& nums, int mid, int max) {
        for (int i = mid; i < nums.size(); i++) {
            int tot = nums[i] - nums[i - mid];
            if (tot <= max) return true;
        }
        return false;
    }
};

Python 代码:

class Solution:
    def equalSubstring(self, ss: str, tt: str, maxv: int) -> int:
        n = len(ss)
        ss, tt = " " + ss, " " + tt
        sumv = [0] * (n + 1)
        for i in range(1, n + 1):
            sumv[i] = sumv[i - 1] + abs(ord(ss[i]) - ord(tt[i]))
        l, r = 1, n
        while l < r:
            mid = l + r + 1 >> 1
            if self.check(sumv, mid, maxv): l = mid
            else: r = mid - 1
        return l if self.check(sumv, r, maxv) else 0

    def check(self, nums, mid, maxv):
        for i in range(mid, len(nums)):
            tot = nums[i] - nums[i - mid]
            if tot <= maxv:
                return True
        return False

TypeScript 代码:

function check(nums: number[], mid: number, max: number): boolean {
    for (let i: number = mid; i <= nums.length; i++) {
        const tot: number = nums[i] - nums[i - mid];
        if (tot <= max) return true;
    }
    return false;
}
function equalSubstring(ss: string, tt: string, max: number): number {
    const n: number = ss.length;
    ss = " " + ss; tt = " " + tt;
    const sum: number[] = [0];
    for (let i: number = 1; i <= n; i++) sum[i] = sum[i - 1] + Math.abs(ss.charCodeAt(i) - tt.charCodeAt(i));
    let l: number = 1, r: number = n;
    while (l < r) {
        const mid: number = l + r + 1 >> 1;
        if (check(sum, mid, max)) l = mid;    
        else r = mid - 1;
    }
    return check(sum, r, max) ? r : 0;
};
  • 时间复杂度:预处理出前缀和的复杂度为 ;二分出「滑动窗口长度」的复杂度为 ,对于每个窗口长度,需要扫描一遍数组进行检查,复杂度为 ,因此二分的复杂度为 。整体复杂度为
  • 空间复杂度:使用了前缀和数组。复杂度为

最后

巨划算的 LeetCode 会员优惠通道目前仍可用 ~

使用福利优惠通道 leetcode.cn/premium/?promoChannel=acoier,年度会员 有效期额外增加两个月,季度会员 有效期额外增加两周,更有超大额专属 🧧 和实物 🎁 福利每月发放。

我是宫水三叶,每天都会分享算法知识,并和大家聊聊近期的所见所闻

欢迎关注,明天见。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值