2024年4月24日华为春招实习试题【三题】-题目+题解+在线评测,2024.4.24,华为机试
- 🏩题目一描述:
- 输入格式
- 输出格式
- 样例1
- 样例2
- 样例3
- 数据范围
- 解题思路一:dfs
- 解题思路二:直接二分查找哇!
- 解题思路三:c++, java
- 💒题目二描述:球员能力评估
- 样例一
- 样例二
- 解题思路一:元组排序
- 解题思路二:排序
- 解题思路三:c++, java
- 🏨题目三描述:
- 样例一
- 样例二
- 解题思路一:
- 解题思路二:c++
- 解题思路三:java
🏩题目一描述:
LYA 是一名计算机专业的学生,最近她正在学习数据结构中的二叉搜索树。二叉搜索树是一种常用的数据结构,它可以实现快速的查找和插入操作。
现在,LYA 有一个由 2 n − 1 2^n-1 2n−1个不同的正整数组成的数列( 1 ≤ n ≤ 10 1 \leq n \leq 10 1≤n≤10,且 n 为整数)。她想用这些数构建一棵平衡的满二叉搜索树。
二叉搜索树满足以下性质:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树也必须是二叉搜索树。
例如,对于数列 [1,2,3,4,5,6,7],可以构建出如下图所示的满二叉搜索树:
4
/ \
2 6
/ \ / \
1 3 5 7
现在,给定一个待查找的数,请你帮助 LYA 计算查找该数的路径和结果。
输入格式
第一行包含若干个用空格分隔的正整数,表示给定的数列。
第二行包含一个正整数,表示待查找的数。
输出格式
输出查找的路径和结果。
路径从根节点开始,用 S
表示。查找左子树用 L
表示,查找右子树用 R
表示。查找到结果用 Y
表示,未找到结果用 N
表示。
样例1
输入
2 1 3 7 5 6 4
6
输出
SRY
解释:
从根节点开始,所以路径的第一部分为 S
。待查找数为 6
,大于根节点 4
,所以要查找右子树,路径增加 R
,正好找到,因此最后增加 Y
。最终输出 SRY
。
样例2
输入
4 2 1 3 6 5 7
5
输出
SRLY
解释:
从根节点开始,先查找右子树,再查找左子树,最终找到结果 5
,因此输出 SRLY
。
样例3
输入
1 2 3 4 5 6 7
8
输出
SRRN
解释:
从根节点开始查找,标记 S
。待查找数 8 大于根节点 4
,所以查找右子树,标记 R
。继续查找右子树,标记 R
。8 比右子树节点 7
还大,但已经到达叶子节点,没有找到,因此最后标记 N
。
数据范围
- 1 ≤ n ≤ 10 1 \leq n \leq 10 1≤n≤10
- 给定的数列中的数互不相同
OJ链接:
https://codefun2000.com/p/P1830
解题思路一:dfs
本题考查二叉搜索树的构建和查找操作。
首先,我们需要根据给定的数列构建一棵平衡的满二叉搜索树。可以按照如下步骤进行:
- 将数列按照从小到大的顺序排序。
- 递归地构建左右子树:
- 如果当前区间为空,则返回空树。
- 取区间的中点作为根节点。
- 递归地构建左子树和右子树。
构建完二叉搜索树后,我们再进行查找操作。从根节点开始,比较当前节点的值与待查找的数:
- 如果相等,则查找成功,返回。
- 如果待查找的数小于当前节点的值,则进入左子树查找。
- 如果待查找的数大于当前节点的值,则进入右子树查找。
在查找的过程中,我们需要记录查找的路径。当查找到目标数时,输出查找路径以及查找结果。
arr = list(map(int, input().split()))
arr.sort()
n = len(arr)
target = int(input())
def dfs(arr, l, r, target):
if l > r:
res.append('N')
return
mid = (l + r) >> 1
if arr[mid] == target:
res.append('Y')
return
elif arr[mid] > target:
if mid - 1 >= l:
res.append('L')
dfs(arr, l, mid-1, target)
else:
if mid + 1 <= r:
res.append('R')
dfs(arr, mid+1, r, target)
res = ['S']
dfs(arr, 0, n-1, target)
print("".join(res))
时间复杂度:O(nlogn)
空间复杂度:O(n)
解题思路二:直接二分查找哇!
nums = list(map(int, input().split()))
target = int(input())
nums.sort()
l, r = 0, len(nums) - 1
res = ['S']
while l <= r:
mid = (l+r) // 2
if nums[mid] == target:
res.append('Y')
break
elif nums[mid] > target:
r = mid - 1
res.append('L')
else:
l = mid + 1
res.append('R')
if res[-1] != 'Y':
res.append('N')
print(''.join(res))
时间复杂度:O(nlogn)
空间复杂度:O(1)
解题思路三:c++, java
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e4+10;
int a[maxn],n;
int main(){
std::ios::sync_with_stdio(false);
while(cin>>a[n]){
n++;
}
n--;
int target=a[n];
string s="S";
int p=(1+n)/2;
int l=1,r=n;
while(p!=target){
if(target<p){
r=p-1;
p=(l+p-1)/2;
s+="L";
}else{
l=p+1;
p=(p+1+r)/2;
s+='R';
}
}
s+="Y";
cout<<s;
return 0;
}
# java
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String[] strings = in.nextLine().split("\\s+");
int[] data = new int[strings.length];
int target = in.nextInt();
for (int i = 0; i < strings.length; i++) {
data[i] = Integer.parseInt(strings[i]);
data[i] = i + 1;
}
System.out.print(solutin(data, target));
}
private static String solutin(int[] data, int target) {
StringBuilder sb = new StringBuilder();
if (data != null) {
sb.append("S");
}
int left = 0, right = data.length - 1;
int mid = (left + right) / 2;
while (left <= right) {
if (data[mid] == target) {
sb.append("Y");
return sb.toString();
}else if (data[mid] > target) {
sb.append("L");
right = mid - 1;
mid = (left + right) / 2;
}else {
sb.append("R");
left = mid + 1;
mid = (left + right) / 2;
}
}
String temp = sb.toString();
return temp.substring(0, temp.length() - 1) + "N";
}
}
时间复杂度:O(n)
空间复杂度:O(1)
💒题目二描述:球员能力评估
K教练正在对足球队的 n 名球员进行射门能力评估。评估共进行 m 次训练,每次训练时,若球员射门得分则记为1,否则记为0。现在K教练需要根据以下规则对球员进行排名:
- 进球总数较多的球员排名靠前。
- 如果进球总数相同,则最长连续进球次数较多的球员排名靠前。
- 如果最长连续进球次数也相同,则第一次未进球的训练序号较大的球员排名靠前。如果第一次未进球的训练序号也相同,则比较第二次、第三次……直到比较出结果。
- 如果按照前三条规则仍然无法区分排名,则编号较小的球员排名靠前。
请你帮助K教练生成一个球员排名。
输入格式
第一行包含两个正整数 n 和 m , 表示参与评估的球员数量和训练次数,球员编号从1到 n 。
第二行包含 n 个空格分隔的长度为 m 的字符串,第 i 个字符串表示编号为 i 的球员在这 m 次训练中的进球记录。
输出格式
输出一行,包含 n 个空格分隔的正整数,表示球员编号按照射门能力从高到低排列的结果。
数据范围
1
≤
n
≤
1000
1 \le n \le 1000
1≤n≤1000
1
≤
m
≤
1000
1 \le m \le 1000
1≤m≤1000
样例一
输入
4 5
11100 00111 10111 01111
输出
4 3 1 2
解释
4个队员,射门训练5次,队员3和4进球数均为4个,比队员1H和2的3个更多,队员3连续进球故最多一次为3个,而队员4最大为4。因此队员4射门能力强于队员3,另外队员2比队员1先丢球,因此队员1射门能力强于队员2,顺序为4 3 1 2。
样例二
输入
2 10
1011100111 1011101101
输出
2 1
解释
2个队员,射门训练10次,两个队员的进球总数均为7个,连续进球最多的均为3个,且第前两次丢球顺序均为第2次和第6次训练射门,而队员2第三次丢球为第9次训练,队员1为第7次训练,因此队员2的射门能力强于队员1,顺序为2 1
OJ链接:
https://codefun2000.com/p/P1831
解题思路一:元组排序
本题的关键是根据题目描述的规则对球员进行排序。我们可以按照以下思路解决:
- 统计每个球员的进球总数和最长连续进球次数。
- 记录每个球员第一次、第二次、第三次……未进球的训练序号。
- 按照题目规则,以进球总数为第一关键字,最长连续进球次数为第二关键字,未进球训练序号为第三关键字,球员编号为第四关键字,对球员进行降序排序。
- 输出排序后的球员编号。
在实现时,我们可以使用一个长度为 n 的数组,数组的每个元素是一个四元组(cnt, maxCnt, rec, id),分别表示球员的进球总数、最长连续进球次数、未进球训练序号和球员编号。然后按照题目规则对这个数组进行排序即可。
n, m = map(int, input().split())
rec = list(map(str, input().split()))
player = []
for i, r in enumerate(rec):
cnt = r.count('1')
maxCnt = max(map(len, r.split('0')))
missrec = ''.join(['0' if c == '1' else '1' for c in r])
player.append((-cnt, -maxCnt, missrec, i+1))
player.sort()
print(*[p[3] for p in player])
时间复杂度:O(nlogn)
空间复杂度:O(n)
解题思路二:排序
n, m = map(int, input().split())
lst = input().split()
one = [0] * n
con = [0] * n
zero = [[] for _ in range(n)]
for i in range(n):
s = lst[i]
for j in range(m):
if s[j] == '1':
one[i] += 1
else:
zero[i].append(j)
j = 0
while j < m:
if s[j] == '0':
j += 1
continue
k = j + 1
while k < m and s[k] == '1':
k += 1
con[i] = max(con[i], k - j)
j = k
a = list(range(n))
a.sort(key=lambda x: (-one[x], -con[x], [-z for z in zero[x]], x))
print(*[x + 1 for x in a])
时间复杂度:O(nlogn)
空间复杂度:O(n)
解题思路三:c++, java
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
String[] records = new String[n];
for (int i = 0; i < n; i++) {
records[i] = sc.next();
}
Integer[][] players = new Integer[n][4];
for (int i = 0; i < n; i++) {
String record = records[i];
int cnt = record.replaceAll("0", "").length();
int maxCnt = Arrays.stream(record.split("0")).mapToInt(String::length).max().getAsInt();
String missRecord = record.replaceAll("1", "2").replaceAll("0", "1").replaceAll("2", "0");
players[i] = new Integer[]{-cnt, -maxCnt, Integer.parseInt(missRecord), i + 1};
}
Arrays.sort(players, Comparator.<Integer[]>comparingInt(p -> p[0])
.thenComparingInt(p -> p[1])
.thenComparingInt(p -> p[2])
.thenComparingInt(p -> p[3]));
for (Integer[] player : players) {
System.out.print(player[3] + " ");
}
}
}
# c++
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
bool cmp(vector<int> a, vector<int> b) {
if (a[0] != b[0]) return a[0] > b[0];
if (a[1] != b[1]) return a[1] > b[1];
if (a[2] != b[2]) return a[2] < b[2];
return a[3] < b[3];
}
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> players(n, vector<int>(4));
for (int i = 0; i < n; i++) {
string record;
cin >> record;
int cnt = 0, maxCnt = 0, curCnt = 0;
for (char c : record) {
if (c == '1') {
cnt++;
curCnt++;
maxCnt = max(maxCnt, curCnt);
} else {
curCnt = 0;
}
}
string missRecord = record;
replace(missRecord.begin(), missRecord.end(), '1', '2');
replace(missRecord.begin(), missRecord.end(), '0', '1');
replace(missRecord.begin(), missRecord.end(), '2', '0');
players[i] = {cnt, maxCnt, stoi(missRecord), i + 1};
}
sort(players.begin(), players.end(), cmp);
for (auto player : players) {
cout << player[3] << " ";
}
return 0;
}
时间复杂度:O(nlogn)
空间复杂度:O(n)
🏨题目三描述:
K小姐是一名软件工程师,她正在对公司的 n 个微服务进行调用情况分析。这些微服务使用 0 到 n − 1 的整数进行编号。
K小姐得到了一个长度为 n 的数组 edges,其中 edges[i] 表示存在一个从微服务 i 到微服务 edges[i] 的调用关系。
如果多个微服务形成了一个环,我们称之为一个微服务群组。对于一个微服务群组,我们定义:
- L 表示该群组内所有微服务的数量。
- V 表示能够调用到该群组内微服务的微服务数量。
- 该群组的内聚值 H=L−V。
已知给定的调用关系数据中至少存在一个微服务群组,请你计算所有群组的内聚值,并按照以下规则对它们进行排序:
- 按照内聚值 H 从大到小排序。
- 如果内聚值相同,则按照群组内最大编号从小到大排序。
最后,请输出排序后的第一个微服务群组,要求从群组内编号最小的微服务开始,按照调用关系的顺序输出其中所有微服务的编号。
输入格式
第一行包含一个正整数 n ,表示微服务的数量。
第二行包含 n 个整数,表示数组 edges,相邻整数之间用空格分隔。其中 edges[i] 表示存在一个从微服务 i 到微服务 edges[i] 的调用关系。
输出格式
输出一行整数,表示内聚值最大的微服务群组,其中微服务编号按照调用关系顺序输出,起始编号为群组内最小编号。相邻整数之间用空格分隔。
数据范围
- 2 ≤ n ≤ 1 0 5 2 \le n \le 10^5 2≤n≤105
- 0 ≤ e d g e s [ i ] ≤ n − 1 0 \le edges[i] \le n-1 0≤edges[i]≤n−1
- e d g e s [ i ] ≠ i edges[i] \neq i edges[i]=i
样例一
输入
4
3 3 0 2
输出
0 3 2
解释
0,3,2组成了微服务群组 (环)a,他的L值为3,对于a来说,只有编号为1的1个微服务可以访问到a,因此a的为1答案输出微服务群组为0 3 2
样例二
输入
12
2 6 10 1 6 0 3 0 5 4 5 8
输出
0 2 10 5
解释
1,6,3组成了微服务群组(环) a1,L1值为3,编号为4、9的2个微服务可以访问到a1,因此√1值为2,H1为L1V1 =1;
0,2,10,5组成了微服务群组 (环) a2,L2值为4,编号为7、8、11的3个微服务可以访问到2,因此v2值为3,H2为L2-V2=1;
先对比H值,H1=H2,H值相等;
再对比环中序号最大值,a1中最大数为6。a2中最大数为10,a2排前面,因此输出答案为:0 2 10 5
OJ链接:
https://codefun2000.com/p/P1832
解题思路一:
from collections import defaultdict
def dfs(u):
global cnt
cnt += 1
group.append(u)
visit[u] = True
if visit[edges[u]]:
if edges[u] in group:
idx = group.index(edges[u])
size = len(group) - idx
connect = sum(indegree[v] for v in group[idx:])
groups.append((-size + connect, -max(group), group[idx:]))
else:
dfs(edges[u])
n = int(input())
edges = list(map(int, input().split()))
adj = defaultdict(list)
indegree = [0] * n
for i, v in enumerate(edges):
adj[v].append(i)
indegree[i] += 1
groups = []
visit = [False] * n
for i in range(n):
if not visit[i]:
cnt = 0
group = []
dfs(i)
groups.sort()
ans = groups[0][2]
start = min(ans)
idx = ans.index(start)
print(*(ans[idx:] + ans[:idx]))
时间复杂度:O(n)
空间复杂度:O(n)
解题思路二:c++
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 1;
int adj[N];
int dfn[N], low[N], dfncnt = 0, scc[N], siz[N], scccnt = 0;
bool in_stk[N];
stack<int> s;
int maxn[N], minn[N]; // 记录环上(一个scc就是一个环)最大和最小编号节点
void dfs(int u) {
dfn[u] = low[u] = ++dfncnt;
s.push(u);
in_stk[u] = true;
int v = adj[u];
if (!dfn[v]) {
dfs(v);
low[u] = min(low[u], low[v]);
} else if (in_stk[v]) {
low[u] = min(low[u], dfn[v]);
}
// 事实上,一个scc里的任何一个点都可以作为这个scc的dfs树根
// 因此在一个scc内部时,用任何一种遍历顺序都可以
if (dfn[u] == low[u]) {
int y;
do {
y = s.top();
scc[y] = scccnt;
++siz[scccnt];
s.pop();
in_stk[y] = false;
maxn[scccnt] = max(maxn[scccnt], y);
minn[scccnt] = min(minn[scccnt], y);
} while (y != u); // 由于u一定在栈里,最后一次pop栈顶一定有y == u
++scccnt;
}
// dfs结束时从不pop堆栈
// 这样当dfs递归调用回到dfs树的根时,堆栈中会保留这个scc的所有节点
}
int main() {
int n;
cin >> n;
// int adj[n];
for (int i = 0; i < n; ++i) {
cin >> adj[i];
}
// tarjan scc(求强连通分量)
// int dfn[n], low[n], dfncnt = 0, scc[n], siz[n], scccnt = 0;
memset(dfn, 0, sizeof dfn);
memset(siz, 0, sizeof siz);
// bool in_stk[n];
memset(in_stk, 0, sizeof in_stk);
// stack<int> s;
// int maxn[n], minn[n]; // 记录环上(一个scc就是一个环)最大和最小编号节点
memset(maxn, 0, sizeof maxn);
memset(minn, 0x3f, sizeof minn);
// 为什么tarjan只需要跑一遍loop dfs
// 假如有两个scc,scc1 -> scc2
// 普通dfs无法正确求出scc的原因是:如果从scc1的点开始dfs,那么dfs会搜索到scc2
// 但事实上scc2里的点是无法回到scc1的,它们不是同一个scc
// 但在使用tarjan算法时,即使从scc1开始dfs,搜索到了scc2
// scc2的点最多也只能回溯到开始进入scc2的起始点(并且那个起始点一定是dfs树根)
for (int i = 0; i < n; ++i) {
if (!dfn[i]) dfs(i);
}
// 拓扑排序
int sccadj[scccnt], d[scccnt]; // 新建meta node邻接表,把一个scc缩成一个点
memset(sccadj, -1, sizeof sccadj); // 如果某个meta node没有出边,那么邻接表值为-1
memset(d, 0, sizeof d);
// 现在邻接表中的节点是scc编号
for (int i = 0; i < n; ++i) {
int x = scc[i], y = scc[adj[i]];
if (x != y) {
++d[y];
sccadj[x] = y;
}
}
int cnt[scccnt]; // 记录每个meta node被指向的节点数,这是拓扑排序真正需要做的事情
memset(cnt, 0, sizeof cnt);
queue<int> q;
for (int i = 0; i < scccnt; ++i) {
if (d[i] == 0) q.push(i);
}
// 这实际上会对多个DAG同时进行拓扑排序,BFS拓扑排序时多个DAG的拓扑序会混杂在一起
while (!q.empty()) {
int u = q.front();
q.pop();
if (sccadj[u] != -1) {
int v = sccadj[u];
cnt[v] += cnt[u] + 1;
--d[v];
if (d[v] == 0) q.push(v);
}
}
int sccid = -1;
for (int i = 0; i < scccnt; ++i) {
if (siz[i] > 1) {
// L = siz[i], V = cnt[i]
// 多关键字排序
if (sccid == -1) sccid = i;
else if (siz[sccid] - cnt[sccid] < siz[i] - cnt[i]) sccid = i;
else if (siz[sccid] - cnt[sccid] == siz[i] - cnt[i] && maxn[sccid] < maxn[i]) sccid = i;
}
}
int cur = minn[sccid];
cout << cur;
cur = adj[cur];
while (cur != minn[sccid]) {
cout << ' ' << cur;
cur = adj[cur];
}
}
时间复杂度:O(n)
空间复杂度:O(n)
解题思路三:java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
/**
* @Author jinwang
* @Date 2024/5/9 10:48
* @Version 1.0 (版本号)
*/
class Main {
private static class node {
int maxIdx;
int minIdx;
int H;
public node(int maxIdx, int minIdx, int H) {
this.maxIdx = maxIdx;
this.minIdx = minIdx;
this.H = H;
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] arr = new int[n];
int[] ru = new int[n];
int[] v = new int[n];
boolean[] vis = new boolean[n];
StringTokenizer st = new StringTokenizer((br.readLine()));
for (int i = 0; i < n; i++) {
arr[i] = Integer.parseInt(st.nextToken());
ru[arr[i]]++;
}
Queue<Integer> q = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
if (ru[i] == 0) {
q.add(i);
}
}
while (!q.isEmpty()) {
int now = q.poll();
vis[now] = true;
v[arr[now]] += v[now] + 1;
ru[arr[now]]--;
if (ru[arr[now]] == 0) q.add(arr[now]);
}
List<node> nodes = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (!vis[i]) {
int now = i, mx = 0, cnt = 0, mn = n, sm = 0;
while (!vis[now]) {
vis[now] = true;
mx = Math.max(mx, now);
mn = Math.min(mn, now);
cnt += v[now];
now = arr[now];
sm++;
}
nodes.add(new node(mx, mn, sm - cnt));
}
}
node ans = nodes.get(0);
for (int i = 1; i < nodes.size(); i++) {
if (nodes.get(i).H > ans.H || nodes.get(i).H == ans.H && nodes.get(i).maxIdx > ans.maxIdx)
ans = nodes.get(i);
}
System.out.print(ans.minIdx);
for (int i = arr[ans.minIdx]; i != ans.minIdx; i = arr[i]) {
System.out.print(" " + i);
}
}
}
时间复杂度:O(n)
空间复杂度:O(n)
♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠ ⊕ ♠