高考410分,想选人工智能

255 阅读8分钟

志愿:人工智能

随着高考成绩和分数线的公布,高考志愿成了仅次于姜萍的教育类最热话题 🤣🤣

今天逛牛客网看到一篇热乎的帖子,标题为《高考410分,想选人工智能》:

帖子再结合评论来看,主角是帖主的表妹,高考 410 分,听家长的想选人工智能,这个成绩在湖北,是没过理科本科线的。

我们可以看一眼湖北今年的分数线:

湖北-物理类

不光没过,还差得不少。

从帖子的截图来看,建议选人工智能专业的家长,是帖主表妹的父亲:

这就是为什么张雪峰在中国能火的原因。

毫无疑问,父亲的初衷,是想给女儿最好的建议,但这里有个大前提:那仅是父亲认知里,最好的意见,和事实上的最好,并无必然关系,甚至相差甚远。

相比于可能只是听说过「人工智能很火,前途无限」的老父亲,身处内卷战场的同学们给的意见则十分实际(虽然带了一些幽默口吻,但确实充分表达了"不认同"):

一个行业的前景很好,但大概率(甚至是绝对)不会缺人缺到,只要读这个专业就能进入这个行业。

跳出该事件本身,如果一个高考志愿建议,并不像帖子的《专科分数,报考人工智能》这般离谱,我们又该如何看待?

例如肯定还是报计算机,瘦死骆驼比马大,至少这薪资水平就已经值得冲这样的热门建议。

这样的话术,如果再到 BOSS 直聘等平台逛一圈,似乎是真理。

但这只是此时的情况,从报考志愿到进入就业市场,至少还有四年时间,接下来四年,这只骆驼还会瘦成什么样,没人知道。

至少现在的计算机就业市场,我估计四年前的 2020 年,没人会预估到。

所以如果想从「报考志愿,押宝未来」的角度来操作的话,不仅需要考虑行业当前是否为朝阳行业,还需要考虑其增速放缓,优势下降等因素。

计算机就是一个刚过完朝阳阶段,优势开始明显下降的行业。

这里不是说四年以后,计算机行业的薪资必然会面临大幅下降,而是说人才缺口将会大幅收缩,进入行业的难度将会大幅上涨,换句话说,在目前看来,计算机专业的值博率是明显下降的。

如果不是真心喜欢,在经过几年的学习后,在众多应届生脱颖而出的概率几乎为零,一张科班出身的毕业证并不能带来光明未来。

...

回归主题。

来一道和「字节跳动」相关的算法题。

题目描述

平台:LeetCode

题号:761

特殊的二进制序列是具有以下两个性质的二进制序列:

  • 0 的数量与 1 的数量相等。
  • 二进制序列的每一个前缀码中 1 的数量要大于等于 0 的数量。

给定一个特殊的二进制序列 S,以字符串形式表示。

定义一个操作为首先选择 S 的两个连续且非空的特殊的子串,然后将它们交换。

两个子串为连续的当且仅当第一个子串的最后一个字符恰好为第二个子串的第一个字符的前一个字符。

在任意次数的操作之后,交换后的字符串按照字典序排列的最大的结果是什么?

示例 1:

输入: S = "11011000"

输出: "11100100"

解释:
将子串 "10" (在S[1]出现) 和 "1100" (在S[3]出现)进行交换。
这是在进行若干次操作后按字典序排列最大的结果。

说明:

  • S 的长度不超过 50。
  • S 保证为一个满足上述定义的特殊的二进制序列。

构造

我们可以定义每个字符的得分:字符 1 得分为 1 分,字符 0 得分为 -1 分。

根据题目对「特殊字符串」的定义可知,给定字符串 s 的总得分为 0,且任意前缀串不会出现得分为负数的情况。

考虑将 s 进行划分为多个足够小特殊字符串 item(足够小的含义为每个 item 无法再进行划分),每个 item 的总得分为 0。根据 s 定义,必然可恰好划分为多个 item

每次操作可以将相邻特殊字符串进行交换,于是问题转换为将 s 进行重排,求其重排后字典序最大的方案。

首先可以证明一个合法 item 必然满足 1...0 的形式,可通过「反证法」进行进行证明:定义了 item 总得分为 0,且长度不为 0,因此必然有 10

若第一位字符为 0,则必然能够从第一位字符作为起点,找到一个得分为负数的子串,这与 s 本身的定义冲突(s 中不存在得分为负数的前缀串);若最后一位为 1,根据 item 总得分为 0,可知当前 item 去掉最后一位后得分为负,也与 s 本身定义冲突(s 中不存在得分为负数的前缀串)。

因此可将构造分两步进行:

  1. 对于每个 item 进行重排,使其调整为字典序最大
  2. 对于 item 之间的位置进行重排,使其整体字典序最大

由于题目没有规定重排后的性质,为第一步调整和第二步调整的保持相对独立,我们只能对 item 中的 1...0 中的非边缘部分进行调整(递归处理子串部分)。

假设所有 item 均被处理后,考虑如何进行重排能够使得最终方案字典序最大。

若有两个 item,分别为 ab,我们可以根据拼接结果 abba 的字典序大小来决定将谁放在前面。

这样根据「排序比较逻辑」需要证明在集合上具有「全序关系」:

我们使用符号 @@ 来代指我们的「排序」逻辑:

  • 如果 aa 必须排在 bb 的前面,我们记作 a@b
  • 如果 aa 必须排在 bb 的后面,我们记作 b@a
  • 如果 aa 既可以排在 bb 的前面,也可以排在 bb 的后面,我们记作 a#b

2.1 完全性

具有完全性是指从集合 items 中任意取出两个元素 aabb,必然满足 a@bb@aa#b 三者之一。

这点其实不需要额外证明,因为由 aabb 拼接的字符串 ababbaba 所在「字典序大小关系中」要么完全相等,要么具有明确的字典序大小关系,导致 aa 必须排在前面或者后面。

2.2 反对称性

具有反对称性是指由 a@bb@a 能够推导出 a#b

a@ba@b 说明字符串 abab 的字典序大小数值要比字符串 baba 字典序大小数值大。

b@ab@a 说明字符串 abab 的字典序大小数值要比字符串 baba 字典序大小数值小。

这样,基于「字典序本身满足全序关系」和「数学上的 aba \geqslant baba \leqslant b 可推导出 a=ba = b」。

得证 a@bb@a 能够推导出 a#b

2.3 传递性

具有传递性是指由 a@bb@c 能够推导出 a@c

我们可以利用「两个等长的拼接字符串,字典序大小关系与数值大小关系一致」这一性质来证明,因为字符串 acaccaca 必然是等长的。

接下来,让我们从「自定义排序逻辑」出发,换个思路来证明 a@c

然后我们只需要证明在不同的 ii jj 关系之间(共三种情况),a@c 恒成立即可:

  1. i==ji == j 的时候:

  1. i>ji > j 的时候:

  1. i<ji < j 的时候:

综上,我们证明了无论在何种情况下,只要有 a@bb@c 的话,那么 a@c 恒成立。

我们之所以能这样证明「传递性」,本质是利用了自定义排序逻辑中「对于确定任意元素 aabb 之间的排序关系只依赖于 aabb 的第一个不同元素之间的大小关系」这一性质。

最终,我们证明了该「排序比较逻辑」必然能排序出字典序最大的方案。

Java 代码:

class Solution {
    public String makeLargestSpecial(String s) {
        if (s.length() == 0) return s;
        List<String> list = new ArrayList<>();
        char[] cs = s.toCharArray();
        for (int i = 0, j = 0, k = 0; i < cs.length; i++) {
            k += cs[i] == '1' ? 1 : -1;
            if (k == 0) {
                list.add("1" + makeLargestSpecial(s.substring(j + 1, i)) + "0");
                j = i + 1;
            }
        }
        Collections.sort(list, (a, b)->(b + a).compareTo(a + b));
        StringBuilder sb = new StringBuilder();
        for (String str : list) sb.append(str);
        return sb.toString();
    }
}

C++ 代码:

class Solution {
public:
    string makeLargestSpecial(string s) {
        if (s.empty()) return s;
        vector<string> list;
        for (int i = 0, j = 0, k = 0; i < s.length(); i++) {
            k += s[i] == '1' ? 1 : -1;
            if (k == 0) {
                list.push_back("1" + makeLargestSpecial(s.substr(j + 1, i - j - 1)) + "0");
                j = i + 1;
            }
        }
        sort(list.begin(), list.end(), [](const string &a, const string &b) {
            return (b + a).compare(a + b) < 0;
        });
        string result;
        for (const string &str : list) result += str;
        return result;
    }
};

TypeScript 代码:

function makeLargestSpecial(s: string): string {
    const list = new Array<string>()
    for (let i = 0, j = 0, k = 0; i < s.length; i++) {
        k += s[i] == '1' ? 1 : -1
        if (k == 0) {
            list.push('1' + makeLargestSpecial(s.substring(j + 1, i)) + '0')
            j = i + 1
        }
    }
    list.sort((a, b)=>(b + a).localeCompare(a + b));
    return [...list].join("")
};
  • 时间复杂度:O(n2)O(n^2)
  • 空间复杂度:O(n)O(n)