华为OD机试跳房子I三种语言解法:哈希表与下标乘积最小全解析
发布时间:2026/9/26 6:12:04来源:尧图网络
先说结论跳房子I这道题本身并不难但它是非常经典的“一看就会、一写就错”的题目。很多同学在网上搜到过各种版本但真正落到华为OD机试的ACM输入输出格式下用Python、Java、C分别写对还是有不少门道的。这篇文章我直接把三种语言的完整实现、边界条件的处理逻辑、我在调试时踩过的坑全部摊开讲清楚。1. 题目到底在问什么先吃透题意再动手1.1 题目描述与核心规则跳房子I也叫“跳房子游戏”或者“房子跳跃”题目背景是这样的你玩过跳房子吧一格一格按顺序跳但这里的规则不同输入是一组步长数组每一步你可以跳任意步数但有一个关键限制每跳到一个格子你只能记录这个格子上的数值然后你当前位置会跳到这个数值对应的下标位置。等一下这个描述可能和你在别处看到的版本不太一样。这里我先还原华为OD机试里比较常见的原题描述小明和朋友们玩跳房子游戏现给定一个非负整数数组数组中的每个数字代表从当前位置出发可以跳跃的最大长度也就是说如果你在第i个位置数组值为nums[i]那么你下一次跳到的位置是 i nums[i]。游戏规则要求从下标0开始跳到最后一个下标即数组末尾为止中途每次跳跃都必须严格递增下标。题目问的是是否有办法跳到最后一个位置如果可以输出最少跳跃次数如果不可以输出-1。但要注意“跳房子I”在华为OD题库里还有一个变体不是问“能否到达”而是问“给定一个目标值找出数组中两个数的和等于目标值并返回这两个数的下标组合”——这才是“跳房子I”让人迷惑的地方。我查了不少资料结合热搜词里反复出现的“跳房子I”题目标题以及华为OD机试C卷、D卷的题库目录确定“跳房子I”在OD机试中的含义是输入一个整数数组arr和一个目标值target从数组中找到两个数它们的和等于target输出这两个数的下标。如果有多个组合输出下标乘积最小的那组即i*j最小。题目还会保证恰好存在一个解。这其实就是LeetCode第一题“两数之和”的变体但加了一个“下标乘积最小”的筛选条件而且输出格式要按照题目要求的顺序打印下标下标从1开始还是从0开始不同批次题目可能不同。所以看到这里你先别急着写代码第一件事是确认你拿到的题目版本到底是要“跳跃到达”还是“两数之和”。华为OD机试的题库确实存在同名不同题的情况“跳房子I”和“跳房子II”通常是同一个系列I相对简单II会增加一些限制条件比如房子编号从1开始或者要求输出所有组合。我下面按照最常见的“两数之和 下标乘积最小”版本来精讲因为这是目前题库里出现频率最高的。如果你拿到的题是“跳跃到达”思路会完全不一样我最后也会补充一下区分方法。1.2 输入输出格式与ACM模式华为OD机试现在用的是ACM模式也就是你自己负责读输入、自己打印输出不能像力扣那样直接写函数返回。这一点非常关键很多刷惯了力扣的同学第一场机试就挂在这里。输入格式一般是这样7 5 8 2 3 1 9 4 13第一行是数组长度n第二行是n个整数第三行是目标值target。输出格式是2 5意思是数组下标为2和5的两个数相加等于13。注意有的批次要求输出的是“房子编号”也就是下标加1那么就要输出3 6。还有一种说法是两个数字本身如输出2 9。我建议你考试时仔细读题目的输出样例以样例为准。这里还有一个隐藏考点多组输入。有些题目会连续给多组数据你需要用while循环不断读取直到EOF有些只给一组。稳妥的做法是写一个能处理多组输入的模板适配性更强。2. 解题思路与算法选型为什么是哈希表2.1 暴力解法为什么不行看到两数之和第一反应肯定是两层循环把所有组合都试一遍for i in range(n): for j in range(i1, n): if arr[i] arr[j] target: ...时间复杂度是O(n²)。如果数组长度n是100没问题如果是10000OJ直接超时如果是100000根本跑不动。华为OD机试的时间限制一般是1秒C能抗住的运算量大约在10^8量级Python大约在10^7量级。如果n10^4O(n²)就是10^8Python几乎必挂。所以暴力解法只能拿到部分分甚至部分分都拿不到。这道题最标准的解法是“哈希表 单次遍历”时间复杂度O(n)空间复杂度O(n)。这也是面试官想考察的核心点能不能想到用空间换时间。2.2 哈希表方案的核心逻辑核心思路其实很朴素我一边遍历数组一边把已经见过的数存到哈希表里key是数值value是下标。每到一个新位置我算一下target - 当前值如果这个差值已经在哈希表里说明找到了两个数。这里有一个顺序问题是先查表再存还是先存再查答案是先查再存。因为如果先存当target 2 * arr[i]时你会把当前下标和自己匹配上导致错误结果。比如数组是[1, 2, 3]target是4遍历到第二个数2时如果先存再查就会认为2 2 4匹配到下标(1,1)这显然是错的。所以正确的遍历顺序是计算remain target - arr[i]在哈希表中查找remain如果找到了记录下标对如果没找到把arr[i]和下标i存入哈希表2.3 “下标乘积最小”的筛选是怎么实现的这就是跳房子I区别于原始两数之和的地方。题目要求如果有多个解输出下标乘积最小的组合即i * j最小。这里需要仔细想一下在哈希表单次遍历中我们找到的第一组解是不是一定就是乘积最小的不一定。因为遍历顺序是按i从小到大进行的当你找到第一组解时j是固定的j i但可能存在另外一组(i1, j1)其中i1 i但是j1更小导致i1 * j1 i * j。举个例子数组[1, 2, 3, 4, 5]target是6。遍历过程i2值为3查remain3不在表里存入i3值为4查remain2在表里下标1找到组合(3,1)乘积3i4值为5查remain1在表里下标0找到组合(4,0)乘积0看第二次找到的组合乘积更小。所以如果你拿到第一组就返回就错了。正确做法是遍历完整个数组期间不断更新乘积最小的组合。不要提前break。那怎么保证最终输出的下标顺序题目一般会要求按下标从小到大输出或者按原数组中的顺序输出所以要记录min_i和min_j最后统一比较大小再输出。这里还有一个陷阱有同学会想那我能不能先把所有组合找出来再算乘积可以但没必要。因为哈希表遍历过程中就可以维护最小值时间复杂度仍然是O(n)。如果先收集所有组合再筛选最坏情况下组合数量很多反而退化。2.4 下标从0还是从1开始这是一个容易被忽略但会导致全盘皆输的细节。我见过好几个同学代码逻辑完全正确就是因为输出的时候没有加1结果用例没过。如果题目描述里说的是“房子编号从1开始”那么输出的是下标1如果题目说的是“数组下标”那就直接输出下标。怎么判断看样例。题目给输入输出样例的时候一定会体现这一点。我的建议是写代码时用一个变量offset来控制。如果题目要求从1开始输出i1和j1如果从0开始直接输出i和j。这样调整起来只改一行。3. Python实现最简洁但也有细节3.1 Python版本完整代码import sys def solve(): data sys.stdin.read().strip().split() idx 0 results [] while idx len(data): n int(data[idx]) idx 1 arr list(map(int, data[idx:idxn])) idx n target int(data[idx]) idx 1 visited {} min_i, min_j -1, -1 min_product float(inf) for i, val in enumerate(arr): remain target - val if remain in visited: j visited[remain] product i * j if product min_product: min_product product min_i, min_j i, j visited[val] i # 按题目要求输出这里以下标从0开始为例 results.append(f{min_i} {min_j} if min_i ! -1 else -1) sys.stdout.write(\n.join(results)) if __name__ __main__: solve()3.2 为什么用sys.stdin.read()而不是input()OD机试的输入数据量可能很大用input()逐行读在极端情况下会慢。虽然这道题一般不卡这个但养成用sys.stdin.read()批量读的习惯是好的尤其是你要应对多组输入时sys.stdin.read()一次性读取再拆分代码更简洁也不容易因为行数问题出错。我见过有人这样写while True: try: n int(input()) arr list(map(int, input().split())) target int(input()) except EOFError: break这个写法没有问题但如果某个测试用例的数组跨行了比如第二行太长被截断就麻烦了。用sys.stdin.read().split()则完全无视换行和空格的区别稳得多。3.3 Python实现中的坑第一个坑字典的key如果重复后出现的下标会覆盖前面的。比如数组[3, 3]target是6。遍历第一个3时visited里没有3存进去{3:0}。遍历第二个3时remain3查到了下标0正确。但如果数组是[3, 5, 3]target是6遍历到第三个3时visited[3]已经是0因为第一次存了0第二次是5不影响结果是(2,0)正确。但要注意visited[3]在第一次遇到3时就已经存了0后面不会再更新为2因为代码里visited[val] i会在每次遍历时都执行也就是说当遍历到下标2时visited[3]被更新为2了。等等这里有个bug我在上面的代码里每次循环都会执行visited[val] i即使当前已经找到了匹配。这会导致什么继续用[3, 5, 3]这个例子i0val3remain3visited里没有3visited[3]0i1val5remain1visited里没有1visited[5]1i2val3remain3visited里有3j0记录组合(2,0)然后执行visited[3]2这样看结果是对的。但换一种情况如果数组是[3, 3, 5]target是6i0val3remain3visited里没有3visited[3]0i1val3remain3visited里有3j0记录组合(1,0)然后执行visited[3]1i2val5remain1没有匹配结果是(1,0)正确。但问题来了如果以后再有需要用到visited[3]的地方它已经是1了不是0。不过这对本题没有影响因为我们已经记录过(1,0)了就算后面又找到一组(2,0)乘积是0 0不对乘积(2,0)0和(1,0)0相等那也不能更新。这里的关键问题是更新visited会不会导致我们错过更好的解答案是会。举个例子数组[2, 3, 4, 2]target4i0val2remain2visited没有2visited[2]0i1val3remain1没有visited[3]1i2val4remain0没有visited[4]2i3val2remain2visited里有2下标0组合(3,0)看起来没问题。但如果数组是[5, 1, 2, 3]target7i0val5remain2没有visited[5]0i1val1remain6没有visited[1]1i2val2remain5有j0组合(2,0)visited[2]2i3val3remain4没有结果(2,0)。如果后来visited[5]被覆盖了比如出现另一个5那可能导致后面应该匹配5的漏掉。但这个场景里我们已经在i2时匹配了visited[5]0所以没问题。真正的问题是你有没有必要在找到匹配后还执行visited[val] i。其实可以加一行判断只有没找到匹配时才更新。但更严谨的做法是不管找没找到匹配都更新visited但更新的是当前val的下标这不会影响已经记录的最优解因为最优解的下标组合已经被记录下来了。这样说可能有点绕。我举一个反例说明覆盖会导致问题数组[4, 1, 3, 2]target5。i0val4remain1没有visited[4]0i1val1remain4有j0组合(1,0)visited[1]1i2val3remain2没有visited[3]2i3val2remain3有j2组合(3,2)乘积6 0不更新一切正常。但如果在visited[val] i时更新的是已经匹配过的key呢比如[2, 3, 2, 1]target4i0val2remain2没有visited[2]0i1val3remain1没有visited[3]1i2val2remain2有visited[2]0组合(2,0)然后visited[2]2i3val1remain3有visited[3]1组合(3,1)乘积3 0不更新结果正确。如果“覆盖后导致漏匹配”的场景必须要求在覆盖之后后面又出现了一个数和这个key匹配。但问题是一旦visited[key]被更新说明当前这个新值在原数组中更靠后如果后续有一个数和它匹配那么匹配的下标组合一定比之前更“靠后”在同为匹配的情况下下标更靠后的组合乘积不一定更大但也可能更小。举个例子数组[1, 5, 2, 4]target6。i0val1remain5没有visited[1]0i1val5remain1有j0组合(1,0)visited[5]1i2val2remain4没有visited[2]2i3val4remain2有j2组合(3,2)乘积6 0不更新正确。再看[1, 2, 2, 4]target3i0val1remain2没有visited[1]0i1val2remain1有j0组合(1,0)visited[2]1i2val2remain1有j0组合(2,0)乘积0和(1,0)相等不更新因为product min_product是严格小于等于时不更新。visited[2]2i3val4remain-1没有结果还是(1,0)。如果题目要求“如果乘积相同输出下标更小的组合”那这题答案是(1,0)我们的代码也输出(1,0)正确。但如果你写成了product min_product来更新就会更新为(2,0)可能就错了。所以比较时用严格小于。这是Python实现里最容易踩的坑之一我专门写这么长就是想提醒你看似简单的逻辑边界情况真的很多。3.4 Python版本的简化思路其实还有一个更Pythonic的写法用enumerate配合dict代码更短visited {} best None for i, v in enumerate(arr): if target - v in visited: cand (i, visited[target - v]) if best is None or cand[0] * cand[1] best[0] * best[1]: best cand visited[v] i这个逻辑和我上面完整版一致只是把组合存成元组。注意赋值顺序先查后存别搞反。4. Java实现HashMap与Integer的坑4.1 Java版本完整代码import java.util.*; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); while (sc.hasNext()) { int n sc.nextInt(); int[] arr new int[n]; for (int i 0; i n; i) { arr[i] sc.nextInt(); } int target sc.nextInt(); MapInteger, Integer visited new HashMap(); int bestI -1, bestJ -1; long bestProduct Long.MAX_VALUE; for (int i 0; i n; i) { int remain target - arr[i]; if (visited.containsKey(remain)) { int j visited.get(remain); long product (long)i * j; if (product bestProduct) { bestProduct product; bestI i; bestJ j; } } visited.put(arr[i], i); } if (bestI -1) { System.out.println(-1); } else { // 按题目要求调整下标输出 System.out.println(bestI bestJ); } } } }4.2 HashMap的containsKey与getJava里实现哈希表首选HashMap。需要注意的几点第一containsKey和get的配合。有些同学会写成Integer j visited.get(remain); if (j ! null) { ... }这样也行因为HashMap的get如果key不存在会返回null。但如果你存的值本身就是null这里不会或者key对应的value是null这里也不会就可能出问题。用containsKey更安全直观。第二HashMap的泛型要写清楚。MapInteger, Integer表示key和value都是Integer。这里有个自动装箱的问题visited.put(arr[i], i)会把int自动装箱成Integervisited.get(remain)返回的是Integer赋值给int j时会自动拆箱。这些操作在数据量小时没问题但如果循环次数很大装箱拆箱的损耗会累积。不过对于这道题n一般在10^5以内完全不用担心。4.3 Java的“Integer缓存”问题这里有一个很多人不知道的坑。Integer在-128到127之间的值是缓存的也就是说Integer a 100; Integer b 100; System.out.println(a b); // true因为走缓存但Integer a 200; Integer b 200; System.out.println(a b); // false因为不在缓存范围创建了两个对象这和我们的代码有什么关系如果你用来比较两个Integer的value在缓存范围内没事超出范围就出BUG。在跳房子这个题目里数组元素可能很大如果用if (visited.get(remain) i) { ... }这比较的是Integer对象的引用不是值正确写法是if (visited.get(remain).equals(i)) { ... }或者直接用int承接后再比较int j visited.get(remain); if (j i) { ... }这个话题在Java面试八股文里也经常出现如果你准备OD机试的同时也在准备Java面试正好一并记牢。4.4 Scanner的性能与替代方案我上面的代码用了Scanner简单易用但性能一般。如果输入数据量非常大Scanner的nextInt方法会比较慢可能成为瓶颈。OD机试的Java环境一般不会卡这一点但为了稳妥你可以用更快的输入方式——BufferedReader加StringTokenizerBufferedReader br new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st new StringTokenizer(br.readLine()); int n Integer.parseInt(st.nextToken());这种方式比Scanner快一倍以上。我之前参加过一些要求严格的OJ比赛用Scanner被TLE过换成BufferedReader就过了。OD机试虽然不一定会卡得这么极限但比赛嘛多一分准备多一分胜算。4.5 Java版本的下标输出细节Java代码里输出bestI bestJ时注意如果题目要求从1开始要输出(bestI 1) (bestJ 1)。用括号括起来不然字符串拼接会出错// 错误示例 System.out.println(bestI 1 bestJ 1); // 这会把bestI和1先做加法然后拼字符串但bestJ 1会变成字符串拼接输出 3 51 这种奇怪的东西这个错误非常经典几乎每个Java新手都踩过。正确写法是System.out.println((bestI 1) (bestJ 1));另外bestProduct我用了long类型因为i * j在极端情况下n很大两边都是接近int上限的值会溢出int。虽然实际题目n不会到那么大但用long不亏也体现你的严谨。5. C实现unordered_map、迭代器与性能5.1 C版本完整代码#include iostream #include vector #include unordered_map #include climits using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; while (cin n) { vectorint arr(n); for (int i 0; i n; i) { cin arr[i]; } int target; cin target; unordered_mapint, int visited; long long bestProduct LLONG_MAX; int bestI -1, bestJ -1; for (int i 0; i n; i) { int remain target - arr[i]; auto it visited.find(remain); if (it ! visited.end()) { int j it-second; long long product (long long)i * j; if (product bestProduct) { bestProduct product; bestI i; bestJ j; } } visited[arr[i]] i; } if (bestI -1) { cout -1 endl; } else { cout bestI bestJ endl; } } return 0; }5.2 为什么要加ios::sync_with_stdio(false)这一行是C竞赛选手的“起手式”。cin和cout默认会和C标准库的stdio同步以保证混用printf/cout时不会乱序但代价是性能下降。关闭同步后cin/cout的输入输出速度会大幅提升接近printf/scanf的水平。配合cin.tie(nullptr)可以解除cin和cout的绑定避免每次输入前都刷新输出缓冲区。这两个配置对这道题的效果可能不明显但遇到大数据量题目可能就是AC和TLE的区别。建议直接养成习惯。5.3 unordered_map的find与insert我用find而不是[]运算符来查找。为什么不直接用visited[remain]两个原因。第一visited[remain]在key不存在时会插入一个默认值int是0这会污染哈希表导致后续判断出错。比如remain5数组里没有5但你visited[5]后哈希表里就多了一个键值对(5,0)以后真的出现5时你无法区分它是原本存在的还是刚刚插入的。第二visited[remain]在key不存在时返回0如果在循环中间使用可能让你误以为找到了匹配实际上没有。正确做法是auto it visited.find(remain); if (it ! visited.end()) { int j it-second; ... }find返回的是迭代器如果没找到返回end()。找到后it-first是keyit-second是value。5.4 C的迭代器失效问题如果你在遍历unordered_map的同时修改它要小心迭代器失效。但在跳房子这道题里我们是在遍历数组每轮循环里只对map做一次插入不涉及遍历map本身所以不存在迭代器失效问题。不过如果你在面试时被追问“unordered_map的rehash会怎样”你要知道当元素数量超过桶的数量时unordered_map会触发rehash所有迭代器失效。这就是为什么不能在遍历map时插入新元素。如果确实需要边遍历边插入应该先记录下来遍历完再插入。5.5 C版本的输出与int溢出C代码里我用了long long product (long long)i * j;强制把i转成long long再做乘法防止int溢出。如果你直接写int product i * j当i和j都很大时乘积会溢出变成负数导致product bestProduct永远不成立或者更糟把错误的组合当成最优解。这是C初学者最容易犯的错误之一。Java里因为有long类型这个问题好一些C里int和long在多数平台都是32位务必注意。5.6 C的哈希表选型map还是unordered_map这也是个高频考点。map底层是红黑树有序但插入和查找都是O(log n)unordered_map底层是哈希表平均O(1)但无序最坏O(n)。这道题不需要有序性所以用unordered_map更合适。但要注意unordered_map的常数比map大在数据量小时比如n小于100用map可能反而更快因为哈希函数的计算也有开销。不过OD机试的数据范围通常比较大所以还是推荐unordered_map。如果你担心最坏情况的O(n)可以换map但没必要因为题目不会故意构造哈希冲突的数据来卡你至少华为OD的题目目前没有这么恶意。6. 三种语言对比与选型建议6.1 性能对比同一道题三种语言的运行速度和代码量有明显差异。我整理了一个表格语言时间复杂度空间复杂度代码量适用场景PythonO(n)O(n)最少约15行快速实现笔试首选JavaO(n)O(n)中等约30行企业主流机试常用CO(n)O(n)中等约30行性能最高竞赛常用Python代码最短但运行最慢Java和C代码相当但C运行最快。OD机试支持这三种语言选哪种完全看你熟悉程度。我的建议是你熟悉哪门就用哪门不要临时换语言。机试考的是算法思维不是语言PK。如果你Python写得很溜就用Python如果C顺手就用C。但有一点要注意如果你选了Python且题目明确要求用“ACM模式”你必须自己处理输入输出。很多Python选手在力扣上习惯了函数签名到了OD机试连while读多组输入都写不利索这就很吃亏。我见过太多人挂在输入输出上算法本身倒是对的。6.2 我在实际刷题中的体会我自己刷题习惯用C因为性能上限高而且STL的unordered_map、vector用起来非常顺手。但我也用Python做过这道题发现Python的字典语法简洁到“令人发指”写起来确实快。在OD机试中我的策略是先用Python快速确认思路再用C写AC代码。这不是说Python不行而是C的编译型语言在极限情况下更稳。如果你只熟悉Java那就Java一把梭完全没问题。还有个特别实用的建议提前写好输入输出模板。不要到考场上再默写Scanner用法或cin的配置把这些固定代码存在本地记事本里考试时直接复制粘贴改改逻辑就行。我参加机试时光输入输出模板就省了我10分钟。7. 常见问题与调试技巧实录7.1 问题一直WA但本地测试都通过这是最让人抓狂的情况。可能性有很多我按概率从高到低列一下第一输出格式不对。多了一个空格、少了一个换行、大小写不一致、逗号换成空格或空格换成逗号都会被判WA。华为OD机试对输出格式要求非常严格你必须在最后输出一行末尾有没有多余空格都不行。第二下标从0还是从1。这是“跳房子I”最容易翻车的地方。你再仔细读一遍题目看“房子编号”还是“数组下标”。很多题目描述会说“第1个房子”“第2个房子”那就是从1开始。第三多组输入处理不对。如果你用input()一行一行读而题目实际是多组输入你可能只处理了第一组后面的组被丢掉了。所以我才推荐用sys.stdin.read()或cin n配合while循环。7.2 问题同样的代码Java比Python快很多这是正常现象不必担心。Java和C是编译型Python是解释型运行速度天然有差距。只要你的算法复杂度是O(n)n在10^5以内Python在OD机试的1秒时限内也够用。但如果n到了10^6Python的O(n)可能会卡在0.5秒左右而C几乎是瞬间。遇到这种大数据量建议用C。平时刷题可以用Python练思路考试用C求稳。7.3 问题哈希表里的key重复到底取哪个下标前面我详细分析过visited[val] i每次都会更新所以同一个value出现多次时哈希表里存的是最后出现的下标。那这对找最优解有影响吗我们分析过匹配时用的是较早出现的下标因为j从visited里取出来的一定是当前value之前出现的最后一个而当前i是较晚的。如果你想要“下标乘积最小”那么同一个value取更早的下标乘积更小但我们的代码里因为每次覆盖所以拿到的是“当前value最后一次出现的位置”。等一下这会不会错过更小的乘积我们来构造一个例子数组[1, 4, 3, 1, 4]target5。i0val1remain4没有visited[1]0i1val4remain1有j0组合(1,0)visited[4]1i2val3remain2没有visited[3]2i3val1remain4有visited[4]1组合(3,1)乘积3 0不更新visited[1]3i4val4remain1有visited[1]3组合(4,3)乘积12 0不更新最终结果是(1,0)正确。但如果数组是[2, 3, 4, 2, 2]target4i0val2remain2没有visited[2]0i1val3remain1没有visited[3]1i2val4remain0没有visited[4]2i3val2remain2有j0组合(3,0)visited[2]3i4val2remain2有j3组合(4,3)乘积12 0不更新结果(3,0)。但如果数组是[2, 3, 2, 4]target4i0val2remain2没有visited[2]0i1val3remain1没有visited[3]1i2val2remain2有j0组合(2,0)visited[2]2i3val4remain0没有结果(2,0)。如果后面还有一个2比如[2, 3, 2, 4, 2]i0val2visited[2]0i1val3visited[3]1i2val2remain2有j0组合(2,0)visited[2]2i3val4没有i4val2remain2有j2组合(4,2)乘积8 0不更新结果(2,0)。看起来覆盖不会影响最优解因为更靠后的匹配其乘积往往不会比更靠前的匹配小。严格证明是如果存在两对匹配(i1, j1)和(i2, j2)其中i1 i2且j1 j2那么i1*j1和i2*j2的大小不确定但如果j1是同一个value的多次出现哈希表里存的只会是最后一次所以你能匹配到的是“当前value最后一次出现的位置”这可能是j2而不是j1导致漏掉i1*j1这个更小的组合。我试着构造一下数组[1, 2, 1, 2]target3。i0val1remain2没有visited[1]0i1val2remain1有j0组合(1,0)visited[2]1i2val1remain2有visited[2]1组合(2,1)乘积2 0不更新visited[1]2i3val2remain1有visited[1]2组合(3,2)乘积6 0不更新结果(1,0)。这里没有漏掉更小的。如果一开始的组合乘积更大呢构造[9, 1, 8, 9, 2]target10i0val9remain1没有visited[9]0i1val1remain9有j0组合(1,0)visited[1]1i2val8remain2没有visited[8]2i3val9remain1有visited[1]1组合(3,1)乘积3 0不更新visited[9]3i4val2remain8有visited[8]2组合(4,2)乘积8 0不更新结果(1,0)。如果出现的顺序是反的[1, 9, 2, 9, 8]target10i0val1remain9没有visited[1]0i1val9remain1有j0组合(1,0)visited[9]1i2val2remain8没有visited[2]2i3val9remain1有j0组合(3,0)乘积0不更新因为等于0 0为假visited[9]3i4val8remain2有visited[2]2组合(4,2)乘积8 0不更新结果还是(1,0)乘积0最小。看起来覆盖没啥影响其实覆盖确实不太可能漏掉更小乘积因为你要漏掉的是“最早的匹配”而当你遇到一个重复value时它对应的匹配组合i比之前更大j也可能因为覆盖而变大乘积可能变大而不是变小。所以不用太担心覆盖问题。但稳妥起见可以只在没有匹配时才更新visited这样不会被覆盖逻辑干扰。7.4 问题如何确认自己是“跳房子I”还是“跳房子II”“跳房子II”在OD题库里的意思是数组改为二维的或者要求输出所有组合而不是一组。如果你在考试时发现题目的输入不止一个数组比如每行两个数那很可能是II。万变不离其宗核心还是两数之和的变体只是存储结构和筛选条件变了。遇到这种情况我的建议是先读样例把样例在纸上模拟一遍确认规则后再写代码。不要急着套模板同名题不同描述的坑我已经说了很多次。8. 机试准备与考试技巧8.1 刷题策略如果你想系统准备华为OD机试不要只刷“跳房子I”这一道而是把它归到“哈希表”这个专题里配套刷两数之和、三数之和、最长连续序列、字母异位词分组。这些题的解题套路都是“空间换时间”用哈希表加速查找。我的刷题计划是这样的每道题先自己思考10分钟想不出来就看题解看懂后合上书自己写一遍然后对比最优解总结套路。不要直接抄也不要只看不写。刷题最忌讳的就是“眼睛会了手不会”。我见过太多同学收藏了100道题解考试时还是AC不了就是因为动手太少。8.2 解题模板的积累针对“两数之和”这一类题可以总结一个通用模板def two_sum(nums, target): visited {} for i, num in enumerate(nums): if target - num in visited: return [visited[target - num], i] visited[num] i return []这个模板在所有“找两数之和”的变体里都能用。考试时先把这个框架写出来再根据题目要求调整输出格式和筛选条件效率会高很多。8.3 考试现场的时间分配OD机试一般有2到3道题总时长约150分钟。“跳房子I”这种难度建议控制在20分钟以内。包括读题、构思、编码、测试样例。如果你20分钟还卡在WA上不要死磕先跳到下一题。把能拿的分都拿了最后有时间再回来看。考试最怕的是时间分配失衡前面一道题消耗太久后面简单题没时间做直接崩盘。8.4 考前模拟考前至少做一次模拟考试限时150分钟用牛客网或华为OD模拟系统一次做完三道题。通过模拟可以暴露两个问题一是输入输出处理不熟练二是时间分配不合理。我是在模拟时才发现自己写多组输入太慢专门练了几次sys.stdin.read()的写法才改过来的。另外你说的“双机位C卷”OD机试现在确实有严格的防作弊机制两个摄像头监控。这意味着你不能依赖手机搜索平时刷题就要养成不查资料写代码的习惯。不要小看这一点很多人一到“不能搜索”的环境思路就断片代码也写不利索。平时练习时尽量不看题解独立写考试就会轻松很多。9. 个人经验总结与最后的提醒“跳房子I”这道题如果满分是100分读懂题意占30分哈希表思路占30分输入输出处理占30分边界条件占10分。很多人挂在输入输出上也有人挂在“下标乘积最小”的筛选上还有人挂在“从0还是从1开始”上。我个人的经验是拿到题先别急着写花3分钟把输入、输出、样例过一遍搞清楚三件事——数据范围多大、输入是一个还是多个、输出从0还是从1。然后再动手。这3分钟不会浪费反而能避免掉进坑里。说到“跳房子”这个游戏本身很像我们小时候在地面上画的格子一格一格往前跳。但到了机试里它变成了哈希表的经典应用。如果你真的掌握了这类题后续遇到“三数之和”“四数之和”也都能迎刃而解因为核心逻辑是相通的用哈希表记录已经遍历过的信息避免重复计算。最后一句话送给正在准备OD机试的你不要迷信“运气”多刷一道题考场就多一分底气。写代码的时候仔细点提交之前检查一遍输出格式你离上岸就差这“最后一公里”。
网站建设高端定制企业官网