寒心,成立 25 年的车企迎来了首次裁员

车企

随着春节假期结束,各行各业也正式复工,但车企却未能迎来属于它们的"新年新气象"。

早在年前(12 月),就有新闻爆出,知名传统车企「广汽本田」为了加快转向电动车市场,宣布解雇中国合资企业广汽本田下的 900 名员工。

据悉,这是因为传统车企在 2023 前 10 个月的销量总计同比下降 18.5% 所做的决定。

那是否只要是站在"风口"的新能源车企,就活得很好呢?

也不是。

燃油车的销量持续下降,除了有电动车崛起的直接因素,当中还有消费降低,经济萎靡的宏观因素。而后者会对所有车企,甚至是消费品行业带来实则性的挑战。

年初九,当别人还沉浸在春节假期或开工红包的喜悦的时候,新能源造车新势力高合汽车宣布停工停产 6 个月。

取消年终奖、全员降薪、停工停产(仅发基本工资),高合汽车选择了大面积裁员的另一条道路。

高合汽车创始人、董事长兼 CEO 丁磊,职业初期在上汽担任高管,从业时间超过 20 年,他本人也亲身经历了中国汽车行业逐渐变强的全过程。

带着这种"天生骄傲"的履历,同时又手握丰富的行业资源。

高合汽车成立时就将自身定位为"高端电动车品牌",首款车的最高售价达到 80 万,一度被视为在电动车高端市场突破 BBA 封锁的先行者。

但可惜那个「只要把车造出来就不愁卖的时代」早就过去了。

现在新能源领域领跑的几家企业,依靠的并非只是单一的造车能力,还包括 软件技术、服务 和 产品力。

前者(造车能力)决定车企能否开得起来,而后者(软件技术/服务/产品力)则是决定车企能否活下去。

那些真正承担起新势力变革角色的车企,都有着它们的鲜明特点。传统车企虽然手握更多的造车资源,但如果只是简单宣布产能调整,并不能实现对新能源造车新势力们的"降维打击"。

因此,我并不看好广汽本田的这次变革。

传统车企想要挤进赛道,需要依托于更加激进的改革方案。

...

回归主线。

都说到了新能源车企了,那就来一道「蔚来」面试原题。

题目描述

平台:LeetCode

题号:792

给定字符串 s 和字符串数组 words, 返回 words[i] 中是 s 的子序列的单词个数 。

字符串的子序列是从原始字符串中生成的新字符串,可以从中删去一些字符(可以是""),而不改变其余字符的相对顺序。

例如, "ace""abcde" 的子序列。

示例 1:

arduino 复制代码
输入: s = "abcde", words = ["a","bb","acd","ace"]

输出: 3

解释: 有三个是 s 的子序列的单词: "a", "acd", "ace"。

示例 2:

ini 复制代码
输入: s = "dsahjpjauf", words = ["ahjpjau","ja","ahbwzgqnuk","tnmlanowax"]

输出: 2

提示:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 < = s . l e n g t h < = 5 × 1 0 4 1 <= s.length <= 5 \times 10^4 </math>1<=s.length<=5×104
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 < = w o r d s . l e n g t h < = 5000 1 <= words.length <= 5000 </math>1<=words.length<=5000
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 < = w o r d s [ i ] . l e n g t h < = 50 1 <= words[i].length <= 50 </math>1<=words[i].length<=50
  • words[i]s 都只由小写字母组成。

预处理 + 哈希表 + 二分

朴素判定某个字符串是为另一字符串的子序列的复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n + m ) O(n + m) </math>O(n+m),对于本题共有 <math xmlns="http://www.w3.org/1998/Math/MathML"> 5000 5000 </math>5000 个字符串需要判定,每个字符串最多长为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 50 50 </math>50,因此整体计算量为 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( 5 × 1 0 4 + 50 ) × 5000 ≈ 2.5 × 1 0 8 (5 \times 10^4 + 50) \times 5000 \approx 2.5 \times 10^8 </math>(5×104+50)×5000≈2.5×108,会超时。

不可避免的是,我们要对每个 <math xmlns="http://www.w3.org/1998/Math/MathML"> w o r d s [ i ] words[i] </math>words[i] 进行检查,因此优化的思路可放在如何优化单个 <math xmlns="http://www.w3.org/1998/Math/MathML"> w o r d s [ i ] words[i] </math>words[i] 的判定操作。

朴素的判定过程需要使用双指针扫描两个字符串,其中对于原串的扫描,会有大量的字符会被跳过(无效匹配),即只有两指针对应的字符相同时,匹配串指针才会后移。

我们考虑如何优化这部分无效匹配。

对于任意一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> w = w o r d s [ i ] w = words[i] </math>w=words[i] 而言,假设我们当前匹配到 <math xmlns="http://www.w3.org/1998/Math/MathML"> w [ j ] w[j] </math>w[j] 位置,此时我们已经明确下一个待匹配的字符为 <math xmlns="http://www.w3.org/1998/Math/MathML"> w [ j + 1 ] w[j + 1] </math>w[j+1],因此我们可以直接在 s 中字符为 <math xmlns="http://www.w3.org/1998/Math/MathML"> w [ j + 1 ] w[j + 1] </math>w[j+1] 的位置中找候选。

具体的,我们可以使用哈希表 maps 进行预处理:以字符 <math xmlns="http://www.w3.org/1998/Math/MathML"> c = s [ i ] c = s[i] </math>c=s[i] 为哈希表的 key,对应的下标 <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i 集合为 value,由于我们从前往后处理 s 进行预处理,因此对于所有的 value 均满足递增性质。

举个 🌰 : 对于 s = abcabc 而言,预处理的哈希表为 {a=[0,3], b=[1,4], c=[2,5]}

最后考虑如何判定某个 <math xmlns="http://www.w3.org/1998/Math/MathML"> w = w o r d s [ i ] w = words[i] </math>w=words[i] 是否满足要求:待匹配字符串 w 长度为 m,我们从前往后对 w 进行判定,假设当前判待匹配位置为 <math xmlns="http://www.w3.org/1998/Math/MathML"> w [ i ] w[i] </math>w[i],我们使用变量 idx 代表能够满足匹配 <math xmlns="http://www.w3.org/1998/Math/MathML"> w [ 0 : i ] w[0:i] </math>w[0:i] 的最小下标(贪心思路)。

对于匹配的 <math xmlns="http://www.w3.org/1998/Math/MathML"> w [ i ] w[i] </math>w[i] 字符,可以等价为在 map[w[i]] 中找到第一个大于 idx 的下标,含义在原串 s 中找到字符为 w[i] 且下标大于 idx 的最小值,由于我们所有的 map[X] 均满足单调递增,该过程可使用「二分」进行。

Java 代码:

Java 复制代码
class Solution {
    public int numMatchingSubseq(String s, String[] words) {
        int n = s.length(), ans = 0;
        Map<Character, List<Integer>> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            List<Integer> list = map.getOrDefault(s.charAt(i), new ArrayList<>());
            list.add(i);
            map.put(s.charAt(i), list);
        }
        for (String w : words) {
            boolean ok = true;
            int m = w.length(), idx = -1;
            for (int i = 0; i < m && ok; i++) {
                List<Integer> list = map.getOrDefault(w.charAt(i), new ArrayList<>());
                int l = 0, r = list.size() - 1;
                while (l < r) {
                    int mid = l + r >> 1;
                    if (list.get(mid) > idx) r = mid;
                    else l = mid + 1;
                }
                if (r < 0 || list.get(r) <= idx) ok = false;
                else idx = list.get(r);
            }
            if (ok) ans++;
        }
        return ans;
    }
}

TypeScript 代码:

TypeScript 复制代码
function numMatchingSubseq(s: string, words: string[]): number {
    let n = s.length, ans = 0
    const map = new Map<String, Array<number>>()
    for (let i = 0; i < n; i++) {
        if (!map.has(s[i])) map.set(s[i], new Array<number>())
        map.get(s[i]).push(i)
    }
    for (const w of words) {
        let ok = true
        let m = w.length, idx = -1
        for (let i = 0; i < m && ok; i++) {
            if (!map.has(w[i])) {
                ok = false
            } else {
                const list = map.get(w[i])
                let l = 0, r = list.length - 1
                while (l < r) {
                    const mid = l + r >> 1
                    if (list[mid] > idx) r = mid
                    else l = mid + 1
                }
                if (r < 0 || list[r] <= idx) ok = false
                else idx = list[r]
            }
        }
        if (ok) ans++
    }
    return ans
}

Python3 代码:

Python 复制代码
class Solution:
    def numMatchingSubseq(self, s: str, words: List[str]) -> int:
        dmap = defaultdict(list)
        for i, c in enumerate(s):
            dmap[c].append(i)
        ans = 0
        for w in words:
            ok = True
            idx = -1
            for i in range(len(w)):
                idxs = dmap[w[i]]
                l, r = 0, len(idxs) - 1
                while l < r :
                    mid = l + r >> 1
                    if dmap[w[i]][mid] > idx:
                        r = mid
                    else:
                        l = mid + 1
                if r < 0 or dmap[w[i]][r] <= idx:
                    ok = False
                    break
                else:
                    idx = dmap[w[i]][r]
            ans += 1 if ok else 0
        return ans
  • 时间复杂度:令 ns 长度,mwords 长度,l = 50 为 <math xmlns="http://www.w3.org/1998/Math/MathML"> w o r d s [ i ] words[i] </math>words[i] 长度的最大值。构造 map 的复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n);统计符合要求的 <math xmlns="http://www.w3.org/1998/Math/MathML"> w o r d s [ i ] words[i] </math>words[i] 的数量复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( m × l × log ⁡ n ) O(m \times l \times \log{n}) </math>O(m×l×logn)。整体复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n + m × l × log ⁡ n ) O(n + m \times l \times \log{n}) </math>O(n+m×l×logn)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)

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

欢迎关注,明天见。

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

相关推荐
祈澈菇凉2 小时前
Webpack的基本功能有哪些
前端·javascript·vue.js
小纯洁w2 小时前
Webpack 的 require.context 和 Vite 的 import.meta.glob 的详细介绍和使用
前端·webpack·node.js
想睡好3 小时前
css文本属性
前端·css
qianmoQ3 小时前
第三章:组件开发实战 - 第五节 - Tailwind CSS 响应式导航栏实现
前端·css
zhoupenghui1683 小时前
golang时间相关函数总结
服务器·前端·golang·time
孤雪心殇3 小时前
简单易懂,解析Go语言中的Map
开发语言·数据结构·后端·golang·go
White graces3 小时前
正则表达式效验邮箱格式, 手机号格式, 密码长度
前端·spring boot·spring·正则表达式·java-ee·maven·intellij-idea
庸俗今天不摸鱼3 小时前
Canvas进阶-4、边界检测(流光,鼠标拖尾)
开发语言·前端·javascript·计算机外设
菠菠萝宝3 小时前
【Java八股文】10-数据结构与算法面试篇
java·开发语言·面试·红黑树·跳表·排序·lru
bubusa~>_<4 小时前
解决npm install 出现error,比如:ERR_SSL_CIPHER_OPERATION_FAILED
前端·npm·node.js