🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员
✨ 本系列打算持续跟新阿里云近期的春秋招笔试题汇总~
💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导
👏 感谢大家的订阅➕ 和 喜欢💗
📧 清隆这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 清隆领取,会在飞书进行同步的跟新。
文章目录
- 🎊 01.最短等价子串
- 问题描述
- 输入格式
- 输出格式
- 样例输入
- 样例输出
- 数据范围
- 题解
- 参考代码
- 🎀 02.完美排列对
- 问题描述
- 输入格式
- 输出格式
- 样例输入
- 样例输出
- 数据范围
- 题解
- 参考代码
- 💝 03.K小姐的森林管理
- 题目描述
- 输入格式
- 输出格式
- 样例输入
- 样例输出
- 数据范围
- 题解
- 参考代码
- 写在最后
- 📧 清隆这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 清隆领取,会在飞书进行同步的跟新。
🎊 01.最短等价子串
问题描述
K 小姐接到了一个字符串处理的任务。给定一个长度为
n
n
n 的小写字母字符串
S
S
S,定义字符串的权值为相邻两个字符相同的对数。例如,字符串 aaabbc
的权值为 3。
现在,K 小姐需要在字符串 S S S 中找到一个长度最短的子串,使得该子串的权值恰好等于 k k k。你能帮助她完成这个任务吗?
输入格式
第一行包含两个正整数 n n n 和 k k k,分别表示字符串 S S S 的长度和目标权值。
第二行包含一个长度为 n n n 的字符串 S S S,仅由小写字母组成。
输出格式
输出一个整数,表示满足条件的最短子串的长度。如果不存在这样的子串,则输出 -1。
样例输入
6 2
aaabbc
样例输出
3
数据范围
1 ≤ k < n ≤ 3 × 1 0 5 1 \leq k < n \leq 3 \times 10^5 1≤k<n≤3×105
题解
本题可以使用双指针算法求解。我们用两个指针 i i i 和 j j j 分别表示当前子串的起点和终点。初始时,两个指针都指向字符串的起点。
我们不断地向右移动指针 j j j,直到当前子串的权值大于等于 k k k。此时,我们尝试向右移动指针 i i i,缩小当前子串的长度,直到子串的权值恰好等于 k k k。在这个过程中,我们记录下满足条件的最短子串长度。
重复这个过程,直到指针 j j j 到达字符串的末尾。最终得到的最短子串长度即为答案。
参考代码
- Python
n, k = map(int, input().split())
s = input()
res = 0
ans = n + 1
i = 0
j = 1
while i < n:
if i > 0 and s[i] == s[i - 1]:
res -= 1
while j < n and res < k:
if s[j] == s[j - 1]:
res += 1
j += 1
if res == k:
ans = min(ans, j - i)
i += 1
print(ans if ans != n + 1 else -1)
- Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
String s = sc.next();
int res = 0;
int ans = n + 1;
int i = 0;
int j = 1;
while (i < n) {
if (i > 0 && s.charAt(i) == s.charAt(i - 1)) {
res--;
}
while (j < n && res < k) {
if (s.charAt(j) == s.charAt(j - 1)) {
res++;
}
j++;
}
if (res == k) {
ans = Math.min(ans, j - i);
}
i++;
}
System.out.println(ans == n + 1 ? -1 : ans);
}
}
- Cpp
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
string s;
cin >> s;
int res = 0;
int ans = n + 1;
int i = 0;
int j = 1;
while (i < n) {
if (i > 0 && s[i] == s[i - 1]) {
res--;
}
while (j < n && res < k) {
if (s[j] == s[j - 1]) {
res++;
}
j++;
}
if (res == k) {
ans = min(ans, j - i);
}
i++;
}
cout << (ans == n + 1 ? -1 : ans) << endl;
return 0;
}
🎀 02.完美排列对
问题描述
K 小姐是一位数学爱好者,她正在研究排列的性质。给定一个长度为 n n n 的整数数组 a a a,K 小姐希望构造两个长度为 n n n 的排列 p p p 和 q q q,满足以下条件:
- 对于任意 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n,都有 p i + q i = n + 1 p_i + q_i = n + 1 pi+qi=n+1。
- 对于任意 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n,都有 ∣ p i − q i ∣ = a i |p_i - q_i| = a_i ∣pi−qi∣=ai。
你能帮助 K 小姐找到这样的排列对吗?
注:排列是指一个长度为 n n n 的数组,其中 1 1 1 到 n n n 每个元素恰好出现一次。
输入格式
第一行包含一个正整数 n n n,表示数组 a a a 的长度。
第二行包含 n n n 个整数,表示数组 a a a 的元素,相邻两个整数之间用单个空格隔开。
输出格式
如果不存在满足条件的排列对,则输出一行,包含一个整数 − 1 -1 −1。
否则输出两行,第一行包含 n n n 个整数,表示排列 p p p;第二行包含 n n n 个整数,表示排列 q q q。相邻两个整数之间用单个空格隔开。如果存在多组解,输出任意一组即可。
样例输入
3
2 2 0
样例输出
1 3 2
3 1 2
数据范围
1
≤
n
≤
1
0
5
1 \leq n \leq 10^5
1≤n≤105
0
≤
a
i
≤
n
−
1
0 \leq a_i \leq n-1
0≤ai≤n−1
题解
根据题目条件,可以得到 p i p_i pi 和 q i q_i qi 的关系:
- p i + q i = n + 1 p_i + q_i = n + 1 pi+qi=n+1
- ∣ p i − q i ∣ = a i |p_i - q_i| = a_i ∣pi−qi∣=ai
由条件 1 可知, p i p_i pi 和 q i q_i qi 的和是固定的,都等于 n + 1 n+1 n+1。因此,可以预处理出所有可能的 ( p i , q i ) (p_i, q_i) (pi,qi) 对,并按照 ∣ p i − q i ∣ |p_i - q_i| ∣pi−qi∣ 的值进行分组。
接下来,将输入的数组 a a a 排序,然后与预处理得到的 ∣ p i − q i ∣ |p_i - q_i| ∣pi−qi∣ 的值进行比较。如果两个数组排序后不相同,说明无解,直接输出 − 1 -1 −1。
如果两个数组排序后相同,则按照 a a a 数组的顺序,依次从对应的 ( p i , q i ) (p_i, q_i) (pi,qi) 分组中取出一对值,即可得到满足条件的排列 p p p 和 q q q。
时间复杂度为 O ( n log n ) O(n \log n) O(nlogn),空间复杂度为 O ( n ) O(n) O(n)。
参考代码
- Python
from collections import defaultdict
n = int(input())
a = list(map(int, input().split()))
b = a[:]
c = [abs(i - (n - 1 - i)) for i in range(n)]
mp = defaultdict(list)
for i in range(n):
mp[c[i]].append((i, n - 1 - i))
b.sort()
c.sort()
if b != c:
print(-1)
else:
p = [0] * n
q = [0] * n
for i in range(n):
x, y = mp[a[i]].pop()
p[i] = x + 1
q[i] = y + 1
print(*p)
print(*q)
- Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] a = new int[n];
int[] b = new int[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
b[i] = a[i];
}
int[] c = new int[n];
Map<Integer, List<int[]>> mp = new HashMap<>();
for (int i = 0; i < n; i++) {
c[i] = Math.abs(i - (n - 1 - i));
mp.computeIfAbsent(c[i], k -> new ArrayList<>()).add(new int[]{i, n - 1 - i});
}
Arrays.sort(b);
Arrays.sort(c);
if (!Arrays.equals(b, c)) {
System.out.println(-1);
} else {
int[] p = new int[n];
int[] q = new int[n];
for (int i = 0; i < n; i++) {
int[] pair = mp.get(a[i]).remove(mp.get(a[i]).size() - 1);
p[i] = pair[0] + 1;
q[i] = pair[1] + 1;
}
for (int i = 0; i < n; i++) {
System.out.print(p[i] + " ");
}
System.out.println();
for (int i = 0; i < n; i++) {
System.out.print(q[i] + " ");
}
}
}
}
- Cpp
#include <iostream>
#include <algorithm>
#include <vector>
#include <unordered_map>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
auto b = a;
vector<int> c(n);
unordered_map<int, vector<pair<int, int>>> mp;
for (int i = 0; i < n; i++) {
c[i] = abs(i - (n - 1 - i));
mp[c[i]].push_back({i, n - 1 - i});
}
sort(b.begin(), b.end());
sort(c.begin(), c.end());
if (b != c) {
cout << -1 << endl;
} else {
vector<int> p(n), q(n);
for (int i = 0; i < n; i++) {
auto pair = mp[a[i]].back();
mp[a[i]].pop_back();
p[i] = pair.first + 1;
q[i] = pair.second + 1;
}
for (int i = 0; i < n; i++) {
cout << p[i] << ' ';
}
cout << endl;
for (int i = 0; i < n; i++) {
cout << q[i] << ' ';
}
}
return 0;
}
💝 03.K小姐的森林管理
题目描述
K小姐是一位热爱大自然的女孩,她拥有一片由 n n n 棵树组成的森林。这片森林可以看作一个无向图,树木之间通过 m m m 条小路相连。
为了更好地管理这片森林,K小姐可以进行以下两种操作:
- 在任意一棵现有的树上种植一棵新的小树苗,并用一条小路将其与原来的树相连。
- 移除一棵叶子节点(即度数为1的节点)以及与之相连的小路。
K小姐的目标是使森林中的每个连通块(即互相连通的树木集合)包含相同数量的树木。你的任务是帮助K小姐计算出达成目标状态所需的最少操作次数。
输入格式
第一行包含两个正整数 n n n 和 m m m,分别表示森林中树木的数量和小路的数量。
接下来 m m m 行,每行包含两个正整数 u u u 和 v v v,表示编号为 u u u 和 v v v 的两棵树之间有一条小路相连。
输出格式
输出一个整数,表示K小姐达成目标状态所需的最少操作次数。
样例输入
4 2
1 2
1 3
样例输出
2
数据范围
1 ≤ m < n ≤ 1 0 5 1 \le m < n \le 10^5 1≤m<n≤105
保证给定的无向图是一个森林。
题解
本题可以通过并查集和前缀和的思想来解决。
首先,我们可以使用并查集来处理出森林中每个连通块的大小。这样就将问题转化为了使数组中所有元素相等的最小代价问题。
接下来,我们可以对表示连通块大小的数组进行排序,然后用前缀和计算出前 i i i 个元素的和。这样就可以在 O ( c n t ) O(cnt) O(cnt) 的时间复杂度内(其中 c n t cnt cnt 为连通块的个数)计算出将所有连通块的大小变为 x x x 的代价。
最后,我们遍历所有可能的连通块大小 x x x,就能得到最小的操作次数。
总时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)。
参考代码
- Python
def find(x):
if p[x] != x:
p[x] = find(p[x])
return p[x]
n, m = map(int, input().split())
p = list(range(n + 1))
num = [1] * (n + 1)
for _ in range(m):
a, b = map(int, input().split())
px, py = find(a), find(b)
if px != py:
p[px] = py
num[py] += num[px]
c = [num[i] for i in range(1, n + 1) if p[i] == i]
cnt = len(c)
c.sort()
sum = [0] * cnt
for i in range(cnt):
sum[i] = c[i]
if i > 0:
sum[i] += sum[i - 1]
res = n
for i in range(cnt):
res = min(res, (i + 1) * c[i] - sum[i] + sum[cnt - 1] - sum[i] - (cnt - i - 1) * c[i])
print(res)
- Java
import java.util.*;
public class Main {
static int[] p, num, c, sum;
static int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
p = new int[n + 1];
num = new int[n + 1];
for (int i = 1; i <= n; i++) {
p[i] = i;
num[i] = 1;
}
for (int i = 0; i < m; i++) {
int a = sc.nextInt(), b = sc.nextInt();
int px = find(a), py = find(b);
if (px != py) {
p[px] = py;
num[py] += num[px];
}
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (p[i] == i) cnt++;
}
c = new int[cnt];
sum = new int[cnt];
cnt = 0;
for (int i = 1; i <= n; i++) {
if (p[i] == i) c[cnt++] = num[i];
}
Arrays.sort(c);
for (int i = 0; i < cnt; i++) {
sum[i] = c[i];
if (i > 0) sum[i] += sum[i - 1];
}
int res = n;
for (int i = 0; i < cnt; i++) {
res = Math.min(res, (i + 1) * c[i] - sum[i] + sum[cnt - 1] - sum[i] - (cnt - i - 1) * c[i]);
}
System.out.println(res);
}
}
- Cpp
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 7;
int n, m, p[N], num[N], c[N], sum[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
p[i] = i;
num[i] = 1;
}
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
int px = find(a), py = find(b);
if (px != py) {
p[px] = py;
num[py] += num[px];
}
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (p[i] == i) c[cnt++] = num[i];
}
sort(c, c + cnt);
for (int i = 0; i < cnt; i++) {
sum[i] = c[i];
if (i > 0) sum[i] += sum[i - 1];
}
int res = n;
for (int i = 0; i < cnt; i++) {
res = min(res, (i + 1) * c[i] - sum[i] + sum[cnt - 1] - sum[i] - (cnt - i - 1) * c[i]);
}
cout << res << endl;
return 0;
}
写在最后
📧 清隆这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 清隆领取,会在飞书进行同步的跟新。