一、问题描述
题目描述
给定一个正整数数组,设为 nums
,最大为100个成员,求从第一个成员开始,正好走到数组最后一个成员,所使用的最少步骤数。
要求:
- 第一步必须从第一元素开始,且
1<=第一步的步长<len/2
;(len为数组的长度,需要自行解析)。 - 从第二步开始,只能以所在成员的数字走相应的步数,不能多也不能少。如果目标不可达返回-1,只输出最少的步骤数量。
- 只能向数组的尾部走,不能往回走。
输入描述
由正整数组成的数组,以空格分隔,数组长度小于100,请自行解析数据数量。
输出描述
正整数,表示最少的步数,如果不存在输出-1。
用例
输入
7 5 9 4 2 6 8 3 5 4 3 9
输出
2
说明
第一步:第一个可选步长选择2,从第一个成员7开始走2步,到达9;
第二步:从9开始,经过自身数字9对应的9个成员到最后。
输入
1 2 3 7 1 5 9 3 2 1
输出
-1
说明
无
题目解析
首先,这题至少要走两步,因为第一步的步长是自选的,只要小于 len/2
,即可,也就是说第一步走完,应该处于 [1, len/2]
的范围中,我们假设处于 A
位置。
而第二步开始,每次走的步长都是前一步结束所在位置的值,比如第二步要走的步长就是 A
位置的值,因此有三种可能:
-
第二步要走的步长刚好到达
arr.length-1
位置:- 则最短步数就是2。
-
第二步要走的步长小于
arr.length-1
位置:- 则说明需要继续往下走,逻辑还是第二步的逻辑,递归下去。每次走到新位置后,检查该位置的值是否能让我们到达或超过数组末尾。如果可以,记录步数并结束;如果不能,继续前进。
-
第二步要走的步长大于
arr.length-1
位置:- 则说明当前路径走不通,需要回溯到上一步,尝试其他可能的步长(如果第一步有多种选择的话)。如果所有可能的步长都无法使我们到达末尾,则最终返回-1。
解题思路
- 第一步的选择:从第一个元素开始,选择一个步长,范围在
[1, len/2)
之间。这一步决定了我们接下来的位置。 - 后续步骤的递归:从新位置开始,根据当前位置的值决定下一步的步长。递归地进行这一步,直到:
- 到达或超过数组末尾,记录步数。
- 发现无法前进(即下一步会超出数组范围),回溯或标记为不可达。
- 结果判定:如果在尝试所有可能的第一步后,都无法到达末尾,则返回-1。否则,返回记录的最小步数。
这种方法确保我们考虑了所有可能的路径,从而找到到达末尾的最少步数。时间复杂度主要取决于数组长度和第一步的选择数量,最坏情况下为O(n^2),其中n是数组的长度。
二、JavaScript算法源码
以下是对您提供的 JavaScript 代码的中文详细注释和讲解。该代码通过 ACM 模式 从控制台获取输入,实现了一个算法来计算在给定数组中到达数组末尾所需的最小步数。每一步中,可以从一个位置跳到该位置上的值所指示的位置。
完整代码
/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline"); // 引入 readline 模块,用于处理控制台输入const rl = readline.createInterface({input: process.stdin, // 从标准输入读取数据output: process.stdout, // 输出到标准输出
});// 当接收到一行输入时进行处理
rl.on("line", (line) => {const arr = line.split(" ").map((ele) => parseInt(ele)); // 将输入的字符串用空格分割,并转换为整数数组console.log(getMinStep(arr)); // 调用算法函数计算最小步数并输出结果
});/* 算法函数 */
function getMinStep(arr) {let res = []; // 用于存储所有可能的步数for (let i = 1; i < Math.floor(arr.length / 2); i++) { // 从数组的第二个元素开始循环到数组的中部// 将每一种可能的路径所需要的步数存入 res 数组中res.push(loop(arr, i, 2)); // 调用递归函数计算步数,初始步数设为 2(因为第一次跳转已经算了一次)}// 过滤掉负值(表示无法到达末尾的路径),并按从小到大排序let step = res.filter((ele) => ele > 0).sort((a, b) => a - b)[0]; // 取最小的正数,即最小步数return step ? step : -1; // 如果没有有效步数,则返回 -1
}/* 递归函数:计算从索引 i 开始,到达数组末尾所需的步数 */
function loop(arr, i, count) {let j = i + arr[i]; // 计算跳转后的新位置if (j === arr.length - 1) { // 如果跳转到了数组的最后一个位置return count; // 返回步数} else if (j < arr.length - 1) { // 如果跳转位置在数组的有效范围内count++; // 步数加 1return loop(arr, j, count); // 继续递归跳转} else {return -1; // 跳转位置超出数组范围,返回 -1 表示此路径不可行}
}
详细注释与讲解
1. 控制台输入处理
const readline = require("readline");const rl = readline.createInterface({input: process.stdin,output: process.stdout,
});
- readline 模块:用于从控制台读取输入数据。
rl.on("line", (line) => {...})
:当用户在控制台输入一行并按下回车时,触发该事件,line
是输入的一行字符串。
2. 输入处理逻辑
rl.on("line", (line) => {const arr = line.split(" ").map((ele) => parseInt(ele)); // 将输入的字符串用空格分割,并转换为整数数组console.log(getMinStep(arr)); // 调用算法函数计算最小步数并输出结果
});
- 假设输入为
"2 3 1 1 4"
,通过split(" ")
得到数组["2", "3", "1", "1", "4"]
。 - 使用
.map((ele) => parseInt(ele))
,将字符串数组转换为整数数组:[2, 3, 1, 1, 4]
。
3. getMinStep 算法解析
function getMinStep(arr) {let res = []; // 用于存储所有可能的步数for (let i = 1; i < Math.floor(arr.length / 2); i++) { // 从数组的第二个元素开始循环到数组的中部res.push(loop(arr, i, 2)); // 调用递归函数计算步数}let step = res.filter((ele) => ele > 0).sort((a, b) => a - b)[0]; // 取最小的正数,即最小步数return step ? step : -1; // 如果没有有效步数,则返回 -1
}
- 目标:从数组中找到一条路径,使得从数组的第一个元素开始跳到最后一个元素,所需的步数最少。
- 逻辑:
res
数组用于存储所有不同的跳跃路径所需要的步数。for
循环从数组的第二个元素开始尝试跳转。因为如果从第一个元素直接跳,通常就被认为是第一步,所以这里i
从1
开始。loop
函数是递归函数,负责以给定的起始点i
进行跳转,并返回所需的步数。- 在
res
数组中过滤并排序所有可能的步数,取最小的正数作为结果,如果没有有效步数返回-1
。
4. loop 递归函数解析
function loop(arr, i, count) {let j = i + arr[i]; // 计算跳转后的新位置if (j === arr.length - 1) { // 如果跳转到了数组的最后一个位置return count; // 返回当前步数} else if (j < arr.length - 1) { // 如果跳转位置在数组的有效范围内count++; // 步数加 1return loop(arr, j, count); // 继续递归跳转} else {return -1; // 跳转位置超出数组范围,返回 -1 表示此路径不可行}
}
- 目标:递归模拟每次跳转并计算所需的步数。
- 逻辑:
let j = i + arr[i]
:根据当前位置i
和数组中的值,计算新的跳转位置j
。- 终止条件:
- 如果跳转到了数组的最后一个位置,则返回当前步数
count
。 - 如果跳转超出了数组的范围,返回
-1
表示此路径不可行。
- 如果跳转到了数组的最后一个位置,则返回当前步数
- 递归跳转:如果跳转位置合法且没有到达最后一个位置,继续递归跳转,步数
count
加 1。
5. 时间复杂度分析
-
递归调用
loop
的复杂度:- 在最坏的情况下,
loop
可能递归调用n
次(n
是数组的长度)。 - 因此,每个递归路径的最大时间复杂度是 O(n)。
- 在最坏的情况下,
-
整体时间复杂度:
- 外层
for
循环最多执行Math.floor(arr.length / 2)
次,每次调用递归函数loop
。 - 整体时间复杂度近似为 O(n^2)(因为递归函数会反复调用,取决于具体路径的数量)。
- 外层
示例解释
输入:
2 3 1 1 4
- 该数组表示:
arr[0] = 2
:可以从位置 0 跳到位置 2。arr[1] = 3
:可以从位置 1 跳到位置 4。- 其他同理。
执行步骤:
- 从
i = 1
开始跳转,调用loop
:- 跳转路径:
1 -> 3 -> 4
,步数为 2。
- 跳转路径:
- 结果:
- 最小的步数是 2,故输出 2。
总结
- 算法核心:使用递归模拟从数组不同位置的跳转过程,并找出到达数组末尾的最小步数。
- 递归作用:递归函数
loop
实现了一种回溯方法,反复尝试从某个位置跳到下一个合法位置,直至跳到末尾或超出范围。 - 优化空间:可通过动态规划或贪心算法进一步优化,避免多次重复递归带来的开销。
这段代码基于 ACM 模式 实现了通过控制台获取输入并计算结果,使用递归算法来解决数组跳转问题。希望这段中文注释与讲解能帮助您更好地理解代码逻辑和工作原理。
三、Java算法源码
以下是对您提供的 Java 代码的中文详细注释和讲解。该代码旨在通过控制台输入一个整数数组,然后计算从数组的开头跳到末尾所需的最小步数。每一步可以根据当前位置的值跳到新的位置。
代码全文
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;public class Main {// 主函数:程序的入口public static void main(String[] args) {Scanner sc = new Scanner(System.in); // 创建一个 Scanner 对象,用于从控制台获取输入// 通过输入一行字符串,按照空格分割,并将其转换为 Integer 数组Integer[] arr = Arrays.stream(sc.nextLine().split(" ")).map(Integer::parseInt).toArray(Integer[]::new);// 调用 getResult 函数,传入数组,并打印结果System.out.println(getResult(arr));}// 主逻辑函数:计算从数组开头跳到末尾的最小步数public static int getResult(Integer[] arr) {ArrayList<Integer> res = new ArrayList<>(); // 创建一个 ArrayList,用于存储所有可能的跳跃步数// 从数组的第二个元素开始,尝试以每个位置作为起始跳转点,进行递归计算步数for (int i = 1; i < arr.length / 2; i++) {res.add(loop(arr, i, 2)); // 调用递归函数 loop,计算从位置 i 开始跳到末尾的步数,初始步数为 2}// 过滤掉负值(负值表示无法到达末尾),然后找出最小的正值步数return res.stream().filter(ele -> ele > 0).min((a, b) -> a - b).orElse(-1);}// 递归函数:从数组 arr 的索引 i 开始跳转,返回跳到数组末尾所需的步数public static int loop(Integer[] arr, int i, int count) {int j = i + arr[i]; // 计算跳转后的新位置if (j == arr.length - 1) { // 如果跳转到了数组的最后一个位置return count; // 返回当前的步数,表示成功跳到末尾} else if (j < arr.length - 1) { // 如果跳转位置在数组有效范围内count++; // 步数增加 1return loop(arr, j, count); // 递归调用,从新的位置继续跳转} else { // 如果跳转超出了数组范围return -1; // 返回 -1,表示此路径不可行}}
}
详细注释和讲解
1. 主函数(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));
}
- 作用:
- 这是程序的入口点,负责处理控制台输入并调用核心算法函数。
- 详细解析:
Scanner sc = new Scanner(System.in);
:创建一个Scanner
对象,用于从控制台读取用户输入。sc.nextLine()
:读取一整行输入(例如:"2 3 1 1 4"
)。.split(" ")
:将输入字符串按照空格分割成字符串数组(例如:["2", "3", "1", "1", "4"]
)。Arrays.stream(...).map(Integer::parseInt)
:将字符串数组中的每个元素转换为整数。.toArray(Integer[]::new)
:将流转换回整数数组Integer[] arr
(例如:[2, 3, 1, 1, 4]
)。getResult(arr)
:调用核心算法函数getResult
计算最小步数。System.out.println(...)
:打印结果。
2. 主逻辑函数(getResult
方法)
public static int getResult(Integer[] arr) {ArrayList<Integer> res = new ArrayList<>(); // 用于存储所有跳跃路径的步数for (int i = 1; i < arr.length / 2; i++) {res.add(loop(arr, i, 2)); // 从每个位置 i 开始进行递归跳转,初始步数为 2}// 过滤掉负值(表示无法成功跳到末尾的路径),并找到最小步数return res.stream().filter(ele -> ele > 0).min((a, b) -> a - b).orElse(-1);
}
- 作用:
- 该函数计算从数组的开头跳到末尾所需的最小步数。
- 详细解析:
ArrayList<Integer> res
:用来存储从不同位置开始跳跃所需的总步数。for
循环从数组的第二个元素开始尝试(即索引1
),调用递归函数loop
计算从位置i
跳到末尾的步数。res.add(loop(arr, i, 2))
:将每次跳跃的步数结果加入res
数组中,初始步数从2
开始,因为第一次跳转通常已经算做一步。- 使用 Java 8 的 Stream API 来过滤掉无效路径(步数为负值),并对剩余的步数进行排序,找出最小的步数。
.orElse(-1)
:如果没有有效步数(即所有路径都无法到达末尾),则返回-1
。
3. 递归函数(loop
方法)
public static int loop(Integer[] arr, int i, int count) {int j = i + arr[i]; // 计算跳转后的新位置if (j == arr.length - 1) { // 如果跳到了数组的最后一个位置return count; // 返回当前步数,表示成功跳到末尾} else if (j < arr.length - 1) { // 如果跳转后位置在数组范围内count++; // 步数加 1return loop(arr, j, count); // 继续递归跳转到下一个新位置} else { // 如果跳转后位置超出数组范围return -1; // 返回 -1,表示此路径不可行}
}
- 作用:
- 递归函数
loop
用于模拟从数组的某个位置开始,反复跳转直到到达末尾或超出数组范围。
- 递归函数
- 详细解析:
int j = i + arr[i];
:计算跳转后的新位置j
。例如,当前位置i
为1
,arr[i]
为3
,则跳到的新位置为1 + 3 = 4
。if (j == arr.length - 1)
:如果跳到了数组的最后一个位置,则返回当前步数count
,表示成功跳到末尾。else if (j < arr.length - 1)
:如果跳转后的新位置仍然在数组范围内,则步数count
加 1,并继续递归调用loop
函数跳转到新位置。else
:如果跳转超出数组范围,返回-1
表示此路径不可行。
示例说明
输入:
2 3 1 1 4
过程解析:
- 从索引
0
开始,跳转arr[0] = 2
,跳到索引2
。 - 从索引
2
开始,跳转arr[2] = 1
,跳到索引3
。 - 从索引
3
开始,跳转arr[3] = 1
,跳到索引4
,成功跳到末尾。
通过多次递归调用 loop
,可以找出从不同位置开始跳跃所需的最小步数。
输出:
2
该输出表示从数组的开头跳到末尾的最小步数是 2
。
总结
- 核心算法:代码使用递归方式模拟跳跃过程,通过
loop
函数递归跳转,并使用getResult
函数遍历所有可能的起始跳转点,从而找出最小的跳跃步数。 - 优化空间:
- 递归算法可能存在大量重复计算,尤其是当数组非常大时,可以考虑通过动态规划或贪心算法优化性能。
- 但递归方式简单直观,适合学习理解跳跃问题的核心思想。
希望通过这段中文注释与讲解能帮助您更好地理解代码的工作原理和算法的实现过程。如果您有任何问题,欢迎进一步探讨!
四、Python算法源码
以下是对您提供的 Python 代码的中文详细注释和讲解。该代码通过控制台输入一个整数数组,然后计算从数组的开头跳到末尾所需的最小步数。每一步可以根据当前位置的值跳到新的位置。
代码全文
# 输入获取
arr = list(map(int, input().split()))# 递归函数:从数组 arr 的索引 i 开始跳转,返回跳到数组末尾所需的步数
def loop(arr, i, count):j = i + arr[i] # 计算跳转后的新位置if j == len(arr) - 1: # 如果跳转到了数组的最后一个位置return count # 返回当前步数elif j < len(arr) - 1: # 如果跳转后的新位置在数组有效范围内count += 1 # 步数加 1return loop(arr, j, count) # 递归跳转到新的位置else: # 如果跳转位置超出了数组范围return -1 # 返回 -1,表示此路径不可行# 算法入口函数:计算从数组开头跳到末尾的最小步数
def getResult():res = [] # 用于存储所有可能的跳跃步数for i in range(1, len(arr) // 2): # 从数组的第二个元素开始尝试跳转res.append(loop(arr, i, 2)) # 调用递归函数 loop,计算从位置 i 开始跳到末尾的步数,初始步数为 2# 过滤掉负值(负值表示无法成功跳到末尾的路径),并排序剩下的正值步数tmp = list(filter(lambda x: x > 0, res))tmp.sort() # 对步数进行排序# 返回最小的正值步数,如果没有正值步数则返回 -1if len(tmp) > 0:return tmp[0]else:return -1# 算法调用:打印结果
print(getResult())
详细注释和讲解
1. 输入获取
arr = list(map(int, input().split()))
-
作用:
- 从控制台获取输入的一行,并将其转换为整数列表。
-
示例:
- 输入:
"2 3 1 1 4"
- 处理后:
[2, 3, 1, 1, 4]
- 输入:
2. 递归函数 loop
def loop(arr, i, count):j = i + arr[i] # 计算跳转后的新位置if j == len(arr) - 1: # 如果跳转到了数组的最后一个位置return count # 返回当前步数elif j < len(arr) - 1: # 如果跳转后的新位置在数组有效范围内count += 1 # 步数加 1return loop(arr, j, count) # 递归跳转到新的位置else: # 如果跳转位置超出了数组范围return -1 # 返回 -1,表示此路径不可行
-
作用:
- 模拟从数组某个位置开始跳转,直到跳到数组末尾或超出范围。
-
详细解析:
j = i + arr[i]
:计算从当前位置i
跳转后的新位置j
。if j == len(arr) - 1
:如果跳到数组的最后一个位置,返回当前步数count
。elif j < len(arr) - 1
:如果跳转后的新位置在数组有效范围内,步数count
加 1,并递归调用loop
函数跳转到新位置。else
:如果跳转位置超出数组范围,返回-1
表示此路径不可行。
3. 算法入口函数 getResult
def getResult():res = [] # 用于存储所有可能的跳跃步数for i in range(1, len(arr) // 2): # 从数组的第二个元素开始尝试跳转res.append(loop(arr, i, 2)) # 调用递归函数 loop,计算从位置 i 开始跳到末尾的步数,初始步数为 2# 过滤掉负值(负值表示无法成功跳到末尾的路径),并排序剩下的正值步数tmp = list(filter(lambda x: x > 0, res))tmp.sort() # 对步数进行排序# 返回最小的正值步数,如果没有正值步数则返回 -1if len(tmp) > 0:return tmp[0]else:return -1
-
作用:
- 计算从数组开头跳到末尾的最小步数。
-
详细解析:
res = []
:用于存储所有可能的跳跃步数。for i in range(1, len(arr) // 2)
:从数组的第二个元素开始尝试跳转(i
从1
到len(arr) // 2
)。res.append(loop(arr, i, 2))
:调用递归函数loop
,计算从位置i
开始跳到末尾的步数,初始步数为2
。tmp = list(filter(lambda x: x > 0, res))
:过滤掉负值(表示无法成功跳到末尾的路径)。tmp.sort()
:对剩下的正值步数进行排序。if len(tmp) > 0: return tmp[0]
:返回最小的正值步数,如果没有正值步数则返回-1
。
4. 算法调用
print(getResult())
- 作用:
- 调用
getResult
函数,并打印结果。
- 调用
示例说明
输入:
2 3 1 1 4
过程解析:
- 从索引
0
开始,跳转arr[0] = 2
,跳到索引2
。 - 从索引
2
开始,跳转arr[2] = 1
,跳到索引3
。 - 从索引
3
开始,跳转arr[3] = 1
,跳到索引4
,成功跳到末尾。
输出:
2
该输出表示从数组的开头跳到末尾的最小步数是 2
。
总结
- 核心算法:代码使用递归方式模拟跳跃过程,通过
loop
函数递归跳转,并使用getResult
函数遍历所有可能的起始跳转点,从而找出最小的跳跃步数。 - 优化空间:
- 递归算法可能存在大量重复计算,尤其是当数组非常大时,可以考虑通过动态规划或贪心算法优化性能。
- 但递归方式简单直观,适合学习理解跳跃问题的核心思想。
希望通过这段中文注释与讲解能帮助您更好地理解代码的工作原理和算法的实现过程。如果您有任何问题,欢迎进一步探讨!
五、C/C++算法源码:
以下是将您提供的 Java 代码分别转换为 C++ 和 C 语言 代码,并附上详细的中文注释和讲解。两段代码的功能与原 Java 代码一致,即通过控制台输入一个整数数组,然后计算从数组的开头跳到末尾所需的最小步数。
C++ 代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <sstream>using namespace std;// 递归函数:从数组 arr 的索引 i 开始跳转,返回跳到数组末尾所需的步数
int loop(vector<int>& arr, int i, int count) {int j = i + arr[i]; // 计算跳转后的新位置if (j == arr.size() - 1) { // 如果跳到了数组的最后一个位置return count; // 返回当前步数} else if (j < arr.size() - 1) { // 如果跳转后的新位置在数组有效范围内count++; // 步数加 1return loop(arr, j, count); // 递归跳转到新的位置} else { // 如果跳转位置超出了数组范围return -1; // 返回 -1,表示此路径不可行}
}// 算法入口函数:计算从数组开头跳到末尾的最小步数
int getResult(vector<int>& arr) {vector<int> res; // 用于存储所有可能的跳跃步数// 从数组的第二个元素开始尝试跳转for (size_t i = 1; i < arr.size() / 2; i++) {res.push_back(loop(arr, i, 2)); // 调用递归函数 loop,计算从位置 i 开始跳到末尾的步数,初始步数为 2}// 使用 C++ 标准库的 min_element 函数找到最小的正值res.erase(remove_if(res.begin(), res.end(), [](int ele) { return ele <= 0; }), res.end());if (!res.empty()) {return *min_element(res.begin(), res.end()); // 返回最小步数} else {return -1; // 如果没有有效步数,返回 -1}
}// 主函数:程序的入口
int main() {string input;getline(cin, input); // 从控制台读取一整行输入// 使用 stringstream 将输入字符串按照空格分割并转换为整数,存入 vector 中vector<int> arr;stringstream ss(input);int num;while (ss >> num) {arr.push_back(num);}// 调用 getResult 函数计算最小步数,并输出结果cout << getResult(arr) << endl;return 0;
}
C++ 代码详解
1. loop
递归函数
int loop(vector<int>& arr, int i, int count) {int j = i + arr[i]; // 计算跳转后的新位置if (j == arr.size() - 1) { // 如果跳到了数组的最后一个位置return count; // 返回当前步数} else if (j < arr.size() - 1) { // 如果跳转后的新位置在数组有效范围内count++; // 步数加 1return loop(arr, j, count); // 递归跳转到新的位置} else { // 如果跳转位置超出了数组范围return -1; // 返回 -1,表示此路径不可行}
}
- 功能:模拟数组中的跳转过程,从给定的索引
i
开始跳转,直到跳到数组的末尾或者跳出数组范围。返回跳到末尾所需的最小步数。 - 参数:
arr
:整数数组。i
:当前所在数组的索引位置。count
:当前已跳跃的步数。
2. getResult
主逻辑函数
int getResult(vector<int>& arr) {vector<int> res; // 用于存储所有可能的跳跃步数// 从数组的第二个元素开始尝试跳转for (size_t i = 1; i < arr.size() / 2; i++) {res.push_back(loop(arr, i, 2)); // 调用递归函数 loop,计算从位置 i 开始跳到末尾的步数,初始步数为 2}// 过滤掉负值,只保留正值(表示成功跳到末尾的路径)res.erase(remove_if(res.begin(), res.end(), [](int ele) { return ele <= 0; }), res.end());if (!res.empty()) {return *min_element(res.begin(), res.end()); // 找到最小的正值步数} else {return -1; // 如果没有有效步数,返回 -1}
}
- 功能:计算从数组的不同位置开始跳转到末尾的最小步数。遍历数组的中间位置,调用递归函数
loop
计算步数,并找到最小的正值步数。
3. main
主函数
int main() {string input;getline(cin, input); // 读取整行输入// 使用 stringstream 将输入字符串按照空格分割并转换为整数,存入 vector 中vector<int> arr;stringstream ss(input);int num;while (ss >> num) {arr.push_back(num);}// 调用 getResult 计算最小步数并输出结果cout << getResult(arr) << endl;return 0;
}
- 功能:从控制台读取一行输入,解析成整数数组,并调用
getResult
计算最小步数,最后输出结果。
C 代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define MAX 100// 递归函数:从数组 arr 的索引 i 开始跳转,返回跳到数组末尾所需的步数
int loop(int arr[], int i, int count, int len) {int j = i + arr[i]; // 计算跳转后的新位置if (j == len - 1) { // 如果跳到了数组的最后一个位置return count; // 返回当前步数} else if (j < len - 1) { // 如果跳转后的新位置在数组有效范围内count++; // 步数加 1return loop(arr, j, count, len); // 递归跳转到新的位置} else { // 如果跳转位置超出了数组范围return -1; // 返回 -1,表示此路径不可行}
}// 辅助函数:比较两个整数大小,用于 qsort
int compare(const void* a, const void* b) {return (*(int*)a - *(int*)b);
}// 算法入口函数:计算从数组开头跳到末尾的最小步数
int getResult(int arr[], int len) {int res[MAX] = {0}; // 用于存储所有可能的跳跃步数int size = 0; // 实际的步数数量// 从数组的第二个元素开始尝试跳转for (int i = 1; i < len / 2; i++) {res[size++] = loop(arr, i, 2, len); // 调用递归函数 loop,计算从位置 i 开始跳到末尾的步数,初始步数为 2}// 过滤掉负值,只保留正值int tmp[MAX];int tmpSize = 0;for (int i = 0; i < size; i++) {if (res[i] > 0) {tmp[tmpSize++] = res[i];}}// 如果有正值步数,则排序并返回最小步数if (tmpSize > 0) {qsort(tmp, tmpSize, sizeof(int), compare);return tmp[0];} else {return -1; // 如果没有有效步数,返回 -1}
}int main() {char input[MAX];fgets(input, sizeof(input), stdin); // 从控制台读取一整行输入// 解析输入的字符串,按照空格分割并转换为整数,存入数组中int arr[MAX];int num, len = 0;char* token = strtok(input, " ");while (token != NULL) {arr[len++] = atoi(token);token = strtok(NULL, " ");}// 调用 getResult 计算最小步数,并输出结果printf("%d\n", getResult(arr, len));return 0;
}
C 代码详解
1. loop
递归函数
int loop(int arr[], int i, int count, int len) {int j = i + arr[i]; // 计算跳转后的新位置if (j == len - 1) { // 如果跳到了数组的最后一个位置return count; // 返回当前步数} else if (j < len - 1) { // 如果跳转后的新位置在数组有效范围内count++; // 步数加 1return loop(arr, j, count, len); // 递归跳转到新的位置} else { // 如果跳转位置超出了数组范围return -1; // 返回 -1,表示此路径不可行}
}
- 功能:与 C++ 的
loop
函数相同,用于模拟跳转过程并计算步数。
2. getResult
主逻辑函数
int getResult(int arr[], int len) {int res[MAX] = {0}; // 用于存储所有可能的跳跃步数int size = 0; // 实际的步数数量// 从数组的第二个元素开始尝试跳转for (int i = 1; i < len / 2; i++) {res[size++] = loop(arr, i, 2, len); // 调用递归函数 loop,计算从位置 i 开始跳到末尾的步数,初始步数为 2}// 过滤掉负值,只保留正值int tmp[MAX];int tmpSize = 0;for (int i = 0; i < size; i++) {if (res[i] > 0) {tmp[tmpSize++] = res[i];}}// 如果有正值步数,则排序并返回最小步数if (tmpSize > 0) {qsort(tmp, tmpSize, sizeof(int), compare);returnif (tmpSize > 0) {qsort(tmp, tmpSize, sizeof(int), compare);return tmp[0]; // 返回排序后的最小步数} else {return -1; // 如果没有有效步数,返回 -1}
}
-
功能:
- 在
loop
函数计算出多个跳跃步数后,对这些步数进行过滤和排序,找到最小的正值步数。 - 如果存在有效的步数(即可以跳到数组末尾),则通过
qsort
对步数进行排序,并返回最小的步数。 - 如果不存在有效的步数,则返回
-1
。
- 在
-
详细解析:
qsort(tmp, tmpSize, sizeof(int), compare)
:使用 C 标准库的qsort
函数对步数数组tmp
进行升序排序,排序规则由compare
函数定义。compare
函数很简单,只是比较两个整数的大小,返回正数、负数或零,具体实现为:int compare(const void* a, const void* b) {return (*(int*)a - *(int*)b); }
- 最后,返回
tmp
数组中的第一个元素(即最小步数)。
3. 辅助函数 compare
int compare(const void* a, const void* b) {return (*(int*)a - *(int*)b);
}
- 功能:该函数是一个标准的比较函数,用于
qsort
函数进行整数排序。它接收两个void*
类型的参数,将它们转换为int*
,并返回它们的差值。这个差值用来决定两个数在排序中的顺序。
4. main
主函数
int main() {char input[MAX];fgets(input, sizeof(input), stdin); // 从控制台读取一整行输入// 解析输入的字符串,按照空格分割并转换为整数,存入数组中int arr[MAX];int num, len = 0;char* token = strtok(input, " ");while (token != NULL) {arr[len++] = atoi(token);token = strtok(NULL, " ");}// 调用 getResult 计算最小步数,并输出结果printf("%d\n", getResult(arr, len));return 0;
}
-
功能:主函数负责从控制台读取输入,解析成整数数组,并调用核心算法函数
getResult
来计算最小跳跃步数,最后输出结果。 -
详细解析:
fgets(input, sizeof(input), stdin)
:从标准输入读取一整行输入,并存储在input
字符数组中。strtok(input, " ")
:使用strtok
函数按空格分割输入的字符串,并逐个转换为整数存入arr
数组中。atoi(token)
:将分割出来的字符串转换为整数。getResult(arr, len)
:调用getResult
函数计算最小步数并输出结果。
完整示例说明
输入:
2 3 1 1 4
过程解析:
-
输入解析:
- 通过
fgets
读取输入"2 3 1 1 4"
。 - 使用
strtok
分割后得到整数数组arr = {2, 3, 1, 1, 4}
,长度len = 5
。
- 通过
-
计算最小步数:
-
从索引
1
开始跳转,递归函数loop
依次计算步数:- 从索引
1
跳转:arr[1] = 3
,跳到索引1 + 3 = 4
,成功跳到末尾,步数为2
。 - 从索引
2
开始跳转,递归过程也是跳到末尾,步数为3
。
- 从索引
-
所有的有效步数存储在
res
中,最终通过对res
排序得到最小步数2
。
-
输出:
2
该输出表示从数组的开头跳到末尾的最小步数是 2
。
总结
C++ 和 C 代码的对比:
-
代码结构:
- 两段代码的逻辑基本一致,核心算法和递归过程完全相同。
- C++ 使用了
vector
动态数组,而 C 使用了定长数组int arr[MAX]
,并且在处理输入时,C++ 使用了stringstream
,C 使用了strtok
。
-
C++ 特性:
- C++ 提供了更方便的标准库函数,例如
vector
、min_element
、remove_if
等,让代码更加简洁。 - C++ 的
vector
可以自动管理数组大小,而 C 的定长数组需要定义一个较大的固定值MAX
。
- C++ 提供了更方便的标准库函数,例如
-
C 特性:
- C 更加底层,需要手动编写更多代码,例如使用
strtok
进行字符串分割,并且通过qsort
手动排序。 - C 需要手动管理数组大小和内存分配。
- C 更加底层,需要手动编写更多代码,例如使用
优化建议:
-
虽然递归方式简单直观,但当数组非常大时,递归会导致大量的重复计算,性能较差。可以考虑使用动态规划或贪心算法来优化。
-
动态规划思路:可以通过记录每个位置的最小步数,避免重复计算,这样时间复杂度会显著降低。
注意事项:
- 在 C 代码中,为了简单起见,数组大小被定义为了一个较大的常量
MAX
。在实际使用中,应根据问题的规模适当调整MAX
的值,或考虑动态内存分配(如使用malloc
)。
希望这段中文注释和讲解能够帮助您更好地理解 C++ 和 C 代码的实现。如有更多问题,欢迎继续讨论!
六、尾言
什么是华为OD?
华为OD(Outsourcing Developer,外包开发工程师)是华为针对软件开发工程师岗位的一种招聘形式,主要包括笔试、技术面试以及综合面试等环节。尤其在笔试部分,算法题的机试至关重要。
为什么刷题很重要?
-
机试是进入技术面的第一关:
华为OD机试(常被称为机考)主要考察算法和编程能力。只有通过机试,才能进入后续的技术面试环节。 -
技术面试需要手撕代码:
技术一面和二面通常会涉及现场编写代码或算法题。面试官会注重考察候选人的思路清晰度、代码规范性以及解决问题的能力。因此提前刷题、多练习是通过面试的重要保障。 -
入职后的可信考试:
入职华为后,还需要通过“可信考试”。可信考试分为三个等级:- 入门级:主要考察基础算法与编程能力。
- 工作级:更贴近实际业务需求,可能涉及复杂的算法或与工作内容相关的场景题目。
- 专业级:最高等级,考察深层次的算法以及优化能力,与薪资直接挂钩。
刷题策略与说明:
2024年8月14日之后,华为OD机试的题库转为 E卷,由往年题库(D卷、A卷、B卷、C卷)和全新题目组成。刷题时可以参考以下策略:
-
关注历年真题:
- 题库中的旧题占比较大,建议优先刷历年的A卷、B卷、C卷、D卷题目。
- 对于每道题目,建议深度理解其解题思路、代码实现,以及相关算法的适用场景。
-
适应新题目:
- E卷中包含全新题目,需要掌握全面的算法知识和一定的灵活应对能力。
- 建议关注新的刷题平台或交流群,获取最新题目的解析和动态。
-
掌握常见算法:
华为OD考试通常涉及以下算法和数据结构:- 排序算法(快速排序、归并排序等)
- 动态规划(背包问题、最长公共子序列等)
- 贪心算法
- 栈、队列、链表的操作
- 图论(最短路径、最小生成树等)
- 滑动窗口、双指针算法
-
保持编程规范:
- 注重代码的可读性和注释的清晰度。
- 熟练使用常见编程语言,如C++、Java、Python等。
如何获取资源?
-
官方参考:
- 华为招聘官网或相关的招聘平台会有一些参考信息。
- 华为OD的相关公众号可能也会发布相关的刷题资料或学习资源。
-
加入刷题社区:
- 找到可信的刷题交流群,与其他备考的小伙伴交流经验。
- 关注知名的刷题网站,如LeetCode、牛客网等,这些平台上有许多华为OD的历年真题和解析。
-
寻找系统性的教程:
- 学习一本经典的算法书籍,例如《算法导论》《剑指Offer》《编程之美》等。
- 完成系统的学习课程,例如数据结构与算法的在线课程。
积极心态与持续努力:
刷题的过程可能会比较枯燥,但它能够显著提升编程能力和算法思维。无论是为了通过华为OD的招聘考试,还是为了未来的职业发展,这些积累都会成为重要的财富。
考试注意细节
-
本地编写代码
- 在本地 IDE(如 VS Code、PyCharm 等)上编写、保存和调试代码,确保逻辑正确后再复制粘贴到考试页面。这样可以减少语法错误,提高代码准确性。
-
调整心态,保持冷静
- 遇到提示不足或实现不确定的问题时,不必慌张,可以采用更简单或更有把握的方法替代,确保思路清晰。
-
输入输出完整性
- 注意训练和考试时都需要编写完整的输入输出代码,尤其是和题目示例保持一致。完成代码后务必及时调试,确保功能符合要求。
-
快捷键使用
- 删除行可用
Ctrl+D
,复制、粘贴和撤销分别为Ctrl+C
,Ctrl+V
,Ctrl+Z
,这些可以正常使用。 - 避免使用
Ctrl+S
,以免触发浏览器的保存功能。
- 删除行可用
-
浏览器要求
- 使用最新版的 Google Chrome 浏览器完成考试,确保摄像头开启并正常工作。考试期间不要切换到其他网站,以免影响考试成绩。
-
交卷相关
- 答题前,务必仔细查看题目示例,避免遗漏要求。
- 每完成一道题后,点击【保存并调试】按钮,多次保存和调试是允许的,系统会记录得分最高的一次结果。完成所有题目后,点击【提交本题型】按钮。
- 确保在考试结束前提交试卷,避免因未保存或调试失误而丢分。
-
时间和分数安排
- 总时间:150 分钟;总分:400 分。
- 试卷结构:2 道一星难度题(每题 100 分),1 道二星难度题(200 分)。及格分为 150 分。合理分配时间,优先完成自己擅长的题目。
-
考试环境准备
- 考试前请备好草稿纸和笔。考试中尽量避免离开座位,确保监控画面正常。
- 如需上厕所,请提前规划好时间以减少中途离开监控的可能性。
-
技术问题处理
- 如果考试中遇到断电、断网、死机等技术问题,可以关闭浏览器并重新打开试卷链接继续作答。
- 出现其他问题,请第一时间联系 HR 或监考人员进行反馈。
祝你考试顺利,取得理想成绩!