华为OD机考双机位A卷 - 跳房子I
题目描述
跳房子,也叫跳飞机,是一种世界性的儿童游戏。
游戏参与者需要分多个回合按顺序跳到第1格直到房子的最后一格
跳房子的过程中,可以向前跳,也可以向后跳。
假设房子的总格数是count,小红每回合可能连续跳的步教都放在数组steps中,请问数组中是否有一种步数的组合,可以让小红两个回合跳到最后一格?
如果有,请输出索引和最小的步数组合。
注意:
-
数组中的步数可以重复,但数组中的元素不能重复使用。
-
提供的数据保证存在满足题目要求的组合,且索引和最小的步数组合是唯一的。
输入描述
第一行输入为每回合可能连续跳的步数,它是整数数组类型。
第二行输入为房子总格数count,它是int整数类型。
count ≤ 1000
0 ≤ steps.length ≤ 5000
-100000000 ≤ steps ≤ 100000000
输出描述
返回索引和最小的满足要求的步数组合(顺序保持steps中原有顺序)
题意理解
这道题目要求从一个给定的步数数组中找到一个步数组合,使得小红能够通过两次跳跃从第1格跳到第 count 格,并且这个组合在原数组中的索引和是最小的。输出是该步数组合中的两个步数,上顺序保持与 steps 数组中的顺序一致。
再说的明白一点,在 steps 数组中选两个数,使其之和等于 count ,并且这两个数在原数组中的索引和是最小的。
算法一:双循环遍历
这道题最容易想到的算法就是双循环遍历,这估计也是这道题被称为简单题的原因,时间复杂度是
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>
using namespace std;
int main() {
string str;
getline(cin, str);
vector<int> steps;
stringstream ss(str.substr(1, str.size() - 2));
string token;
while (getline(ss, token, ',')) {
steps.push_back(stoi(token));
}
int count;
cin >> count;
// 初始化最小索引和为整数最大值,用于后续比较
int min_index = INT_MAX;
// 存储最优解的数对
vector<int> bestResult;
// 双循环遍历所有元素
for (int i = 0; i < steps.size(); i++) {
for (int j = i + 1; j < steps.size(); j++) {
if (steps[i] + steps[j] == count) {
// 如果当前数对的索引和小于已知的最小索引和
if (i + j < min_index) {
min_index = i + j;
bestResult = {steps[i], steps[j]};
break;
}
}
}
}
cout << "[" << bestResult[0] << ", " << bestResult[1] << "]" << endl;
return 0;
}
算法二:哈希表法
这道题的题意就是已知一个数组,要我们在这个数组中寻找和
,使得:
我们可以对这个方程做一个简单的处理,得到:
从而,我们可以从数组第0个元素开始遍历,去寻找这个数组中,后面是否有等于的元素,如果有
,使得
,就这一对索引之和是否小于之前的最小值,如果是,就更新最小值。
这种方法的时间复杂度只需要。所以非常高效。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>
#include <unordered_map>
using namespace std;
int main() {
string str;
getline(cin, str);
vector<int> steps;
stringstream ss(str.substr(1, str.size() - 2));
string token;
while (getline(ss, token, ',')) {
steps.push_back(stoi(token));
}
int count;
cin >> count;
// 存储最优结果的数对
pair<int, int> bestResult;
int min_index = INT_MAX;
// 创建哈希表,用于存储值到索引的映射
unordered_map<int, int> value_to_index;
// 遍历所有元素
for (int i = 0; i < steps.size(); i++) {
int target = count - steps[i];
// 检查哈希表中是否存在互补值
if (value_to_index.find(target) != value_to_index.end()) {
// 获取互补值对应的索引
int index = value_to_index[target];
// 如果当前找到的数对索引之和小于已知的最小索引和
if (i + index < min_index) {
min_index = i + index;
// 确保结果中的两个数按索引顺序排列(较小索引的数在前)
if (i < index) {
bestResult = {steps[i], steps[index]};
} else {
bestResult = {steps[index], steps[i]};
}
}
}
// 更新哈希表:
// 1. 如果当前值不在哈希表中,或者
// 2. 当前值已存在但当前索引更小(确保存储的是相同值中最小的索引)
if (value_to_index.find(steps[i]) == value_to_index.end() || i < value_to_index[steps[i]]) {
value_to_index[steps[i]] = i;
}
}
cout << "[" << bestResult.first << "," << bestResult.second << "]" << endl;
return 0;
}
算法三:回溯算法(DFS+剪枝+状态重置)
回溯算法的基本原理可以参考【算法】回溯算法的基本原理与实例:华为OD机考双机位A卷 - 乘坐保密电梯-CSDN博客
这道题我们可以把它看作是一个组合问题,也就是从N个数中找出K个数的组合问题,因此可以用回溯算法求解所有可能,找出下标只和最小的组合。
但问题是,回溯算法时间复杂度是,这个题中,即使限制了K的个数是2,其时间复杂度也是
,所以效率很低,很大可能超过题目要求的运行时间。
虽然回溯算法并不是解这道题的高效算法,但是它是一种非常经典的算法,也需要理解。如果K的个数大于2,它的优势就会逐渐体现出来。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>
using namespace std;
/**
* @brief 深度优先搜索函数,用于寻找两个不同步数的最优组合
*
* @param steps 存储每个可能的连续跳跃步数
* @param count 目标总格子数
* @param curResult 存储当前选中的索引组合(最多2个)
* @param bestResult 引用参数,用于保存最优索引组合
* @param visited 标记各步数是否已被访问的布尔向量
* @param cur_pos 当前累计的步数总和
* @param index_min 引用参数,用于保存当前找到的最小索引和
*/
void dfs(vector<int>& steps, const int count, vector<int>& curResult, vector<int>& bestResult, vector<bool>& visited, int& cur_pos, int& index_min) {
// 递归终止条件:累计步数等于目标值且恰好选中了两个不同的步数
if (cur_pos == count && curResult.size() == 2) {
int index_temp = curResult[0] + curResult[1];
// 如果当前索引和小于已知的最小索引和,则更新最优解
if (index_temp < index_min) {
index_min = index_temp;
bestResult = curResult;
}
return;
}
// 遍历所有可能的步数
for (int i = 0; i < steps.size(); i++) {
// 剪枝条件:
// 1. 当前步数已被访问
// 2. 已经选了一个索引且当前索引小于已选索引(避免重复组合,如i=1,j=0和i=0,j=1视为同一组合)
// 3. 已经选了两个或更多索引(题目只要求选两个)
if (visited[i] || (curResult.size() == 1 && curResult[0] > i) || (curResult.size() >= 2)) {
continue;
}
visited[i] = true;
curResult.push_back(i);
cur_pos += steps[i];
// 递归搜索下一层
dfs(steps, count, curResult, bestResult, visited, cur_pos, index_min);
cur_pos -= steps[i];
visited[i] = false;
curResult.pop_back();
}
}
int main() {
string str;
getline(cin, str);
vector<int> steps;
stringstream ss(str.substr(1, str.size() - 2));
string token;
while (getline(ss, token, ',')) {
steps.push_back(stoi(token));
}
int count;
cin >> count;
vector<int> curResult; // 存储当前选中的两个索引
vector<int> bestResult; // 存储最优的两个索引
vector<bool> visited(steps.size(), false);
int cur_pos = 0; // 当前位置的累加和
int index_min = INT_MAX;// 最小索引和
dfs(steps, count, curResult, bestResult, visited, cur_pos, index_min);
if (bestResult.size() == 2) {
cout << "[" << steps[bestResult[0]] << ", " << steps[bestResult[1]] << "]" << endl;
}
return 0;
}