【模拟】找终点(许老师题解)
【模拟】找终点(许老师题解)
题目描述与示例
本题练习地址:https://www.algomooc.com/problem/P2495
题目描述
给定一个正整数数组,设为 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
解题思路
乍一看本题很像LeetCode55、跳跃游戏,实际上本题比后者简单很多。
在后者中,每一步的选择都可以选择小于等于当前位置数字的步数,但在本题中只有第一步的选择是可变的。
第一步的选择是可变的,其范围为[1, ceil(n/2))
。
题目的描述是
1<=第一步的步长<n/2
假设
n
为偶数,那么len/2
是一个整数,步长取值的右开边界为n//2
- 譬如,
n = 6
,那么n/2 = 3
,我们需要取到3
的右开边界假设
n
为奇数,那么len/2
是一个非整数,步长取值的右开边界为(n+1)//2
- 譬如,
n = 7
,那么n/2 = 3.5
,我们需要取到4
的右开边界
n//2
和(n+1)//2
可以用向上取整ceil(n/2)
来统一表示。
假设我们确定了第一步的步长为step,那么第一步走完之后的位置会是idx = 0+step = step
。
接下来每一步的选择都是固定的,我们需要讨论这些固定的步数选择能否让我们顺利走到终点(数组的最后一个位置也就是索引n-1
的位置),以及所花费的步数是多少。可以构建一个函数check()
来判断
# 判断第一步为step时,能否到达终点,以及花费的步数
def check(nums, n, step):
# 总步数初始化为1,因为刚刚已经走了第一步
total = 1
# 走完第一步后,索引位置为step
idx = step
# 执行循环,退出循环的条件为idx >= n
while idx < n:
# 获得idx位置对应的步数next_step
next_step = nums[idx]
# idx进行前进
idx += next_step
# 总步数+1
total += 1
# 如果idx恰好到了数组最末尾的位置,也就是索引n-1的位置
# 则直接返回当前选择了step方案的步数total
if idx == n-1:
return total
# 如果无法到达n-1的位置,说明无法恰好到达终点,返回-1
return -1
将check()
放在关于step
的循环中,每次更新全局变量ans
即可。整体代码为
# 初始化ans为一个不可能取得到的极大值,方便后面取得最小值
# 由于数组长度最多只有100,此处初始化为101即可
ans = 101
# 初始化第一步的选择,范围为[1, ceil(n/2))
for step in range(1, ceil(n/2)):
# 计算在选择了step作为第一步的前提下,花费的总步数
total = check(nums, n, step)
# 如果total不为-1,则更新ans
if total != -1:
ans = min(ans, total)
# 如果ans仍然为101,说明没有合适的方案输出-1
# 否则输出ans的值
print(ans if ans != 101 else -1)
代码
Python
# 题目:【模拟】2024E-找终点
# 分值:100
# 作者:许老师-闭着眼睛学数理化
# 算法:模拟
# 代码看不懂的地方,请直接在群上提问
from math import ceil
# 判断第一步为step时,能否到达终点,以及花费的步数
def check(nums, n, step):
# 总步数初始化为1,因为刚刚已经走了第一步
total = 1
# 走完第一步后,索引位置为step
idx = step
# 执行循环,退出循环的条件为idx >= n
while idx < n:
# 获得idx位置对应的步数next_step
next_step = nums[idx]
# idx进行前进
idx += next_step
# 总步数+1
total += 1
# 如果idx恰好到了数组最末尾的位置,也就是索引n-1的位置
# 则直接返回当前选择了step方案的步数total
if idx == n-1:
return total
# 如果无法到达n-1的位置,说明无法恰好到达终点,返回-1
return -1
# 输入数组,并且计算其长度n
nums = list(map(int, input().split()))
n = len(nums)
# 初始化ans为一个不可能取得到的极大值,方便后面取得最小值
# 由于数组长度最多只有100,此处初始化为101即可
ans = 101
# 初始化第一步的选择,范围为[1, ceil(n/2))
for step in range(1, ceil(n/2)):
# 计算在选择了step作为第一步的前提下,花费的总步数
total = check(nums, n, step)
# 如果total不为-1,则更新ans
if total != -1:
ans = min(ans, total)
# 如果ans仍然为101,说明没有合适的方案输出-1
# 否则输出ans的值
print(ans if ans != 101 else -1)
Java
import java.util.*;
public class Main {
// 判断第一步为step时,能否到达终点,以及花费的步数
public static int check(int[] nums, int n, int step) {
// 总步数初始化为1,因为刚刚已经走了第一步
int total = 1;
// 走完第一步后,索引位置为step
int idx = step;
// 执行循环,退出循环的条件为idx >= n
while (idx < n) {
// 获得idx位置对应的步数next_step
int next_step = nums[idx];
// idx进行前进
idx += next_step;
// 总步数+1
total++;
// 如果idx恰好到了数组最末尾的位置,也就是索引n-1的位置
// 则直接返回当前选择了step方案的步数total
if (idx == n - 1) {
return total;
}
}
// 如果无法到达n-1的位置,说明无法恰好到达终点,返回-1
return -1;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 输入数组,并且计算其长度n
List<Integer> numsList = new ArrayList<>();
while (sc.hasNextInt()) {
numsList.add(sc.nextInt());
}
int n = numsList.size();
int[] nums = numsList.stream().mapToInt(Integer::intValue).toArray();
// 初始化ans为一个不可能取得到的极大值,方便后面取得最小值
// 由于数组长度最多只有100,此处初始化为101即可
int ans = 101;
// 初始化第一步的选择,范围为[1, ceil(n/2))
for (int step = 1; step < Math.ceil(n / 2.0); step++) {
// 计算在选择了step作为第一步的前提下,花费的总步数
int total = check(nums, n, step);
// 如果total不为-1,则更新ans
if (total != -1) {
ans = Math.min(ans, total);
}
}
// 如果ans仍然为101,说明没有合适的方案输出-1
// 否则输出ans的值
System.out.println(ans == 101 ? -1 : ans);
}
}
C++
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
// 判断第一步为step时,能否到达终点,以及花费的步数
int check(vector<int>& nums, int n, int step) {
// 总步数初始化为1,因为刚刚已经走了第一步
int total = 1;
// 走完第一步后,索引位置为step
int idx = step;
// 执行循环,退出循环的条件为idx >= n
while (idx < n) {
// 获得idx位置对应的步数next_step
int next_step = nums[idx];
// idx进行前进
idx += next_step;
// 总步数+1
total++;
// 如果idx恰好到了数组最末尾的位置,也就是索引n-1的位置
// 则直接返回当前选择了step方案的步数total
if (idx == n - 1) {
return total;
}
}
// 如果无法到达n-1的位置,说明无法恰好到达终点,返回-1
return -1;
}
int main() {
// 输入数组,并且计算其长度n
vector<int> nums;
int num;
while (cin >> num) {
nums.push_back(num);
}
int n = nums.size();
// 初始化ans为一个不可能取得到的极大值,方便后面取得最小值
// 由于数组长度最多只有100,此处初始化为101即可
int ans = 101;
// 初始化第一步的选择,范围为[1, ceil(n/2))
for (int step = 1; step < ceil(n / 2.0); step++) {
// 计算在选择了step作为第一步的前提下,花费的总步数
int total = check(nums, n, step);
// 如果total不为-1,则更新ans
if (total != -1) {
ans = min(ans, total);
}
}
// 如果ans仍然为101,说明没有合适的方案输出-1
// 否则输出ans的值
cout << (ans == 101 ? -1 : ans) << endl;
return 0;
}
C
#include <stdio.h>
#include <math.h>
int check(int nums[], int n, int step) {
// 总步数初始化为1,因为刚刚已经走了第一步
int total = 1;
// 走完第一步后,索引位置为step
int idx = step;
// 执行循环,退出循环的条件为idx >= n
while (idx < n) {
// 获得idx位置对应的步数next_step
int next_step = nums[idx];
// idx进行前进
idx += next_step;
// 总步数+1
total++;
// 如果idx恰好到了数组最末尾的位置,也就是索引n-1的位置
// 则直接返回当前选择了step方案的步数total
if (idx == n - 1) {
return total;
}
}
// 如果无法到达n-1的位置,说明无法恰好到达终点,返回-1
return -1;
}
int main() {
int nums[100];
int n = 0;
// 输入数组,并且计算其长度n
while (scanf("%d", &nums[n]) != EOF) {
n++;
}
// 初始化ans为一个不可能取得到的极大值,方便后面取得最小值
// 由于数组长度最多只有100,此处初始化为101即可
int ans = 101;
// 初始化第一步的选择,范围为[1, ceil(n/2))
for (int step = 1; step < ceil(n / 2.0); step++) {
// 计算在选择了step作为第一步的前提下,花费的总步数
int total = check(nums, n, step);
// 如果total不为-1,则更新ans
if (total != -1) {
ans = (total < ans) ? total : ans;
}
}
// 如果ans仍然为101,说明没有合适的方案输出-1
// 否则输出ans的值
printf("%d\n", ans == 101 ? -1 : ans);
return 0;
}
Node JavaScript
// 判断第一步为step时,能否到达终点,以及花费的步数
function check(nums, n, step) {
// 总步数初始化为1,因为刚刚已经走了第一步
let total = 1;
// 走完第一步后,索引位置为step
let idx = step;
// 执行循环,退出循环的条件为idx >= n
while (idx < n) {
// 获得idx位置对应的步数next_step
let next_step = nums[idx];
// idx进行前进
idx += next_step;
// 总步数+1
total++;
// 如果idx恰好到了数组最末尾的位置,也就是索引n-1的位置
// 则直接返回当前选择了step方案的步数total
if (idx === n - 1) {
return total;
}
}
// 如果无法到达n-1的位置,说明无法恰好到达终点,返回-1
return -1;
}
// 主程序
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.on('line', (line) => {
const nums = line.split(' ').map(Number);
const n = nums.length;
let ans = 101; // 初始化ans为一个不可能取得到的极大值
// 初始化第一步的选择,范围为[1, Math.ceil(n/2))
for (let step = 1; step < Math.ceil(n / 2); step++) {
// 计算在选择了step作为第一步的前提下,花费的总步数
const total = check(nums, n, step);
// 如果total不为-1,则更新ans
if (total !== -1) {
ans = Math.min(ans, total);
}
}
// 如果ans仍然为101,说明没有合适的方案输出-1
// 否则输出ans的值
console.log(ans === 101 ? -1 : ans);
rl.close();
});
Go
package main
import (
"fmt"
"math"
)
// 判断第一步为step时,能否到达终点,以及花费的步数
func check(nums []int, n, step int) int {
// 总步数初始化为1,因为刚刚已经走了第一步
total := 1
// 走完第一步后,索引位置为step
idx := step
// 执行循环,退出循环的条件为idx >= n
for idx < n {
// 获得idx位置对应的步数next_step
next_step := nums[idx]
// idx进行前进
idx += next_step
// 总步数+1
total++
// 如果idx恰好到了数组最末尾的位置,也就是索引n-1的位置
// 则直接返回当前选择了step方案的步数total
if idx == n-1 {
return total
}
}
// 如果无法到达n-1的位置,说明无法恰好到达终点,返回-1
return -1
}
func main() {
var nums []int
var num int
// 输入数组
for {
_, err := fmt.Scan(&num)
if err != nil {
break
}
nums = append(nums, num)
}
n := len(nums)
// 初始化ans为一个不可能取得到的极大值
ans := 101
// 初始化第一步的选择,范围为[1, ceil(n/2))
for step := 1; step < int(math.Ceil(float64(n)/2.0)); step++ {
// 计算在选择了step作为第一步的前提下,花费的总步数
total := check(nums, n, step)
// 如果total不为-1,则更新ans
if total != -1 {
if total < ans {
ans = total
}
}
}
// 如果ans仍然为101,说明没有合适的方案输出-1
// 否则输出ans的值
if ans == 101 {
fmt.Println(-1)
} else {
fmt.Println(ans)
}
}
时空复杂度
时间复杂度:O(N^2)
。双重循环所需时间复杂度
空间复杂度:O(1)
。仅需若干常数变量维护遍历过程。