您现在的位置是:首页 >技术教程 >【2024年华为OD机试】(B卷,200分)- 跳房子II (JavaScript&Java & Python&C/C++)网站首页技术教程
【2024年华为OD机试】(B卷,200分)- 跳房子II (JavaScript&Java & Python&C/C++)
一、问题描述
跳房子游戏步数组合问题
题目描述
跳房子是一种世界性的儿童游戏。游戏参与者需要分多个回合按顺序跳到第1格直到房子的最后一格,然后获得一次选房子的机会,直到所有房子被选完,房子最多的人获胜。跳房子的过程中,如果有踩线等违规行为,会结束当前回合,甚至可能倒退几步。
假设房子的总格数是count
,小红每回合可能连续跳的步数都放在数组steps
中。请问数组中是否有一种步数的组合,可以让小红三个回合跳到最后一格?如果有,请输出索引和最小的步数组合(数据保证索引和最小的步数组合是唯一的)。
注意:数组中的步数可以重复,但数组中的元素不能重复使用。
输入描述
- 第一行输入为房子总格数
count
,它是int
整数类型。 - 第二行输入为每回合可能连续跳的步数,它是
int
整数数组类型。
输出描述
返回索引和最小的满足要求的步数组合(顺序保持steps
中原有顺序)。
备注
count ≤ 10000
3 ≤ steps.length ≤ 10000
-100000 ≤ steps[i] ≤ 100000
用例
示例1
输入:
[1,4,5,2,0,2]
9
输出:
[4,5,0]
说明: 无
示例2
输入:
[1,5,2,0,2,4]
9
输出:
[5,2,2]
说明: 无
示例3
输入:
[-1,2,4,9]
12
输出:
[-1,4,9]
说明: 无
题目解析
本题是“三数之和”问题的变种。给定一个数组steps
,需要从中找出三个数,保证这三个数的和等于count
。本题的变化在于,可能存在多个三数组合满足要求,但我们只取其中三数的索引和最小的组合。
解题思路
-
数据结构转换:
- 首先根据输入的
steps
数组,将其转化为newSteps
数组,newSteps
数组的元素是{val: steps[i], idx: i}
。 - 然后将
newSteps
数组按照元素的val
值进行升序排序,若val
值相同,则继续按照idx
值升序排序。
- 首先根据输入的
-
三数之和逻辑:
- 定义一个双重循环,外层
for
循环遍历newSteps
每一个元素,指针为i
。 - 定义两个指针
l = i + 1
,r = newSteps.length - 1
。 - 计算
valSum = newSteps[i].val + newSteps[l].val + newSteps[r].val
。- 如果
valSum < count
,则l++
。 - 如果
valSum > count
,则r--
。 - 如果
valSum == count
,则i
、l
、r
指向的三数组合符合要求,计算三数的索引之和idxSum = newSteps[i].idx + newSteps[l].idx + newSteps[r].idx
。- 如果
idxSum < minIdxSum
,则更新minIdxSum = idxSum
,并继续尝试更优解(r--
)。 - 如果
idxSum > minIdxSum
,则丢弃。 - 如果
idxSum == minIdxSum
,则不存在此场景(题目保证索引和最小的步数组合是唯一的)。
- 如果
- 如果
- 定义一个双重循环,外层
-
剪枝优化:
- 剪枝1:如果
steps[i] > count
且steps[i] > 0
、count > 0
,则steps[i] + steps[l] + steps[r] > count
是必然的,可以break
掉i
的遍历。 - 剪枝2:避免统计重复组合。
- R指针去重:如果
steps[r] == steps[r-1]
,则左移R
指针。 - L指针去重:如果
steps[l] == steps[l+1]
,则右移L
指针。 - i指针去重:如果
steps[i] == steps[i-1]
,则直接continue
。
- R指针去重:如果
- 剪枝1:如果
-
进一步剪枝:
- 确定
i
指针后,L
、R
指针指向数的目标和已经确定为count - steps[i]
。 - 由于
steps[L] <= steps[R]
,因此必然存在:steps[L] <= (count - steps[i]) / 2
steps[R] >= (count - steps[i]) / 2
- 如果初始时
steps[L]
、steps[R]
不满足上述关系,则可以剪枝。
- 确定
总结
本题通过将问题转化为“三数之和”问题,并结合剪枝优化策略,能够有效地找到索引和最小的步数组合。通过合理的剪枝和去重策略,可以在大规模数据下避免超时问题。
二、JavaScript算法源码
代码整体结构
代码分为几个部分:
- 读取输入:使用
readline
模块从标准输入读取数据。 - 数据处理:将读取的数据进行处理,转换为适合后续计算的格式。
- 逻辑运算:执行特定的逻辑运算来找到满足条件的结果。
- 输出结果:将计算结果输出到标准输出。
详细讲解
读取输入
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const lines = [];
rl.on("line", (line) => {
lines.push(line);
if (lines.length == 2) {
const steps = lines[0].slice(1, -1).split(",").map(Number); // 去掉首尾字符,分割并转换为数字
const count = parseInt(lines[1]); // 将第二行转换为整数
console.log(getResult(steps, count)); // 调用函数处理数据并输出结果
lines.length = 0; // 清空数组,准备接收下一组数据
}
});
- 使用
readline
模块创建接口,监听标准输入。 - 每读取一行数据,将其存入
lines
数组。 - 当读取到两行数据时,处理这两行数据:第一行是步骤数组(去掉首尾的方括号,分割成字符串数组,再转换为数字数组),第二行是一个整数。
- 调用
getResult
函数处理这些数据,并输出结果。 - 清空
lines
数组,为下一组数据做准备。
逻辑运算
function getResult(steps, count) {
const n = steps.length;
// 将步骤数组转换为包含值和索引的对象数组
const newSteps = [];
for (let i = 0; i < n; i++) {
newSteps.push(new Step(steps[i], i));
}
// 按值和索引排序
newSteps.sort((a, b) => (a.val != b.val ? a.val - b.val : a.idx - b.idx));
let minStepIdxSum = Infinity;
let ans = "";
// 遍历步骤数组,尝试找到满足条件的三元组
for (let i = 0; i < n; i++) {
// 剪枝优化:如果当前值大于0且剩余步数大于0但当前值大于剩余步数,则不可能找到满足条件的三元组
if (newSteps[i].val > 0 && count > 0 && newSteps[i].val > count) {
break;
}
// 剪枝优化:如果当前值与上一个值相同,则跳过,避免重复计算
if (i > 0 && newSteps[i].val == newSteps[i - 1].val) {
continue;
}
let l = i + 1;
let r = n - 1;
while (l < r) {
// 剪枝优化:根据中间值计算阈值,如果L或R指针指向的值超出阈值范围,则不可能找到满足条件的三元组
const threshold = (count - newSteps[i].val) / 2;
if (newSteps[l].val > threshold || newSteps[r].val < threshold) break;
const stepValSum = newSteps[i].val + newSteps[l].val + newSteps[r].val;
if (stepValSum < count) {
l++;
} else if (stepValSum > count) {
r--;
} else {
// 剪枝优化:跳过重复值
while (l < r - 1 && newSteps[r].val == newSteps[r - 1].val) {
r--;
}
// 计算索引和,更新最小索引和及对应的三元组值
const stepIdxSum = newSteps[i].idx + newSteps[l].idx + newSteps[r].idx;
if (stepIdxSum < minStepIdxSum) {
minStepIdxSum = stepIdxSum;
const arr = [newSteps[i], newSteps[l], newSteps[r]];
arr.sort((a, b) => a.idx - b.idx);
ans = `[${arr.map((step) => step.val).join(",")}]`;
}
// 剪枝优化:跳过重复值
while (l + 1 < r && newSteps[l].val == newSteps[l + 1].val) {
l++;
}
l++;
r--;
}
}
}
return ans;
}
- 将步骤数组转换为包含值和索引的对象数组,并按值和索引排序。
- 遍历排序后的步骤数组,尝试找到三个步骤,它们的值之和等于给定的
count
。 - 使用双指针方法,在遍历过程中应用多个剪枝优化来减少不必要的计算。
- 当找到满足条件的三元组时,计算其索引和,如果这是目前找到的最小索引和,则更新结果。
- 返回结果字符串,格式为
[值1,值2,值3]
,表示满足条件且索引和最小的三个步骤的值。
Step类
class Step {
constructor(val, idx) {
this.val = val;
this.idx = idx;
}
}
Step
类用于存储步骤的值和索引。- 在处理步骤数组时,将每个步骤转换为一个
Step
对象,方便后续按值和索引排序及计算。
总结
这段代码实现了一个特定的逻辑运算,用于从输入数据中找出满足特定条件(值之和等于给定数,且索引和最小)的三个步骤。通过排序和双指针方法,结合多个剪枝优化,提高了算法的效率。
三、Java算法源码
该代码的主要功能是处理一个由整数构成的数组(代表步数),并找到三个数,它们的和等于给定的目标数count
,同时这三个数的索引之和最小。如果找到多个满足条件的组合,则选择索引之和最小的那个,并且输出这三个数的升序排列。
import java.util.Arrays;
import java.util.Scanner;
import java.util.StringJoiner;
public class Main {
// 定义一个内部类Step,用于存储步数和对应的索引
static class Step {
int val; // 步数
int idx; // 索引
public Step(int val, int idx) {
this.idx = idx; // 初始化索引
this.val = val; // 初始化步数
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取输入的第一行,即步数数组,去掉首尾的方括号,并按逗号分割成字符串数组
String tmp = sc.nextLine();
int[] steps =
Arrays.stream(tmp.substring(1, tmp.length() - 1).split(","))
.mapToInt(Integer::parseInt)
.toArray();
// 读取第二行,即目标和count
int count = Integer.parseInt(sc.nextLine());
// 输出结果
System.out.println(getResult(steps, count));
}
// 查找三个数的和等于count,且索引之和最小的组合
public static String getResult(int[] steps, int count) {
int n = steps.length; // 获取步数数组的长度
// 将步数数组转换为Step数组,同时记录每个步数的索引
Step[] newSteps = new Step[n];
for (int i = 0; i < n; i++) {
newSteps[i] = new Step(steps[i], i);
}
// 按步数值升序排序,如果步数值相同,则按索引升序排序
Arrays.sort(newSteps, (a, b) -> a.val != b.val ? a.val - b.val : a.idx - b.idx);
int minStepIdxSum = Integer.MAX_VALUE; // 初始化索引之和的最小值为最大整数
String ans = ""; // 初始化结果字符串为空
// 遍历数组,尝试找到三个数的组合
for (int i = 0; i < n; i++) {
// 如果当前步数值大于count且count大于0,则无需继续查找,因为后面的值都会更大
if (newSteps[i].val > count && newSteps[i].val > 0 && count > 0) break;
// 如果当前步数值与前一个相同,则跳过,避免重复计算
if (i > 0 && newSteps[i].val == newSteps[i - 1].val) continue;
int l = i + 1; // 左指针,从当前元素的下一个元素开始
int r = n - 1; // 右指针,指向数组的最后一个元素
// 使用双指针法查找另外两个数
while (l < r) {
// 计算目标和的一半,用于剪枝优化
int threshold = (count - newSteps[i].val) / 2;
// 如果左指针指向的值大于阈值或右指针指向的值小于阈值,则无需继续查找
if (newSteps[l].val > threshold || newSteps[r].val < threshold) break;
int stepValSum = newSteps[i].val + newSteps[l].val + newSteps[r].val; // 计算三个数的和
if (stepValSum < count) {
l++; // 和小于目标和,移动左指针
} else if (stepValSum > count) {
r--; // 和大于目标和,移动右指针
} else {
// 找到满足条件的组合,进一步处理
// 跳过右指针指向的重复值,避免重复计算
while (l < r - 1 && newSteps[r].val == newSteps[r - 1].val) {
r--;
}
// 计算当前组合的索引之和
int stepIdxSum = newSteps[i].idx + newSteps[l].idx + newSteps[r].idx;
// 如果当前组合的索引之和更小,则更新最小索引之和和结果字符串
if (stepIdxSum < minStepIdxSum) {
minStepIdxSum = stepIdxSum;
// 将满足条件的三个Step对象放入数组,并按索引升序排序
Step[] arr = {newSteps[i], newSteps[l], newSteps[r]};
Arrays.sort(arr, (a, b) -> a.idx - b.idx);
// 使用StringJoiner将结果转换为字符串格式
StringJoiner sj = new StringJoiner(",", "[", "]");
for (Step step : arr) sj.add(step.val + "");
ans = sj.toString(); // 更新结果字符串
}
// 跳过左指针指向的重复值,避免重复计算
while (l + 1 < r && newSteps[l].val == newSteps[l + 1].val) {
l++;
}
// 移动指针,继续查找其他可能的组合
l++;
r--;
}
}
}
return ans; // 返回结果字符串
}
}
这段代码通过双指针法和一些剪枝优化来高效地查找满足条件的三个数的组合。它首先将所有步数及其索引存储在Step
数组中,并对该数组进行排序。然后,它遍历排序后的数组,并使用双指针法查找另外两个数,使得三个数的和等于给定的目标数count
。在查找过程中,它还会检查索引之和,并更新最小索引之和和结果字符串。
四、Python算法源码
这段代码的主要功能是从一个整数列表 steps
中找到三个数,使得它们的和等于给定的目标值 count
,并且这三个数的索引之和最小。如果存在多个满足条件的三元组,则选择索引之和最小的那个,并返回该三元组的值。
代码详细注释与讲解
import sys
# 输入获取
steps = list(map(int, input()[1:-1].split(","))) # 从输入中获取整数列表,去掉首尾的方括号并按逗号分割
count = int(input()) # 获取目标值 count
class Step:
def __init__(self, val, idx):
self.val = val # 存储整数值
self.idx = idx # 存储该整数的索引
# 算法入口
def getResult():
n = len(steps) # 获取 steps 列表的长度
# 将 steps 列表中的每个元素转换为 Step 对象,方便后续操作
newSteps = [Step(steps[i], i) for i in range(n)]
# 对 newSteps 列表进行排序,首先按 val 升序,其次按 idx 升序
newSteps.sort(key=lambda x: (x.val, x.idx))
minStepIdxSum = sys.maxsize # 初始化最小索引和为系统最大整数值
ans = "" # 初始化结果字符串
# 遍历 newSteps 列表,寻找满足条件的三元组
for i in range(n):
# 剪枝优化:如果当前值大于 0 且目标值小于当前值,则后续的值都会更大,直接跳出循环
if newSteps[i].val > 0 and 0 < count < newSteps[i].val:
break
# 剪枝优化:如果当前值与上一个值相同,则跳过,避免重复计算
if i > 0 and newSteps[i].val == newSteps[i - 1].val:
continue
l = i + 1 # 左指针,指向当前元素的下一个元素
r = n - 1 # 右指针,指向列表的最后一个元素
# 使用双指针法在剩余的元素中寻找满足条件的三元组
while l < r:
# 剪枝优化:L 和 R 指针指向的值的和应该等于 count - newSteps[i].val
# 因此 L 指针指向的值应该小于等于 (count - newSteps[i].val) / 2
# R 指针指向的值应该大于等于 (count - newSteps[i].val) / 2
threshold = (count - newSteps[i].val) / 2
if newSteps[l].val > threshold or newSteps[r].val < threshold:
break
# 计算当前三元组的和
stepValSum = newSteps[i].val + newSteps[l].val + newSteps[r].val
# 根据和的大小调整指针
if stepValSum < count:
l += 1 # 和小于目标值,左指针右移
elif stepValSum > count:
r -= 1 # 和大于目标值,右指针左移
else:
# 剪枝优化:如果右指针指向的值与下一个值相同,则右指针左移,避免重复计算
while l < r - 1 and newSteps[r].val == newSteps[r - 1].val:
r -= 1
# 计算当前三元组的索引和
stepIdxSum = newSteps[i].idx + newSteps[l].idx + newSteps[r].idx
# 如果当前三元组的索引和小于最小索引和,则更新最小索引和和结果字符串
if stepIdxSum < minStepIdxSum:
minStepIdxSum = stepIdxSum
# 将三元组按索引排序
arr = [newSteps[i], newSteps[l], newSteps[r]]
arr.sort(key=lambda x: x.idx)
# 生成结果字符串
ans = "[" + ",".join(map(lambda x: str(x.val), arr)) + "]"
# 剪枝优化:如果左指针指向的值与下一个值相同,则左指针右移,避免重复计算
while l + 1 < r and newSteps[l].val == newSteps[l + 1].val:
l += 1
# 移动指针,继续寻找下一个可能的三元组
l += 1
r -= 1
return ans # 返回结果字符串
# 算法调用
print(getResult())
代码逻辑讲解
-
输入处理:
- 从输入中获取整数列表
steps
和目标值count
。 - 将
steps
列表中的每个元素转换为Step
对象,方便后续操作。
- 从输入中获取整数列表
-
排序:
- 对
newSteps
列表进行排序,首先按val
升序,其次按idx
升序。这样做的目的是为了后续的双指针法能够高效地找到满足条件的三元组。
- 对
-
剪枝优化:
- 在遍历
newSteps
列表时,如果当前值大于 0 且目标值小于当前值,则后续的值都会更大,直接跳出循环。 - 如果当前值与上一个值相同,则跳过,避免重复计算。
- 在遍历
-
双指针法:
- 使用双指针法在剩余的元素中寻找满足条件的三元组。左指针
l
指向当前元素的下一个元素,右指针r
指向列表的最后一个元素。 - 根据当前三元组的和与目标值的大小关系,调整指针的位置。
- 使用双指针法在剩余的元素中寻找满足条件的三元组。左指针
-
结果更新:
- 如果找到满足条件的三元组,则计算其索引和,并与当前最小索引和进行比较。如果更小,则更新最小索引和和结果字符串。
-
返回结果:
- 最终返回结果字符串,表示满足条件的三元组。
总结
这段代码通过排序和双指针法,高效地找到了满足条件的三元组,并且通过剪枝优化减少了不必要的计算。最终返回的结果是索引和最小的三元组。
五、C/C++算法源码:
以下是 C语言代码 和 C++代码 的实现,并附带详细的中文注释和讲解。
C语言代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#define MAX_SIZE 10000
// 定义结构体 Step,用于存储值和索引
typedef struct {
int val; // 存储整数值
int idx; // 存储该整数的索引
} Step;
// 函数声明
void getResult(const int steps[], int steps_size, int count);
// 主函数
int main() {
int steps[MAX_SIZE];
int steps_size = 0;
// 从输入中读取 steps 数组
while (scanf("[%d", &steps[steps_size]) || scanf("%d", &steps[steps_size])) {
steps_size++;
if (getchar() != ',') break; // 如果下一个字符不是逗号,则结束输入
}
int count;
scanf("%d", &count); // 读取目标值 count
getResult(steps, steps_size, count); // 调用算法函数
return 0;
}
// 比较函数,用于排序 Step 结构体数组
int cmp(const void* a, const void* b) {
Step* A = (Step*)a;
Step* B = (Step*)b;
// 先按 val 升序,再按 idx 升序
return A->val != B->val ? A->val - B->val : A->idx - B->idx;
}
// 比较函数,用于按索引排序 Step 结构体数组
int cmp2(const void* a, const void* b) {
Step* A = (Step*)a;
Step* B = (Step*)b;
return A->idx - B->idx; // 按 idx 升序
}
// 算法函数
void getResult(const int steps[], int steps_size, int count) {
Step newSteps[steps_size];
// 将 steps 数组转换为 Step 结构体数组
for (int i = 0; i < steps_size; i++) {
newSteps[i].val = steps[i];
newSteps[i].idx = i;
}
// 对 newSteps 数组按 val 和 idx 排序
qsort(newSteps, steps_size, sizeof(Step), cmp);
int minStepIdxSum = INT_MAX; // 初始化最小索引和为最大整数值
char ans[100000] = ""; // 初始化结果字符串
// 遍历 newSteps 数组,寻找满足条件的三元组
for (int i = 0; i < steps_size; i++) {
// 剪枝优化:如果当前值大于 count 且 count > 0,则后续值更大,直接跳出循环
if (newSteps[i].val > count && count > 0) break;
// 剪枝优化:如果当前值与上一个值相同,则跳过,避免重复计算
if (i > 0 && newSteps[i].val == newSteps[i - 1].val) continue;
int l = i + 1; // 左指针
int r = steps_size - 1; // 右指针
// 双指针法寻找满足条件的三元组
while (l < r) {
// 剪枝优化:L 和 R 指针指向的值的和应该等于 count - newSteps[i].val
int threshold = (count - newSteps[i].val) / 2;
if (newSteps[l].val > threshold || newSteps[r].val < threshold) break;
int stepValSum = newSteps[i].val + newSteps[l].val + newSteps[r].val;
if (stepValSum < count) {
l++; // 和小于目标值,左指针右移
} else if (stepValSum > count) {
r--; // 和大于目标值,右指针左移
} else {
// 剪枝优化:如果右指针指向的值与下一个值相同,则右指针左移,避免重复计算
while (l < r - 1 && newSteps[r].val == newSteps[r - 1].val) {
r--;
}
// 计算当前三元组的索引和
int stepIdxSum = newSteps[i].idx + newSteps[l].idx + newSteps[r].idx;
// 如果当前三元组的索引和小于最小索引和,则更新最小索引和和结果字符串
if (stepIdxSum < minStepIdxSum) {
minStepIdxSum = stepIdxSum;
Step arr[] = {newSteps[i], newSteps[l], newSteps[r]};
qsort(arr, 3, sizeof(Step), cmp2); // 按索引排序
char res[100000] = {'