跳到主要内容

HJ17. 坐标移动

题目描述​

无限大二维网格上有小人,初始位置为原点 (0, 0)。接收一系列指令进行移动。

指令格式:

  • 首字符为 A/S/D/W(左/下/右/上)
  • 末字符固定为 ;
  • 中间为 1-99 的数字(可含前导零)

非法指令(格式不正确、数字超出范围、方向字符非法等)应被忽略。

输入格式​

一行字符串,长度 ≤ 10000,由大写字母、数字和分号组成。保证至少一个 ; 且末尾为 ;。

输出格式​

一行两个整数,横纵坐标用逗号间隔。

示例​

示例 1​

输入:

A10;S20;W10;D30;X;A1A;B10A11;;A10;

输出:

10,-10

说明:

  • A10:左移 10 → (-10, 0)
  • S20:下移 20 → (-10, -20)
  • W10:上移 10 → (-10, -10)
  • D30:右移 30 → (20, -10)
  • X:非法,忽略
  • A1A:非法,忽略
  • B10A11:非法,忽略
  • 空指令:忽略
  • A10:左移 10 → (10, -10)

示例 2​

输入:

ABC;AKL;DA1;D001;W023;A100;S00;

输出:

0,0

说明:

  • A100:数字 100 超出 1-99 范围,非法
  • S00:数字 00 = 0,不在 1-99 范围内,非法

示例 3​

输入:

A00;S01;W2;

输出:

0,1

说明:

  • A00:数字 0 非法
  • S01:数字 1 合法(含前导零),下移 1 → (0, -1)
  • W2:数字 2 合法,上移 2 → (0, 1)

解题思路​

第一步:理解问题本质​

本题是一个字符串解析 + 模拟题。需要将输入字符串按 ; 分割成多条指令,逐一验证合法性,合法指令则更新坐标。

第二步:分析指令合法性​

一条合法指令必须同时满足:

  1. 非空
  2. 首字符是 A/S/D/W 之一
  3. 后续部分是 1-2 位数字
  4. 数字值在 1-99 范围内(含前导零时按实际值计算)

第三步:确定解析方案​

使用正则表达式验证指令格式最为简洁:

import re
# 正则只管「形状」:一个方向字符 + 1~2 位数字
match = re.fullmatch(r'([ASDW])(\d{1,2})', cmd)
# 用 fullmatch 而不是 match,A1A、B10A11 这类首尾多出字符的才会被拒掉
  • ([ASDW]):匹配方向字符
  • (\d{1,2}):匹配 1-2 位数字
  • fullmatch:要求整个字符串完全匹配

再用 int() 转换后验证范围 1 <= distance <= 99。


完整代码实现​

算法可视化 · 逐帧回放这道题的代码12 帧 · 录制输入
import sys
import re


def solve():
# 读入整行并去掉换行符,末尾的分号留给 split 处理
line = sys.stdin.readline().strip()

# 方向映射:A=左(x-), D=右(x+), W=上(y+), S=下(y-)
delta = {
'A': (-1, 0),
'D': (1, 0),
'W': (0, 1),
'S': (0, -1),
}

# 小人从原点 (0, 0) 出发
x, y = 0, 0
# 按分号切成一条条指令;末尾分号会切出一个空串,循环里统一跳过
commands = line.split(';')

for cmd in commands:
# 空指令(包括连续分号切出来的)不是合法指令
if not cmd:
continue

# 整串校验格式:方向字符 + 1~2 位数字,多一个字符都不行
match = re.fullmatch(r'([ASDW])(\d{1,2})', cmd)
# 格式不对就整条丢弃,继续看下一条
if not match:
continue

# group(1) 是方向字符,group(2) 是数字部分
direction = match.group(1)
distance = int(match.group(2))

# 位数合法不等于数值合法:"00" 也能过正则,实际值是 0,要挡掉
if distance < 1 or distance > 99:
continue

# 取出该方向的单位位移,再乘以步数
dx, dy = delta[direction]
# A/S 的位移是负的,直接用乘加即可,不用写四个分支
x += dx * distance
y += dy * distance

# 按题目要求输出「x,y」,中间不带空格
print(f"{x},{y}")


if __name__ == "__main__":
solve()

示例推演​

以输入 A10;S20;W10;D30;X;A1A;B10A11;;A10; 为例:

指令方向数字合法性坐标变化当前坐标
A10A(左)10✓x -= 10(-10, 0)
S20S(下)20✓y -= 20(-10, -20)
W10W(上)10✓y += 10(-10, -10)
D30D(右)30✓x += 30(20, -10)
X--✗忽略(20, -10)
A1A--✗忽略(20, -10)
B10A11--✗忽略(20, -10)
空--✗忽略(20, -10)
A10A(左)10✓x -= 10(10, -10)

结果:10,-10


复杂度分析​

指标复杂度说明
时间O(n)遍历所有字符一次,正则匹配为常数时间
空间O(n)split 产生的指令列表

n 为输入字符串长度


易错点总结​

1. 范围判断遗漏​

# 错误!只判断了格式,没判断数值范围
match = re.fullmatch(r'([ASDW])(\d{1,2})', cmd)
if match: # 但 \d{1,2} 也能匹配 "00",需要进一步验证
# 位数合法不等于数值合法,"00" 的 1~2 位形式过关、实际值是 0

解决:提取数字后验证 1 <= distance <= 99。

2. 方向映射错误​

# 易混淆的方向映射
delta = {
'A': (-1, 0), # 左:x 减小
'D': (1, 0), # 右:x 增大
'W': (0, 1), # 上:y 增大(注意不是减小!)
'S': (0, -1), # 下:y 减小
}
# 上下的正负号最容易写反,一旦反了整个结果就是上下镜像

记忆技巧:W = Up( North,y+),S = Down(South,y-)。

3. 空指令处理​

split(';') 会在末尾分号后产生空字符串,必须跳过:

if not cmd:
continue
# 连续分号也会切出空串,这一句一并挡掉,后面就不用再猜字符串是否为空

4. 不用正则的手动解析​

如果不想用正则,也可以手动解析,但代码会更长:

def is_valid(cmd):
# 至少要有「方向字符 + 一位数字」,长度不足的直接否掉
if len(cmd) < 2:
return False
# 首字符必须是四个方向之一
if cmd[0] not in 'ASDW':
return False
# 除首字符外必须全是数字,A1A 这类会在这里被拦下
if not cmd[1:].isdigit():
return False
# 转成整数后按实际值判范围,前导零不影响结果
dist = int(cmd[1:])
return 1 <= dist <= 99

扩展思考​

  • 如果支持负数距离? 如 A-10,需要调整正则为 [ASDW]-?\d{1,2}。
  • 如果允许多行输入? 外层加 for line in sys.stdin 循环。
  • 如果指令含小数? 如 A1.5,需要浮点数处理和更复杂的正则。

相关题目​

自测​

加载评论中...