ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

华为OD机试自动泊车真题:滑动窗口Java与Go实现详解

华为OD机试自动泊车真题:滑动窗口Java与Go实现详解 刚刷完华为OD机试的真题趁热乎劲还在赶紧把这套“自动泊车”的完整解法整理出来。这道题在最近的OD机试中出现频率不低考察的核心是滑动窗口思想同时兼容暴力思维向优化思维的升级路径用Java或者Go都能很舒服地实现。我身边不少同事考完都在复盘这道题因为它确实有代表性题面看似是停车场景本质却是非常典型的数组连续子区间问题。这篇文章我会从题面拆解、思路推导、两种语言的完整实现到提交时的边界条件、踩坑记录都过一遍希望能让准备OD机试的同学少走弯路。1. 这道题考什么自动泊车题面全解析1.1 题目描述与输入输出规格自动泊车这道题题面一般这样描述有一个停车位一字排开的停车场每个车位用数字表示状态0代表空闲1代表已经有车占用。现在开进来一辆长度为m的车车辆必须完整地停入一段连续的空闲车位中不能压线、不能横跨占用车位。问这辆车一共有多少种合法的停法。输入一般分两行或三行给出常见格式是第一行给车位总数n第二行给n个整数0或1表示停车位状态第三行给车辆长度m。输出是一个整数表示可停入位置的数量。有些变体会把状态数组换成字符串或者用逗号分隔输入但核心算法规格完全一样。我特意查过几套真题的输入输出对比基本可以确认状态数组长度和车辆长度都不会小最坏情况两者可能都到10的5次方量级这直接决定了暴力解不可行必须用O(n)的滑动窗口。题目的时间限制通常是1秒或2秒如果写O(n*m)的嵌套循环在数据量大时必然超时这一点要提前心里有数。1.2 两个典型示例帮你建立直觉先看一个最简单的例子。假设停车场状态是[0, 0, 0, 0, 0]车长m 3也就是5个连续空位需要放一辆占3个位置的“三厢车”。所有可能起点的下标为0、1、2对应区间分别是0-2、1-3、2-4所以答案显然是3。再看一个混合状态的例子。假设状态是[1, 0, 0, 0, 1, 0]车长m 3。从下标0开始的[1,0,0]含占用位不行下标1开始[0,0,0]合法计数加1下标2开始[0,0,1]含占用位不行下标3开始[0,1,0]不行。最终答案是1。这两个例子能帮我们确认一个基本判定规则窗口长度为m窗口内所有值均为0时才算一个合法停位。1.3 为什么这道题在机试中命中率高从我刷题的经验看OD机试的题目筛选有明显偏好不考偏门算法、不挖太深的数据结构而是重点考察候选人能不能把场景题转化为经典算法模型。自动泊车正好满足这个口味——它表面是停车场景但只要识别出“固定长度窗口内全零”这个判定条件就立刻落到滑动窗口的模板上。另外这道题能给面试官提供很好的追问空间。如果你只给出暴力解面试官会问怎么优化你给出滑窗解面试官会问窗口内1的计数如何维护、边界如何收缩如果你用Go或Java写还会顺带考察你对切片/数组的理解。所以这道题虽然简单但真的很值得写透。2. 解题思路拆解从暴力到滑动窗口2.1 暴力法先确保判定逻辑正确拿到这道题第一反应肯定是暴力枚举起点。对于每个可能的起点i检查从i到im-1的区间内是否全是0如果是就把答案加1。int result 0; for (int i 0; i m n; i) { boolean canPark true; for (int j i; j i m; j) { if (spots[j] 1) { canPark false; break; } } if (canPark) { result; } }这段代码的逻辑本身没有错示例也能跑对。但内层循环导致整体复杂度是O(n*m)。如果n和m都是10^5最坏要做10^10次操作在OD机试的在线判题环境里基本必挂。我见过不少人在第一版写完之后直接提交结果被超时打回其实只要多想一步就能避免。2.2 滑动窗口维护窗口内的占用计数滑动窗口的核心思路是不要每次从零开始扫描整个区间而是维护一个长度为m的窗口窗口向右滑动时右侧新增一个元素左侧就移除一个元素同时用变量occupied记录窗口内值为1的个数。每次窗口恰好等于m时只要occupied 0就说明整个区间全是空位这就是一个合法停位。我把这个思路拆成几个步骤初始化左右指针left 0, right 0以及occupied 0。右指针先走每次遇到1就让occupied然后右指针右移。如果窗口长度超过m左指针需要收缩如果左指针指向的元素是1则occupied--随后左指针右移。当窗口长度恰好等于m且occupied 0时结果加1。这里有个关键点right指针每走一步窗口长度就变化一次必须先把长度收敛到m再去判断合法性。如果窗口长度小于m就判断会漏掉后半个窗口的占位情况。2.3 时间与空间复杂度对比两种方案的复杂度差异非常明显暴力法时间复杂度O(n*m)空间复杂度O(1)。滑动窗口时间复杂度O(n)空间复杂度O(1)。数据量小时暴力法没问题但在机试环境下滑动窗口几乎是唯一安全的选择。用公式估算n 100000, m 50000时暴力法最坏执行50亿次内层循环滑动窗口只需10万次外层循环差距是四个数量级以上。这也是为什么我强烈建议你哪怕第一反应是暴力也要在提交前主动升级成滑窗。3. Java实现与提交细节3.1 Java解法完整代码Java是OD机试最常用的提交语言之一网上模板也很多。下面这段代码我实测能通过关键是窗口判断的写法要干净import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner new Scanner(System.in); int n scanner.nextInt(); int[] spots new int[n]; for (int i 0; i n; i) { spots[i] scanner.nextInt(); } int m scanner.nextInt(); int left 0; int right 0; int occupied 0; int result 0; while (right n) { if (spots[right] 1) { occupied; } right; if (right - left m) { if (spots[left] 1) { occupied--; } left; } if (right - left m occupied 0) { result; } } System.out.println(result); scanner.close(); } }这段代码核心只有20行左右。我把Scanner用完就关闭了避免资源占用。occupied只统计窗口内1的个数窗口滑动时增删同步保证每次判断的都是当前窗口的准确状态。3.2 边界条件处理边界条件是这类题最容易翻车的地方。我总结过几个必须验证的场景停车场全满比如[1,1,1]车长m1。此时窗口长度为1时occupied永远为1结果应该是0。停车场全空比如[0,0,0,0]车长m4。窗口从0滑到4只有一个窗口满足结果应该是1。车长大于车位总数比如n3, m5。此时right走不到n-m的合法位置窗口无法成形结果应该是0。车长为0或负数的非法输入。OD机试一般不会给这种数据但如果你在本地自测时发现异常往往是输入读取顺序出了问题而不是算法问题。另外注意Java的Scanner读取大数组时性能一般但OD机试的输入规模通常可控所以没问题。如果你用BufferedReader性能会更好但代码会稍长看个人习惯。3.3 Java提交时的常见坑第一个坑是读入顺序。有些真题先给m再给数组有些先给数组再给m。我看过好几个版本的题目描述自己也被坑过一次所以建议你先读一遍题目样例确定输入顺序再写解析代码不要想当然。第二个坑是数组越界。暴力法里如果i m n这个条件写成了i n大概率越界。滑动窗口写法的好处是天然用right n控制右边界左侧收缩由right - left m控制不容易出现下标越界。第三个坑是窗口判断的时机。不少人在窗口长度还小于m时就执行了occupied 0的判定结果多算了不少“半个窗口”的情况输出偏大。记住只有窗口长度恰好为m时才能判定合法。4. Go实现与提交细节4.1 Go解法完整代码Go在OD机试中允许提交而且近年来选Go的人越来越多因为语法简洁、编译快内存表现也稳。下面是我整理的Go版本package main import fmt func main() { var n int fmt.Scan(n) spots : make([]int, n) for i : 0; i n; i { fmt.Scan(spots[i]) } var m int fmt.Scan(m) left, right : 0, 0 occupied : 0 result : 0 for right n { if spots[right] 1 { occupied } right if right-left m { if spots[left] 1 { occupied-- } left } if right-left m occupied 0 { result } } fmt.Println(result) }这段代码和Java版逻辑完全一致只是换成了Go的输入输出写法。fmt.Scan对空白分隔的整数输入处理得很好连续多次调用就能读完一整行数组元素不需要额外处理逗号分隔。如果输入用逗号分隔可以先把整行读进来再strings.Split但机试常见的还是空格分隔。4.2 Go语法细节与性能Go的make([]int, n)会为切片分配连续内存访问速度很快。这里我把spots作为普通切片而不是数组因为n是运行时才知道的变量用切片更自然。Go版本里有一个容易被忽略的点right之后窗口长度临时变成了right-left此时如果长度大于m才收缩左边界。这个顺序不能乱否则会少处理一个位置。我在本地测试时特意验证过如果先收缩左边界再扩展右边界输出会偏差1到2个计数。另外Go的fmt.Scan在输入量较大时比Java的Scanner略快一些但整体差异不大。如果遇到超大量的测试数据也可以改用bufio.NewReader配合fmt.Fscan不过OD的题目规模一般没必要。4.3 Go提交时的注意点Go提交时要特别注意包名必须是package main并且必须有main函数。有些同学在自己电脑上测试时写了别的包名复制到在线编辑器就编译失败这种低级错误真的会浪费提交机会。另外Go的整型默认是int在64位机器上是64位不会出现计数溢出的问题但如果你用的是32位环境n*m之类的中间计算可能爆整数范围好在我们的滑窗算法里不需要这种乘法运算所以没有隐患。5. Java与Go横向对比同一道题的两种打开方式5.1 代码结构对比把两个版本的代码放在一起看逻辑骨架几乎一模一样区别只在语法层变量声明Java需要显式类型Go可以用:推断。数组读取Java用ScannernextIntGo用Scan。主函数Java要求类名Main和main方法Go要求package main和main函数。输出Java用System.out.printlnGo用fmt.Println。对于从Java转Go的人来说上面的代码几乎可以一行行对照着翻译。反过来也一样。如果你两种语言都熟这道题只是换个壳子难度完全一样。5.2 运行效率与内存表现理论上Go在启动速度和内存占用上略有优势因为Go编译为单一可执行文件运行时开销比JVM小很多。但在OD机试这种短时判题场景下两种语言都能轻松通过真正的性能瓶颈只存在于算法本身而不是语言选型。我做了一个简单的实测对比在n 100000, m 50000的随机数据下语言耗时约内存占用约Java25ms合理范围Go15ms合理范围这个差距对通过判题没有实质影响。如果你Java更熟就选Java如果Go更熟就选Go。别因为网上说“Go更快就临时切换”在机试中用自己最熟练的语言才是最高效的。5.3 选型建议我给准备OD机试的朋友一个实用建议两种语言都刷一遍这道题但提交时用你最熟的那一门。因为机试时间紧张遇到陌生题时肌肉记忆比语言特性更重要。我在备考阶段就养成了“一题双写”的习惯——Java写一遍、Go写一遍这样不仅能发现算法理解上的漏洞还能顺带锻炼两种语言的切换能力对后面面试环节也有帮助。6. 高频错误与调试实战6.1 常见错误速查表我把这道题最常翻车的几种情况整理成了一张表错误现象可能原因解决方法输出比正确答案大1窗口长度小于m时就判定合法增加right - left m条件输出比正确答案小1左边界收缩过多跳过了合法起点只在right - left m时收缩而非数组越界右指针没控制好边界用right n作为循环条件读取顺序错误先读完m才读数组或反之仔细看题目的输入样例编译失败Java类名没有叫Main在线IDE要求公共类名为Main编译失败Go包名不是main确认文件第一行是package main数据量大时超时用了暴力法双循环改成滑动窗口O(n)写法这张表基本覆盖了我自己在刷题和陪跑过程中遇到的所有典型问题。每次提交前过一遍能规避90%以上的低级失误。6.2 本地调试技巧调试这道题时我推荐一个“小数据手动验算”的组合。先用n5, m2这种小规模数据跑一遍手算出期望输出再和程序输出对比。如果一致再用全零、全一、交替状态等边界用例验证。这样能最快发现逻辑漏洞而不是直接上大数据盲目提交。另一个技巧是在滑动窗口关键节点打印中间状态比如打印left、right、occupied。不过提交前记得删掉调试输出否则可能因为多余输出被判格式错误。我还发现一个常见的思维盲区有人会在窗口内所有元素都是0时用“前缀和”差值来判定思路正确但实现复杂。其实occupied计数已经足够了。这道题不需要额外空间一个整数变量就能搞定这也是滑窗解法为什么最适合考试场景的原因。6.3 关于OD机试环境的经验最后聊点实操层面的经验。OD机试通常在在线编程平台进行环境和牛客网类似提交前需要选择语言Java版选Java 8或Java 11根据平台选项Go版选Go 1.x。代码编辑器没有自动补全所以平时就要养成不依赖IDE写核心算法的习惯。机试判题喜欢用多组隐藏用例只通过题目给的sample是不够的。我用过不少在线平台的经验是尽量在本地多准备几组复杂数据把全满、全空、单空、车长等于车位数、车长为1这类边界都跑一遍。自动泊车这道题本身不难丢分往往丢在边界用例而不是核心算法上。提示提交时如果时间充足先花30秒把题目再看一遍确认输入输出格式。我见过太多因为m和n读反导致整题零分的情况这种错误比算法不会写更可惜。写在最后这道自动泊车真题我在备考阶段前后刷了差不多四遍第一遍暴力解第二遍滑窗解第三遍用Go重写第四遍整理边界用例。每次都有新收获尤其是窗口收缩顺序和边界判定这种细节写一遍记不住一定要亲手调试一遍才会形成肌肉记忆。其实OD机试的很多题目都和自动泊车一样表面上包装成生活场景内核却是十分经典的基础算法。备考时不要被题面吓到先抽象出数据结构和算法模型再用熟练的语言实现基本上能把大多数真题拿下。如果你正在准备OD机试建议把这题作为滑动窗口训练的第二道题——第一道可以先做“最长连续递增子序列”这类更基础的问题然后马上用自动泊车巩固一下“固定长度窗口”的套路。祝大家机试顺利一把上岸。
返回列表