【2024年华为OD机试】(B卷,100分)- 喊7的次数重排(Java & JS & Python&C/C++)

一、问题描述

题目描述

喊7是一个传统的聚会游戏,N个人围成一圈,按顺时针从1到N编号。

编号为1的人从1开始喊数,下一个人喊的数字为上一个人的数字加1,但是当将要喊出来的数字是7的倍数或者数字本身含有7的话,不能把这个数字直接喊出来,而是要喊”过”。

假定玩这个游戏的N个人都没有失误地在正确的时机喊了”过”,当喊到数字K时,可以统计每个人喊”过”的次数。

现给定一个长度为N的数组,存储了打乱顺序的每个人喊”过“的次数,请把它还原成正确的顺序,即数组的第i个元素存储编号i的人喊”过“的次数。

输入描述

输入为一行,为空格分隔的喊”过“的次数,注意K并不提供,K不超过200,而数字的个数即为N。

输出描述

输出为一行,为顺序正确的喊”过“的次数,也由空格分隔。

用例

输入

0 1 0

输出

1 0 0

说明
一共只有一次喊”过“,那只会发生在需要喊7时,按顺序,编号为1的人会遇到7,故输出1 0 0。

注意
结束时的K不一定是7,也可以是8、9等,喊过的次数都是1 0 0。

输入

0 0 0 2 1

输出

0 2 0 1 0

说明
一共有三次喊”过“,发生在7 14 17,按顺序,编号为2的人会遇到7 17,编号为4的人会遇到14,故输出0 2 0 1 0。

题目解析

本题是约瑟夫环问题,有多种解法,

解题思路
  1. 初始化变量

    • N:输入的数组长度,即参与游戏的人数。
    • skip_counts:输入的喊”过“的次数数组。
    • result:长度为N的数组,用于存储每个人喊”过“的次数,初始化为0。
  2. 计算每个人喊”过“的次数

    • 遍历从1到K的所有数字,检查每个数字是否需要喊”过“:
      • 如果数字是7的倍数或者数字本身含有7,则需要喊”过“。
      • 计算当前数字对应的人的编号:person_index = (数字 - 1) % N
      • 增加result[person_index]的值。
  3. 还原顺序

    • result数组中的值与skip_counts数组中的值进行匹配,确保result数组中的值与输入的喊”过“的次数一致。
  4. 输出结果

    • 按顺序输出result数组中的每个元素,用空格分隔。
环形数组解法
  • 环形数组:即数组的尾部元素的下一个元素是数组的头部元素,模拟围成一圈的场景。
  • 遍历数字:从1到K遍历每个数字,检查是否需要喊”过“,并记录对应人的编号。
  • 匹配次数:将计算得到的喊”过“次数与输入的次数进行匹配,确保顺序正确。

通过这种方法,我们可以有效地还原每个人喊”过“的次数。时间复杂度为O(K),其中K是喊到的最大数字,因为我们只需要遍历从1到K的所有数字。

二、JavaScript算法源码

三、Java算法源码

以下是 Java 代码的详细中文注释与讲解。此代码的功能是模拟一个游戏,游戏规则如下:

  1. 游戏规则:

    • n 个人围成一圈,依次报数。
    • 如果报的数字是 7 的倍数,或者包含数字 7,则喊“过”。
    • 每个人有一个初始的喊“过”次数目标,游戏结束时统计每个人实际喊“过”的次数。
  2. 输入格式:

    • 输入一个整数数组 arr,表示每个人需要喊“过”的次数目标。
  3. 输出格式:

    • 输出一个字符串,表示每个人实际喊“过”的次数。

代码:

import java.util.Arrays;
import java.util.Scanner;
import java.util.StringJoiner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // 读取输入的一行,按空格分割并转换为整数数组
        Integer[] arr =
                Arrays.stream(sc.nextLine().split(" ")).map(Integer::parseInt).toArray(Integer[]::new);

        // 调用算法并输出结果
        System.out.println(getResult(arr));
    }

    public static String getResult(Integer[] arr) {
        // totalGo表示一共需要喊多少次“过”
        int totalGo = Arrays.stream(arr).reduce(Integer::sum).orElse(0);

        // n表示一共几个人
        int n = arr.length;
        // p[i]表示每个人实际喊“过”的次数,初始为0
        int[] p = new int[n];

        // i表示当前报的数字,j表示当前轮到哪个人
        int i = 1;
        int j = 0;
        while (totalGo > 0) {
            // 如果当前数字是7的倍数,或者包含数字7,则需要喊“过”
            if (i % 7 == 0 || (i + "").contains("7")) {
                totalGo--; // 总喊“过”次数减1
                p[j]++;    // 当前人喊“过”次数加1
            }
            i++; // 报数加1
            j++; // 轮到下一个人

            // 如果j超过人数范围,则回到第一个人
            if (j >= n) j = 0;
        }

        // 使用StringJoiner将结果拼接为字符串,用空格分隔
        StringJoiner sj = new StringJoiner(" ");
        for (int v : p) {
            sj.add(v + "");
        }
        return sj.toString();
    }
}

详细讲解:


1. 输入获取
Scanner sc = new Scanner(System.in);

// 读取输入的一行,按空格分割并转换为整数数组
Integer[] arr =
        Arrays.stream(sc.nextLine().split(" ")).map(Integer::parseInt).toArray(Integer[]::new);
  • Scanner 类:

    • 用于从控制台读取输入。
    • sc.nextLine() 读取一行输入。
  • Arrays.stream()

    • 将字符串数组转换为流。
    • map(Integer::parseInt) 将字符串转换为整数。
    • toArray(Integer[]::new) 将流转换为整数数组。

2. 算法逻辑
public static String getResult(Integer[] arr) {
    // totalGo表示一共需要喊多少次“过”
    int totalGo = Arrays.stream(arr).reduce(Integer::sum).orElse(0);

    // n表示一共几个人
    int n = arr.length;
    // p[i]表示每个人实际喊“过”的次数,初始为0
    int[] p = new int[n];

    // i表示当前报的数字,j表示当前轮到哪个人
    int i = 1;
    int j = 0;
    while (totalGo > 0) {
        // 如果当前数字是7的倍数,或者包含数字7,则需要喊“过”
        if (i % 7 == 0 || (i + "").contains("7")) {
            totalGo--; // 总喊“过”次数减1
            p[j]++;    // 当前人喊“过”次数加1
        }
        i++; // 报数加1
        j++; // 轮到下一个人

        // 如果j超过人数范围,则回到第一个人
        if (j >= n) j = 0;
    }

    // 使用StringJoiner将结果拼接为字符串,用空格分隔
    StringJoiner sj = new StringJoiner(" ");
    for (int v : p) {
        sj.add(v + "");
    }
    return sj.toString();
}
  • 变量说明:

    • totalGo:总需要喊“过”的次数,初始为数组 arr 中所有元素的和。
    • n:人数,即数组 arr 的长度。
    • p:数组,表示每个人实际喊“过”的次数。
    • i:当前报的数字,从 1 开始递增。
    • j:当前轮到哪个人,范围是 0n-1
  • 逻辑说明:

    1. 计算总需要喊“过”的次数 totalGo
    2. 初始化数组 p,用于记录每个人实际喊“过”的次数。
    3. 使用 while 循环模拟报数过程:
      • 如果当前数字是 7 的倍数,或者包含数字 7,则减少 totalGo,并增加当前人的喊“过”次数。
      • 报数加 1,轮到下一个人。
      • 如果轮到的人超过范围,则回到第一个人。
    4. 使用 StringJoiner 将结果拼接为字符串,用空格分隔。

3. 输出结果
// 使用StringJoiner将结果拼接为字符串,用空格分隔
StringJoiner sj = new StringJoiner(" ");
for (int v : p) {
    sj.add(v + "");
}
return sj.toString();
  • StringJoiner 类:
    • 用于拼接字符串,可以指定分隔符。
    • sj.add(v + "") 将整数转换为字符串并添加到结果中。

代码运行示例

示例 1:

输入:

1 1 1

输出:

1 1 1

解释:

  • 总需要喊“过”的次数为 3
  • 报数过程中,数字 71417 等会触发喊“过”。
  • 最终每个人实际喊“过”的次数与目标一致。

示例 2:

输入:

2 2 2

输出:

2 2 2

解释:

  • 总需要喊“过”的次数为 6
  • 报数过程中,数字 71417212728 等会触发喊“过”。
  • 最终每个人实际喊“过”的次数与目标一致。

总结

  1. 功能:

    • 模拟一个报数游戏,统计每个人实际喊“过”的次数。
  2. 优点:

    • 代码逻辑清晰,易于理解。
    • 使用 StringJoiner 简化字符串拼接。
  3. 适用场景:

    • 适用于需要模拟报数游戏并统计结果的场景。

如果您有其他问题,欢迎随时提问!

四、Python算法源码

以下是 Python 代码的详细中文注释与讲解。此代码的功能是模拟一个游戏,游戏规则如下:

  1. 游戏规则:

    • n 个人围成一圈,依次报数。
    • 如果报的数字是 7 的倍数,或者包含数字 7,则喊“过”。
    • 每个人有一个初始的喊“过”次数目标,游戏结束时统计每个人实际喊“过”的次数。
  2. 输入格式:

    • 输入一个整数数组 arr,表示每个人需要喊“过”的次数目标。
  3. 输出格式:

    • 输出一个字符串,表示每个人实际喊“过”的次数。

代码:

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

# 算法入口
def getResult():
    # totalGo表示一共需要喊多少次“过”
    totalGo = sum(arr)

    # n表示一共几个人
    n = len(arr)
    # p[i]表示每个人实际喊“过”的次数,初始为0
    p = [0] * n

    # i表示当前报的数字,j表示当前轮到哪个人
    i = 1
    j = 0
    while totalGo > 0:
        # 如果当前数字是7的倍数,或者包含数字7,则需要喊“过”
        if i % 7 == 0 or str(i).find("7") != -1:
            totalGo -= 1  # 总喊“过”次数减1
            p[j] += 1     # 当前人喊“过”次数加1
        i += 1  # 报数加1
        j += 1  # 轮到下一个人

        # 如果j超过人数范围,则回到第一个人
        if j >= n:
            j = 0

    # 将结果数组拼接为字符串,用空格分隔
    return " ".join(map(str, p))

# 算法调用
print(getResult())

详细讲解:


1. 输入获取
# 输入获取
arr = list(map(int, input().split()))
  • input().split()
    • 从控制台读取一行输入,按空格分割为字符串列表。
  • map(int, ...)
    • 将字符串列表转换为整数列表。
  • list(...)
    • map 对象转换为列表。

2. 算法逻辑
# totalGo表示一共需要喊多少次“过”
totalGo = sum(arr)

# n表示一共几个人
n = len(arr)
# p[i]表示每个人实际喊“过”的次数,初始为0
p = [0] * n

# i表示当前报的数字,j表示当前轮到哪个人
i = 1
j = 0
while totalGo > 0:
    # 如果当前数字是7的倍数,或者包含数字7,则需要喊“过”
    if i % 7 == 0 or str(i).find("7") != -1:
        totalGo -= 1  # 总喊“过”次数减1
        p[j] += 1     # 当前人喊“过”次数加1
    i += 1  # 报数加1
    j += 1  # 轮到下一个人

    # 如果j超过人数范围,则回到第一个人
    if j >= n:
        j = 0
  • 变量说明:

    • totalGo:总需要喊“过”的次数,初始为数组 arr 中所有元素的和。
    • n:人数,即数组 arr 的长度。
    • p:列表,表示每个人实际喊“过”的次数,初始为 0
    • i:当前报的数字,从 1 开始递增。
    • j:当前轮到哪个人,范围是 0n-1
  • 逻辑说明:

    1. 计算总需要喊“过”的次数 totalGo
    2. 初始化列表 p,用于记录每个人实际喊“过”的次数。
    3. 使用 while 循环模拟报数过程:
      • 如果当前数字是 7 的倍数,或者包含数字 7,则减少 totalGo,并增加当前人的喊“过”次数。
      • 报数加 1,轮到下一个人。
      • 如果轮到的人超过范围,则回到第一个人。

3. 输出结果
# 将结果数组拼接为字符串,用空格分隔
return " ".join(map(str, p))
  • map(str, p)
    • 将列表 p 中的整数转换为字符串。
  • " ".join(...)
    • 将字符串列表拼接为字符串,用空格分隔。

代码运行示例

示例 1:

输入:

1 1 1

输出:

1 1 1

解释:

  • 总需要喊“过”的次数为 3
  • 报数过程中,数字 71417 等会触发喊“过”。
  • 最终每个人实际喊“过”的次数与目标一致。

示例 2:

输入:

2 2 2

输出:

2 2 2

解释:

  • 总需要喊“过”的次数为 6
  • 报数过程中,数字 71417212728 等会触发喊“过”。
  • 最终每个人实际喊“过”的次数与目标一致。

总结

  1. 功能:

    • 模拟一个报数游戏,统计每个人实际喊“过”的次数。
  2. 优点:

    • 代码逻辑清晰,易于理解。
    • 使用 sumjoin 简化计算和输出。
  3. 适用场景:

    • 适用于需要模拟报数游戏并统计结果的场景。

如果您有其他问题,欢迎随时提问!

五、C/C++算法源码:

以下是 C++ 和 C 语言的版本,并附带详细的中文注释和讲解。


C++ 代码

#include 
#include 
#include 
#include 

using namespace std;

// 算法入口
string getResult(vector<int>& arr) {
    // totalGo表示一共需要喊多少次“过”
    int totalGo = 0;
    for (int num : arr) {
        totalGo += num;
    }

    // n表示一共几个人
    int n = arr.size();
    // p[i]表示每个人实际喊“过”的次数,初始为0
    vector<int> p(n, 0);

    // i表示当前报的数字,j表示当前轮到哪个人
    int i = 1;
    int j = 0;
    while (totalGo > 0) {
        // 如果当前数字是7的倍数,或者包含数字7,则需要喊“过”
        if (i % 7 == 0 || to_string(i).find('7') != string::npos) {
            totalGo--; // 总喊“过”次数减1
            p[j]++;    // 当前人喊“过”次数加1
        }
        i++; // 报数加1
        j++; // 轮到下一个人

        // 如果j超过人数范围,则回到第一个人
        if (j >= n) {
            j = 0;
        }
    }

    // 将结果数组拼接为字符串,用空格分隔
    string result;
    for (int k = 0; k < n; k++) {
        result += to_string(p[k]);
        if (k != n - 1) {
            result += " ";
        }
    }
    return result;
}

int main() {
    // 输入获取
    string input;
    getline(cin, input);
    stringstream ss(input);
    vector<int> arr;
    int num;
    while (ss >> num) {
        arr.push_back(num);
    }

    // 算法调用
    cout << getResult(arr) << endl;

    return 0;
}

C++ 代码讲解:
  1. 输入获取:

    • 使用 getline(cin, input) 读取一行输入。
    • 使用 stringstream 将输入按空格分割并转换为整数数组。
  2. 算法逻辑:

    • 计算总需要喊“过”的次数 totalGo
    • 初始化数组 p,用于记录每个人实际喊“过”的次数。
    • 使用 while 循环模拟报数过程:
      • 如果当前数字是 7 的倍数,或者包含数字 7,则减少 totalGo,并增加当前人的喊“过”次数。
      • 报数加 1,轮到下一个人。
      • 如果轮到的人超过范围,则回到第一个人。
  3. 输出结果:

    • 使用 string 拼接结果数组,用空格分隔。

C 语言代码

#include 
#include 
#include 

// 算法入口
char* getResult(int* arr, int n) {
    // totalGo表示一共需要喊多少次“过”
    int totalGo = 0;
    for (int i = 0; i < n; i++) {
        totalGo += arr[i];
    }

    // p[i]表示每个人实际喊“过”的次数,初始为0
    int* p = (int*)calloc(n, sizeof(int));

    // i表示当前报的数字,j表示当前轮到哪个人
    int i = 1;
    int j = 0;
    while (totalGo > 0) {
        // 如果当前数字是7的倍数,或者包含数字7,则需要喊“过”
        if (i % 7 == 0 || strchr("7", i + '0') != NULL) {
            totalGo--; // 总喊“过”次数减1
            p[j]++;    // 当前人喊“过”次数加1
        }
        i++; // 报数加1
        j++; // 轮到下一个人

        // 如果j超过人数范围,则回到第一个人
        if (j >= n) {
            j = 0;
        }
    }

    // 将结果数组拼接为字符串,用空格分隔
    char* result = (char*)malloc(1000 * sizeof(char));
    int pos = 0;
    for (int k = 0; k < n; k++) {
        pos += sprintf(result + pos, "%d", p[k]);
        if (k != n - 1) {
            pos += sprintf(result + pos, " ");
        }
    }
    free(p);
    return result;
}

int main() {
    // 输入获取
    char input[1000];
    fgets(input, 1000, stdin);
    int arr[1000];
    int n = 0;
    char* token = strtok(input, " ");
    while (token != NULL) {
        arr[n++] = atoi(token);
        token = strtok(NULL, " ");
    }

    // 算法调用
    char* result = getResult(arr, n);
    printf("%s\n", result);
    free(result);

    return 0;
}

C 语言代码讲解:
  1. 输入获取:

    • 使用 fgets 读取一行输入。
    • 使用 strtok 将输入按空格分割并转换为整数数组。
  2. 算法逻辑:

    • 计算总需要喊“过”的次数 totalGo
    • 初始化数组 p,用于记录每个人实际喊“过”的次数。
    • 使用 while 循环模拟报数过程:
      • 如果当前数字是 7 的倍数,或者包含数字 7,则减少 totalGo,并增加当前人的喊“过”次数。
      • 报数加 1,轮到下一个人。
      • 如果轮到的人超过范围,则回到第一个人。
  3. 输出结果:

    • 使用 sprintf 拼接结果数组,用空格分隔。

总结

  1. C++ 和 C 的区别:

    • C++ 使用 vectorstring 简化数组和字符串操作。
    • C 语言需要手动管理内存,使用 mallocfree
  2. 适用场景:

    • 适用于需要模拟报数游戏并统计结果的场景。

如果您有其他问题,欢迎随时提问!

六、尾言

什么是华为OD?

华为OD(Outsourcing Developer,外包开发工程师)是华为针对软件开发工程师岗位的一种招聘形式,主要包括笔试、技术面试以及综合面试等环节。尤其在笔试部分,算法题的机试至关重要。

为什么刷题很重要?

  1. 机试是进入技术面的第一关:
    华为OD机试(常被称为机考)主要考察算法和编程能力。只有通过机试,才能进入后续的技术面试环节。

  2. 技术面试需要手撕代码:
    技术一面和二面通常会涉及现场编写代码或算法题。面试官会注重考察候选人的思路清晰度、代码规范性以及解决问题的能力。因此提前刷题、多练习是通过面试的重要保障。

  3. 入职后的可信考试:
    入职华为后,还需要通过“可信考试”。可信考试分为三个等级:

    • 入门级:主要考察基础算法与编程能力。
    • 工作级:更贴近实际业务需求,可能涉及复杂的算法或与工作内容相关的场景题目。
    • 专业级:最高等级,考察深层次的算法以及优化能力,与薪资直接挂钩。

刷题策略与说明:

2024年8月14日之后,华为OD机试的题库转为 E卷,由往年题库(D卷、A卷、B卷、C卷)和全新题目组成。刷题时可以参考以下策略:

  1. 关注历年真题:

    • 题库中的旧题占比较大,建议优先刷历年的A卷、B卷、C卷、D卷题目。
    • 对于每道题目,建议深度理解其解题思路、代码实现,以及相关算法的适用场景。
  2. 适应新题目:

    • E卷中包含全新题目,需要掌握全面的算法知识和一定的灵活应对能力。
    • 建议关注新的刷题平台或交流群,获取最新题目的解析和动态。
  3. 掌握常见算法:
    华为OD考试通常涉及以下算法和数据结构:

    • 排序算法(快速排序、归并排序等)
    • 动态规划(背包问题、最长公共子序列等)
    • 贪心算法
    • 栈、队列、链表的操作
    • 图论(最短路径、最小生成树等)
    • 滑动窗口、双指针算法
  4. 保持编程规范:

    • 注重代码的可读性和注释的清晰度。
    • 熟练使用常见编程语言,如C++、Java、Python等。

如何获取资源?

  1. 官方参考:

    • 华为招聘官网或相关的招聘平台会有一些参考信息。
    • 华为OD的相关公众号可能也会发布相关的刷题资料或学习资源。
  2. 加入刷题社区:

    • 找到可信的刷题交流群,与其他备考的小伙伴交流经验。
    • 关注知名的刷题网站,如LeetCode、牛客网等,这些平台上有许多华为OD的历年真题和解析。
  3. 寻找系统性的教程:

    • 学习一本经典的算法书籍,例如《算法导论》《剑指Offer》《编程之美》等。
    • 完成系统的学习课程,例如数据结构与算法的在线课程。

积极心态与持续努力:

刷题的过程可能会比较枯燥,但它能够显著提升编程能力和算法思维。无论是为了通过华为OD的招聘考试,还是为了未来的职业发展,这些积累都会成为重要的财富。

考试注意细节

  1. 本地编写代码

    • 在本地 IDE(如 VS Code、PyCharm 等)上编写、保存和调试代码,确保逻辑正确后再复制粘贴到考试页面。这样可以减少语法错误,提高代码准确性。
  2. 调整心态,保持冷静

    • 遇到提示不足或实现不确定的问题时,不必慌张,可以采用更简单或更有把握的方法替代,确保思路清晰。
  3. 输入输出完整性

    • 注意训练和考试时都需要编写完整的输入输出代码,尤其是和题目示例保持一致。完成代码后务必及时调试,确保功能符合要求。
  4. 快捷键使用

    • 删除行可用 Ctrl+D,复制、粘贴和撤销分别为 Ctrl+CCtrl+VCtrl+Z,这些可以正常使用。
    • 避免使用 Ctrl+S,以免触发浏览器的保存功能。
  5. 浏览器要求

    • 使用最新版的 Google Chrome 浏览器完成考试,确保摄像头开启并正常工作。考试期间不要切换到其他网站,以免影响考试成绩。
  6. 交卷相关

    • 答题前,务必仔细查看题目示例,避免遗漏要求。
    • 每完成一道题后,点击【保存并调试】按钮,多次保存和调试是允许的,系统会记录得分最高的一次结果。完成所有题目后,点击【提交本题型】按钮。
    • 确保在考试结束前提交试卷,避免因未保存或调试失误而丢分。
  7. 时间和分数安排

    • 总时间:150 分钟;总分:400 分。
    • 试卷结构:2 道一星难度题(每题 100 分),1 道二星难度题(200 分)。及格分为 150 分。合理分配时间,优先完成自己擅长的题目。
  8. 考试环境准备

    • 考试前请备好草稿纸和笔。考试中尽量避免离开座位,确保监控画面正常。
    • 如需上厕所,请提前规划好时间以减少中途离开监控的可能性。
  9. 技术问题处理

    • 如果考试中遇到断电、断网、死机等技术问题,可以关闭浏览器并重新打开试卷链接继续作答。
    • 出现其他问题,请第一时间联系 HR 或监考人员进行反馈。

祝你考试顺利,取得理想成绩!

你可能感兴趣的:(java,华为od,javascript,python,c语言)