【春招笔试】2025.05.12阿里国际机考真题改编题

✅ 春招备战指南 ✅

💡 学习建议:

  • 先尝试独立解题(建议用时:90分钟/套)
  • 对照解析查漏补缺

🧸 题面描述背景等均已深度改编,做法和题目本质基本保持一致。

🍹 感谢各位朋友们的订阅,你们的支持是我们创作的最大动力

🌸 目前本专栏已经上线80+套真题改编解析,后续会持续更新的

春秋招合集 -> 互联网必备刷题宝典🔗

题目一:翡翠梦境断点密码

1️⃣:预处理前缀交替和和后缀交替和

2️⃣:枚举断点位置,利用前后缀计算最大交替和

难度:中等

这道题目的关键在于理解环形序列断开后的交替求和特性。通过数学推导,我们发现可以利用前缀和后缀交替和快速计算任意断点位置的结果,从而避免重复计算,实现 O(n) 的高效解法。

题目二:排列变换同构问题

1️⃣:计算排列的逆序位数量

2️⃣:当原排列有逆序位时,尝试元素交换构造新排列

3️⃣:验证新排列逆序位数量是否相同

难度:中等

这道题目考察了排列的性质和构造能力。关键观察是:若原排列无逆序位,则无解;否则,通过有限次元素交换尝试,通常能构造出满足条件的新排列。算法采用贪心思想,优先尝试局部交换,保持逆序位数量不变。

题目三:繁星森林的零尾传说

1️⃣:将问题转化为乘积末位为0的连通子集计数

2️⃣:运用容斥原理划分为四类连通子集

3️⃣:通过树形DP高效计算各类连通子集数量

难度:中等偏难

这道题目结合了数论和树形动态规划。关键洞察是:末位为0等价于至少含一个因子2和一个因子5。使用容斥原理巧妙地将问题转化为计算四类不同约束条件下的树连通子集数,而树形DP则提供了高效的计算方法。

01. 翡翠梦境断点密码

问题描述

小基 公主拥有一条神秘的翡翠手链,上面镶嵌着 个彩色宝石,每个宝石都有一个能量值 。手链是环形的,需要在某处断开才能获取其中蕴含的魔法能量。

断开手链后,宝石会重新排列成一个线性序列 。若在第 个宝石处断开(),则得到的序列为:

随后,公主需要按照特定的能量提取法则来获取魔法能量,即计算交替求和

公主希望找到最佳的断点位置,使得获取的魔法能量 最大。请你帮她计算这个最大能量值。

输入格式

第一行包含一个整数 ),表示手链上宝石的数量。

第二行包含 个整数 ),表示每个宝石的能量值。

输出格式

输出一个整数,表示 小基 公主能获取的最大魔法能量值。

样例输入

4
1 2 3 2
6
1 2 3 2 5 0

样例输出

0
5

数据范围

样例 解释说明
样例1 无论在哪个位置断开,交替求和结果都是 0
样例2 在第 1 个宝石处断开,得到序列 [1,2,3,2,5,0],交替求和为 1-2+3-2+5-0=5,这是所有断点位置中的最大值

题解

这道题看起来很神奇,实际上是一个关于环形序列的经典问题:在环上选择一个起点,计算交替求和的最大值。

首先,我们需要理解问题本质。对于环形序列,在位置 处断开后,我们得到一个线性序列,然后计算交替求和。直觉上,我们可以尝试所有的 个断点位置,但这样会有重复计算,效率不高。

观察交替求和公式:

我们可以将其重写为:

通过数学推导,可以将其拆分成两部分:

  1. 从断点 到数组结尾的交替和
  2. 从数组开头到断点前一个位置的交替和(可能带有符号变化)

具体地,如果我们预先计算出前缀交替和和后缀交替和,便可在 时间内计算任意断点位置的交替和值。

算法步骤:

  1. 预处理前缀交替和
  2. 预处理后缀交替和
  3. 对于每个断点位置 ,计算
  4. 返回所有 中的最大值

时间复杂度:,我们只需要两次遍历数组进行预处理,然后再遍历一次找出最大值。 空间复杂度:,用于存储前缀和后缀交替和数组。

这个解法高效且优雅,适用于给定的数据范围。对于数组长度达到 的情况,我们的 算法能够轻松应对。

参考代码

  • Python
import sys
input = lambda: sys.stdin.readline().strip()

# 读取输入
n = int(input())
a = [0] + list(map(int, input().split()))  # 1-indexed

# 预处理前缀交替和
pre = [0] * (n+1)
for i in range(1, n+1):
    # 计算前缀交替和: P[i] = P[i-1] + (a[i] 或 -a[i])
    sign = 1 if i % 2 == 1 else -1
    pre[i] = pre[i-1] + sign * a[i]

# 预处理后缀交替和
suf = [0] * (n+2)
for i in range(n, 0, -1):
    # 计算后缀交替和: Q[i] = Q[i+1] + (a[i] 或 -a[i])
    sign = 1 if i % 2 == 1 else -1
    suf[i] = suf[i+1] + sign * a[i]

# 计算每个断点位置的交替和,找出最大值
ans = float('-inf')
for k in range(1, n+1):
    # 计算 S_k = (-1)^(k+1) * Q[k] + (-1)^(n-k+1) * P[k-1]
    sign1 = -1 if (k+1) % 2 == 1 else 1
    sign2 = -1 if (n-k+1) % 2 == 1 else 1
    sk = sign1 * suf[k] + sign2 * pre[k-1]
    ans = max(ans, sk)

print(ans)
  • Cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    // 读取输入
    int n;
    cin >> n;
    vector<ll> a(n+1);
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    
    // 计算前缀交替和
    vector<ll> pre(n+1, 0);
    for (int i = 1; i <= n; ++i) {
        int sgn = (i % 2 == 1) ? 1 : -1;
        pre[i] = pre[i-1] + sgn * a[i];
    }
    
    // 计算后缀交替和
    vector<ll> suf(n+2, 0);
    for (int i = n; i >= 1; --i) {
        int sgn = (i % 2 == 1) ? 1 : -1;
        suf[i] = suf[i+1] + sgn * a[i];
    }
    
    // 枚举所有断点位置,找最大交替和
    ll ans = -1e18;
    for (int k = 1; k <= n; ++k) {
        int sgn1 = ((k+1) % 2 == 1) ? -1 : 1;
        int sgn2 = ((n-k+1) % 2 == 1) ? -1 : 1;
        ll cur = sgn1 * suf[k] + sgn2 * pre[k-1];
        ans = max(ans, cur);
    }
    
    cout << ans << endl;
    return 0;
}
  • Java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        long[] a = new long[n+1];
        for (int i = 1; i <= n; i++) {
            a[i] = sc.nextLong();
        }
        
        // 计算前缀交替和
        long[] pre = new long[n+1];
        for (int i = 1; i <= n; i++) {
            int sign = (i % 2 == 1) ? 1 : -1;
            pre[i] = pre[i-1] + sign * a[i];
        }
        
        // 计算后缀交替和
        long[] suf = new long[n+2];
        for (int i = n; i >= 1; i--) {
            int sign = (i % 2 == 1) ? 1 : -1;
            suf[i] = suf[i+1] + sign * a[i];
        }
        
        // 枚举断点位置,找最大值
        long ans = Long.MIN_VALUE;
        for (int k = 1; k <= n; k++) {
            int sign1 = ((k+1) % 2 == 1) ? -1 : 1;
            int sign2 = ((n-k+1) % 2 == 1) ? -1 : 1;
            long sk = sign1 * suf[k] + sign2 * pre[k-1];
            ans = Math.max(ans, sk);
        }
        
        System.out.println(ans);
    }
}

02. 排列变换同构问题

问题描述

在排列分析领域,小毛定义了一个名为"逆序位"的概念:对于排列 中的位置 ),若存在 )使得 ,则称位置 为"逆序位"。

小柯现在拿到了一个长度为 的排列 ,她希望找到另一个不同的排列 ,使得 具有完全相同数量的"逆序位"。换句话说,要求 ,但 中"逆序位"的个数相等。

请你帮小柯判断是否存在这样的排列 ,如果存在,请构造出一个满足条件的解。

输入格式

第一行包含一个整数 ),表示测试用例的数量。

对于每个测试用例:

  • 第一行包含一个整数 ),表示排列的长度。
  • 第二行包含 个用空格分隔的整数 ),表示排列 。保证给定的数构成一个排列。

所有测试用例中 的总和不超过

输出格式

对于每个测试用例:

  • 如果存在满足条件的排列 ,先输出一行 "YES"(不含引号),然后在下一行输出 个用空格分隔的整数,表示排列 。如果有多个解,输出任意一个即可。
  • 如果不存在满足条件的排列 ,输出一行 "NO"(不含引号)。

样例输入

2 
4
2 1 3 4
3 
1 2 3

样例输出

YES
1 2 4 3
NO

数据范围

  • 所有测试用例中 的总和不超过
  • 输入保证 是一个排列
样例 解释说明
样例1 排列 [2,1,3,4] 的"逆序位"为位置 1,因为
排列 [1,2,4,3] 的"逆序位"也是 1 个,位置 3,因为
两个排列的"逆序位"数量相同且排列不同,所以输出 YES。
样例2 排列 [1,2,3] 是一个严格递增的排列,没有"逆序位"。任何与它不同的排列必然会产生逆序位,无法满足条件,所以输出 NO。

题解

这道题目考察的是排列的性质和构造能力。让我们一步一步分析这个问题。

首先,明确什么是"逆序位":对于排列中的位置 ,如果存在位置 使得 ,那么位置 就是一个"逆序位"。换句话说,"逆序位"就是那些"右侧存在比自己小的元素"的位置。

现在,我们需要构造一个不同于原排列 的新排列 ,使得两个排列的"逆序位"数量相同。

关键观察:

  1. 如果原排列 没有任何"逆序位"(即完全递增),那么任何与 不同的排列必然会引入至少一个"逆序位",此时无解。
  2. 如果原排列 有至少一个"逆序位",我们通常可以通过交换某些元素构造出一个不同的排列,同时保持"逆序位"数量不变。

我的解题思路是:

  1. 首先统计原排列 的"逆序位"数量。
  2. 如果"逆序位"数量为 0,输出 NO。
  3. 否则,尝试通过交换元素构造新排列。最简单的尝试是交换相邻或接近的元素,因为这样对"逆序位"的影响比较可控。

具体实现上,我使用以下策略:

  • 对于每个位置 ,通过查找其后缀最小值,判断它是否是"逆序位"。
  • 尝试交换不同位置的元素,检查新排列的"逆序位"数量是否与原排列相同。
  • 为了效率,我们优先尝试交换前几个位置的元素,通常能更快找到解。

这种方法的时间复杂度是 最坏情况下,但在实际情况中,由于我们限制了尝试的交换次数,复杂度会低得多。空间复杂度是 ,主要用于存储排列和辅助数组。

对于给定的数据范围( 总和不超过 ),这个算法是高效的。有趣的是,大多数情况下,只需尝试少量交换就能找到满足条件的解。

参考代码

  • Python
import sys
input = lambda: sys.stdin.readline().strip()

def count_inv(arr):
    """计算数组中的逆序位数量"""
    n = len(arr)
    suf_min = [float('inf')] * n  # 后缀最小值
    suf_min[-1] = arr[-1]
    
    # 计算后缀最小值
    for i in range(n-2, -1, -1):
        suf_min[i] = min(suf_min[i+1], arr[i+1])
    
    # 统计逆序位
    cnt = 0
    for i in range(n-1):
        if arr[i] > suf_min[i]:
            cnt += 1
    
    return cnt

# 读取测试用例数量
t = int(input())

for _ in range(t):
    n = int(input())
    p = list(map(int, input().split()))
    
    # 计算原排列的逆序位数量
    g = count_inv(p)
    
    if g == 0:
        print("NO")
        continue
    
    # 尝试构造新排列
    found = False
    q = p.copy()
    
    # 尝试交换元素
    for i in range(min(n-1, 4)):  # 限制尝试次数以提高效率
        for j in range(i+1, min(n, i+6)):  # 尝试与附近位置交换
            # 交换尝试
            q[i], q[j] = q[j], q[i]
            
            # 检查新排列的逆序位数量
            if q != p and count_inv(q) == g:
                found = True
                break
            
            # 恢复原排列
            q[i], q[j] = q[j], q[i]
            
        if found:
            break
    
    # 如果前面的尝试失败,尝试交换相邻元素
    if not found:
        for i in range(n-1):
            q[i], q[i+1] = q[i+1], q[i]
            if q != p and count_inv(q) == g:
                found = True
                break
            q[i], q[i+1] = q[i+1], q[i]
    
    # 输出结果
    if found:
        print("YES")
        print(*q)
    else:
        print("NO")
  • Cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

// 计算排列中的逆序位数量
int countInv(const vector<int>& arr) {
    int n = arr.size();
    vector<int> sufMin(n, INT_MAX);
    sufMin[n-1] = arr[n-1];
    
    // 计算后缀最小值
    for (int i = n-2; i >= 0; --i) {
        sufMin[i] = min(sufMin[i+1], arr[i+1]);
    }
    
    // 统计逆序位
    int cnt = 0;
    for (int i = 0; i < n-1; ++i) {
        if (arr[i] > sufMin[i]) {
            ++cnt;
        }
    }
    
    return cnt;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int t;
    cin >> t;
    
    while (t--) {
        int n;
        cin >> n;
        
        vector<int> p(n);
        for (int i = 0; i < n; ++i) {
            cin >> p[i];
        }
        
        // 计算原排列的逆序位数量
        int g = countInv(p);
        
        if (g == 0) {
            cout << "NO\n";
            continue;
        }
        
        // 尝试构造新排列
        bool found = false;
        vector<int> q = p;
        
        // 尝试有限次交换
        const int maxTry = min(n-1, 4);
        for (int i = 0

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

互联网刷题笔试宝典 文章被收录于专栏

互联网刷题笔试宝典,这里涵盖了市面上大部分的笔试题合集,希望助大家春秋招一臂之力

全部评论

相关推荐

评论
1
1
分享

创作者周榜

更多
牛客网
牛客企业服务