资讯动态

华为OD机试勇攀数字高峰:最长连续山峰算法Python/JS双实现

发布时间:2026/9/11 12:17:50 来源:尧图企业网站定制
第一次在交流群里看到“勇攀数字高峰”这个题名我还以为是脑筋急转弯直到自己动手跑了一遍才确认这就是华为OD机试2026新系统批次里相当典型的数组算法题。题目包装得挺有画面感但核心其实非常朴素——连续子数组遍历、严格递增递减的判断、以及如何在O(n)时间内找到最长的数字“山峰”。这篇文章我会按自己的理解把题面整理清楚给出两种解法思路再用Python和JavaScript分别落地实现最后把边界用例和现场容易踩的坑一起列出来。适合准备OD机考的同学、想练经典数组题的选手以及想对比Python与JS写法的朋友。先不管网上各种转述版本按我整理的题面来给定一个长度为N的整数数组找出其中最长的连续山峰长度。所谓连续山峰就是一段连续的子数组它先严格递增到达峰顶再从峰顶严格递减到结尾左右两边都不能为空。如果数组里不存在这样的结构输出0。这里的关键词是“连续”和“严格”。连续意味着找的是子数组而不是子序列严格意味着相等元素一旦出现当前山峰直接作废。1. 题目拆解先搞清楚“勇攀数字高峰”到底在考什么1.1 我整理的题面版本与核心条件网上关于这道题的说法有好几个版本有些说“判断整个数组是否是数字山峰”有些说“找出一个先升后降的序列”我把最常见的版本整理成下面这个也是我接下来两套解法都默认处理的版本输入第一行一个整数N1 ≤ N ≤ 10^5第二行N个整数。输出最长连续山峰的长度若不存在输出0。为了让你快速进入状态先看两个例子。输入1 3 5 4 2整个数组就是一座完整的山长度是5。输入1 2 3 4只有上升没有下降构不成山峰输出0。这道题有几个容易忽略的约束我单独列出来关键条件说明严格递增、严格递减相等元素出现时当前山峰直接中断山峰左右都不能为空单调递增或单调递减的数组都不算有山峰连续子数组找的是一段连续区间不是跳跃的子序列数据范围较大N最多10^5O(n^2)会超时必须O(n)或O(n log n)把条件翻译成人话你要在数组里找到一段“先爬坡、下坡”的区域坡必须越走越高、越下越低中间一旦出现平路整段作废。这样一想整道题就从“勇攀数字高峰”变成了很标准的“最长山脉问题”。1.2 为什么说它是机试里的高频常客接触OD机试一段时间后你会发现数组遍历类的题占的比例相当高“勇攀数字高峰”这种题几乎是必刷类型。原因不复杂第一它考察的是最基础的编程能力。能不能把一个数组完整遍历一遍、能不能在遍历过程中维护好几个状态变量这是后端、嵌入式、测试开发等岗位日常都要做的事。第二题目本身不偏不怪但坑很多。包括相等元素怎么处理、状态什么时候重置、数组长度为1或2时怎么办。这些细节既能拉开分数又不是那种需要背高级算法才能解的题非常适合线上机试的筛选场景。第三它对复杂度有要求。N到10^5之后双重循环基本会超时逼着你必须想清楚“能不能一趟扫描搞定”。这种复杂度意识正是机试想考察的东西。如果你之前刷过LeetCode 845Longest Mountain in Array你会发现这道题的内核几乎一模一样。所以这篇文章给的两套解法不是只为了应付这一次机试它能帮你在后续遇到类似“连续区间严格条件最优值”的题目时快速找到思路。2. 两套解法思路状态机与前缀后缀先想通再动手2.1 解法一一次遍历状态机第一套解法是我推荐的机试首选一次遍历维护两个变量up和down思维模型可以想象成你自己真的在爬山。你从山脚出发手里有两个计数器。up记录你从山脚往上走了多少步上升边数down记录你从峰顶往下走了多少步下降边数。只要下坡还没结束当前这座山就还有可能更长一旦遇到平路或者你在下坡之后重新开始上坡就说明前面这座山已经翻完了需要把计数器全部归零从当前元素重新计数。完整的状态流转逻辑是up 0, down 0, ans 0 遍历 i 从 1 到 n-1: 如果 down 0 且 arr[i] arr[i-1]: 说明下坡后重新上坡旧山结束 up 0, down 0 如果 arr[i] arr[i-1]: 平路直接中断up 0, down 0 继续下一个元素 如果 arr[i] arr[i-1]: up 1 否则: down 1 如果 up 0 且 down 0: ans max(ans, up down 1)为什么山峰长度是up down 1而不是up down因为up和down记录的都是“边数”也就是相邻元素之间的大小关系次数。比如1 3 5 4 2这组数上升边是两次1→33→5下降边是两次5→44→2但实际元素有5个。把两条路径加上峰顶本身正好是5。用这组数据走一遍流程i13 1up1i25 3up2i34 5down1此时up2、down1ans2114i42 4down2ans2215一趟下来输出5结果正确。这个解法的关键就是“遇到下坡后重新上坡必须重置”很多人漏掉这个判断导致两座紧挨着的山被错误算成一座这是最常见的错误点之一。2.2 解法二前缀上升后缀下降如果你觉得状态机需要脑补的过程比较多第二个思路会更直观也更适合写完之后向别人讲清楚。核心想法是对于数组里的每个位置分别算出“以它为结尾的最长连续递增长度”和“以它为开头的最长连续递降长度”然后看这个位置能不能当峰顶。定义两个数组left[i]以arr[i]为结尾的最长连续严格递增长度。如果arr[i] arr[i-1]那么left[i] left[i-1] 1否则left[i] 1。right[i]从arr[i]开始向右的最长连续严格递降长度。如果arr[i] arr[i1]那么right[i] right[i1] 1否则right[i] 1。之后遍历每一个可能成为峰顶的位置i如果left[i] 2且right[i] 2说明这个位置左边有一段上升、右边有一段下降可以拼成一座山峰长度为left[i] right[i] - 1。减1是因为arr[i]被算了两次。拿1 2 3 2 1举例left数组是[1, 2, 3, 1, 1]right数组是[1, 1, 3, 2, 1]在i2这个峰顶位置left[2]3right[2]3山峰长度33-15完全正确。这套方法的好处是每个数组的含义一眼就能看懂不涉及状态重置的先后顺序问题。代价是需要额外开两个长度为N的数组空间复杂度从O(1)变成O(n)。对N最大10^5的题来说这点内存完全不是问题机试时如果状态机写不顺手果断切到这个思路。2.3 两种思路怎么选我的建议是分场景机考现场优先用状态机代码短、空间O(1)、不容易写出一大段重复逻辑。如果你平时练习更看重可读性或者到时候脑子比较紧张前缀后缀法反而更稳因为它的每一步都很线性不用考虑“下坡后重新上坡”这种特殊情况。两个方法的时间复杂度都是O(n)都满足大数据量要求。真正决定你选哪个的是你自己对哪种思路更有把握。我个人的经验是状态机写前两题很爽但一旦中间被打断回来容易忘记重置条件前缀后缀虽然空间多了一点但写错概率明显更低。3. Python与JavaScript双实现代码可以直接抄3.1 Python完整实现与关键行注释Python版本我建议直接用sys.stdin.read()读取全部输入而不是一行input()。机试的输入经常存在多余的换行和空格一次性读取再按空白切分会稳很多。import sys def longest_mountain(arr): n len(arr) if n 3: return 0 up 0 down 0 ans 0 for i in range(1, n): # 下坡之后重新上坡说明上一座山已经结束 if down 0 and arr[i] arr[i - 1]: up 0 down 0 # 平路直接中断 if arr[i] arr[i - 1]: up 0 down 0 continue if arr[i] arr[i - 1]: up 1 else: down 1 if up 0 and down 0: ans max(ans, up down 1) return ans def solve(): data sys.stdin.read().strip().split() if not data: return n int(data[0]) arr list(map(int, data[1:1 n])) print(longest_mountain(arr)) if __name__ __main__: solve()代码有几个地方值得多说一句。if n 3直接返回0是因为至少需要“1个上升元素 1个峰顶 1个下降元素”少于3个元素不可能构成山峰。arr[i] arr[i - 1]这里用了continue是为了避免相等元素之后又进入后面的大小判断造成状态错乱。还有data[1:1n]这种切片写法只取前N个防止输入行后面多带了无关内容。如果题目输入没有给出N而是直接给一行数组把solve里的这部分改成arr list(map(int, data))这样代码更通用哪怕第二行缺N也能正常运行。3.2 JSNode.js环境完整实现JavaScript在华为OD机试里通常使用Node.js环境核心逻辑和Python版几乎一一对应但输入输出写法完全不同。用readline逐行读取把数据收集齐后在close事件里统一处理。const readline require(readline); const rl readline.createInterface({ input: process.stdin, output: process.stdout }); const lines []; rl.on(line, (line) { lines.push(line.trim()); }); rl.on(close, () { if (lines.length 0) return; const n parseInt(lines[0], 10); const arr lines[1].split(/\s/).map(Number); console.log(longestMountain(arr)); }); function longestMountain(arr) { const n arr.length; if (n 3) return 0; let up 0; let down 0; let ans 0; for (let i 1; i n; i) { if (down 0 arr[i] arr[i - 1]) { up 0; down 0; } if (arr[i] arr[i - 1]) { up 0; down 0; continue; } if (arr[i] arr[i - 1]) { up; } else { down; } if (up 0 down 0) { ans Math.max(ans, up down 1); } } return ans; }这里有个非常容易踩的坑arr[i] arr[i - 1]必须写严格相等不能顺手写成虽然JS的在数字比较时也能用但机试评分环境对代码规范的敏感度不高可一旦数字和字符串混用就会出现诡异结果。另一个坑是.map(Number)如果漏掉arr里的元素都是字符串“1 10”这种比较会按字符顺序走结果完全乱套。如果碰到输入不是标准两行而是数组分多行给的情况可以在收集lines之后把所有行拼接起来再切分const allInput lines.join( ); const tokens allInput.split(/\s/).map(Number);先取tokens[0]作N再取后面数组部分这种处理方式最抗造。3.3 两种语言实现要点对比对比项PythonJavaScript (Node.js)读取标准输入sys.stdin.read().split()readline逐行收集后再split(/\s/)字符串转数字map(int, ...).map(Number)取最大值max(a, b)Math.max(a, b)严格相等判断通常够用推荐推荐调试输出print()console.log()数组切片data[1:1n]tokens.slice(1, 1 n)常见失误map结果忘了转list字符串数组直接参与比较从实现角度来说两种语言的算法核心完全一致差别只在I/O和语法细节。有Python基础的人把逻辑写成JS并不难但要注意JS数组没有Python那种list(map(int, ...))的链式写法必须分两步先split再map(Number)。4. 边界用例、易错点与现场避坑经验4.1 拿来即用的边界测试用例很多同学样例能过、一交就挂多数原因是没覆盖边界。我把这道题容易出问题的输入整理成了一张表你在本地写完代码后把这几个用例全跑一遍能过滤掉90%的隐藏bug。用例输入期望输出说明整体就是山5/1 3 5 4 25全程包含完整上升和下降只有上升段4/1 2 3 40没有下降段只有下降段4/5 4 3 20没有上升段单元素1/70长度不够平顶山4/1 3 3 20相等元素打断严格条件两座山6/1 2 1 4 5 34取较长的一座先降后升再降5/2 1 2 3 14最前面下降被正确丢弃负数与跨段9/-5 -3 2 4 1 -2 -7 -1 07负数同样适用整套逻辑最后一个负数用例很多人想不到。我一开始也差点漏掉后来发现只要数组里存在严格递增再严格递减的结构负数和正数没有任何区别。这个用例也能验证重置逻辑是否正确-7到-1是重新上坡必须把旧的山峰计数清空否则会把两座山拼在一起算成长度9直接错。4.2 常见错误与排查思路先说最经典的错误相等数字没处理。写第一版的人很容易只判断arr[i] arr[i - 1]和else然后把else当成下降处理。遇到1 2 2 1中间两个2相等按错误逻辑会被当成“下降”最终算出长度为4实际正确结果应该是0。再看状态重置不完整的问题。有些解法只重置up不重置down或者反过来。这会导致下一座山开始计数时down还残留着上一座山的下降边数最终算出一个超出实际范围的长度。我排查这类bug的方法是在循环里加一行调试输出打印每个位置up和down的当前值一眼就能看出状态有没有被正确清零。还有输入格式问题。机试的输入有时候第一行给N有时候不给有时候连续多行都是数组内容。我的建议是写代码时统一把所有输入先收集成一个token数组再去判断第一个token是不是N。如果你只有时间处理一种情况优先处理“第一行N、第二行数组”这种标准格式这是出题人最常用的。对于JS还有一个本地跑不出来的坑split( )遇到连续多个空格会产生空字符串map(Number)会把空字符串转成0这样数组里凭空多出0元素结果一定会错。解决办法就是统一用split(/\s/)。4.3 机考现场三个救命提醒第一先看数据范围再写代码。如果N最大是10^5直接放弃O(n^2)的暴力双循环。两个解法里任选一个O(n)的就行不要再犹豫。第二提交前至少跑三个用例样例、全升序列、单元素。这三个是最基础的烟雾测试过了再提交。机试通常按通过的测试用例给分样例过了不代表数据全覆盖边界用例才是真正的拉分项。第三如果卡住超过30分钟先跳去做后面的题。OD机试题目是分组计分前面卡的时间太长后面简单题的分也拿不到了。回头再思考这道题时建议从前缀后缀思路入手把它当作“给每个位置算左右坡长”比硬想状态机更不容易卡壳。最后再说点我的个人体会做完“勇攀数字高峰”这道题之后我最大的感受是题目包装越花哨内核往往越朴素。连续子数组、严格递增递减、求最优长度这三个关键点在剑指Offer和LeetCode里反复出现学会一套状态机思路等于同时积累了“最长有效括号”“最长连续递增序列”“买卖股票最佳时机”几类题的底层套路。我习惯在刷完一遍之后把题改成两个变体再写一遍一个变体是“输出最长山峰的峰顶下标”另一个是“如果山峰允许等高等底结果如何变”。这样举一反三练下来比机械地刷十道新题更管用。备考OD机试的同学建议把这类数组状态机题作为优先复习项性价比真的高。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价