🍭 大家好这里是KK爱Coding ,一枚热爱算法的程序员
✨ 本系列打算持续跟新蚂蚁近期的春秋招笔试题汇总~
💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导
👏 感谢大家的订阅➕ 和 喜欢💗
📧 KK这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 KK领取,会在飞书进行同步的跟新。
文章目录
01.卢小姐的字母重排
问题描述
卢小姐在研究一种古老的密码学方法,这种方法通过分析字符串的循环节来解密信息。例如,字符串 “abcabc” 的循环节是 “abc”,而 “aba” 的循环节是 “aba” 本身。卢小姐现在手头有一个字符串 s s s,她想通过重新排列这个字符串,使得新字符串的循环节长度最短。她需要你的帮助来找出这个最短循环节,并输出重排后的字符串。如果有多种重排方式,她希望得到字典序最小的一种。
输入格式
第一行输入一个长度不超过 1 0 5 10^5 105 的字符串 s s s,该字符串只包含小写字母。
输出格式
输出一行,表示重排后的字符串。如果存在多种重排方案,输出字典序最小的方案。
样例输入
cabbac
样例输出
abcabc
解释
在这个例子中,“abc” 是长度最短的循环节之一,且可以证明 “abcabc” 是字典序最小的重排方案。
数据范围
- 字符串长度不超过 1 0 5 10^5 105。
- 字符串只包含小写字母。
题解
要解决这个问题,我们首先需要计算字符串中每个字符出现的次数,并找到这些次数的最大公约数(GCD)。这个 GCD 将决定循环节的长度。接着,我们构造一个循环节,使得每个字符在循环节中出现的次数等于它的出现次数除以 GCD。最后,我们重复这个循环节 GCD 次,以构造出最终的字符串。如果有多种构造方式,我们选择字典序最小的方式,即按照字母顺序添加字符。
参考代码
- Cpp
#include<bits/stdc++.h>
using namespace std;
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
string s;
cin >> s;
map<char, int> freq;
for (char ch : s) freq[ch]++;
int g = 0;
for (auto it : freq) g = gcd(g, it.second);
string res = "";
for (int i = 0; i < 26; i++) {
char ch = 'a' + i;
if (freq.count(ch)) res += string(freq[ch] / g, ch);
}
string final_res = res;
for (int i = 1; i < g; i++) final_res += res;
cout << final_res << "\n";
return 0;
}
- Python
from collections import Counter
from math import gcd
from functools import reduce
def find_gcd(list):
x = reduce(gcd, list)
return x
s = input()
freq = Counter(s)
g = find_gcd(freq.values())
res = ''.join([ch * (freq[ch] // g) for ch in sorted(freq)])
final_res = res * g
print(final_res)
- Java
import java.util.*;
public class Main {
public static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
Map<Character, Integer> freq = new HashMap<>();
for (char ch : s.toCharArray()) freq.put(ch, freq.getOrDefault(ch, 0) + 1);
int g = freq.values().stream().reduce(0, Main::gcd);
StringBuilder res = new StringBuilder();
freq.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry -> {
for (int i = 0; i < entry.getValue() / g; i++) res.append(entry.getKey());
});
String finalRes = res.toString().repeat(g);
System.out.println(finalRes);
}
}
02.LYA的宠物摆放问题
问题描述
LYA有一个 3 × 3 3 \times 3 3×3 的矩阵花园,她准备在花园里放置 x x x 只小狗和 y y y 只小猫。为了避免小动物们打架,要求小猫之间、小狗之间不能放在相邻的位置(上下左右都算相邻)。LYA想知道,一共有多少种不同的放置方案呢?
输入格式
输入包含两个整数
x
x
x 和
y
y
y,用空格隔开,分别表示小狗和小猫的数量。
0
≤
x
+
y
≤
9
0 \leq x+y \leq 9
0≤x+y≤9
输出格式
输出一个整数,表示放置方案的总数。
样例输入
1 0
样例输出
9
数据范围
0 ≤ x + y ≤ 9 0 \leq x+y \leq 9 0≤x+y≤9
题解
这是一道典型的搜索题,我们可以用DFS来解决。用一个一维数组 g r i d grid grid 来表示花园的状态,0表示空位,1表示放小狗,2表示放小猫。
在DFS函数中,我们用变量 p o s pos pos 表示当前搜索到的位置, d o g dog dog 和 c a t cat cat 分别表示已经放置的小狗和小猫的数量。当 p o s pos pos 等于9时,说明已经搜索完整个花园,此时判断放置的小狗和小猫数量是否符合要求,并且当前放置方案是否合法,如果合法则将答案加1。
在搜索过程中,对于每个位置,我们有三种选择:放小狗、放小猫或者不放。如果选择放小狗,则 d o g dog dog 加1;如果选择放小猫,则 c a t cat cat 加1。然后递归搜索下一个位置。
为了判断当前放置方案是否合法,我们可以写一个 i s _ v a l i d is\_valid is_valid 函数,枚举每个位置,如果该位置放了小动物,则判断它的上下左右四个相邻位置是否放了相同的小动物,如果是则说明不合法。
时间复杂度 O ( 3 9 ) O(3^9) O(39),空间复杂度 O ( 9 ) O(9) O(9)。
参考代码
- Python
def dfs(pos, dog, cat, grid):
if pos == 9:
return int(dog == n1 and cat == n2 and is_valid(grid))
res = 0
for i in range(3):
grid[pos] = i
if i == 0:
res += dfs(pos + 1, dog, cat, grid)
elif i == 1:
res += dfs(pos + 1, dog + 1, cat, grid)
else:
res += dfs(pos + 1, dog, cat + 1, grid)
return res
def is_valid(grid):
for i in range(9):
if grid[i] == 0:
continue
r, c = i // 3, i % 3
for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
nr, nc = r + dr, c + dc
if 0 <= nr < 3 and 0 <= nc < 3 and grid[nr * 3 + nc] == grid[i]:
return False
return True
n1, n2 = map(int, input().split())
grid = [0] * 9
print(dfs(0, 0, 0, grid))
- Java
import java.util.Scanner;
public class Main {
static int n1, n2;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n1 = sc.nextInt();
n2 = sc.nextInt();
int[] grid = new int[9];
System.out.println(dfs(0, 0, 0, grid));
}
static int dfs(int pos, int dog, int cat, int[] grid) {
if (pos == 9) {
return (dog == n1 && cat == n2 && isValid(grid)) ? 1 : 0;
}
int res = 0;
for (int i = 0; i < 3; i++) {
grid[pos] = i;
if (i == 0) {
res += dfs(pos + 1, dog, cat, grid);
} else if (i == 1) {
res += dfs(pos + 1, dog + 1, cat, grid);
} else {
res += dfs(pos + 1, dog, cat + 1, grid);
}
}
return res;
}
static boolean isValid(int[] grid) {
for (int i = 0; i < 9; i++) {
if (grid[i] == 0) {
continue;
}
int r = i / 3, c = i % 3;
int[][] dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
for (int[] dir : dirs) {
int nr = r + dir[0], nc = c + dir[1];
if (nr >= 0 && nr < 3 && nc >= 0 && nc < 3 && grid[nr * 3 + nc] == grid[i]) {
return false;
}
}
}
return true;
}
}
- Cpp
#include <iostream>
using namespace std;
int n1, n2;
int dfs(int pos, int dog, int cat, int grid[]) {
if (pos == 9) {
return (dog == n1 && cat == n2 && isValid(grid)) ? 1 : 0;
}
int res = 0;
for (int i = 0; i < 3; i++) {
grid[pos] = i;
if (i == 0) {
res += dfs(pos + 1, dog, cat, grid);
} else if (i == 1) {
res += dfs(pos + 1, dog + 1, cat, grid);
} else {
res += dfs(pos + 1, dog, cat + 1, grid);
}
}
return res;
}
bool isValid(int grid[]) {
for (int i = 0; i < 9; i++) {
if (grid[i] == 0) {
continue;
}
int r = i / 3, c = i % 3;
int dirs[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
for (auto& dir : dirs) {
int nr = r + dir[0], nc = c + dir[1];
if (nr >= 0 && nr < 3 && nc >= 0 && nc < 3 && grid[nr * 3 + nc] == grid[i]) {
return false;
}
}
}
return true;
}
int main() {
cin >> n1 >> n2;
int grid[9];
cout << dfs(0, 0, 0, grid) << endl;
return 0;
}
03.LYA的森林探险
问题描述
LYA在一个由 n n n 个点 m m m 条边组成的森林中探险。这个森林中任意两个点要么不连通,要么有且仅有一条路径。LYA有 q q q 次询问,每次询问分为两类:
-
输入一个点 p p p,请输出 p p p 所在的连通块中任意两点的最大距离。
-
输入两个点 x x x 和 y y y,如果 x x x 和 y y y 不连通,将 x x x 和 y y y 所在的连通块连通,需要保证连通后这个连通块的最长路径最小。如果 x x x 和 y y y 已经连通,则不做任何操作。
请你帮助LYA解决这些问题。
输入格式
第一行三个整数 n , m , q n,m,q n,m,q,表示点数、边数和询问数。
接下来 m m m 行,每行两个整数 u , v u,v u,v,表示 u u u 和 v v v 之间有一条边,保证输入是一个森林。
接下来 q q q 行,每行表示一个询问:
- 如果第一个数是 1 1 1,则后面一个数是 p p p,表示询问 p p p 所在的连通块中任意两点的最大距离。
- 如果第一个数是 2 2 2,则后面两个数是 x , y x,y x,y,表示将 x x x 和 y y y 所在的连通块连通,需要保证连通后这个连通块的最长路径最小。如果 x x x 和 y y y 已经连通,则不做任何操作。
输出格式
对于每个第一类询问,输出一行一个整数,表示 p p p 所在的连通块中任意两点的最大距离。
样例输入
5 2 5
1 2
3 4
1 1
1 3
1 5
2 1 3
1 1
样例输出
1
1
0
3
数据范围
1
≤
m
<
n
≤
1
0
5
1 \leq m < n \leq 10^5
1≤m<n≤105
1
≤
q
≤
1
0
5
1 \leq q \leq 10^5
1≤q≤105
1
≤
u
,
v
,
p
,
x
,
y
≤
n
1 \leq u,v,p,x,y \leq n
1≤u,v,p,x,y≤n
题解
这道题可以用并查集和树形DP来解决。
首先,我们用并查集来维护连通块。对于每个连通块,我们用一个变量 m a x D i a maxDia maxDia 来记录该连通块中任意两点的最大距离。
然后,我们遍历每个连通块,对其进行树形DP,求出该连通块中任意两点的最大距离,更新 m a x D i a maxDia maxDia。
树形DP的过程如下:对于每个点 u u u,我们找到它的两个最深子树的深度 d 1 d_1 d1 和 d 2 d_2 d2,则经过 u u u 的最长路径为 d 1 + d 2 d_1+d_2 d1+d2,我们用它来更新答案。
对于第一类询问,我们直接输出该点所在连通块的 m a x D i a maxDia maxDia 即可。
对于第二类询问,我们先判断两个点是否已经连通,如果不连通,我们就将它们所在的连通块合并。合并时,我们需要更新新的连通块的 m a x D i a maxDia maxDia,新的 m a x D i a maxDia maxDia 为两个连通块的 m a x D i a maxDia maxDia 的较大值,或者是它们的 m a x D i a maxDia maxDia 的一半上取整后加上 1 1 1。
时间复杂度 O ( n + m + q log n ) O(n+m+q\log n) O(n+m+qlogn),空间复杂度 O ( n + m ) O(n+m) O(n+m)。
参考代码
- Cpp
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
vector<int> maxDia(MAXN, 0);
vector<int> par(MAXN);
vector<int> sz(MAXN, 1);
void initDSU(int n) {
iota(par.begin(), par.end(), 0);
}
int find(int x) {
if (x != par[x]) par[x] = find(par[x]);
return par[x];
}
bool join(int a, int b) {
if (maxDia[a] < maxDia[b]) swap(a, b);
a = find(a);
b = find(b);
if (a == b) return false;
par[b] = a;
sz[a] += sz[b];
maxDia[a] = max(maxDia[a], (maxDia[a] + 1) / 2 + (maxDia[b] + 1) / 2 + 1);
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n, m, q;
cin >> n >> m >> q;
initDSU(n);
vector<vector<int>> gr(n + 1);
for (int i = 0; i < m; ++i) {
int u, v;
cin >> u >> v;
gr[u].push_back(v);
gr[v].push_back(u);
join(find(u), find(v));
}
set<int> uniq;
for (int i = 1; i <= n; ++i) {
uniq.insert(find(i));
}
function<int(int, int)> dfs = [&](int u, int p) -> int {
int d1 = 0, d2 = 0;
for (int v : gr[u]) {
if (v != p) {
int d = dfs(v, u) + 1;
if (d >= d1) {
d2 = d1;
d1 = d;
} else if (d > d2) {
d2 = d;
}
}
}
maxDia[find(u)] = max(maxDia[find(u)], d1 + d2);
return d1;
};
for (int rt : uniq) {
dfs(rt, -1);
}
for (int i = 0; i < q; ++i) {
int op;
cin >> op;
if (op == 1) {
int x;
cin >> x;
cout << maxDia[find(x)] << "\n";
} else {
int a, b;
cin >> a >> b;
join(find(a), find(b));
}
}
return 0;
}
- Java
import java.util.*;
import java.io.*;
public class GraphDiam {
static final int MAXN = 100010;
static int[] maxDia = new int[MAXN];
static int[] par = new int[MAXN];
static int[] sz = new int[MAXN];
static List<List<Integer>> gr = new ArrayList<>();
static {
for (int i = 0; i < MAXN; i++) {
par[i] = i;
sz[i] = 1;
gr.add(new ArrayList<>());
}
}
static int find(int x) {
if (x != par[x]) par[x] = find(par[x]);
return par[x];
}
static boolean join(int a, int b) {
if (maxDia[a] < maxDia[b]) {
int tmp = a;
a = b;
b = tmp;
}
a = find(a);
b = find(b);
if (a == b) return false;
par[b] = a;
sz[a] += sz[b];
maxDia[a] = Math.max(maxDia[a], (maxDia[a] + 1) / 2 + (maxDia[b] + 1) / 2 + 1);
return true;
}
static int dfs(int u, int p) {
int d1 = 0, d2 = 0;
for (int v : gr.get(u)) {
if (v != p) {
int d = dfs(v, u) + 1;
if (d >= d1) {
d2 = d1;
d1 = d;
} else if (d > d2) {
d2 = d;
}
}
}
maxDia[find(u)] = Math.max(maxDia[find(u)], d1 + d2);
return d1;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int m = Integer.parseInt(st.nextToken());
int q = Integer.parseInt(st.nextToken());
for (int i = 0; i < m; i++) {
st = new StringTokenizer(br.readLine());
int u = Integer.parseInt(st.nextToken());
int v = Integer.parseInt(st.nextToken());
gr.get(u).add(v);
gr.get(v).add(u);
join(find(u), find(v));
}
Set<Integer> uniq = new HashSet<>();
for (int i = 1; i <= n; i++) {
uniq.add(find(i));
}
for (int rt : uniq) {
dfs(rt, -1);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
for (int i = 0; i < q; i++) {
st = new StringTokenizer(br.readLine());
int op = Integer.parseInt(st.nextToken());
if (op == 1) {
int x = Integer.parseInt(st.nextToken());
bw.write(maxDia[find(x)] + "\n");
} else {
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
join(find(a), find(b));
}
}
bw.flush();
}
}
- Python
from collections import defaultdict
MAXN = 100010
maxDia = [0] * MAXN
par = list(range(MAXN))
sz = [1] * MAXN
gr = defaultdict(list)
def find(x):
if par[x] != x:
par[x] = find(par[x])
return par[x]
def join(a, b):
if maxDia[a] < maxDia[b]:
a, b = b, a
a = find(a)
b = find(b)
if a == b:
return False
par[b] = a
sz[a] += sz[b]
maxDia[a] = max(maxDia[a], (maxDia[a] + 1) // 2 + (maxDia[b] + 1) // 2 + 1)
return True
def dfs(u, p):
d1, d2 = 0, 0
for v in gr[u]:
if v != p:
d = dfs(v, u) + 1
if d >= d1:
d2, d1 = d1, d
elif d > d2:
d2 = d
maxDia[find(u)] = max(maxDia[find(u)], d1 + d2)
return d1
n, m, q = map(int, input().split())
for _ in range(m):
u, v = map(int, input().split())
gr[u].append(v)
gr[v].append(u)
join(find(u), find(v))
uniq = set(find(i) for i in range(1, n + 1))
for rt in uniq:
dfs(rt, -1)
for _ in range(q):
qry = list(map(int, input().split()))
op = qry[0]
if op == 1:
x = qry[1]
print(maxDia[find(x)])
else:
a, b = qry[1], qry[2]
join(find(a), find(b))
写在最后
📧 KK这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 KK领取,会在飞书进行同步的跟新。