大厂真题 / 阿里巴巴

阿里 4.8 笔试真题 - 研发岗

本场考试概述

考试时间:2026年4月8日 考试岗位:研发岗 难度评级:困难

考点分析

  1. 第一题:哈希集合 + 枚举分割点(难度中等)
  2. 第二题:奇偶分组贪心(难度困难)
  3. 第三题:离线树状数组(难度困难)

建议策略

  1. 第一题是字符串哈希枚举,思路直接,优先保分
  2. 第二题需要发现”操作不改变 i+j 奇偶性”这一关键性质,需要一定观察力
  3. 第三题是经典离线 + 树状数组组合,需要较强的算法基础

第一题:可删去的字符串

题目描述

给你 n 个字符串。称某个字符串是”可删去的”,当且仅当它能由序列中两个不同位置的非空字符串拼接而成。统计满足条件的字符串下标数量。

样例

输入

2
5
a
b
ab
abc
bc
4
a
aa
a
aaa

输出

2
2

思路分析

第一步:观察题目性质

对于每个字符串 s_i,如果它长度为 L,则至多有 L-1 个分割点。只要枚举每个分割点,判断左半段和右半段是否同时出现在集合中即可。

第二步:问题转化

所有字符串的分割点总数等于”字符串总长度”,因此整体复杂度只与总长度线性相关。将所有字符串放入哈希集合,并用计数器记录每个字符串的出现次数。

第三步:实现要点

对每个 s_i 枚举分割点 k,得到 a = s_i[:k] 与 b = s_i[k:]。若 a != b,两者都在集合中即可;若 a == b,需要该字符串出现至少 2 次。

题解代码

import sys
from collections import Counter
input = sys.stdin.readline

def solve():
    n = int(input())
    strs = [input().strip() for _ in range(n)]
    cnt = Counter(strs)

    ans = 0
    for s in strs:
        L = len(s)
        ok = False
        for k in range(1, L):
            a, b = s[:k], s[k:]
            if a not in cnt or b not in cnt:
                continue
            # 若 a == b 需要两个不同位置都等于 a
            if a != b or cnt[a] >= 2:
                ok = True
                break
        if ok:
            ans += 1
    print(ans)

T = int(input())
for _ in range(T):
    solve()

复杂度分析

时间复杂度:O(S),其中 S 为所有字符串总长度。每个分割点的子串构造与哈希查找均摊为常数。 空间复杂度:O(S),用于存放所有字符串及哈希集合。


第二题:网格路径最大和

题目描述

给定一个 2×n 的网格。可以任意次交换 a[0][j] 与 a[1][j+1](即交换异行且列号相差 1 的两个格子)。交换完成后,从 (0,0) 出发只能向下或向右走到 (1,n-1),求路径上元素之和的最大值。

样例

输入

2
2
1 2
3 4
3
5 1 7
3 10 4

输出

8
24

思路分析

第一步:观察题目性质

每次操作交换 a[0][j] 与 a[1][j+1],被交换的两格的坐标和 (i+j) 同奇偶。因此所有操作都不改变每个值所在格子的 (i+j) 奇偶性——每个值始终待在它原本属于的”奇偶类”里。

第二步:问题转化

同奇偶类内部可以任意置换(连通性可证明),但两类之间不能混。从 (0,0) 走到 (1,n-1) 的路径恰好包含 ceil((n+1)/2) 个偶格和 floor((n+1)/2) 个奇格。

第三步:算法选择

将原网格 2n 个数按 (i+j) 的奇偶分入两组。偶组取前 ceil((n+1)/2) 大、奇组取前 floor((n+1)/2) 大,二者之和即为答案。

题解代码

import sys
input = sys.stdin.readline

def solve():
    n = int(input())
    row0 = list(map(int, input().split()))
    row1 = list(map(int, input().split()))

    evens = []
    odds = []
    for j in range(n):
        # row0[j] 的坐标和为 0+j=j
        if j % 2 == 0:
            evens.append(row0[j])
        else:
            odds.append(row0[j])
        # row1[j] 的坐标和为 1+j
        if (1 + j) % 2 == 0:
            evens.append(row1[j])
        else:
            odds.append(row1[j])

    evens.sort(reverse=True)
    odds.sort(reverse=True)
    # 路径包含 ceil((n+1)/2) 个偶格,floor((n+1)/2) 个奇格
    ke = (n + 2) // 2
    ko = (n + 1) // 2
    ans = sum(evens[:ke]) + sum(odds[:ko])
    print(ans)

T = int(input())
for _ in range(T):
    solve()

复杂度分析

时间复杂度:O(n log n),瓶颈是两次排序。 空间复杂度:O(n),用于保存两个分组数组。


第三题:相邻等值对贡献和

题目描述

给定一个长度为 n 的数组。称一对下标 (i,j) 为”相邻等值对”,当且仅当 i < j,a[i] = a[j],且对于任意 i < k < j 都有 a[k] != a[i](即 i 和 j 是同一个值在数组中相邻的两次出现)。

对每个相邻等值对 (i,j),定义其贡献为区间 (i,j) 内严格小于 a[i] 的元素个数。求所有相邻等值对的贡献之和。

样例

输入

2
5
2 1 2 1 2
6
3 2 2 1 3 2

输出

2
4

思路分析

第一步:观察题目性质

枚举所有相邻等值对 (i,j),需要快速回答”区间 [i+1,j-1] 内值小于 a[i] 的元素个数”。等值对最多 n 个,若每次朴素扫描则总复杂度可达 O(n^2),无法承受。

第二步:问题转化

所有询问都是”区间 + 阈值”形式,且阈值就是等值对的值。把询问按阈值升序排序后,每个询问要求”位置在 [i+1,j-1] 且值 < v 的元素个数”。

第三步:算法选择——离线 + 树状数组

把所有原数组下标按值升序排序,用双指针配合询问推进。处理阈值 v 的询问前,先把所有值 < v 的下标插入树状数组(标记为 1),然后对每个询问做一次区间前缀和差,即可得到区间内值 < v 的元素计数。每个下标只插入一次,总复杂度 O(n log n)。

题解代码

import sys
input = sys.stdin.readline

def solve():
    n = int(input())
    a = list(map(int, input().split()))

    # 树状数组
    bit = [0] * (n + 2)

    def add(i):
        i += 1
        while i <= n:
            bit[i] += 1
            i += i & -i

    def pre(i):
        i += 1
        r = 0
        while i > 0:
            r += bit[i]
            i -= i & -i
        return r

    def query(l, r):
        if l > r:
            return 0
        return pre(r) - (pre(l - 1) if l > 0 else 0)

    # 收集相邻等值对
    prev_pos = {}
    queries = []  # (阈值v, l, r)
    for i in range(n):
        v = a[i]
        if v in prev_pos:
            queries.append((v, prev_pos[v] + 1, i - 1))
        prev_pos[v] = i

    # 下标按值升序排列
    idx_order = sorted(range(n), key=lambda x: a[x])
    # 查询按阈值升序排列
    queries.sort()

    ptr = 0
    ans = 0
    for v, l, r in queries:
        # 把所有值 < v 的位置插入树状数组
        while ptr < n and a[idx_order[ptr]] < v:
            add(idx_order[ptr])
            ptr += 1
        ans += query(l, r)

    print(ans)

T = int(input())
for _ in range(T):
    # 重置树状数组需要在 solve 内部
    solve()

复杂度分析

时间复杂度:O(n log n),每个下标只插入一次,每次查询是树状数组的两次前缀和。 空间复杂度:O(n),用于存储数组、树状数组以及查询列表。


小结

  • 第一题哈希集合 + 枚举分割点,所有分割点总数等于字符串总长度,线性复杂度
  • 第二题奇偶分组贪心,核心是发现交换操作不改变 (i+j) 奇偶性,同类内部可任意置换
  • 第三题离线树状数组,按阈值升序排列询问和元素,双指针 + 树状数组实现 O(n log n) 查询