2026-11-06:数组中的峰值Ⅱ。用go语言,给定一个整数数组 nums,长度为 n;再给定若干查询,存放在二维数组 queries 中。需要按顺序处理

🤖 AI总结

主题

力扣4017题:数组中的峰值Ⅱ的线段树解法。

摘要

用线段树维护区间峰值信息,合并时扣除不合法跨区间子段,实现O(log n)查询与修改。

关键信息

  • 1 将合法子段转化为包含内部峰值的子段。
  • 2 线段树节点维护len、hasPeak、pre、suf、cnt五个信息。
  • 3 单点修改最多影响三个位置的峰值状态,总复杂度O(n+q log n)。

2026-11-06:数组中的峰值Ⅱ。用go语言,给定一个整数数组 nums,长度为 n;再给定若干查询,存放在二维数组 queries 中。需要按顺序处理这些查询,并维护数组 nums 的当前值。
一个连续子段如果同时满足下面两个条件,就称为“符合条件的子段”:

第一,子段长度不少于 3;

第二,子段内部存在某个位置 k,使得该位置的数严格大于它左边相邻的数,并且严格大于它右边相邻的数。也就是说,在子段内部存在一个严格峰值。

查询分为两类:

第一类查询格式为 [1, li, ri]。它的任务是统计:所有完全位于 nums[li] 到 nums[ri] 这个区间内的连续子段中,有多少个是上述“符合条件的子段”。这里要求子段的起点和终点都不能超出 li 和 ri。

第二类查询格式为 [2, indexi, vali]。它的任务是把 nums[indexi] 的值修改为 vali。修改会立即生效,并影响之后的所有查询。

最终需要返回一个结果数组 answer。answer 中依次保存每个第一类查询的答案,顺序与这些查询在 queries 中出现的顺序一致。第二类查询不产生输出。

3 <= n == nums.length <= 100000。

0 <= nums[i] <= 100000。

1 <= queries.length <= 100000。

queries[i] = [1, li, ri] 或 queries[i] = [2, indexi, vali]。

0 <= li < ri <= n – 1。

0 <= indexi <= n – 1。

0 <= vali <= 100000。

输入: nums = [1,3,2,4], queries = [[1,0,3],[2,1,1],[1,0,3]]。

输出: [2,0]。

解释:

查询 [1, 0, 3]:

[1, 3, 2]:选择 k = 1。则 nums[k] = 3,nums[k – 1] = 1,且 nums[k + 1] = 2。因为 3 > 1 且 3 > 2,这是一个峰值子数组。

[1, 3, 2, 4]:选择 k = 1。则 nums[k] = 3,nums[k – 1] = 1,且 nums[k + 1] = 2。因为 3 > 1 且 3 > 2,这是一个峰值子数组。

查询 [2, 1, 1]:将 nums[1] 更新为 1。数组变为 [1, 1, 2, 4]。

查询 [1, 0, 3]:现在没有峰值子数组。

因此,answer = [2, 0]。

题目来自力扣4017。

大体步骤如下: 1. 峰值和“符合条件的子段”的转化

数组某个位置p是峰值,当且仅当:

•p不是数组左端点;

  • •p不是数组右端点;

  • •nums[p-1] < nums[p];

  • •nums[p] > nums[p+1]。

    题目要求统计的子段必须满足:

    • 长度至少为 3;

  • • 子段内部存在一个峰值,即该峰值不能是子段端点。

    因此,一个子段“符合条件”,等价于:这个子段内部至少包含一个峰值位置。
    因为峰值本身需要左右相邻元素,所以只要峰值在子段内部,子段长度自然至少为 3。

    代码中的线段树维护的正是:某个区间内有多少个连续子段包含至少一个“内部峰值”。

    2. 线段树节点保存的信息

    每个线段树节点对应原数组的一个连续区间,保存五个信息:

    1.len:这个区间的长度。

  • 2.hasPeak:这个区间内是否存在至少一个峰值位置。

  • 3.pre:从区间左端点开始,到区间内第一个峰值位置为止的长度,包含该峰值位置。
    如果整个区间没有峰值,那么pre就等于区间长度。

  • 4.suf:从区间右端点开始,到区间内最后一个峰值位置为止的长度,包含该峰值位置。
    如果整个区间没有峰值,那么suf就等于区间长度。

  • 5.cnt:这个区间内,所有包含至少一个内部峰值的连续子段数量。
    对于第一类查询,最终答案就是查询区间对应节点的cnt。

    对于单个元素构成的叶子节点:

    •len = 1;

  • •pre = 1;

  • •suf = 1;

  • •cnt = 0,因为单个元素不可能形成符合条件的子段;

  • •hasPeak根据原数组中该位置是否为峰值来确定。
    叶子节点的hasPeak只表示“这个位置本身是不是峰值”。

    3. 两个区间的合并过程

    线段树的核心是把左右两个子区间的信息合并成父区间的信息。

    假设左区间为a,右区间为b。

    3.1 合并后的长度

    合并后长度就是:

    a.len + b.len。

    3.2 合并后的hasPeak

    只要左区间或右区间任意一个有峰值,合并后就有峰值:

    a.hasPeak || b.hasPeak。

    3.3 合并后的pre

    pre本来应该从合并区间的左端点开始,找到第一个峰值位置。

    • 先继承左区间的pre;

  • • 如果左区间完全没有峰值,那么左区间的整个前缀都不会遇到峰值,于是还要继续延伸到右区间,加上右区间的pre;

  • • 如果左区间有峰值,那么第一个峰值已经在左区间内出现,不能继续延伸到右区间。

    所以合并后的pre由左区间的pre决定,必要时加上右区间的pre。

    3.4 合并后的suf

    suf是对称的:

    • 先继承右区间的suf;

  • • 如果右区间完全没有峰值,那么还要继续向左延伸到左区间,加上左区间的suf;

  • • 如果右区间有峰值,那么最后一个峰值已经在右区间内出现,不能继续延伸到左区间。

    3.5 合并后的cnt

    左右区间内部已经统计过的合法子段数量分别是:

    •a.cnt;

  • •b.cnt。

    还需要统计“跨越左右两个区间的子段”。

    跨越左右区间的子段总数量是:

    a.len * b.len。

    因为跨区间子段的起点可以在左区间任意位置,终点可以在右区间任意位置。

    但是这些跨区间子段中,并不是全部都包含“内部峰值”。有些跨区间子段虽然跨过左右区间,但峰值可能落在子段端点上,或者根本没有峰值。

    需要扣除的数量是:

    a.suf * b.pre。

    原因是:

    •a.suf表示左区间从右端开始到最后一个峰值位置的长度;

  • •b.pre表示右区间从左端开始到第一个峰值位置的长度;

  • • 如果一个跨区间子段的起点落在左区间的这段后缀中,终点落在右区间的这段前缀中,那么这个子段不会把任何峰值包在内部,峰值最多只能落在子段端点上,因此不合法。

    所以跨区间新增的合法子段数量为:

    a.len * b.len - a.suf * b.pre。

    最终:

    cnt = a.cnt + b.cnt + a.len * b.len - a.suf * b.pre。

    4. 建树过程

    建树时递归处理整个数组区间。

    • 如果当前区间只有一个位置,就建立叶子节点:

  • • 长度、pre、suf都是 1;

  • •cnt是 0;

  • • 根据左右邻居判断该位置是否是峰值,得到hasPeak。

  • • 如果当前区间长度大于 1:

  • • 先递归建立左子树;

  • • 再递归建立右子树;

  • • 然后使用上面的合并规则,把左右子节点合并成当前节点。

    建树完成后,根节点保存整个数组区间的统计信息。

    5. 第一类查询:统计区间答案

    第一类查询是[1, li, ri],需要统计所有完全位于[li, ri]内的合法子段数量。

    线段树的查询过程如下:

    1. 如果当前节点区间完全被查询区间[li, ri]覆盖,就直接返回当前节点的信息。

  • 2. 如果查询区间只在左子树中,就递归查询左子树。

  • 3. 如果查询区间只在右子树中,就递归查询右子树。

  • 4. 如果查询区间同时覆盖左右子树,就分别查询左右两边,然后把两个查询结果按照合并规则合并。

    最终返回的结果中,cnt就是区间[li, ri]内所有符合条件的子段数量。
    把这个cnt加入答案数组即可。

    注意:查询返回的pre、suf是相对于查询区间的,而不是相对于整棵线段树原始区间的,这样不会把查询区间外的峰值错误算进来。

    6. 第二类查询:单点修改

    第二类查询是[2, index, val],表示把nums[index]改成val。

    一个位置是否是峰值,只取决于它自己和左右相邻元素。因此修改nums[index]后,只有最多三个位置的峰值状态可能改变:

    1. 位置index - 1:它的右邻居是index,可能受影响;

  • 2. 位置index:它自身被修改,可能受影响;

  • 3. 位置index + 1:它的左邻居是index,可能受影响。

    代码中分别检查这三个位置。

    对于每个可能受影响的位置:

    1. 用修改前的数组值计算它原来是不是峰值,记为旧状态;

  • 2. 用新的值val临时替代nums[index],计算它修改后是不是峰值,记为新状态;

  • 3. 如果新旧状态不同,说明这个位置的hasPeak需要翻转,于是调用线段树的点更新,把这个叶子节点的hasPeak取反;

  • 4. 点更新会沿着路径向上重新合并节点,更新祖先的pre、suf、cnt、hasPeak。

    三个可能位置检查完后,再把nums[index]真正改成val。

    注意边界条件:

    • 位置 0 和位置 n-1 不可能是峰值,因此相关检查会跳过;

  • • 如果index太靠左或太靠右,某些相邻位置不存在,也不会检查。

    由于每次修改最多只影响三个位置的峰值状态,所以最多只做三次线段树点更新。

    7. 主流程总结

    整体处理流程是:

    1. 根据初始数组nums建立线段树;

  • 2. 按顺序遍历所有查询;

  • 3. 如果当前查询是第一类查询:

    • 查询线段树区间[li, ri];

  • • 取出结果的cnt;

  • • 加入答案数组;

    4. 如果当前查询是第二类查询:

    • 检查index-1、index、index+1三个位置的峰值状态是否变化;

  • • 对发生变化的位置在线段树中翻转对应叶子的hasPeak;

  • • 最后把nums[index]更新为val;

    5. 所有查询处理完后返回答案数组。

    对于示例:

    • 初始nums = [1,3,2,4],位置 1 是峰值;

  • • 查询[1,0,3]时,符合条件的子段是[1,3,2]和[1,3,2,4],答案为 2;

  • • 更新nums[1] = 1后,数组变为[1,1,2,4],峰值消失;

  • • 再次查询[1,0,3],答案为 0。

    所以最终答案是[2,0]。

    8. 时间复杂度

    设数组长度为n,查询数量为q。

    • 建树需要遍历所有叶子并合并,时间复杂度为O(n);

  • • 每次第一类查询是线段树区间查询,时间复杂度为O(log n);

  • • 每次第二类修改最多检查三个位置,最多进行三次线段树点更新,每次点更新为O(log n),因此修改总代价仍为O(log n)。

    所以总时间复杂度为:

    O(n + q log n)。

    9. 额外空间复杂度

    • 线段树需要存储约O(n)个节点;

  • • 递归建树、查询、更新的递归深度为O(log n);

  • • 答案数组需要保存所有第一类查询的结果,最多O(q)。

    如果只计算辅助数据结构,不计返回答案数组,则额外空间复杂度为:

    O(n)。

    如果把返回答案数组也计入额外空间,则为:

    O(n + q)。

    Go完整代码如下:

    package main

    import (
    "fmt"
    "math/bits"
    )

    type data struct {
    cnt, pre, suf, lenint
    hasPeak bool
    }

    type seg []data

    func newSegmentTree(a []int) seg {
    n := len(a)
    t := make(seg, 2< uint (n -1 )))
    t.build(a, 1 , 0 , n -1 )
    return t
    }

    func (seg) mergeData(a, b data) data {
    cnt := a.cnt + b.cnt + a. len *b. len - a.suf*b.pre
    pre := a.pre
    if !a.hasPeak {
    pre += b.pre
    }
    suf := b.suf
    if !b.hasPeak {
    suf += a.suf
    }
    return data{cnt, pre, suf, a. len + b. len , a.hasPeak || b.hasPeak}
    }

    func (t seg) maintain(node int ) {
    t[node] = t.mergeData(t[node* 2 ], t[node* 2 + 1 ])
    }

    func (t seg) build(a [] int , node, l, r int ) {
    if l == r { // 叶子
    hasPeak := 0 < l && l < len (a) -1 && a[l -1 ] < a[l] && a[l] > a[l+ 1 ]
    t[node] = data{ 0 , 1 , 1 , 1 , hasPeak} // 初始化叶节点的值
    return
    }
    m := (l + r) >> 1
    t.build(a, node* 2 , l, m) // 初始化左子树
    t.build(a, node* 2 + 1 , m+ 1 , r) // 初始化右子树
    t.maintain(node)
    }

    func (t seg) update(node, l, r, i int ) {
    if l == r { // 叶子(到达目标)
    t[node].hasPeak = !t[node].hasPeak
    return
    }
    m := (l + r) >> 1
    if i <= m { // i 在左子树
    t.update(node* 2 , l, m, i)
    } else { // i 在右子树
    t.update(node* 2 + 1 , m+ 1 , r, i)
    }
    t.maintain(node)
    }

    func (t seg) query(node, l, r, ql, qr int ) data {
    if ql <= l && r <= qr { // 当前子树完全在 [ql, qr] 内
    return t[node]
    }
    m := (l + r) >> 1
    if qr <= m { // [ql, qr] 与右子树无交集,仅需递归左子树
    return t.query(node* 2 , l, m, ql, qr)
    }
    if ql > m { // [ql, qr] 与左子树无交集,仅需递归右子树
    return t.query(node* 2 + 1 , m+ 1 , r, ql, qr)
    }
    // [ql, qr] 与左右子树均有交集,分别递归,然后合并结果
    return t.mergeData(t.query(node* 2 , l, m, ql, qr), t.query(node* 2 + 1 , m+ 1 , r, ql, qr))
    }

    func countOfPeaks(nums []int, queries [][]int) (ans [] int64 ) {
    n := len (nums)
    t := newSegmentTree(nums)
    for _, q := range queries {
    if q[ 0 ] == 1 {
    ans = append (ans, int64 (t.query( 1 , 0 , n -1 , q[ 1 ], q[ 2 ]).cnt))
    continue
    }

    i, v := q[ 1 ], q[ 2 ]

    if i > 1 {
    oldHas := nums[i -2 ] < nums[i -1 ] && nums[i -1 ] > nums[i]
    newHas := nums[i -2 ] < nums[i -1 ] && nums[i -1 ] > v
    if newHas != oldHas {
    t.update( 1 , 0 , n -1 , i -1 )
    }
    }

    if 0 < i && i < n -1 {
    oldHas := nums[i -1 ] < nums[i] && nums[i] > nums[i+ 1 ]
    newHas := nums[i -1 ] < v && v > nums[i+ 1 ]
    if newHas != oldHas {
    t.update( 1 , 0 , n -1 , i)
    }
    }

    if i < n -2 {
    oldHas := nums[i] < nums[i+ 1 ] && nums[i+ 1 ] > nums[i+ 2 ]
    newHas := v < nums[i+ 1 ] && nums[i+ 1 ] > nums[i+ 2 ]
    if newHas != oldHas {
    t.update( 1 , 0 , n -1 , i+ 1 )
    }
    }

    nums[i] = v
    }
    return
    }

    func main() {
    nums := [] int { 1 , 3 , 2 , 4 }
    queries := [][] int {{ 1 , 0 , 3 }, { 2 , 1 , 1 }, { 1 , 0 , 3 }}
    result := countOfPeaks(nums, queries)
    fmt.Println(result)
    }

    2026-11-06:数组中的峰值Ⅱ。用go语言,给定一个整数数组 nums,长度为 n;再给定若干查询,存放在二维数组 queries 中。需要按顺序处理

    Python完整代码如下:

    # -*-coding:utf-8-*-

    from typing import List

    class Data:
    __slots__ = ("cnt", "pre", "suf", "length", "hasPeak")

    def __init__(self, cnt=0, pre=0, suf=0, length=0, hasPeak=False):
    self.cnt = cnt
    self.pre = pre
    self.suf = suf
    self.length = length
    self.hasPeak = hasPeak

    class SegmentTree:
    def __init__(self, a: List[int]):
    self.n = len(a)
    size = 2 << ((self.n - 1).bit_length()) if self.n > 0else2
    self.t = [Data() for _ in range(size)]
    if self.n > 0:
    self.build(a, 1, 0, self.n - 1)

    def merge_data(self, a: Data, b: Data) -> Data:
    cnt = a.cnt + b.cnt + a.length * b.length - a.suf * b.pre

    pre = a.pre
    if not a.hasPeak:
    pre += b.pre

    suf = b.suf
    if not b.hasPeak:
    suf += a.suf

    return Data(
    cnt,
    pre,
    suf,
    a.length + b.length,
    a.hasPeak or b.hasPeak,
    )

    def maintain(self, node: int) -> None:
    self.t[node] = self.merge_data(self.t[node * 2], self.t[node * 2 + 1])

    def build(self, a: List[int], node: int, l: int, r: int) -> None:
    if l == r:
    has_peak = (
    0 < l < len(a) - 1
    and a[l - 1] < a[l]
    and a[l] > a[l + 1]
    )
    self.t[node] = Data(0, 1, 1, 1, has_peak)
    return

    m = (l + r) // 2
    self.build(a, node * 2, l, m)
    self.build(a, node * 2 + 1, m + 1, r)
    self.maintain(node)

    def update(self, node: int, l: int, r: int, i: int) -> None:
    if l == r:
    self.t[node].hasPeak = not self.t[node].hasPeak
    return

    m = (l + r) // 2
    if i <= m:
    self.update(node * 2, l, m, i)
    else:
    self.update(node * 2 + 1, m + 1, r, i)

    self.maintain(node)

    def query(self, node: int, l: int, r: int, ql: int, qr: int) -> Data:
    if ql <= l and r <= qr:
    return self.t[node]

    m = (l + r) // 2

    if qr <= m:
    return self.query(node * 2, l, m, ql, qr)

    if ql > m:
    return self.query(node * 2 + 1, m + 1, r, ql, qr)

    return self.merge_data(
    self.query(node * 2, l, m, ql, qr),
    self.query(node * 2 + 1, m + 1, r, ql, qr),
    )

    def count_of_peaks(nums: List[int], queries: List[List[int]]) -> List[int]:
    n = len(nums)
    tree = SegmentTree(nums)
    ans = []

    for q in queries:
    if q[0] == 1:
    ans.append(tree.query(1, 0, n - 1, q[1], q[2]).cnt)
    continue

    i, v = q[1], q[2]

    if i > 1:
    old_has = nums[i - 2] < nums[i - 1] and nums[i - 1] > nums[i]
    new_has = nums[i - 2] < nums[i - 1] and nums[i - 1] > v
    if new_has != old_has:
    tree.update(1, 0, n - 1, i - 1)

    if0 < i < n - 1:
    old_has = nums[i - 1] < nums[i] and nums[i] > nums[i + 1]
    new_has = nums[i - 1] < v and v > nums[i + 1]
    if new_has != old_has:
    tree.update(1, 0, n - 1, i)

    if i < n - 2:
    old_has = nums[i] < nums[i + 1] and nums[i + 1] > nums[i + 2]
    new_has = v < nums[i + 1] and nums[i + 1] > nums[i + 2]
    if new_has != old_has:
    tree.update(1, 0, n - 1, i + 1)

    nums[i] = v

    return ans

    def main() -> None:
    nums = [1, 3, 2, 4]
    queries = [[1, 0, 3], [2, 1, 1], [1, 0, 3]]
    result = count_of_peaks(nums, queries)
    print(result)

    if __name__ == "__main__":
    main()

    2026-11-06:数组中的峰值Ⅱ。用go语言,给定一个整数数组 nums,长度为 n;再给定若干查询,存放在二维数组 queries 中。需要按顺序处理

    C++完整代码如下:

      
    
    using namespace std;

    struct Data {
    long long cnt;
    int pre, suf, len;
    bool hasPeak;

    Data() : cnt(0), pre(0), suf(0), len(0), hasPeak(false) {}
    Data(long long c, int p, int s, int l, bool h)
    : cnt(c), pre(p), suf(s), len(l), hasPeak(h) {}
    };

    class SegmentTree {
    private:
    vector t;
    int n;

    Data mergeData(const Data& a, const Data& b) {
    long long cnt = a.cnt + b.cnt
    + 1LL * a.len * b.len
    - 1LL * a.suf * b.pre;

    int pre = a.pre;
    if (!a.hasPeak) {
    pre += b.pre;
    }

    int suf = b.suf;
    if (!b.hasPeak) {
    suf += a.suf;
    }

    return Data(cnt, pre, suf, a.len + b.len, a.hasPeak || b.hasPeak);
    }

    void maintain(int node) {
    t[node] = mergeData(t[node * 2], t[node * 2 + 1]);
    }

    void build(const vector& a, int node, int l, int r) {
    if (l == r) {
    int n = (int)a.size();
    bool hasPeak = (0 < l && l < n - 1 && a[l - 1] < a[l] && a[l] > a[l + 1]);
    t[node] = Data(0, 1, 1, 1, hasPeak);
    return;
    }

    int m = (l + r) >> 1;
    build(a, node * 2, l, m);
    build(a, node * 2 + 1, m + 1, r);
    maintain(node);
    }

    public:
    SegmentTree(const vector& a) {
    n = (int)a.size();
    t.assign(4 * n + 5, Data());
    if (n > 0) {
    build(a, 1, 0, n - 1);
    }
    }

    void update(int node, int l, int r, int i) {
    if (l == r) {
    t[node].hasPeak = !t[node].hasPeak;
    return;
    }

    int m = (l + r) >> 1;
    if (i <= m) {
    update(node * 2, l, m, i);
    } else {
    update(node * 2 + 1, m + 1, r, i);
    }
    maintain(node);
    }

    Data query(int node, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr) {
    return t[node];
    }

    int m = (l + r) >> 1;
    if (qr <= m) {
    return query(node * 2, l, m, ql, qr);
    }
    if (ql > m) {
    return query(node * 2 + 1, m + 1, r, ql, qr);
    }

    return mergeData(
    query(node * 2, l, m, ql, qr),
    query(node * 2 + 1, m + 1, r, ql, qr)
    );
    }
    };

    vector countOfPeaks(vector< int>& nums, vector int >>& queries) {
    int n = ( int )nums.size();
    SegmentTree tree(nums);
    vector ans;

    for (auto& q : queries) {
    if (q[ 0 ] == 1 ) {
    ans.push_back(tree.query( 1 , 0 , n - 1 , q[ 1 ], q[ 2 ]).cnt);
    continue ;
    }

    int i = q[ 1 ], v = q[ 2 ];

    if (i > 1 ) {
    bool oldHas = nums[i - 2 ] < nums[i - 1 ] && nums[i - 1 ] > nums[i];
    bool newHas = nums[i - 2 ] < nums[i - 1 ] && nums[i - 1 ] > v;
    if (newHas != oldHas) {
    tree.update( 1 , 0 , n - 1 , i - 1 );
    }
    }

    if ( 0 < i && i < n - 1 ) {
    bool oldHas = nums[i - 1 ] < nums[i] && nums[i] > nums[i + 1 ];
    bool newHas = nums[i - 1 ] < v && v > nums[i + 1 ];
    if (newHas != oldHas) {
    tree.update( 1 , 0 , n - 1 , i);
    }
    }

    if (i < n - 2 ) {
    bool oldHas = nums[i] < nums[i + 1 ] && nums[i + 1 ] > nums[i + 2 ];
    bool newHas = v < nums[i + 1 ] && nums[i + 1 ] > nums[i + 2 ];
    if (newHas != oldHas) {
    tree.update( 1 , 0 , n - 1 , i + 1 );
    }
    }

    nums[i] = v;
    }

    return ans;
    }

    int main() {
    vector< int > nums = { 1 , 3 , 2 , 4 };
    vector int >> queries = {
    { 1 , 0 , 3 },
    { 2 , 1 , 1 },
    { 1 , 0 , 3 }
    };

    vector result = countOfPeaks(nums, queries);

    cout << "[" ;
    for (size_t i = 0 ; i < result.size(); ++i) {
    if (i > 0 ) cout << " " ;
    cout << result[i];
    }
    cout << "]" << endl;

    return 0 ;
    }

    2026-11-06:数组中的峰值Ⅱ。用go语言,给定一个整数数组 nums,长度为 n;再给定若干查询,存放在二维数组 queries 中。需要按顺序处理

    芯谷ai:https://xinguhub.com/sign-up?aff=txk5

    我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的AI知识。在这里,您可以找到最新的AI科普文章、工具评测、提升效率的秘籍以及行业洞察。 欢迎关注“福大大架构师每日一题”,发消息可获得面试资料,让AI助力您的未来发展。

    © 版权声明

    相关文章