【春招笔试】2025.04.19-淘天算法岗笔试题

✅ 春招备战指南 ✅

💡 学习建议:

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

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

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

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

淘天

题目一:字符交换智慧

1️⃣:将字符按ASCII码奇偶性分组

2️⃣:对每组字符分别排序

3️⃣:按照原字符串中字符的奇偶性填回排序后的字符

难度:中等

这道题目的关键在于理解ASCII码奇偶性相同的字符才能相互交换的性质。通过将字符分组排序,可以在O(n log n)的时间复杂度内得到字典序最小的字符串。

题目二:秒杀顺子查找

1️⃣:利用动态规划统计不同长度的顺子子序列

2️⃣:使用哈希表存储状态

3️⃣:从长度1到5逐步构建顺子子序列

难度:中等

这道题目结合了子序列和顺子的概念,通过巧妙的动态规划状态设计,可以高效统计满足条件的子序列数量,避免了暴力枚举所有可能的组合。

题目三:数值平衡之道

1️⃣:确定目标值为原始值的中位数

2️⃣:利用树形动态规划计算最优连通子图

3️⃣:通过后序和前序遍历,计算最小操作成本

难度:中等偏难

这道题目需要综合应用数学推导和树形动态规划,关键思路是将最小化成本问题转化为寻找最大权连通子图问题。通过巧妙的状态设计,可以在O(n)的时间复杂度内解决问题。

01. 字符交换智慧

问题描述

小柯有一个长度为 的字符串 ,该字符串仅由大小写字母构成。

她发现了一种神奇的交换规则:当两个字符的 ASCII 码差值为偶数时,它们可以被交换。具体来说,她可以进行任意次如下操作:

  • 选择两个不同的位置 ,要求按照 ASCII 表, 为偶数。
  • 交换 的值。

小柯想知道,经过任意次操作后,能得到的字典序最小的字符串是什么。

【字典序比较】:从字符串的第一个字符开始逐个比较,直到找到第一个不同的位置,通过比较这个位置字符的 ASCII 码大小得到字符串的大小,称为字典序比较。

输入格式

第一行输入一个正整数 ,表示字符串的长度。

第二行输入一个字符串 ,保证仅由大小写字母构成。

输出格式

输出一个字符串,表示可以经过操作得到的字典序最小的字符串。

样例输入

4
cfae

样例输出

afce

数据范围

  • 字符串 仅由大小写字母构成
样例 解释说明
样例1 将 'c' 和 'a' 交换,'c'(99) 和 'a'(97) 的ASCII码差为2(偶数),所以可以交换。交换后字符串变为 "afce",这是可以得到的字典序最小的字符串。

题解

这道题目要求我们通过交换字符得到字典序最小的字符串,关键是理解交换的限制条件:只有ASCII码差值为偶数的字符才能交换。

仔细分析可以发现一个重要性质:ASCII码为奇数的字符只能与ASCII码为奇数的字符交换,ASCII码为偶数的字符只能与ASCII码为偶数的字符交换。这是因为两个奇数或两个偶数的差值一定是偶数,而一个奇数和一个偶数的差值一定是奇数。

根据这个性质,我们可以将字符串中的字符分成两组:

  1. ASCII码为奇数的字符组
  2. ASCII码为偶数的字符组

在每组内,字符可以任意交换位置。因此,为了得到字典序最小的字符串,我们应该:

  1. 将奇数组的字符按升序排序
  2. 将偶数组的字符按升序排序
  3. 按照原字符串中字符的奇偶性,依次填入排序后的奇数或偶数字符

例如对于示例"cfae":

  • 'c'的ASCII码是99(奇数)
  • 'f'的ASCII码是102(偶数)
  • 'a'的ASCII码是97(奇数)
  • 'e'的ASCII码是101(奇数)

奇数组:['c', 'a', 'e'] → 排序后变成 ['a', 'c', 'e'] 偶数组:['f'] → 排序后还是 ['f']

然后按照原字符串中字符的奇偶性重构:

  • 第1个位置原来是'c'(奇数),填入'a'
  • 第2个位置原来是'f'(偶数),填入'f'
  • 第3个位置原来是'a'(奇数),填入'c'
  • 第4个位置原来是'e'(奇数),填入'e'

得到结果:"afce",这就是字典序最小的字符串。

算法的时间复杂度是 O(n log n),主要来自对两组字符的排序,空间复杂度是 O(n)。

参考代码

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

# 读取输入
n = int(input())
s = input()

# 将字符分为ASCII码奇数和偶数两组
odd = []
even = []
for c in s:
    if ord(c) % 2 == 0:
        even.append(c)
    else:
        odd.append(c)

# 对两组字符分别排序
odd.sort()
even.sort()

# 构建结果字符串
res = ""
oi, ei = 0, 0
for c in s:
    if ord(c) % 2 == 0:
        # ASCII码为偶数的位置
        res += even[ei]
        ei += 1
    else:
        # ASCII码为奇数的位置
        res += odd[oi]
        oi += 1

print(res)
  • Cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    // 读取输入
    int n;
    string s;
    cin >> n >> s;
    
    // 分离奇偶ASCII码字符
    vector<char> odd, even;
    for (char ch : s) {
        if (ch % 2 == 0) {
            even.push_back(ch);
        } else {
            odd.push_back(ch);
        }
    }
    
    // 排序
    sort(odd.begin(), odd.end());
    sort(even.begin(), even.end());
    
    // 构建结果
    string ans = "";
    int o = 0, e = 0;
    for (char ch : s) {
        if (ch % 2 == 0) {
            ans += even[e++];
        } else {
            ans += odd[o++];
        }
    }
    
    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();
        sc.nextLine(); // 消耗换行符
        String s = sc.nextLine();
        
        // 分离奇偶ASCII码字符
        List<Character> odd = new ArrayList<>();
        List<Character> even = new ArrayList<>();
        
        for (char c : s.toCharArray()) {
            if (c % 2 == 0) {
                even.add(c);
            } else {
                odd.add(c);
            }
        }
        
        // 排序
        Collections.sort(odd);
        Collections.sort(even);
        
        // 构建结果
        StringBuilder res = new StringBuilder();
        int oddIdx = 0, evenIdx = 0;
        
        for (char c : s.toCharArray()) {
            if (c % 2 == 0) {
                res.append(even.get(evenIdx++));
            } else {
                res.append(odd.get(oddIdx++));
            }
        }
        
        System.out.println(res.toString());
    }
}

02. 秒杀顺子查找

问题描述

小兰是一名热爱扑克牌的玩家。她定义一个数列是"顺子",当且仅当将该数列排序后,每个元素恰好比前一个元素大 。比如, 排序后是 ,相邻元素之差为 ,所以这是一个"顺子"。而 排序后是 ,包含重复元素,不满足条件,所以不是"顺子"。

现在,小兰拿到了一个长度为 的数列,她想知道这个数列中有多少个长度恰好为 的子序列可以构成"顺子"。由于答案可能很大,请将结果对 取模后输出。

【子序列定义】:从原数列中选取部分元素并保持它们在原数列中的相对顺序组成的新数列,称为原数列的子序列。例如,对于数列 是其子序列,而 不是,因为不满足相对顺序。

输入格式

第一行输入一个正整数 ,代表数列的长度。

第二行输入 个正整数 ,代表数列的元素。

输出格式

一个整数,代表顺子的数量对 取模的值。

样例输入

6
1 2 3 4 5 6

样例输出

2

样例输入

10
1 2 3 4 5 1 2 3 4 5

样例输出

32

数据范围

样例 解释说明
样例1 数列 [1,2,3,4,5,6] 中,可以构成顺子的长度为5的子序列有 [1,2,3,4,5] 和 [2,3,4,5,6],共2个
样例2 数列 [1,2,3,4,5,1,2,3,4,5] 中,有多种方式可以选取元素组成长度为5的顺子,例如可以从数列前半部分选择5个连续数,也可以从后半部分选择,还可以混合选择。计算得到共有32个满足条件的子序列

题解

这道题目要求我们计算长度为5的顺子子序列的数量。首先明确一下,顺子是指排序后相邻元素之差为1的数列,长度为5的顺子实际上就是连续的5个整数。

关键是理解:无论这5个数的初始顺序如何,只要它们组成连续的5个整数,就构成一个顺子。

一个巧妙的解法是使用动态规划,定义状态:

  • dp[1][v]:以值v结尾的长度为1的子序列个数
  • dp[2][v]:以值v结尾的长度为2的子序列个数,且这些子序列满足条件(即v前面的数等于v-1)
  • dp[3][v]:以值v结尾的长度为3的子序列个数,且满足顺子条件
  • dp[4][v]:以值v结尾的长度为4的子序列个数,且满足顺子条件
  • dp[5][v]:以值v结尾的长度为5的子序列个数,且满足顺子条件

状态转移方程为:

  1. dp[1][v] += 1(每次遇到值v,长度为1的子序列增加1个)
  2. dp[2][v] += dp[1][v-1](以v-1结尾的长度为1的子序列后面接上v,构成长度为2的子序列)
  3. dp[3][v] += dp[2][v-1](以v-1结尾的长度为2的子序列后面接上v,构成长度为3的子序列)
  4. dp[4][v] += dp[3][v-1](以此类推)
  5. dp[5][v] += dp[4][v-1](以此类推)

最终答案是所有dp[5][v]的总和。

由于数据范围较大(a_i可达10^9),我们可以使用哈希表而不是数组来存储状态,以节省空间。

例如,对于样例1 [1,2,3,4,5,6]:

  • 处理1:dp[1][1]=1
  • 处理2:dp[1][2]=1, dp[2][2]=dp[1][1]=1
  • 处理3:dp[1][3]=1, dp[2][3]=dp[1][2]=1, dp[3][3]=dp[2][2]=1
  • 处理4:dp[1][4]=1, dp[2][4]=dp[1][3]=1, dp[3][4]=dp[2][3]=1, dp[4][4]=dp[3][3]=1
  • 处理5:dp[1][5]=1, dp[2][5]=dp[1][4]=1, dp[3][5]=dp[2][4]=1, dp[4][5]=dp[3][4]=1, dp[5][5]=dp[4][4]=1
  • 处理6:dp[1][6]=1, dp[2][6]=dp[1][5]=1, dp[3][6]=dp[2][5]=1, dp[4][6]=dp[3][5]=1, dp[5][6]=dp[4][5]=1

最终答案为dp[5][5]+dp[5][6]=1+1=2。

算法的时间复杂度是O(n),空间复杂度也是O(n),可以高效处理给定的数据范围。

参考代码

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

# 读取输入
n = int(input())
arr = list(map(int, input().split()))

# 初始化哈希表
MOD = 10**9 + 7
dp1 = {}  # 长度为1的子序列
dp2 = {}  # 长度为2的顺子子序列
dp3 = {}  # 长度为3的顺子子序列
dp4 = {}  # 长度为4的顺子子序列

# 动态规划过程
ans = 0
for val in arr:
    # 获取前一个值的各长度顺子数量
    c4 = dp4.get(val-1, 0)
    c3 = dp3.get(val-1, 0)
    c2 = dp2.get(val-1, 0)
    c1 = dp1.get(val-1, 0)
    
    # 累加长度为5的顺子数量
    ans = (ans + c4) % MOD
    
    # 更新状态
    dp4[val] = (dp4.get(val, 0) + c3) % MOD
    dp3[val] = (dp3.get(val, 0) + c2) % MOD
    dp2[val] = (dp2.g

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

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

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

全部评论

相关推荐

头像
04-11 11:45
已编辑
河海大学 Java
#牛客AI配图神器#&nbsp;最快的一集,结束当场出结果.说通过了,让好好准备等复试本周的面试海到此结束了,胜率50%下周加油!电话面无手撕&nbsp;八股都是结合项目出的&nbsp;八股(直接开始吟唱哇啦哇啦哇啦):1.Java的集合体系2.什么时候用Set什么时候用List,是怎么判断的呢?3.多线程的情况下,并发安全的数据结构有了解么?4.ConcurrentHashMap的具体原理说说5.发生OOM了怎么办?怎么排查!场景题&amp;amp;闲聊&amp;amp;反问1.高并发的Id系统,说了几个,讲讲雪花算法的原理,优点,why能保持唯一?2.你认为什么样的代码是好代码?3.那你觉得设计模式是用得越多越好么?(我特意在上面没说这个怕挖坑,结果还是问了我这个)4.问了下部门业务:&nbsp;电商领域吧,算是核心部门,商品的从卖家-查看-订单-用户,toB&nbsp;toC都有5.分布式的技术栈怎么样,我看你简历没写,我们日常还是要会用的,有相关了解么?#牛客在线求职答疑中心# #淘天# 项目拷打:1.自我介绍2.你做这些项目的一个立项是什么呢?3.说说这个ai项目叭4.除开这个框架本身,你都是实现了什么功能5.你的邮件发送是怎么是实现的?具体实现这个功能的过程?6.LLM用的什么?7.用的redis是怎么实现会话记忆的功能的?8.token都是有限制的,我想要拿到特别远的一个会话记忆,该怎么办?9.我就是要我就是任性,我就是要拿特别远的,你给来个解决办法叭10.RAG的原理你了解么?11.RAG相比简单的直接文本匹配,有什么优点?12.你这个集合了沙箱的机制是什么?13.这个功能是框架的功能还是你自己写的呀?14.你是怎么实现tool工具随插随用这么一个功能的?15.有研究过这个具体的原理么?16.能支持一个高并发么?(有点尴尬,ai生成的简历,我都没注意还有这部分)17.多模态是怎么实现的呢?18.开发过程中,你是怎么学习的呢?19.你认为ai技术对于现在有什么具体的影响呢?说说你的见解另一个项目(太久没看了,我都忘了这个项目的实现了)1.你是怎么是实现这种多级缓存的?怎么保证的一致性(一周问三次了)2.消息队列的话,都有哪些应用?3.在消息处理过程中,如何保证?
点赞 评论 收藏
分享
评论
1
3
分享

创作者周榜

更多
牛客网
牛客企业服务