模拟行走机器人 图解题解
这道题到底在问什么
- 输入
- commands=[4,-1,3], obstacles=[]
- 输出
- 25 (走到 [3,4],3²+4²=25)
- 输入
- commands=[4,-1,4,-2,4], obstacles=[[2,4]]
- 输出
- 65 (被障碍挡后绕到 [1,8])
最优解:为什么这么做
一句话答案:LeetCode 874 模拟行走机器人:方向数组管左右转、障碍放哈希集合让查挡降到 O(1)、每走一步就刷新离原点距离平方,一趟模拟到底,时间 O(n+k)、空间 O(m)。
机器人在网格上按指令走,最后要返回什么
机器人从 [0,0] 出发、面朝北,读一串 commands:-2 左转 90 度,-1 右转 90 度,正数 x 朝当前方向前进 x 步;途中撞障碍就停在原地、该指令剩余步数作废。要返回一路离原点最远那刻的欧氏距离平方。题面 commands=[4,-1,4,-2,4]、obstacles=[[2,4]] 时答案 65。
障碍逐个比对,为什么步数一多就拖垮
前进时每走一格都要先答「下一格是不是障碍」。若把障碍堆成列表、每步拿坐标逐个比对,前进步数和障碍数都可能上万,最坏是步数乘障碍数次比较,规模一大就拖垮。这每步都问的查询,得压到常数时间。
转向、查挡、记距离,这道题就三件事各管一段
先说转向。北、东、南、西按顺时针排成一圈,用下标 k 指当前朝向:右转(-1)让 k 加 1、左转(-2)让 k 加 3、都对 4 取模——下标转满一圈就回到 0,这就是对 4 取模;加 3 即往回退一格,用加法绕开负数取模。再配方向数组 dirs=(0,1,0,-1,0),位移取 (dirs[k], dirs[k+1]),下标 0 朝北、1 朝东,往下是南、西。
查挡靠哈希集合:障碍坐标 (x,y) 全塞进集合,只管在不在、平均查询 O(1),前进前算出下一格、问它在不在集合里。记平方而非距离,是因欧氏距离要开平方根、会引浮点误差,而只比谁更远时平方与距离顺序一致,索性存 x²+y²、既是答案又免开方。
转向改下标、前进逐格探,一步一刷新最远值
主循环逐条读指令。遇 -2 或 -1 只动下标 k,人不挪、最远值不变。遇正数 x 就重复 x 次:按朝向算出下一格、查集合是不是障碍;是就立刻停、跳出这条剩余步数;不是才走过去更新坐标,并马上用 x²+y² 和已记最远值取大。关键在「马上」:最远点可能就在撞墙前那步,走完整条再算就漏了。
[4,-1,4,-2,4]、障碍 [2,4] 一步步走到 65
起点 [0,0] 朝北,ans=0,集合只有 (2,4)。第一条 4:朝北走 4 步,依次到 [0,1]、[0,2]、[0,3]、[0,4],ans 刷成 1、4、9、16。第二条 -1:右转,北转东,人不动,ans 仍 16。
第三条 4:朝东走。第一步到 [1,4],ans=1²+4²=17;第二步下一格 [2,4] 是障碍,停在 [1,4]、剩 3 步作废,ans 保持 17。第四条 -2:左转回北。第五条 4:朝北走 4 步经 [1,5]、[1,6]、[1,7] 到 [1,8],ans 依次刷成 26、37、50、65,终点即最远,答案 65。
-2 和 -1 记反,整条路线跟着偏到别处
最常见的错是把 -2 当右转、-1 当左转,方向一反、之后位移全错,记牢 -2 左、-1 右。撞障碍别把后面指令全丢,作废的只这条前进,人停原地、后续照常执行,绕过后可能走更远。还有个隐蔽的:走完整条才算距离,漏掉半路被挡时更靠外的落点。
复杂度:n 条指令扫一遍、前进总步数记作 k、每步查障碍 O(1),时间 O(n+k);空间在障碍集合、O(m)。全是转向没前进时人在原点、答案 0;第一条前进第一格就撞障碍、没迈一步,同样 0。
▶ 动画逐步走查(共 18 步)——想跟着动画一帧帧对照就展开
- 3记住这套「转向改下标、前进逐格查障碍、每步刷新最远距离平方」,下面每一帧都在套它。
- 4开局:机器人在原点 [0,0](紫色箭头朝北 ↑),红色 ▦ 是障碍 [2,3]。最远距离平方 ans 先记 0,下面开始读指令。
- 5先认舞台:横向是 x(向右增大、即东,◎ 标的就是右边一格),纵向是 y(向上增大、即北)。四个朝向按「北 东 南 西」排成一圈,机器人现在朝北,右转顺时针挪一格、左转逆时针挪一格。
- 6第 1 条指令是 3,朝 北 ↑ 前进 3 步,一步步走。第 1 步:先探前方一格 [0,1](◎ 标记),查它在不在障碍集合里,再决定走不走。
- 7前方没障碍,真走过去,到达 [0,1]。算一下 dist² = 0² + 1² = 1,比旧的最远还大,刷新 ans = 1。
- 8第 2 步:先探前方一格 [0,2](◎ 标记),查它在不在障碍集合里,再决定走不走。
- 9前方没障碍,真走过去,到达 [0,2]。算一下 dist² = 0² + 2² = 4,比旧的最远还大,刷新 ans = 4。
- 10第 3 步:先探前方一格 [0,3](◎ 标记),查它在不在障碍集合里,再决定走不走。
- 11前方没障碍,真走过去,到达 [0,3]。算一下 dist² = 0² + 3² = 9,比旧的最远还大,刷新 ans = 9。
- 12第 2 条指令是 -1,右转 90 度。朝向从 北 ↑ 转到 东 →(箭头跟着转),下标 k 往后挪一格。机器人原地不动,ans 仍是 9。
- 13第 3 条指令是 2,朝 东 → 前进 2 步,一步步走。第 1 步:先探前方一格 [1,3](◎ 标记),查它在不在障碍集合里,再决定走不走。
- 14前方没障碍,真走过去,到达 [1,3]。算一下 dist² = 1² + 3² = 10,比旧的最远还大,刷新 ans = 10。
- 15第 2 步:先探前方一格 [2,3](◎ 标记),查它在不在障碍集合里,再决定走不走。
- 16前方 [2,3] 是障碍 ▦,走不过去。机器人停在 [1,3] 不动,这条指令剩下的步数全部作废,直接去下一条指令。ans 保持 10。
- 17第 4 条指令是 -2,左转 90 度。朝向从 东 → 转到 北 ↑(箭头跟着转),下标 k 往回挪一格(加 3 再对 4 取模)。机器人没移动,ans 还是 10。
- 18第 5 条指令是 1,朝 北 ↑ 前进 1 步,一步步走。第 1 步:先探前方一格 [1,4](◎ 标记),查它在不在障碍集合里,再决定走不走。
- 19前方没障碍,真走过去,到达 [1,4]。算一下 dist² = 1² + 4² = 17,比旧的最远还大,刷新 ans = 17。
- 20全程走完,机器人最后停在 [1,4]。一路上离原点最远的那一刻就在这里,dist² = 1² + 4² = 17,这就是答案 17。
⚠️ 容易写错的地方
✗ 错:走完整条前进指令才更新最远距离
✓ 对:每走一步就用 x²+y² 刷新 ans
被障碍中途挡停时,最远点可能恰好在半路那一步,逐步更新才不漏
✗ 错:把 -2 当右转、-1 当左转
✓ 对:-2 是左转、-1 是右转
方向反了整条路径全错,下标加减要对应正确
✗ 错:撞障碍后放弃后面所有指令
✓ 对:只是停在前一格,继续执行下一条指令
机器人会绕障碍接着走,后面可能到更远的点
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def robotSim(self, commands: List[int], obstacles: List[List[int]]) -> int:
dirs = (0, 1, 0, -1, 0)
s = {(x, y) for x, y in obstacles}
ans = k = 0
x = y = 0
for c in commands:
if c == -2:
k = (k + 3) % 4
elif c == -1:
k = (k + 1) % 4
else:
for _ in range(c):
nx, ny = x + dirs[k], y + dirs[k + 1]
if (nx, ny) in s:
break
x, y = nx, ny
ans = max(ans, x * x + y * y)
return ansC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
int dirs[5] = {0, 1, 0, -1, 0};
auto f = [](int x, int y) {
return x * 60010 + y;
};
unordered_set<int> s;
for (auto& e : obstacles) {
s.insert(f(e[0], e[1]));
}
int ans = 0, k = 0;
int x = 0, y = 0;
for (int c : commands) {
if (c == -2) {
k = (k + 3) % 4;
} else if (c == -1) {
k = (k + 1) % 4;
} else {
while (c--) {
int nx = x + dirs[k], ny = y + dirs[k + 1];
if (s.count(f(nx, ny))) {
break;
}
x = nx;
y = ny;
ans = max(ans, x * x + y * y);
}
}
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int robotSim(int[] commands, int[][] obstacles) {
int[] dirs = {0, 1, 0, -1, 0};
Set<Integer> s = new HashSet<>(obstacles.length);
for (int[] e : obstacles) {
s.add(f(e[0], e[1]));
}
int ans = 0, k = 0;
int x = 0, y = 0;
for (int c : commands) {
if (c == -2) {
k = (k + 3) % 4;
} else if (c == -1) {
k = (k + 1) % 4;
} else {
while (c-- > 0) {
int nx = x + dirs[k], ny = y + dirs[k + 1];
if (s.contains(f(nx, ny))) {
break;
}
x = nx;
y = ny;
ans = Math.max(ans, x * x + y * y);
}
}
}
return ans;
}
private int f(int x, int y) {
return x * 60010 + y;
}
}复杂度
时间
O(n + k)
n 条指令依次执行,前进总步数加起来记 k,每步查障碍 O(1)
空间
O(m)
m 个障碍存进哈希集合,方便 O(1) 判断某格是不是障碍
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 模拟行走机器人 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么用方向数组,而不是写四个 if-else 判东南西北?+
方向数组把「转向」压成对下标 k 的加减取模——右转 k 加 1、左转 k 加 3,前进的位移直接从 dirs 里按 k 取,左右转各一行、不用四组分支判方向,代码短也不易把方向写反;哪天要扩成八个方向,也只是把数组和取模的底数改大,比一堆 if-else 好扩展。
障碍有上万个,怎么快速判断某一格是不是障碍?+
把所有障碍坐标塞进哈希集合,之后判断某格是不是障碍就是一次 O(1) 查询,不必每步都扫一遍障碍列表。坐标可以像有些写法那样编码成一个整数(例如 x×60010+y),也可以直接用 (x,y) 元组或 pair 当集合元素,效果一样。
为什么返回的是距离的平方,而不是距离本身?+
欧氏距离得对 x²+y² 再开平方根,浮点开方既慢又可能有精度误差;而题目要的最大值只是比较谁离原点更远,平方值和真实距离的大小顺序完全一致,比较平方就够了。于是全程只维护 x²+y² 的最大值、最后直接返回,省掉开方也避开浮点坑——这也正是题目要求返回平方的原因。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 模拟行走机器人 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。